diff --git a/doc/api/resources/sendmail_rules.rst b/doc/api/resources/sendmail_rules.rst index 40597be9c2..8c688aaaa4 100644 --- a/doc/api/resources/sendmail_rules.rst +++ b/doc/api/resources/sendmail_rules.rst @@ -23,10 +23,14 @@ limit_products list of integers List of product restrict_to_status list List of order states to restrict recipients to. Valid entries are ``p`` for paid, ``e`` for expired, ``c`` for canceled, ``n__pending_approval`` for pending approval, - ``n__not_pending_approval_and_not_valid_if_pending`` for payment pending, - ``n__valid_if_pending`` for payment pending but already confirmed, + ``n__not_pending_approval_and_not_valid_if_pending`` for payment + pending, ``n__valid_if_pending`` for payment pending but already confirmed, and ``n__pending_overdue`` for pending with payment overdue. The default is ``["p", "n__valid_if_pending"]``. +checked_in_status string Check-in status to restrict recipients to. Valid strings are: + ``null`` for no filtering (default), ``checked_in`` for + limiting to attendees that are or have been checked in, and + ``no_checkin`` for limiting to attendees who have not checked in. date_is_absolute boolean If ``true``, the email is set at a specific point in time. send_date datetime If ``date_is_absolute`` is set: Date and time to send the email. send_offset_days integer If ``date_is_absolute`` is not set, this is the number of days @@ -89,6 +93,7 @@ Endpoints "n__not_pending_approval_and_not_valid_if_pending", "n__valid_if_pending" ], + "checked_in_status": null, "send_date": null, "send_offset_days": 1, "send_offset_time": "18:00", @@ -139,6 +144,7 @@ Endpoints "n__not_pending_approval_and_not_valid_if_pending", "n__valid_if_pending" ], + "checked_in_status": null, "send_date": null, "send_offset_days": 1, "send_offset_time": "18:00", @@ -180,6 +186,7 @@ Endpoints "n__not_pending_approval_and_not_valid_if_pending", "n__valid_if_pending" ], + "checked_in_status": "checked_in", "send_date": null, "send_offset_days": 1, "send_offset_time": "18:00", @@ -209,6 +216,7 @@ Endpoints "n__not_pending_approval_and_not_valid_if_pending", "n__valid_if_pending" ], + "checked_in_status": "checked_in", "send_date": null, "send_offset_days": 1, "send_offset_time": "18:00", @@ -266,6 +274,7 @@ Endpoints "n__not_pending_approval_and_not_valid_if_pending", "n__valid_if_pending" ], + "checked_in_status": "checked_in", "send_date": null, "send_offset_days": 1, "send_offset_time": "18:00", diff --git a/src/pretix/plugins/sendmail/api.py b/src/pretix/plugins/sendmail/api.py index f3a31bab39..1bb0baec08 100644 --- a/src/pretix/plugins/sendmail/api.py +++ b/src/pretix/plugins/sendmail/api.py @@ -34,7 +34,7 @@ class RuleSerializer(I18nAwareModelSerializer): class Meta: model = Rule fields = ['id', 'subject', 'template', 'all_products', 'limit_products', 'restrict_to_status', - 'send_date', 'send_offset_days', 'send_offset_time', 'date_is_absolute', + 'checked_in_status', 'send_date', 'send_offset_days', 'send_offset_time', 'date_is_absolute', 'offset_to_event_end', 'offset_is_after', 'send_to', 'enabled'] read_only_fields = ['id'] @@ -88,6 +88,10 @@ class RuleSerializer(I18nAwareModelSerializer): ]: raise ValidationError(f'status {s} not allowed: restrict_to_status may only include valid states') + if full_data.get('checked_in_status') == "": + # even though "blank" is not allowed on this field, "" gets accepted without this check + raise ValidationError('empty string not allowed: use null to disable check-in based filtering') + return full_data def save(self, **kwargs): diff --git a/src/pretix/plugins/sendmail/forms.py b/src/pretix/plugins/sendmail/forms.py index 8fc3b78382..e407df6fb0 100644 --- a/src/pretix/plugins/sendmail/forms.py +++ b/src/pretix/plugins/sendmail/forms.py @@ -312,7 +312,7 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm): fields = ['subject', 'template', 'attach_ical', 'send_date', 'send_offset_days', 'send_offset_time', 'all_products', 'limit_products', 'restrict_to_status', - 'send_to', 'enabled'] + 'checked_in_status', 'send_to', 'enabled'] field_classes = { 'subevent': SafeModelMultipleChoiceField, @@ -337,6 +337,7 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm): 'data-inverse-dependency': '#id_all_products'}, ), 'send_to': forms.RadioSelect, + 'checked_in_status': forms.RadioSelect, } def __init__(self, *args, **kwargs): diff --git a/src/pretix/plugins/sendmail/migrations/0005_rule_checked_in_status.py b/src/pretix/plugins/sendmail/migrations/0005_rule_checked_in_status.py new file mode 100644 index 0000000000..a3b163ad44 --- /dev/null +++ b/src/pretix/plugins/sendmail/migrations/0005_rule_checked_in_status.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-08-09 11:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sendmail', '0004_rule_restrict_to_status'), + ] + + operations = [ + migrations.AddField( + model_name='rule', + name='checked_in_status', + field=models.CharField(max_length=10, null=True), + ), + ] diff --git a/src/pretix/plugins/sendmail/models.py b/src/pretix/plugins/sendmail/models.py index 2c250ed108..e3e6089bb8 100644 --- a/src/pretix/plugins/sendmail/models.py +++ b/src/pretix/plugins/sendmail/models.py @@ -34,7 +34,8 @@ from i18nfield.fields import I18nCharField, I18nTextField from pretix.base.email import get_email_context from pretix.base.i18n import language from pretix.base.models import ( - Event, InvoiceAddress, Item, Order, OrderPosition, SubEvent, fields, + Checkin, Event, InvoiceAddress, Item, Order, OrderPosition, SubEvent, + fields, ) from pretix.base.models.base import LoggingMixin from pretix.base.services.mail import SendMailException @@ -112,19 +113,30 @@ class ScheduledMail(models.Model): e = self.event orders = e.orders.all() - limit_products = self.rule.limit_products.values_list('pk', flat=True) if not self.rule.all_products else None + + filter_orders_by_op = False + op_qs = OrderPosition.objects.filter( + order__event=self.event, + canceled=False, + ) if self.subevent: - orders = orders.filter( - Exists(OrderPosition.objects.filter(order=OuterRef('pk'), subevent=self.subevent)) - ) + filter_orders_by_op = True + op_qs = op_qs.filter(subevent=self.subevent) elif e.has_subevents: return # This rule should not even exist if not self.rule.all_products: - orders = orders.filter( - Exists(OrderPosition.objects.filter(order=OuterRef('pk'), item_id__in=limit_products)) - ) + filter_orders_by_op = True + limit_products = self.rule.limit_products.values_list('pk', flat=True) + op_qs = op_qs.filter(item_id__in=limit_products) + + if self.rule.checked_in_status == "no_checkin": + filter_orders_by_op = True + op_qs = op_qs.filter(~Exists(Checkin.objects.filter(position_id=OuterRef('pk')))) + elif self.rule.checked_in_status == "checked_in": + filter_orders_by_op = True + op_qs = op_qs.filter(Exists(Checkin.objects.filter(position_id=OuterRef('pk')))) status_q = Q(status__in=self.rule.restrict_to_status) if 'n__pending_approval' in self.rule.restrict_to_status: @@ -142,6 +154,8 @@ class ScheduledMail(models.Model): pk__gt=self.last_successful_order_id ) + if filter_orders_by_op: + orders = orders.filter(pk__in=op_qs.values_list('order_id', flat=True)) orders = orders.filter( status_q, ).order_by('pk').select_related('invoice_address').prefetch_related('positions') @@ -205,6 +219,12 @@ class Rule(models.Model, LoggingMixin): (BOTH, _('Both (all order contact addresses and all attendee email addresses)')) ] + CHECK_IN_STATUS_CHOICES = [ + (None, _("Everyone")), + ("checked_in", _("Anyone who is or was checked in")), + ("no_checkin", _("Anyone who never checked in before")) + ] + id = models.BigAutoField(primary_key=True) event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='sendmail_rules') @@ -219,6 +239,15 @@ class Rule(models.Model, LoggingMixin): default=['p', 'n__valid_if_pending'], ) + checked_in_status = models.CharField( + verbose_name=_("Restrict to check-in status"), + default=None, + choices=CHECK_IN_STATUS_CHOICES, + max_length=10, + null=True, + blank=True, + ) + attach_ical = models.BooleanField( default=False, verbose_name=_("Attach calendar files"), diff --git a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_create.html b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_create.html index b9c13b042e..fa9dc3ef3e 100644 --- a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_create.html +++ b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_create.html @@ -28,6 +28,8 @@ {% bootstrap_field form.send_to layout='control' %} {% bootstrap_field form.restrict_to_status layout='control' %} + {% bootstrap_field form.checked_in_status layout='control' %} +