From 6af759b615720b790a9dd4065f6fbcba052b8e49 Mon Sep 17 00:00:00 2001 From: Raphael Michel <michel@rami.io> Date: Tue, 14 Jan 2025 15:09:44 +0100 Subject: [PATCH] Allow to exclude products from service fees (Z#23177030) (#42) --- pretix_servicefees/migrations/0001_initial.py | 36 ++++++++ pretix_servicefees/migrations/__init__.py | 0 pretix_servicefees/models.py | 13 +++ pretix_servicefees/signals.py | 85 +++++++++++++++++-- pretix_servicefees/views.py | 3 +- 5 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 pretix_servicefees/migrations/0001_initial.py create mode 100644 pretix_servicefees/migrations/__init__.py create mode 100644 pretix_servicefees/models.py diff --git a/pretix_servicefees/migrations/0001_initial.py b/pretix_servicefees/migrations/0001_initial.py new file mode 100644 index 0000000..0f01b10 --- /dev/null +++ b/pretix_servicefees/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.17 on 2025-01-08 11:50 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("pretixbase", "0274_tax_codes"), + ] + + operations = [ + migrations.CreateModel( + name="ItemServicefeesSettings", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ("exclude", models.BooleanField()), + ( + "item", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="servicefees_settings", + to="pretixbase.item", + ), + ), + ], + ), + ] diff --git a/pretix_servicefees/migrations/__init__.py b/pretix_servicefees/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pretix_servicefees/models.py b/pretix_servicefees/models.py new file mode 100644 index 0000000..5a33cb5 --- /dev/null +++ b/pretix_servicefees/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class ItemServicefeesSettings(models.Model): + item = models.OneToOneField( + "pretixbase.Item", related_name="servicefees_settings", on_delete=models.CASCADE + ) + exclude = models.BooleanField( + verbose_name=_( + "Exclude this product from the calculation of per-ticket service fees" + ) + ) diff --git a/pretix_servicefees/signals.py b/pretix_servicefees/signals.py index 8ab3d7d..884cb83 100644 --- a/pretix_servicefees/signals.py +++ b/pretix_servicefees/signals.py @@ -1,5 +1,6 @@ from collections import defaultdict from decimal import Decimal +from django import forms from django.dispatch import receiver from django.http import HttpRequest from django.urls import resolve, reverse @@ -8,15 +9,23 @@ from pretix.base.decimal import round_decimal from pretix.base.models import CartPosition, Event, TaxRule from pretix.base.models.orders import OrderFee from pretix.base.settings import settings_hierarkey -from pretix.base.signals import order_fee_calculation +from pretix.base.signals import ( + event_copy_data, + item_copy_data, + order_fee_calculation, +) from pretix.base.templatetags.money import money_filter -from pretix.control.signals import nav_event_settings +from pretix.control.signals import item_forms, nav_event_settings from pretix.presale.signals import ( - fee_calculation_for_cart, front_page_top, order_meta_from_request, + fee_calculation_for_cart, + front_page_top, + order_meta_from_request, ) from pretix.presale.views import get_cart from pretix.presale.views.cart import cart_session +from .models import ItemServicefeesSettings + @receiver(nav_event_settings, dispatch_uid="service_fee_nav_settings") def navbar_settings(sender, request, **kwargs): @@ -58,6 +67,14 @@ def get_fees( if skip_addons: positions = [pos for pos in positions if not pos.addon_to_id] + excluded_products = set( + ItemServicefeesSettings.objects.filter( + item__event=event, + exclude=True, + ).values_list("item_id", flat=True) + ) + positions = [pos for pos in positions if pos.item_id not in excluded_products] + skip_non_admission = event.settings.get( "service_fee_skip_non_admission", as_type=bool ) @@ -185,7 +202,8 @@ def cart_fee(sender: Event, request: HttpRequest, invoice_address, total, **kwar mod = "" try: from pretix_resellers.utils import ( - ResellerException, get_reseller_and_user, + ResellerException, + get_reseller_and_user, ) except ImportError: pass @@ -269,7 +287,8 @@ def order_meta_signal(sender: Event, request: HttpRequest, **kwargs): meta = {} try: from pretix_resellers.utils import ( - ResellerException, get_reseller_and_user, + ResellerException, + get_reseller_and_user, ) except ImportError: pass @@ -282,5 +301,61 @@ def order_meta_signal(sender: Event, request: HttpRequest, **kwargs): return meta +class ItemServicefeesSettingsForm(forms.ModelForm): + class Meta: + model = ItemServicefeesSettings + fields = ["exclude"] + exclude = [] + + def __init__(self, *args, **kwargs): + self.event = kwargs.pop("event") + super().__init__(*args, **kwargs) + + def save(self, commit=True): + if not self.cleaned_data.get("exclude"): + if self.instance.pk: + self.instance.delete() + else: + return + else: + return super().save(commit=commit) + + +@receiver(item_forms, dispatch_uid="servicefees_item_forms") +def control_item_forms(sender, request, item, **kwargs): + try: + inst = ItemServicefeesSettings.objects.get(item=item) + except ItemServicefeesSettings.DoesNotExist: + inst = ItemServicefeesSettings(item=item) + return ItemServicefeesSettingsForm( + instance=inst, + event=sender, + data=(request.POST if request.method == "POST" else None), + prefix="servicefees", + ) + + +@receiver(item_copy_data, dispatch_uid="servicefees_item_copy") +def copy_item(sender, source, target, **kwargs): + try: + inst = ItemServicefeesSettings.objects.get(item=source) + inst = copy.copy(inst) + inst.pk = None + inst.item = target + inst.save() + except ItemServicefeesSettings.DoesNotExist: + pass + + +@receiver(signal=event_copy_data, dispatch_uid="servicefees_copy_data") +def event_copy_data_receiver(sender, other, question_map, item_map, **kwargs): + for ip in ItemServicefeesSettings.objects.filter(item__event=other): + ip = copy.copy(ip) + ip.pk = None + ip.event = sender + ip.item = item_map[ip.item_id] + ip.save() + + settings_hierarkey.add_default("service_fee_skip_addons", "True", bool) settings_hierarkey.add_default("service_fee_skip_free", "True", bool) diff --git a/pretix_servicefees/views.py b/pretix_servicefees/views.py index ed15957..8c39cdc 100644 --- a/pretix_servicefees/views.py +++ b/pretix_servicefees/views.py @@ -4,7 +4,8 @@ from django.utils.translation import gettext_lazy as _ from pretix.base.forms import SettingsForm from pretix.base.models import Event from pretix.control.views.event import ( - EventSettingsFormView, EventSettingsViewMixin, + EventSettingsFormView, + EventSettingsViewMixin, )