Remove deprecated auto_checkin_sales_channels (#4587)

* Remove deprecated auto_checkin_sales_channels

* Fix Query count
This commit is contained in:
Raphael Michel
2024-11-06 12:30:41 +01:00
committed by GitHub
parent 4ca9a43890
commit 3d85d9d865
16 changed files with 61 additions and 232 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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",
),
]

View File

@@ -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')

View File

@@ -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:

View File

@@ -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):

View File

@@ -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,
}

View File

@@ -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 %}

View File

@@ -101,7 +101,6 @@
<a href="?{% url_replace request 'ordering' 'subevent' %}"><i class="fa fa-caret-up"></i></a>
</th>
{% endif %}
<th class="iconcol">{% trans "Automated check-in" %}</th>
<th>{% trans "Products" %}</th>
<th class="action-col-2"></th>
</tr>
@@ -137,17 +136,6 @@
</td>
{% endif %}
{% endif %}
<td>
{% for channel in cl.auto_checkin_sales_channels.all %}
{% if "." in channel.icon %}
<img src="{% static channel.icon %}" class="fa-like-image"
data-toggle="tooltip" title="{{ channel.label }}">
{% else %}
<span class="fa fa-{{ channel.icon }} text-muted"
data-toggle="tooltip" title="{{ channel.label }}"></span>
{% endif %}
{% endfor %}
</td>
<td>
{% if cl.all_products %}
<em>{% trans "All" %}</em>

View File

@@ -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():

View File

@@ -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):

View File

@@ -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)

View File

@@ -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",

View File

@@ -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

View File

@@ -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()