diff --git a/doc/api/resources/checkinlists.rst b/doc/api/resources/checkinlists.rst index 29c33cd7df..0db56047d6 100644 --- a/doc/api/resources/checkinlists.rst +++ b/doc/api/resources/checkinlists.rst @@ -31,8 +31,6 @@ subevent integer ID of the date position_count integer Number of tickets that match this list (read-only). checkin_count integer Number of check-ins performed on this list (read-only). include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state. -auto_checkin_sales_channels list of strings All items on the check-in list will be automatically marked as checked-in when purchased through any of the listed sales channels. - **Deprecated, will be removed in pretix 2024.10.** Use :ref:`rest-autocheckinrules`: instead. allow_multiple_entries boolean If ``true``, subsequent scans of a ticket on this list should not show a warning but instead be stored as an additional check-in. allow_entry_after_exit boolean If ``true``, subsequent scans of a ticket on this list are valid if the last scan of the ticket was an exit scan. rules object Custom check-in logic. The contents of this field are currently not considered a stable API and modifications through the API are highly discouraged. @@ -91,10 +89,7 @@ Endpoints "allow_entry_after_exit": true, "exit_all_at": null, "rules": {}, - "addon_match": false, - "auto_checkin_sales_channels": [ - "pretixpos" - ] + "addon_match": false } ] } @@ -146,10 +141,7 @@ Endpoints "allow_entry_after_exit": true, "exit_all_at": null, "rules": {}, - "addon_match": false, - "auto_checkin_sales_channels": [ - "pretixpos" - ] + "addon_match": false } :param organizer: The ``slug`` field of the organizer to fetch @@ -246,10 +238,7 @@ Endpoints "subevent": null, "allow_multiple_entries": false, "allow_entry_after_exit": true, - "addon_match": false, - "auto_checkin_sales_channels": [ - "pretixpos" - ] + "addon_match": false } **Example response**: @@ -271,10 +260,7 @@ Endpoints "subevent": null, "allow_multiple_entries": false, "allow_entry_after_exit": true, - "addon_match": false, - "auto_checkin_sales_channels": [ - "pretixpos" - ] + "addon_match": false } :param organizer: The ``slug`` field of the organizer of the event/item to create a list for @@ -326,10 +312,7 @@ Endpoints "subevent": null, "allow_multiple_entries": false, "allow_entry_after_exit": true, - "addon_match": false, - "auto_checkin_sales_channels": [ - "pretixpos" - ] + "addon_match": false } :param organizer: The ``slug`` field of the organizer to modify diff --git a/src/pretix/api/serializers/checkin.py b/src/pretix/api/serializers/checkin.py index fea029dad1..c13a14a62e 100644 --- a/src/pretix/api/serializers/checkin.py +++ b/src/pretix/api/serializers/checkin.py @@ -26,31 +26,22 @@ from rest_framework.exceptions import ValidationError from pretix.api.serializers.event import SubEventSerializer from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.base.media import MEDIA_TYPES -from pretix.base.models import Checkin, CheckinList, SalesChannel +from pretix.base.models import Checkin, CheckinList class CheckinListSerializer(I18nAwareModelSerializer): checkin_count = serializers.IntegerField(read_only=True) position_count = serializers.IntegerField(read_only=True) - auto_checkin_sales_channels = serializers.SlugRelatedField( - slug_field="identifier", - queryset=SalesChannel.objects.none(), - required=False, - allow_empty=True, - many=True, - ) class Meta: model = CheckinList fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count', - 'include_pending', 'auto_checkin_sales_channels', 'allow_multiple_entries', 'allow_entry_after_exit', + 'include_pending', 'allow_multiple_entries', 'allow_entry_after_exit', 'rules', 'exit_all_at', 'addon_match', 'ignore_in_statistics', 'consider_tickets_used') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['auto_checkin_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all() - if 'subevent' in self.context['request'].query_params.getlist('expand'): self.fields['subevent'] = SubEventSerializer(read_only=True) diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index eed32e5531..82f0eac191 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -116,7 +116,7 @@ class CheckinListViewSet(viewsets.ModelViewSet): if 'subevent' in self.request.query_params.getlist('expand'): qs = qs.prefetch_related( 'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set', - 'subevent__seat_category_mappings', 'subevent__meta_values', 'auto_checkin_sales_channels' + 'subevent__seat_category_mappings', 'subevent__meta_values', ) return qs diff --git a/src/pretix/base/migrations/0273_remove_checkinlist_auto_checkin_sales_channels.py b/src/pretix/base/migrations/0273_remove_checkinlist_auto_checkin_sales_channels.py new file mode 100644 index 0000000000..9517a882bc --- /dev/null +++ b/src/pretix/base/migrations/0273_remove_checkinlist_auto_checkin_sales_channels.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.16 on 2024-10-29 15:03 + +from django.db import migrations + + +def migrate_autocheckin(apps, schema_editor): + CheckinList = apps.get_model("pretixbase", "CheckinList") + AutoCheckinRule = apps.get_model("autocheckin", "AutoCheckinRule") + + for cl in CheckinList.objects.filter(auto_checkin_sales_channels__isnull=False).select_related("event", "event__organizer"): + sales_channels = cl.auto_checkin_sales_channels.all() + all_sales_channels = cl.event.organizer.sales_channels.all() + + if "pretix.plugins.autocheckin" not in cl.event.plugins: + cl.event.plugins = cl.event.plugins + ",pretix.plugins.autocheckin" + cl.event.save() + + r = AutoCheckinRule.objects.get_or_create( + list=cl, + event=cl.event, + all_products=True, + all_payment_methods=True, + defaults=dict( + mode="placed", + all_sales_channels=len(sales_channels) == len(all_sales_channels), + ) + )[0] + if len(sales_channels) != len(all_sales_channels): + r.limit_sales_channels.set(sales_channels) + + +class Migration(migrations.Migration): + + dependencies = [ + ("pretixbase", "0272_printlog"), + ("autocheckin", "0001_initial"), + ] + + operations = [ + migrations.RunPython( + migrate_autocheckin, + migrations.RunPython.noop, + ), + migrations.RemoveField( + model_name="checkinlist", + name="auto_checkin_sales_channels", + ), + ] diff --git a/src/pretix/base/models/checkin.py b/src/pretix/base/models/checkin.py index 4545649759..9f42a9844d 100644 --- a/src/pretix/base/models/checkin.py +++ b/src/pretix/base/models/checkin.py @@ -99,14 +99,6 @@ class CheckinList(LoggedModel): verbose_name=_('Automatically check out everyone at'), null=True, blank=True ) - auto_checkin_sales_channels = models.ManyToManyField( - "SalesChannel", - verbose_name=_('Sales channels to automatically check in'), - help_text=_('This option is deprecated and will be removed in the next months. As a replacement, our new plugin ' - '"Auto check-in" can be used. When we remove this option, we will automatically migrate your event ' - 'to use the new plugin.'), - blank=True, - ) rules = models.JSONField(default=dict, blank=True) objects = ScopedManager(organizer='event__organizer') diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 7b2bec1e7a..caacf81922 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -1024,10 +1024,9 @@ class Event(EventMixin, LoggedModel): checkin_list_map = {} for cl in other.checkin_lists.filter(subevent__isnull=True).prefetch_related( - 'limit_products', 'auto_checkin_sales_channels' + 'limit_products' ): items = list(cl.limit_products.all()) - auto_checkin_sales_channels = list(cl.auto_checkin_sales_channels.all()) checkin_list_map[cl.pk] = cl cl.pk = None cl._prefetched_objects_cache = {} @@ -1039,8 +1038,6 @@ class Event(EventMixin, LoggedModel): cl.log_action('pretix.object.cloned') for i in items: cl.limit_products.add(item_map[i.pk]) - if auto_checkin_sales_channels: - cl.auto_checkin_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in auto_checkin_sales_channels])) if other.seating_plan: if other.seating_plan.organizer_id == self.organizer_id: diff --git a/src/pretix/base/services/checkin.py b/src/pretix/base/services/checkin.py index 9b5731ec51..7c2e6237fa 100644 --- a/src/pretix/base/services/checkin.py +++ b/src/pretix/base/services/checkin.py @@ -57,7 +57,7 @@ from pretix.base.models import ( Checkin, CheckinList, Device, Event, Gate, Item, ItemVariation, Order, OrderPosition, QuestionOption, ) -from pretix.base.signals import checkin_created, order_placed, periodic_task +from pretix.base.signals import checkin_created, periodic_task from pretix.helpers import OF_SELF from pretix.helpers.jsonlogic import Logic from pretix.helpers.jsonlogic_boolalg import convert_to_dnf @@ -1154,23 +1154,6 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict, ) -@receiver(order_placed, dispatch_uid="legacy_autocheckin_order_placed") -def order_placed(sender, **kwargs): - order = kwargs['order'] - event = sender - - cls = list(event.checkin_lists.filter(auto_checkin_sales_channels=order.sales_channel).prefetch_related( - 'limit_products')) - if not cls: - return - for op in order.positions.all(): - for cl in cls: - if cl.all_products or op.item_id in {i.pk for i in cl.limit_products.all()}: - if not cl.subevent_id or cl.subevent_id == op.subevent_id: - ci = Checkin.objects.create(position=op, list=cl, auto_checked_in=True, type=Checkin.TYPE_ENTRY) - checkin_created.send(event, checkin=ci) - - @receiver(periodic_task, dispatch_uid="autocheckout_exit_all") @scopes_disabled() def process_exit_all(sender, **kwargs): diff --git a/src/pretix/control/forms/checkin.py b/src/pretix/control/forms/checkin.py index 52a52217fd..207a3d78c5 100644 --- a/src/pretix/control/forms/checkin.py +++ b/src/pretix/control/forms/checkin.py @@ -33,9 +33,7 @@ from django_scopes.forms import ( from pretix.base.forms.widgets import SplitDateTimePickerWidget from pretix.base.models import Gate from pretix.base.models.checkin import Checkin, CheckinList -from pretix.control.forms import ( - ItemMultipleChoiceField, SalesChannelCheckboxSelectMultiple, -) +from pretix.control.forms import ItemMultipleChoiceField from pretix.control.forms.widgets import Select2 @@ -67,10 +65,6 @@ class CheckinListForm(forms.ModelForm): kwargs.pop('locales', None) super().__init__(**kwargs) self.fields['limit_products'].queryset = self.event.items.all() - self.fields['auto_checkin_sales_channels'].queryset = self.event.organizer.sales_channels.all() - self.fields['auto_checkin_sales_channels'].widget = SalesChannelCheckboxSelectMultiple( - self.event, choices=self.fields['auto_checkin_sales_channels'].widget.choices - ) if not self.event.organizer.gates.exists(): del self.fields['gates'] @@ -102,7 +96,6 @@ class CheckinListForm(forms.ModelForm): 'limit_products', 'subevent', 'include_pending', - 'auto_checkin_sales_channels', 'allow_multiple_entries', 'allow_entry_after_exit', 'rules', @@ -125,7 +118,6 @@ class CheckinListForm(forms.ModelForm): 'limit_products': ItemMultipleChoiceField, 'gates': SafeModelMultipleChoiceField, 'subevent': SafeModelChoiceField, - 'auto_checkin_sales_channels': SafeModelMultipleChoiceField, 'exit_all_at': NextTimeField, } diff --git a/src/pretix/control/templates/pretixcontrol/checkin/list_edit.html b/src/pretix/control/templates/pretixcontrol/checkin/list_edit.html index 44e1386e33..a56b90b83f 100644 --- a/src/pretix/control/templates/pretixcontrol/checkin/list_edit.html +++ b/src/pretix/control/templates/pretixcontrol/checkin/list_edit.html @@ -67,7 +67,6 @@ {% bootstrap_field form.allow_entry_after_exit layout="control" %} {% bootstrap_field form.addon_match layout="control" %} {% bootstrap_field form.exit_all_at layout="control" %} - {% bootstrap_field form.auto_checkin_sales_channels layout="control" %} {% if form.gates %} {% bootstrap_field form.gates layout="control" %} {% endif %} diff --git a/src/pretix/control/templates/pretixcontrol/checkin/lists.html b/src/pretix/control/templates/pretixcontrol/checkin/lists.html index 00d3de598a..e131f7fdb0 100644 --- a/src/pretix/control/templates/pretixcontrol/checkin/lists.html +++ b/src/pretix/control/templates/pretixcontrol/checkin/lists.html @@ -101,7 +101,6 @@ {% endif %} - {% trans "Automated check-in" %} {% trans "Products" %} @@ -137,17 +136,6 @@ {% endif %} {% endif %} - - {% for channel in cl.auto_checkin_sales_channels.all %} - {% if "." in channel.icon %} - - {% else %} - - {% endif %} - {% endfor %} - {% if cl.all_products %} {% trans "All" %} diff --git a/src/pretix/control/views/checkin.py b/src/pretix/control/views/checkin.py index 235df2f3d8..e38992efe4 100644 --- a/src/pretix/control/views/checkin.py +++ b/src/pretix/control/views/checkin.py @@ -299,7 +299,7 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView): def get_queryset(self): qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related( - "limit_products", "auto_checkin_sales_channels" + "limit_products", ) if self.filter_form.is_valid(): diff --git a/src/tests/api/test_checkin.py b/src/tests/api/test_checkin.py index 3a7a9508a9..361e9d391e 100644 --- a/src/tests/api/test_checkin.py +++ b/src/tests/api/test_checkin.py @@ -252,9 +252,8 @@ def test_list_list(token_client, organizer, event, clist, item, subevent, django res = dict(TEST_LIST_RES) res["id"] = clist.pk res["limit_products"] = [item.pk] - res["auto_checkin_sales_channels"] = [] - with django_assert_num_queries(12): + with django_assert_num_queries(11): resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/'.format(organizer.slug, event.slug)) assert resp.status_code == 200 assert [res] == resp.data['results'] @@ -292,7 +291,6 @@ def test_list_detail(token_client, organizer, event, clist, item): res["id"] = clist.pk res["limit_products"] = [item.pk] - res["auto_checkin_sales_channels"] = [] resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/'.format(organizer.slug, event.slug, clist.pk)) assert resp.status_code == 200 @@ -327,9 +325,6 @@ def test_list_create(token_client, organizer, event, item, item_on_wrong_event): "limit_products": [item.pk], "all_products": False, "subevent": None, - "auto_checkin_sales_channels": [ - "web" - ] }, format='json' ) @@ -339,7 +334,6 @@ def test_list_create(token_client, organizer, event, item, item_on_wrong_event): assert cl.name == "VIP" assert cl.limit_products.count() == 1 assert not cl.all_products - assert cl.auto_checkin_sales_channels.filter(identifier="web").exists() resp = token_client.post( '/api/v1/organizers/{}/events/{}/checkinlists/'.format(organizer.slug, event.slug), @@ -369,24 +363,6 @@ def test_list_create_with_subevent(token_client, organizer, event, event3, item, ) assert resp.status_code == 201 - resp = token_client.post( - '/api/v1/organizers/{}/events/{}/checkinlists/'.format(organizer.slug, event.slug), - { - "name": "VIP", - "limit_products": [item.pk], - "all_products": True, - "subevent": subevent.pk, - "auto_checkin_sales_channels": [ - "web" - ] - }, - format='json' - ) - assert resp.status_code == 201 - with scopes_disabled(): - cl = CheckinList.objects.get(pk=resp.data['id']) - assert cl.auto_checkin_sales_channels.filter(identifier="web").exists() - resp = token_client.post( '/api/v1/organizers/{}/events/{}/checkinlists/'.format(organizer.slug, event.slug), { @@ -440,20 +416,6 @@ def test_list_update(token_client, organizer, event, clist): cl = CheckinList.objects.get(pk=resp.data['id']) assert cl.name == "VIP" - resp = token_client.patch( - '/api/v1/organizers/{}/events/{}/checkinlists/{}/'.format(organizer.slug, event.slug, clist.pk), - { - "auto_checkin_sales_channels": [ - "web" - ], - }, - format='json' - ) - assert resp.status_code == 200 - with scopes_disabled(): - cl = CheckinList.objects.get(pk=resp.data['id']) - assert cl.auto_checkin_sales_channels.filter(identifier="web").exists() - @pytest.mark.django_db def test_list_all_items_positions(token_client, organizer, event, clist, clist_all, item, other_item, order, django_assert_num_queries): diff --git a/src/tests/api/test_order_create.py b/src/tests/api/test_order_create.py index 64d73b5850..fd0d5e807a 100644 --- a/src/tests/api/test_order_create.py +++ b/src/tests/api/test_order_create.py @@ -165,13 +165,6 @@ def order(event, item, taxrule, question): return o -@pytest.fixture -def clist_autocheckin(event): - c = event.checkin_lists.create(name="Default", all_products=True) - c.auto_checkin_sales_channels.add(event.organizer.sales_channels.get(identifier="web")) - return c - - ORDER_CREATE_PAYLOAD = { "email": "dummy@dummy.test", "phone": "+49622112345", @@ -548,36 +541,6 @@ def test_order_create_positionids_addons_simulated(token_client, organizer, even ] -@pytest.mark.django_db -def test_order_create_autocheckin(token_client, organizer, event, item, quota, question, clist_autocheckin): - res = copy.deepcopy(ORDER_CREATE_PAYLOAD) - res['positions'][0]['item'] = item.pk - res['positions'][0]['answers'][0]['question'] = question.pk - resp = token_client.post( - '/api/v1/organizers/{}/events/{}/orders/'.format( - organizer.slug, event.slug - ), format='json', data=res - ) - assert resp.status_code == 201 - with scopes_disabled(): - o = Order.objects.get(code=resp.data['code']) - assert clist_autocheckin.auto_checkin_sales_channels.contains(organizer.sales_channels.get(identifier="web")) - assert o.positions.first().checkins.first().auto_checked_in - - clist_autocheckin.auto_checkin_sales_channels.clear() - - resp = token_client.post( - '/api/v1/organizers/{}/events/{}/orders/'.format( - organizer.slug, event.slug - ), format='json', data=res - ) - assert resp.status_code == 201 - with scopes_disabled(): - o = Order.objects.get(code=resp.data['code']) - assert clist_autocheckin.auto_checkin_sales_channels.count() == 0 - assert o.positions.first().checkins.count() == 0 - - @pytest.mark.django_db def test_order_create_require_approval_free(token_client, organizer, event, item, quota, question): res = copy.deepcopy(ORDER_CREATE_PAYLOAD) diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 01cd2a330e..46c0608237 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -180,13 +180,6 @@ def order2(event2, item2): return o -@pytest.fixture -def clist_autocheckin(event): - c = event.checkin_lists.create(name="Default", all_products=True) - c.auto_checkin_sales_channels.add(event.organizer.sales_channels.get(identifier="web")) - return c - - TEST_ORDERPOSITION_RES = { "id": 1, "order": "FOO", diff --git a/src/tests/base/test_event_clone.py b/src/tests/base/test_event_clone.py index aa235549cc..6464e84675 100644 --- a/src/tests/base/test_event_clone.py +++ b/src/tests/base/test_event_clone.py @@ -126,7 +126,6 @@ def test_full_clone_same_organizer(): ], }) clist.limit_products.add(item1) - clist.auto_checkin_sales_channels.add(sc) copied_event = Event.objects.create( organizer=organizer, name='Dummy2', slug='dummy2', @@ -212,7 +211,6 @@ def test_full_clone_same_organizer(): ], } assert copied_clist.limit_products.get() == copied_item1 - assert copied_clist.auto_checkin_sales_channels.get() == sc # todo: test that the plugin hook is called # todo: test custom style @@ -248,9 +246,6 @@ def test_full_clone_cross_organizer_differences(): item2 = event.items.create(name="T-shirt", default_price=15) item2.require_membership_types.add(membership_type) - clist = event.checkin_lists.create(name="Default") - clist.auto_checkin_sales_channels.add(sc) - copied_event = Event.objects.create( organizer=organizer2, name='Dummy2', slug='dummy2', date_from=datetime.datetime(2022, 4, 15, 9, 0, 0, tzinfo=datetime.timezone.utc), @@ -270,6 +265,3 @@ def test_full_clone_cross_organizer_differences(): assert copied_item1.grant_membership_type is None assert copied_item2.require_membership_types.count() == 0 assert copied_item1.limit_sales_channels.get() == sc2 - - copied_clist = copied_event.checkin_lists.get() - assert copied_clist.auto_checkin_sales_channels.get() == sc2 diff --git a/src/tests/base/test_orders.py b/src/tests/base/test_orders.py index fe1edf0e29..33cd4f057b 100644 --- a/src/tests/base/test_orders.py +++ b/src/tests/base/test_orders.py @@ -71,13 +71,6 @@ def event(): yield event -@pytest.fixture -def clist_autocheckin(event): - c = event.checkin_lists.create(name="Default", all_products=True) - c.auto_checkin_sales_channels.add(event.organizer.sales_channels.get(identifier="web")) - return c - - @pytest.mark.django_db def test_expiry_days(event): today = now() @@ -3458,53 +3451,6 @@ class OrderChangeManagerTests(TestCase): assert self.op1.valid_until is None -@pytest.mark.django_db -def test_autocheckin(clist_autocheckin, event): - today = now() - tr7 = event.tax_rules.create(rate=Decimal('17.00')) - ticket = Item.objects.create(event=event, name='Early-bird ticket', tax_rule=tr7, - default_price=Decimal('23.00'), admission=True) - cp1 = CartPosition.objects.create( - item=ticket, price=23, expires=now() + timedelta(days=1), event=event, cart_id="123" - ) - order = _create_order(event, email='dummy@example.org', positions=[cp1], - now_dt=today, - sales_channel=event.organizer.sales_channels.get(identifier="web"), - payment_requests=[{ - "id": "test0", - "provider": "free", - "max_value": None, - "min_value": None, - "multi_use_supported": False, - "info_data": {}, - "pprov": FreeOrderProvider(event), - }], - locale='de')[0] - assert clist_autocheckin.auto_checkin_sales_channels.contains(event.organizer.sales_channels.get(identifier="web")) - assert order.positions.first().checkins.first().auto_checked_in - - clist_autocheckin.auto_checkin_sales_channels.clear() - - cp1 = CartPosition.objects.create( - item=ticket, price=23, expires=now() + timedelta(days=1), event=event, cart_id="123" - ) - order = _create_order(event, email='dummy@example.org', positions=[cp1], - now_dt=today, - sales_channel=event.organizer.sales_channels.get(identifier="web"), - payment_requests=[{ - "id": "test0", - "provider": "free", - "max_value": None, - "min_value": None, - "multi_use_supported": False, - "info_data": {}, - "pprov": FreeOrderProvider(event), - }], - locale='de')[0] - assert clist_autocheckin.auto_checkin_sales_channels.count() == 0 - assert order.positions.first().checkins.count() == 0 - - @pytest.mark.django_db def test_saleschannel_testmode_restriction(event): today = now()