forked from CGM_Public/pretix_original
Move auto check-in to plugin with more functionality (#4331)
* Move auto check-in to plugin with more functionality * Rename field * Add to MANIFEST.in
This commit is contained in:
@@ -10,6 +10,8 @@ recursive-include src/pretix/helpers/locale *
|
||||
recursive-include src/pretix/base/templates *
|
||||
recursive-include src/pretix/control/templates *
|
||||
recursive-include src/pretix/presale/templates *
|
||||
recursive-include src/pretix/plugins/autocheckin/templates *
|
||||
recursive-include src/pretix/plugins/autocheckin/static *
|
||||
recursive-include src/pretix/plugins/banktransfer/templates *
|
||||
recursive-include src/pretix/plugins/banktransfer/static *
|
||||
recursive-include src/pretix/plugins/manualpayment/templates *
|
||||
|
||||
259
doc/api/resources/auto_checkin_rules.rst
Normal file
259
doc/api/resources/auto_checkin_rules.rst
Normal file
@@ -0,0 +1,259 @@
|
||||
.. _rest-autocheckinrules:
|
||||
|
||||
Auto check-in rules
|
||||
===================
|
||||
|
||||
This feature requires the bundled ``pretix.plugins.autocheckin`` plugin to be active for the event in order to work properly.
|
||||
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
Auto check-in rules specify that tickets should under specific conditions automatically be considered checked in after
|
||||
they have been purchased.
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the rule
|
||||
list integer ID of the check-in list to check the ticket in on. If
|
||||
``None``, the system will select all matching check-in lists.
|
||||
mode string ``"placed"`` if the rule should be evaluated right after
|
||||
an order has been created, ``"paid"`` if the rule should
|
||||
be evaluated after the order has been fully paid.
|
||||
all_sales_channels boolean If ``true`` (default), the rule applies to tickets sold on all sales channels.
|
||||
limit_sales_channels list of strings List of sales channel identifiers the rule should apply to
|
||||
if ``all_sales_channels`` is ``false``.
|
||||
all_products boolean If ``true`` (default), the rule affects all products and variations.
|
||||
limit_products list of integers List of item IDs, if ``all_products`` is not set. If the
|
||||
product listed here has variations, all variations will be matched.
|
||||
limit_variations list of integers List of product variation IDs, if ``all_products`` is not set.
|
||||
The parent product does not need to be part of ``limit_products``.
|
||||
all_payment_methods boolean If ``true`` (default), the rule applies to tickets paid with all payment methods.
|
||||
limit_payment_methods list of strings List of payment method identifiers the rule should apply to
|
||||
if ``all_payment_methods`` is ``false``.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionadded:: 2024.7
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/auto_checkin_rules/
|
||||
|
||||
Returns a list of all rules configured for an event.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/auto_checkin_rules/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"list": 12345,
|
||||
"mode": "placed",
|
||||
"all_sales_channels": false,
|
||||
"limit_sales_channels": ["web"],
|
||||
"all_products": False,
|
||||
"limit_products": [2, 3],
|
||||
"limit_variations": [456],
|
||||
"all_payment_methods": true,
|
||||
"limit_payment_methods": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:query page: The page number in case of a multi-page result set, default is 1
|
||||
:param organizer: The ``slug`` field of a valid organizer
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/auto_checkin_rules/(id)/
|
||||
|
||||
Returns information on one rule, identified by its ID.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/auto_checkin_rules/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"list": 12345,
|
||||
"mode": "placed",
|
||||
"all_sales_channels": false,
|
||||
"limit_sales_channels": ["web"],
|
||||
"all_products": False,
|
||||
"limit_products": [2, 3],
|
||||
"limit_variations": [456],
|
||||
"all_payment_methods": true,
|
||||
"limit_payment_methods": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param id: The ``id`` field of the rule to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/auto_checkin_rules/
|
||||
|
||||
Create a new rule.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/auto_checkin_rules/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 166
|
||||
|
||||
{
|
||||
"list": 12345,
|
||||
"mode": "placed",
|
||||
"all_sales_channels": false,
|
||||
"limit_sales_channels": ["web"],
|
||||
"all_products": False,
|
||||
"limit_products": [2, 3],
|
||||
"limit_variations": [456],
|
||||
"all_payment_methods": true,
|
||||
"limit_payment_methods": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"list": 12345,
|
||||
"mode": "placed",
|
||||
"all_sales_channels": false,
|
||||
"limit_sales_channels": ["web"],
|
||||
"all_products": False,
|
||||
"limit_products": [2, 3],
|
||||
"limit_variations": [456],
|
||||
"all_payment_methods": true,
|
||||
"limit_payment_methods": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to create a rule for
|
||||
:param event: The ``slug`` field of the event to create a rule for
|
||||
:statuscode 201: no error
|
||||
:statuscode 400: The rule could not be created due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create rules.
|
||||
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/auto_checkin_rules/(id)/
|
||||
|
||||
Update a rule. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/auto_checkin_rules/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 34
|
||||
|
||||
{
|
||||
"mode": "paid",
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"list": 12345,
|
||||
"mode": "placed",
|
||||
"all_sales_channels": false,
|
||||
"limit_sales_channels": ["web"],
|
||||
"all_products": False,
|
||||
"limit_products": [2, 3],
|
||||
"limit_variations": [456],
|
||||
"all_payment_methods": true,
|
||||
"limit_payment_methods": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the rule to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The rule could not be modified due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to change it.
|
||||
|
||||
|
||||
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/auto_checkin_rules/(id)/
|
||||
|
||||
Delete a rule.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /api/v1/organizers/bigevents/events/sampleconf/auto_checkin_rules/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Vary: Accept
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the rule to delete
|
||||
:statuscode 204: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to change it **or** this rule cannot be deleted since it is currently in use.
|
||||
@@ -32,6 +32,7 @@ position_count integer Number of ticke
|
||||
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.
|
||||
|
||||
@@ -44,5 +44,6 @@ at :ref:`plugin-docs`.
|
||||
scheduled_exports
|
||||
shredders
|
||||
sendmail_rules
|
||||
auto_checkin_rules
|
||||
billing_invoices
|
||||
billing_var
|
||||
@@ -1,6 +1,8 @@
|
||||
Scheduled email rules
|
||||
=====================
|
||||
|
||||
This feature requires the bundled ``pretix.plugins.sendmail`` plugin to be active for the event in order to work properly.
|
||||
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
@@ -48,6 +50,7 @@ send_to string Can be ``"order
|
||||
or ``"both"``.
|
||||
date. Otherwise it is relative to the event start date.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 2023.7
|
||||
|
||||
The ``include_pending`` field has been deprecated.
|
||||
|
||||
@@ -62,6 +62,7 @@ INSTALLED_APPS = [
|
||||
'pretix.plugins.badges',
|
||||
'pretix.plugins.manualpayment',
|
||||
'pretix.plugins.returnurl',
|
||||
'pretix.plugins.autocheckin',
|
||||
'pretix.plugins.webcheckin',
|
||||
'django_countries',
|
||||
'oauth2_provider',
|
||||
|
||||
@@ -102,9 +102,9 @@ class CheckinList(LoggedModel):
|
||||
auto_checkin_sales_channels = models.ManyToManyField(
|
||||
"SalesChannel",
|
||||
verbose_name=_('Sales channels to automatically check in'),
|
||||
help_text=_('All items on this check-in list will be automatically marked as checked-in when purchased through '
|
||||
'any of the selected sales channels. This option can be useful when tickets sold at the box office '
|
||||
'are not checked again before entry and should be considered validated directly upon purchase.'),
|
||||
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)
|
||||
|
||||
@@ -1154,7 +1154,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
)
|
||||
|
||||
|
||||
@receiver(order_placed, dispatch_uid="autocheckin_order_placed")
|
||||
@receiver(order_placed, dispatch_uid="legacy_autocheckin_order_placed")
|
||||
def order_placed(sender, **kwargs):
|
||||
order = kwargs['order']
|
||||
event = sender
|
||||
@@ -1171,7 +1171,7 @@ def order_placed(sender, **kwargs):
|
||||
checkin_created.send(event, checkin=ci)
|
||||
|
||||
|
||||
@receiver(periodic_task, dispatch_uid="autocheckin_exit_all")
|
||||
@receiver(periodic_task, dispatch_uid="autocheckout_exit_all")
|
||||
@scopes_disabled()
|
||||
def process_exit_all(sender, **kwargs):
|
||||
qs = CheckinList.objects.filter(
|
||||
|
||||
21
src/pretix/plugins/autocheckin/__init__.py
Normal file
21
src/pretix/plugins/autocheckin/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
140
src/pretix/plugins/autocheckin/api.py
Normal file
140
src/pretix/plugins/autocheckin/api.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.core.exceptions import ValidationError
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import serializers, viewsets
|
||||
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.base.models import ItemVariation
|
||||
from pretix.plugins.autocheckin.models import AutoCheckinRule
|
||||
from pretix.plugins.sendmail.models import Rule
|
||||
|
||||
|
||||
class AutoCheckinRuleSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = AutoCheckinRule
|
||||
fields = [
|
||||
"id",
|
||||
"list",
|
||||
"mode",
|
||||
"all_sales_channels",
|
||||
"limit_sales_channels",
|
||||
"all_products",
|
||||
"limit_products",
|
||||
"limit_variations",
|
||||
"all_payment_methods",
|
||||
"limit_payment_methods",
|
||||
]
|
||||
read_only_fields = ["id"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields["limit_sales_channels"].child_relation.queryset = self.context[
|
||||
"event"
|
||||
].organizer.sales_channels.all()
|
||||
self.fields["limit_products"].child_relation.queryset = self.context[
|
||||
"event"
|
||||
].items.all()
|
||||
self.fields["limit_variations"].child_relation.queryset = (
|
||||
ItemVariation.objects.filter(item__event=self.context["event"])
|
||||
)
|
||||
self.fields["limit_payment_methods"] = serializers.MultipleChoiceField(
|
||||
choices=[
|
||||
(f.identifier, f.verbose_name)
|
||||
for f in self.context["event"].get_payment_providers().values()
|
||||
],
|
||||
required=False,
|
||||
allow_empty=True,
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
full_data = (
|
||||
self.to_internal_value(self.to_representation(self.instance))
|
||||
if self.instance
|
||||
else {}
|
||||
)
|
||||
full_data.update(data)
|
||||
|
||||
if full_data.get("mode") == AutoCheckinRule.MODE_PLACED and not full_data.get(
|
||||
"all_payment_methods"
|
||||
):
|
||||
raise ValidationError("all_payment_methods should be used for mode=placed")
|
||||
|
||||
if isinstance(full_data.get("limit_payment_methods"), set):
|
||||
full_data["limit_payment_methods"] = list(
|
||||
full_data["limit_payment_methods"]
|
||||
)
|
||||
|
||||
return full_data
|
||||
|
||||
def save(self, **kwargs):
|
||||
return super().save(event=self.context["request"].event)
|
||||
|
||||
|
||||
class RuleViewSet(viewsets.ModelViewSet):
|
||||
queryset = Rule.objects.none()
|
||||
serializer_class = AutoCheckinRuleSerializer
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ("id",)
|
||||
ordering_fields = ("id",)
|
||||
permission = "can_change_event_settings"
|
||||
|
||||
def get_queryset(self):
|
||||
return AutoCheckinRule.objects.filter(event=self.request.event)
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {
|
||||
**super().get_serializer_context(),
|
||||
"event": self.request.event,
|
||||
}
|
||||
|
||||
def perform_create(self, serializer):
|
||||
super().perform_create(serializer)
|
||||
serializer.instance.log_action(
|
||||
"pretix.plugins.autocheckin.rule.added",
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
super().perform_update(serializer)
|
||||
serializer.instance.log_action(
|
||||
"pretix.plugins.autocheckin.rule.changed",
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
instance.log_action(
|
||||
"pretix.plugins.autocheckin.rule.deleted",
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
43
src/pretix/plugins/autocheckin/apps.py
Normal file
43
src/pretix/plugins/autocheckin/apps.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix import __version__ as version
|
||||
|
||||
|
||||
class AutoCheckinApp(AppConfig):
|
||||
name = "pretix.plugins.autocheckin"
|
||||
verbose_name = _("Automated check-in")
|
||||
|
||||
class PretixPluginMeta:
|
||||
name = _("Automated check-in")
|
||||
author = _("the pretix team")
|
||||
version = version
|
||||
experimental = False
|
||||
category = "FEATURE"
|
||||
description = _(
|
||||
"Automatically check-in specific tickets after they have been sold."
|
||||
)
|
||||
|
||||
def ready(self):
|
||||
from . import signals # NOQA
|
||||
253
src/pretix/plugins/autocheckin/forms.py
Normal file
253
src/pretix/plugins/autocheckin/forms.py
Normal file
@@ -0,0 +1,253 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
|
||||
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
|
||||
#
|
||||
# This file contains Apache-licensed contributions copyrighted by: Alexey Kislitsin, Daniel, Flavia Bastos, Sanket
|
||||
# Dasgupta, Sohalt, pajowu
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes.forms import (
|
||||
SafeModelChoiceField, SafeModelMultipleChoiceField,
|
||||
)
|
||||
|
||||
from pretix.base.models import ItemVariation
|
||||
from pretix.control.forms import SalesChannelCheckboxSelectMultiple
|
||||
from pretix.control.forms.widgets import Select2
|
||||
from pretix.plugins.autocheckin.models import AutoCheckinRule
|
||||
|
||||
from pretix.base.services.placeholders import FormPlaceholderMixin # noqa
|
||||
|
||||
|
||||
class AutoCheckinRuleForm(forms.ModelForm):
|
||||
itemvars = forms.MultipleChoiceField(
|
||||
label=_("Products"),
|
||||
required=False,
|
||||
)
|
||||
limit_payment_methods = forms.MultipleChoiceField(
|
||||
label=_("Only including usage of payment providers"),
|
||||
choices=[],
|
||||
required=False,
|
||||
widget=forms.RadioSelect,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AutoCheckinRule
|
||||
|
||||
fields = [
|
||||
"list",
|
||||
"mode",
|
||||
"all_sales_channels",
|
||||
"limit_sales_channels",
|
||||
"all_products",
|
||||
"all_payment_methods",
|
||||
]
|
||||
field_classes = {
|
||||
"mode": forms.RadioSelect,
|
||||
"list": SafeModelChoiceField,
|
||||
"limit_sales_channels": SafeModelMultipleChoiceField,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop("event")
|
||||
self.instance = kwargs.get("instance", None)
|
||||
|
||||
initial = kwargs.get("initial", {})
|
||||
if self.instance and self.instance.pk and "itemvars" not in initial:
|
||||
initial["itemvars"] = [
|
||||
str(i.pk) for i in self.instance.limit_products.all()
|
||||
] + [
|
||||
"{}-{}".format(v.item_id, v.pk)
|
||||
for v in self.instance.limit_variations.all()
|
||||
]
|
||||
if (
|
||||
self.instance
|
||||
and self.instance.pk
|
||||
and "limit_payment_methods" not in initial
|
||||
):
|
||||
initial["limit_payment_methods"] = self.instance.limit_payment_methods
|
||||
kwargs["initial"] = initial
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields["limit_sales_channels"].queryset = (
|
||||
self.event.organizer.sales_channels.all()
|
||||
)
|
||||
self.fields["limit_sales_channels"].widget = SalesChannelCheckboxSelectMultiple(
|
||||
self.event,
|
||||
attrs={
|
||||
"data-inverse-dependency": "<[name$=all_sales_channels]",
|
||||
"class": "scrolling-multiple-choice",
|
||||
},
|
||||
choices=self.fields["limit_sales_channels"].widget.choices,
|
||||
)
|
||||
|
||||
choices = []
|
||||
for item in self.event.items.all():
|
||||
if len(item.variations.all()) > 0:
|
||||
allvars = _("All variations")
|
||||
choices.append(
|
||||
(
|
||||
"{}".format(item.pk),
|
||||
(
|
||||
f"{item} – {allvars}"
|
||||
if item.active
|
||||
else mark_safe(
|
||||
f'<strike class="text-muted">{escape(item)} – {allvars}</strike>'
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
choices.append(
|
||||
(
|
||||
"{}".format(item.pk),
|
||||
(
|
||||
str(item)
|
||||
if item.active
|
||||
else mark_safe(
|
||||
f'<strike class="text-muted">{escape(item)}</strike>'
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
for v in item.variations.all():
|
||||
choices.append(
|
||||
(
|
||||
"{}-{}".format(item.pk, v.pk),
|
||||
(
|
||||
"{} – {}".format(item, v.value)
|
||||
if item.active
|
||||
else mark_safe(
|
||||
f'<strike class="text-muted">{escape(item)} – {escape(v.value)}</strike>'
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
self.fields["itemvars"].widget = forms.CheckboxSelectMultiple(
|
||||
attrs={
|
||||
"data-inverse-dependency": "<[name$=all_products]",
|
||||
"class": "scrolling-multiple-choice",
|
||||
},
|
||||
)
|
||||
self.fields["itemvars"].choices = choices
|
||||
|
||||
self.fields["list"].queryset = self.event.checkin_lists.all()
|
||||
self.fields["list"].widget = Select2(
|
||||
attrs={
|
||||
"data-model-select2": "generic",
|
||||
"data-select2-url": reverse(
|
||||
"control:event.orders.checkinlists.select2",
|
||||
kwargs={
|
||||
"event": self.event.slug,
|
||||
"organizer": self.event.organizer.slug,
|
||||
},
|
||||
),
|
||||
"data-placeholder": _("Check-in list"),
|
||||
}
|
||||
)
|
||||
self.fields["list"].widget.choices = self.fields["list"].choices
|
||||
self.fields["list"].label = _("Check-in list")
|
||||
|
||||
self.fields["list"].widget.choices = self.fields["list"].choices
|
||||
|
||||
self.fields["limit_payment_methods"].choices += [
|
||||
(p.identifier, p.verbose_name)
|
||||
for p in self.event.get_payment_providers().values()
|
||||
]
|
||||
self.fields["limit_payment_methods"].widget = forms.CheckboxSelectMultiple(
|
||||
attrs={
|
||||
"data-inverse-dependency": "<[name$=all_payment_methods]",
|
||||
"class": "scrolling-multiple-choice",
|
||||
},
|
||||
choices=self.fields["limit_payment_methods"].choices,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
creating = not self.instance.pk
|
||||
|
||||
self.instance.limit_payment_methods = (
|
||||
self.cleaned_data.get("limit_payment_methods") or []
|
||||
)
|
||||
|
||||
inst = super().save(*args, **kwargs)
|
||||
|
||||
selected_items = set(
|
||||
list(
|
||||
self.event.items.filter(
|
||||
id__in=[i for i in self.cleaned_data["itemvars"] if "-" not in i]
|
||||
)
|
||||
)
|
||||
)
|
||||
selected_variations = list(
|
||||
ItemVariation.objects.filter(
|
||||
item__event=self.event,
|
||||
id__in=[
|
||||
i.split("-")[1] for i in self.cleaned_data["itemvars"] if "-" in i
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
current_items = [] if creating else self.instance.limit_products.all()
|
||||
current_variations = [] if creating else self.instance.limit_variations.all()
|
||||
|
||||
self.instance.limit_products.remove(
|
||||
*[i for i in current_items if i not in selected_items]
|
||||
)
|
||||
self.instance.limit_products.add(
|
||||
*[i for i in selected_items if i not in current_items]
|
||||
)
|
||||
self.instance.limit_variations.remove(
|
||||
*[i for i in current_variations if i not in selected_variations]
|
||||
)
|
||||
self.instance.limit_variations.add(
|
||||
*[i for i in selected_variations if i not in current_variations]
|
||||
)
|
||||
return inst
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
|
||||
if d["mode"] == AutoCheckinRule.MODE_PLACED and not d["all_payment_methods"]:
|
||||
raise ValidationError(
|
||||
{
|
||||
"mode": _(
|
||||
"When restricting by payment method, the rule should run after the payment was received."
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return d
|
||||
61
src/pretix/plugins/autocheckin/migrations/0001_initial.py
Normal file
61
src/pretix/plugins/autocheckin/migrations/0001_initial.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# Generated by Django 4.2.14 on 2024-07-24 08:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0269_order_api_meta"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="AutoCheckinRule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("mode", models.CharField(default="placed", max_length=100)),
|
||||
("all_sales_channels", models.BooleanField(default=True)),
|
||||
("all_products", models.BooleanField(default=True)),
|
||||
("all_payment_methods", models.BooleanField(default=True)),
|
||||
(
|
||||
"limit_payment_methods",
|
||||
pretix.base.models.fields.MultiStringField(null=True),
|
||||
),
|
||||
(
|
||||
"event",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="pretixbase.event",
|
||||
),
|
||||
),
|
||||
("limit_products", models.ManyToManyField(to="pretixbase.item")),
|
||||
(
|
||||
"limit_sales_channels",
|
||||
models.ManyToManyField(to="pretixbase.saleschannel"),
|
||||
),
|
||||
(
|
||||
"limit_variations",
|
||||
models.ManyToManyField(to="pretixbase.itemvariation"),
|
||||
),
|
||||
(
|
||||
"list",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="pretixbase.checkinlist",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
85
src/pretix/plugins/autocheckin/models.py
Normal file
85
src/pretix/plugins/autocheckin/models.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.models import (
|
||||
CheckinList, Event, Item, ItemVariation, LoggedModel, SalesChannel,
|
||||
)
|
||||
from pretix.base.models.fields import MultiStringField
|
||||
|
||||
|
||||
class AutoCheckinRule(LoggedModel):
|
||||
MODE_PLACED = "placed"
|
||||
MODE_PAID = "paid"
|
||||
MODE_CHOICES = (
|
||||
(MODE_PLACED, _("After order was placed")),
|
||||
(MODE_PAID, _("After order was paid")),
|
||||
)
|
||||
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
list = models.ForeignKey(
|
||||
CheckinList,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Check-in list"),
|
||||
help_text=_(
|
||||
"If you keep this empty, all lists that match the purchased product will be used."
|
||||
),
|
||||
)
|
||||
|
||||
mode = models.CharField(
|
||||
max_length=100,
|
||||
choices=MODE_CHOICES,
|
||||
default=MODE_PLACED,
|
||||
)
|
||||
|
||||
all_sales_channels = models.BooleanField(
|
||||
verbose_name=_("All sales channels"),
|
||||
default=True,
|
||||
)
|
||||
limit_sales_channels = models.ManyToManyField(
|
||||
SalesChannel,
|
||||
verbose_name=_("Sales channels"),
|
||||
blank=True,
|
||||
)
|
||||
|
||||
all_products = models.BooleanField(
|
||||
verbose_name=_("All products and variations"),
|
||||
default=True,
|
||||
)
|
||||
limit_products = models.ManyToManyField(Item, verbose_name=_("Products"), blank=True)
|
||||
limit_variations = models.ManyToManyField(
|
||||
ItemVariation, blank=True, verbose_name=_("Variations")
|
||||
)
|
||||
|
||||
all_payment_methods = models.BooleanField(
|
||||
verbose_name=_("All payment methods"),
|
||||
default=True,
|
||||
)
|
||||
limit_payment_methods = MultiStringField(
|
||||
verbose_name=_("Only including usage of payment providers"),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
194
src/pretix/plugins/autocheckin/signals.py
Normal file
194
src/pretix/plugins/autocheckin/signals.py
Normal file
@@ -0,0 +1,194 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import copy
|
||||
|
||||
from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.models import Checkin, OrderPayment
|
||||
from pretix.base.signals import (
|
||||
checkin_created, event_copy_data, item_copy_data, logentry_display,
|
||||
order_paid, order_placed,
|
||||
)
|
||||
from pretix.control.signals import nav_event
|
||||
from pretix.plugins.autocheckin.models import AutoCheckinRule
|
||||
|
||||
|
||||
@receiver(nav_event, dispatch_uid="autocheckin_nav_event")
|
||||
def nav_event_receiver(sender, request, **kwargs):
|
||||
url = request.resolver_match
|
||||
if not request.user.has_event_permission(
|
||||
request.organizer, request.event, "can_change_event_settings", request=request
|
||||
):
|
||||
return []
|
||||
return [
|
||||
{
|
||||
"label": _("Auto check-in"),
|
||||
"url": reverse(
|
||||
"plugins:autocheckin:index",
|
||||
kwargs={
|
||||
"event": request.event.slug,
|
||||
"organizer": request.organizer.slug,
|
||||
},
|
||||
),
|
||||
"parent": reverse(
|
||||
"control:event.orders.checkinlists",
|
||||
kwargs={
|
||||
"event": request.event.slug,
|
||||
"organizer": request.event.organizer.slug,
|
||||
},
|
||||
),
|
||||
"active": url.namespace == "plugins:autocheckin",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@receiver(signal=logentry_display)
|
||||
def logentry_display_receiver(sender, logentry, **kwargs):
|
||||
plains = {
|
||||
"pretix.plugins.autocheckin.rule.added": _("An auto check-in rule was created"),
|
||||
"pretix.plugins.autocheckin.rule.changed": _(
|
||||
"An auto check-in rule was updated"
|
||||
),
|
||||
"pretix.plugins.autocheckin.rule.deleted": _(
|
||||
"An auto check-in rule was deleted"
|
||||
),
|
||||
}
|
||||
if logentry.action_type in plains:
|
||||
return plains[logentry.action_type]
|
||||
|
||||
|
||||
@receiver(item_copy_data, dispatch_uid="autocheckin_item_copy")
|
||||
def item_copy_data_receiver(sender, source, target, **kwargs):
|
||||
for acr in AutoCheckinRule.objects.filter(limit_products=source):
|
||||
acr.limit_products.add(target)
|
||||
|
||||
|
||||
@receiver(signal=event_copy_data, dispatch_uid="autocheckin_copy_data")
|
||||
def event_copy_data_receiver(
|
||||
sender, other, item_map, variation_map, checkin_list_map, **kwargs
|
||||
):
|
||||
for acr in other.autocheckinrule_set.all():
|
||||
if acr.list and acr.list.subevent:
|
||||
continue # Impossible to copy
|
||||
|
||||
oldacr = acr
|
||||
|
||||
acr = copy.copy(acr)
|
||||
acr.pk = None
|
||||
acr.event = sender
|
||||
|
||||
if acr.list_id:
|
||||
acr.list = checkin_list_map[acr.list_id]
|
||||
|
||||
acr.save()
|
||||
|
||||
if not acr.all_sales_channels:
|
||||
acr.limit_sales_channels.set(
|
||||
sender.organizer.sales_channels.filter(
|
||||
identifier__in=oldacr.limit_sales_channels.values_list(
|
||||
"identifier", flat=True
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if not acr.all_products:
|
||||
acr.limit_products.set([item_map[o.pk] for o in oldacr.limit_products.all()])
|
||||
acr.limit_variations.set(
|
||||
[variation_map[o.pk] for o in oldacr.limit_variations.all()]
|
||||
)
|
||||
|
||||
|
||||
def perform_auto_checkin(sender, order, mode, payment_methods):
|
||||
positions = list(order.positions.all())
|
||||
payment_q = Q(all_payment_methods=True)
|
||||
for p in payment_methods:
|
||||
payment_q = payment_q | Q(limit_payment_methods__contains=p)
|
||||
|
||||
rules = list(
|
||||
sender.autocheckinrule_set.filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=order.sales_channel_id),
|
||||
Q(all_products=True)
|
||||
| Q(limit_products__in=[op.item_id for op in positions])
|
||||
| Q(limit_variations__in=[op.variation_id for op in positions]),
|
||||
payment_q,
|
||||
mode=mode,
|
||||
)
|
||||
.distinct()
|
||||
.select_related("list")
|
||||
)
|
||||
|
||||
if any(r.list is None for r in rules):
|
||||
all_lists = sender.checkin_lists.filter(
|
||||
Q(subevent__isnull=True)
|
||||
| Q(subevent__in=[op.subevent_id for op in positions])
|
||||
).prefetch_related("limit_products")
|
||||
else:
|
||||
all_lists = []
|
||||
|
||||
for r in rules:
|
||||
if r.list is not None:
|
||||
lists = [r.list]
|
||||
else:
|
||||
lists = all_lists
|
||||
|
||||
for cl in lists:
|
||||
for op in positions:
|
||||
if not cl.all_products and op.item_id not in {
|
||||
i.pk for i in cl.limit_products.all()
|
||||
}:
|
||||
continue
|
||||
if cl.subevent_id and cl.subevent_id != op.subevent_id:
|
||||
continue
|
||||
|
||||
ci, created = Checkin.objects.get_or_create(
|
||||
position=op,
|
||||
list=cl,
|
||||
auto_checked_in=True,
|
||||
type=Checkin.TYPE_ENTRY,
|
||||
)
|
||||
if created:
|
||||
checkin_created.send(sender, checkin=ci)
|
||||
|
||||
|
||||
@receiver(order_placed, dispatch_uid="autocheckin_order_placed")
|
||||
def order_placed_receiver(sender, order, **kwargs):
|
||||
mode = AutoCheckinRule.MODE_PLACED
|
||||
payment_methods = set()
|
||||
perform_auto_checkin(sender, order, mode, payment_methods)
|
||||
|
||||
|
||||
@receiver(order_paid, dispatch_uid="autocheckin_order_paid")
|
||||
def order_paid_receiver(sender, order, **kwargs):
|
||||
mode = AutoCheckinRule.MODE_PAID
|
||||
payment_methods = {
|
||||
p.provider
|
||||
for p in order.payments.filter(
|
||||
state__in=[
|
||||
OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
OrderPayment.PAYMENT_STATE_REFUNDED,
|
||||
]
|
||||
)
|
||||
}
|
||||
perform_auto_checkin(sender, order, mode, payment_methods)
|
||||
@@ -0,0 +1,34 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Create auto check-in rule" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Create auto check-in rule" %}</h1>
|
||||
{% block inner %}
|
||||
<form class="form-horizontal" method="post" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Auto check-in" %}</legend>
|
||||
{% bootstrap_field form.mode layout='control' %}
|
||||
{% bootstrap_field form.list layout='control' %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Conditions" %}</legend>
|
||||
{% bootstrap_field form.all_sales_channels layout='control' %}
|
||||
{% bootstrap_field form.limit_sales_channels layout='control' %}
|
||||
{% bootstrap_field form.all_products layout='control' %}
|
||||
{% bootstrap_field form.itemvars layout='control' %}
|
||||
{% bootstrap_field form.all_payment_methods layout='control' %}
|
||||
{% bootstrap_field form.limit_payment_methods layout='control' %}
|
||||
</fieldset>
|
||||
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Delete auto-checkin rule" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Delete auto-checkin rule" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans %}Are you sure you want to delete the auto check-in rule?{% endblocktrans %}</p>
|
||||
<div class="form-group submit-group">
|
||||
<a class="btn btn-default btn-cancel" href="{% url "plugins:autocheckin:index" organizer=request.organizer.slug event=request.event.slug %}">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-danger btn-save">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,34 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Auto check-in rule" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Auto check-in rule" %}</h1>
|
||||
{% block inner %}
|
||||
<form class="form-horizontal" method="post" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Auto check-in" %}</legend>
|
||||
{% bootstrap_field form.mode layout='control' %}
|
||||
{% bootstrap_field form.list layout='control' %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Conditions" %}</legend>
|
||||
{% bootstrap_field form.all_sales_channels layout='control' %}
|
||||
{% bootstrap_field form.limit_sales_channels layout='control' %}
|
||||
{% bootstrap_field form.all_products layout='control' %}
|
||||
{% bootstrap_field form.itemvars layout='control' %}
|
||||
{% bootstrap_field form.all_payment_methods layout='control' %}
|
||||
{% bootstrap_field form.limit_payment_methods layout='control' %}
|
||||
</fieldset>
|
||||
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,100 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load money %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Auto check-in rules" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Auto check-in rules" %}</h1>
|
||||
{% if rules|length == 0 %}
|
||||
<div class="empty-collection">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You haven't created any rules yet.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<a href="{% url "plugins:autocheckin:add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new check-in rule" %}
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>
|
||||
<a href="{% url "plugins:autocheckin:add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new check-in rule" %}
|
||||
</a>
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-quotas">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Check-in list" %}</th>
|
||||
<th>{% trans "Sales channels" %}</th>
|
||||
<th>{% trans "Products" %}</th>
|
||||
<th>{% trans "Payment methods" %}</th>
|
||||
<th class="action-col-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rules %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if r.list %}
|
||||
{{ r.list }}
|
||||
{% else %}
|
||||
<em>{% trans "All" %}</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% for c in sales_channels %}
|
||||
{% if r.all_sales_channels or c in r.limit_sales_channels.all %}
|
||||
{% if "." in c.icon %}
|
||||
<img src="{% static c.icon %}" class="fa-like-image"
|
||||
data-toggle="tooltip" title="{{ c.label }}">
|
||||
{% else %}
|
||||
<span class="fa fa-fw fa-{{ c.icon }} text-muted"
|
||||
data-toggle="tooltip" title="{{ c.label }}"></span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.all_products %}
|
||||
<em>{% trans "All" %}</em>
|
||||
{% else %}
|
||||
<ul>
|
||||
{% for i in r.limit_products.all %}
|
||||
<li>{{ i }}</li>
|
||||
{% endfor %}
|
||||
{% for v in r.limit_variations.all %}
|
||||
<li>{{ v.item }} – {{ v.value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.all_payment_methods %}
|
||||
<em>{% trans "All" %}</em>
|
||||
{% else %}
|
||||
<ul>
|
||||
{% for p in r.pprovs %}
|
||||
<li>{{ p.verbose_name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right flip">
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<a href="{% url "plugins:autocheckin:edit" organizer=request.event.organizer.slug event=request.event.slug rule=r.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "plugins:autocheckin:add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ r.id }}"
|
||||
class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>
|
||||
<a href="{% url "plugins:autocheckin:delete" organizer=request.event.organizer.slug event=request.event.slug rule=r.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
51
src/pretix/plugins/autocheckin/urls.py
Normal file
51
src/pretix/plugins/autocheckin/urls.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import event_router
|
||||
|
||||
from . import views
|
||||
from .api import RuleViewSet
|
||||
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
r"^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/autocheckin/$",
|
||||
views.IndexView.as_view(),
|
||||
name="index",
|
||||
),
|
||||
re_path(
|
||||
r"^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/autocheckin/add$",
|
||||
views.RuleAddView.as_view(),
|
||||
name="add",
|
||||
),
|
||||
re_path(
|
||||
r"^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/autocheckin/(?P<rule>\d+)/delete$",
|
||||
views.RuleDeleteView.as_view(),
|
||||
name="delete",
|
||||
),
|
||||
re_path(
|
||||
r"^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/autocheckin/(?P<rule>\d+)/$",
|
||||
views.RuleEditView.as_view(),
|
||||
name="edit",
|
||||
),
|
||||
]
|
||||
event_router.register(r"auto_checkin_rules", RuleViewSet, basename="autocheckinrules")
|
||||
210
src/pretix/plugins/autocheckin/views.py
Normal file
210
src/pretix/plugins/autocheckin/views.py
Normal file
@@ -0,0 +1,210 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DeleteView, ListView
|
||||
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import CreateView, UpdateView
|
||||
from pretix.helpers.models import modelcopy
|
||||
from pretix.plugins.autocheckin.forms import AutoCheckinRuleForm
|
||||
from pretix.plugins.autocheckin.models import AutoCheckinRule
|
||||
|
||||
|
||||
class IndexView(EventPermissionRequiredMixin, ListView):
|
||||
permission = "can_change_event_settings"
|
||||
template_name = "pretixplugins/autocheckin/index.html"
|
||||
paginate_by = 50
|
||||
context_object_name = "rules"
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
self.request.event.autocheckinrule_set.select_related(
|
||||
"list",
|
||||
)
|
||||
.prefetch_related(
|
||||
"limit_sales_channels",
|
||||
"limit_products",
|
||||
"limit_variations",
|
||||
"limit_variations__item",
|
||||
)
|
||||
.order_by(
|
||||
"list__name",
|
||||
"list_id",
|
||||
"pk",
|
||||
)
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx["sales_channels"] = self.request.organizer.sales_channels.all()
|
||||
|
||||
pprovs = self.request.event.get_payment_providers()
|
||||
for r in ctx["rules"]:
|
||||
r.pprovs = [pprovs[p] for p in r.limit_payment_methods if p in pprovs]
|
||||
return ctx
|
||||
|
||||
|
||||
class RuleAddView(EventPermissionRequiredMixin, CreateView):
|
||||
template_name = "pretixplugins/autocheckin/add.html"
|
||||
permission = "can_change_event_settings"
|
||||
form_class = AutoCheckinRuleForm
|
||||
model = AutoCheckinRule
|
||||
|
||||
@cached_property
|
||||
def copy_from(self):
|
||||
if self.request.GET.get("copy_from") and not getattr(self, "object", None):
|
||||
try:
|
||||
return AutoCheckinRule.objects.get(
|
||||
pk=self.request.GET.get("copy_from"), event=self.request.event
|
||||
)
|
||||
except AutoCheckinRule.DoesNotExist:
|
||||
pass
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["event"] = self.request.event
|
||||
|
||||
if self.copy_from:
|
||||
i = modelcopy(self.copy_from)
|
||||
i.pk = None
|
||||
kwargs["instance"] = i
|
||||
kwargs.setdefault("initial", {})
|
||||
kwargs["initial"]["itemvars"] = [
|
||||
str(i.pk) for i in self.copy_from.limit_products.all()
|
||||
] + [
|
||||
"{}-{}".format(v.item_id, v.pk)
|
||||
for v in self.copy_from.limit_variations.all()
|
||||
]
|
||||
kwargs["initial"][
|
||||
"limit_payment_methods"
|
||||
] = self.copy_from.limit_payment_methods
|
||||
kwargs["initial"][
|
||||
"limit_sales_channels"
|
||||
] = self.copy_from.limit_sales_channels.all()
|
||||
return kwargs
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(
|
||||
self.request, _("We could not save your changes. See below for details.")
|
||||
)
|
||||
return super().form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
self.output = {}
|
||||
|
||||
messages.success(self.request, _("Your rule has been created."))
|
||||
|
||||
form.instance.event = self.request.event
|
||||
|
||||
with transaction.atomic():
|
||||
self.object = form.save()
|
||||
form.instance.log_action(
|
||||
"pretix.plugins.autocheckin.rule.added",
|
||||
user=self.request.user,
|
||||
data=dict(form.cleaned_data),
|
||||
)
|
||||
|
||||
return redirect(
|
||||
"plugins:autocheckin:edit",
|
||||
event=self.request.event.slug,
|
||||
organizer=self.request.event.organizer.slug,
|
||||
rule=self.object.pk,
|
||||
)
|
||||
|
||||
|
||||
class RuleEditView(EventPermissionRequiredMixin, UpdateView):
|
||||
model = AutoCheckinRule
|
||||
form_class = AutoCheckinRuleForm
|
||||
template_name = "pretixplugins/autocheckin/edit.html"
|
||||
permission = "can_change_event_settings"
|
||||
|
||||
def get_object(self, queryset=None) -> AutoCheckinRule:
|
||||
return get_object_or_404(
|
||||
AutoCheckinRule.objects.all(),
|
||||
event=self.request.event,
|
||||
id=self.kwargs["rule"],
|
||||
)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(
|
||||
"plugins:autocheckin:edit",
|
||||
kwargs={
|
||||
"organizer": self.request.event.organizer.slug,
|
||||
"event": self.request.event.slug,
|
||||
"rule": self.object.pk,
|
||||
},
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _("Your changes have been saved."))
|
||||
form.instance.log_action(
|
||||
"pretix.plugins.autocheckin.rule.changed",
|
||||
user=self.request.user,
|
||||
data=dict(form.cleaned_data),
|
||||
)
|
||||
return super().form_valid(form)
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(
|
||||
self.request, _("We could not save your changes. See below for details.")
|
||||
)
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class RuleDeleteView(EventPermissionRequiredMixin, DeleteView):
|
||||
model = AutoCheckinRule
|
||||
permission = "can_change_event_settings"
|
||||
template_name = "pretixplugins/autocheckin/delete.html"
|
||||
context_object_name = "rule"
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(
|
||||
"plugins:autocheckin:index",
|
||||
kwargs={
|
||||
"organizer": self.request.event.organizer.slug,
|
||||
"event": self.request.event.slug,
|
||||
},
|
||||
)
|
||||
|
||||
def get_object(self, queryset=None) -> AutoCheckinRule:
|
||||
return get_object_or_404(
|
||||
AutoCheckinRule, event=self.request.event, id=self.kwargs["rule"]
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
success_url = self.get_success_url()
|
||||
|
||||
self.request.event.log_action(
|
||||
"pretix.plugins.autocheckin.rule.deleted", user=self.request.user, data={}
|
||||
)
|
||||
|
||||
self.object.delete()
|
||||
messages.success(self.request, _("The selected rule has been deleted."))
|
||||
return redirect(success_url)
|
||||
@@ -212,7 +212,7 @@ if config.getboolean('pretix', 'trust_x_forwarded_proto', fallback=False):
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default',
|
||||
fallback='pretix.plugins.sendmail,pretix.plugins.statistics,pretix.plugins.checkinlists,pretix.plugins.autocheckin')
|
||||
fallback='pretix.plugins.sendmail,pretix.plugins.statistics,pretix.plugins.checkinlists')
|
||||
PRETIX_PLUGINS_EXCLUDE = config.get('pretix', 'plugins_exclude', fallback='').split(',')
|
||||
PRETIX_PLUGINS_SHOW_META = config.getboolean('pretix', 'plugins_show_meta', fallback=True)
|
||||
|
||||
|
||||
21
src/tests/plugins/autocheckin/__init__.py
Normal file
21
src/tests/plugins/autocheckin/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
96
src/tests/plugins/autocheckin/conftest.py
Normal file
96
src/tests/plugins/autocheckin/conftest.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from pretix.base.models import Event, Organizer, Team
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def organizer():
|
||||
return Organizer.objects.create(name="Dummy", slug="dummy")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def event(organizer):
|
||||
e = Event.objects.create(
|
||||
organizer=organizer,
|
||||
name="Dummy",
|
||||
slug="dummy",
|
||||
date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=timezone.utc),
|
||||
plugins="pretix.plugins.banktransfer,pretix.plugins.ticketoutputpdf,pretix.plugins.autocheckin",
|
||||
is_public=True,
|
||||
)
|
||||
e.settings.timezone = "Europe/Berlin"
|
||||
return e
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def item(event):
|
||||
return event.items.create(name="foo", default_price=3)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def checkin_list(event):
|
||||
return event.checkin_lists.create(name="foo")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def team(organizer):
|
||||
return Team.objects.create(
|
||||
organizer=organizer,
|
||||
name="Test-Team",
|
||||
all_events=True,
|
||||
can_change_teams=True,
|
||||
can_manage_gift_cards=True,
|
||||
can_change_items=True,
|
||||
can_create_events=True,
|
||||
can_change_event_settings=True,
|
||||
can_change_vouchers=True,
|
||||
can_view_vouchers=True,
|
||||
can_view_orders=True,
|
||||
can_change_orders=True,
|
||||
can_manage_customers=True,
|
||||
can_manage_reusable_media=True,
|
||||
can_change_organizer_settings=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
return APIClient()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def token_client(client, team):
|
||||
t = team.tokens.create(name="Foo")
|
||||
client.credentials(HTTP_AUTHORIZATION="Token " + t.token)
|
||||
return client
|
||||
155
src/tests/plugins/autocheckin/test_api.py
Normal file
155
src/tests/plugins/autocheckin/test_api.py
Normal file
@@ -0,0 +1,155 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import copy
|
||||
|
||||
import pytest
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def acr(event, item):
|
||||
acr = event.autocheckinrule_set.create(
|
||||
all_products=False,
|
||||
)
|
||||
acr.limit_products.add(item)
|
||||
return acr
|
||||
|
||||
|
||||
RES_RULE = {
|
||||
"list": None,
|
||||
"mode": "placed",
|
||||
"all_sales_channels": True,
|
||||
"limit_sales_channels": [],
|
||||
"all_products": False,
|
||||
"limit_products": [],
|
||||
"limit_variations": [],
|
||||
"all_payment_methods": True,
|
||||
"limit_payment_methods": set(),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_api_list(event, acr, item, token_client):
|
||||
res = copy.copy(RES_RULE)
|
||||
res["id"] = acr.pk
|
||||
res["limit_products"] = [item.pk]
|
||||
r = token_client.get(
|
||||
"/api/v1/organizers/{}/events/{}/auto_checkin_rules/".format(
|
||||
event.organizer.slug, event.slug
|
||||
)
|
||||
).data
|
||||
|
||||
assert r["results"] == [res]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_api_detail(event, acr, item, token_client):
|
||||
res = copy.copy(RES_RULE)
|
||||
res["id"] = acr.pk
|
||||
res["limit_products"] = [item.pk]
|
||||
r = token_client.get(
|
||||
"/api/v1/organizers/{}/events/{}/auto_checkin_rules/{}/".format(
|
||||
event.organizer.slug, event.slug, acr.pk
|
||||
)
|
||||
).data
|
||||
assert r == res
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_api_create(event, acr, item, token_client):
|
||||
resp = token_client.post(
|
||||
"/api/v1/organizers/{}/events/{}/auto_checkin_rules/".format(
|
||||
event.slug, event.slug
|
||||
),
|
||||
{
|
||||
"all_products": False,
|
||||
"limit_products": [item.pk],
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
acr = event.autocheckinrule_set.get(pk=resp.data["id"])
|
||||
assert list(acr.limit_products.all()) == [item]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_api_create_validate_pprov(event, acr, item, token_client):
|
||||
resp = token_client.post(
|
||||
"/api/v1/organizers/{}/events/{}/auto_checkin_rules/".format(
|
||||
event.slug, event.slug
|
||||
),
|
||||
{
|
||||
"mode": "placed",
|
||||
"all_payment_methods": False,
|
||||
"limit_payment_methods": ["manual"],
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {
|
||||
"non_field_errors": ["all_payment_methods should be used for mode=placed"]
|
||||
}
|
||||
|
||||
resp = token_client.post(
|
||||
"/api/v1/organizers/{}/events/{}/auto_checkin_rules/".format(
|
||||
event.slug, event.slug
|
||||
),
|
||||
{
|
||||
"mode": "paid",
|
||||
"all_payment_methods": False,
|
||||
"limit_payment_methods": ["unknown"],
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {"limit_payment_methods": ['"unknown" is not a valid choice.']}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_api_update(event, acr, item, token_client):
|
||||
resp = token_client.patch(
|
||||
"/api/v1/organizers/{}/events/{}/auto_checkin_rules/{}/".format(
|
||||
event.slug, event.slug, acr.pk
|
||||
),
|
||||
{
|
||||
"mode": "paid",
|
||||
"all_payment_methods": False,
|
||||
"limit_payment_methods": ["manual"],
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
acr.refresh_from_db()
|
||||
assert acr.all_payment_methods is False
|
||||
assert acr.limit_payment_methods == ["manual"]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_api_delete(event, acr, item, token_client):
|
||||
resp = token_client.delete(
|
||||
"/api/v1/organizers/{}/events/{}/auto_checkin_rules/{}/".format(
|
||||
event.slug, event.slug, acr.pk
|
||||
),
|
||||
)
|
||||
assert resp.status_code == 204
|
||||
assert not event.autocheckinrule_set.exists()
|
||||
310
src/tests/plugins/autocheckin/test_checkin.py
Normal file
310
src/tests/plugins/autocheckin/test_checkin.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import copy
|
||||
from datetime import timedelta
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
from tests.api.test_order_create import ORDER_CREATE_PAYLOAD
|
||||
|
||||
from pretix.base.models import Order, OrderPayment
|
||||
from pretix.base.signals import order_paid, order_placed
|
||||
from pretix.plugins.autocheckin.models import AutoCheckinRule
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def order(organizer, event, item):
|
||||
order = Order.objects.create(
|
||||
event=event,
|
||||
status=Order.STATUS_PENDING,
|
||||
expires=now() + timedelta(days=3),
|
||||
sales_channel=organizer.sales_channels.get(identifier="web"),
|
||||
total=4,
|
||||
)
|
||||
order.positions.create(order=order, item=item, price=2)
|
||||
return order
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_sales_channel_all(event, item, order, checkin_list):
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PLACED,
|
||||
all_sales_channels=True,
|
||||
)
|
||||
order_placed.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_sales_channel_limit(event, item, order, checkin_list):
|
||||
acr = event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PLACED,
|
||||
all_sales_channels=False,
|
||||
)
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert not order.positions.first().checkins.exists()
|
||||
|
||||
acr.limit_sales_channels.add(order.sales_channel)
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_items_all(event, item, order, checkin_list):
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PLACED,
|
||||
all_products=True,
|
||||
)
|
||||
order_placed.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_items_limit(event, item, order, checkin_list):
|
||||
acr = event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PLACED,
|
||||
all_products=False,
|
||||
)
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert not order.positions.first().checkins.exists()
|
||||
|
||||
acr.limit_products.add(item)
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_variations_limit(event, item, order, checkin_list):
|
||||
var = item.variations.create(value="V1")
|
||||
op = order.positions.first()
|
||||
op.variation = var
|
||||
op.save()
|
||||
|
||||
acr = event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PLACED,
|
||||
all_products=False,
|
||||
)
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert not order.positions.first().checkins.exists()
|
||||
|
||||
acr.limit_variations.add(var)
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
order.positions.first().checkins.all().delete()
|
||||
acr.limit_products.add(item)
|
||||
acr.limit_variations.clear()
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_mode_placed(event, item, order, checkin_list):
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PLACED,
|
||||
)
|
||||
|
||||
order_paid.send(event, order=order)
|
||||
assert not order.positions.first().checkins.exists()
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_mode_paid(event, item, order, checkin_list):
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
)
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
assert not order.positions.first().checkins.exists()
|
||||
|
||||
order_paid.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_payment_provider_limit(event, item, order, checkin_list):
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
all_payment_methods=False,
|
||||
limit_payment_methods=["manual"],
|
||||
)
|
||||
|
||||
p = order.payments.create(
|
||||
amount=order.total,
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider="banktransfer",
|
||||
)
|
||||
|
||||
order_paid.send(event, order=order)
|
||||
assert not order.positions.first().checkins.exists()
|
||||
|
||||
p.provider = "manual"
|
||||
p.save()
|
||||
order_paid.send(event, order=order)
|
||||
assert order.positions.first().checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_idempodency(event, item, order, checkin_list):
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
)
|
||||
order_paid.send(event, order=order)
|
||||
assert order.positions.first().checkins.count() == 1
|
||||
order_paid.send(event, order=order)
|
||||
assert order.positions.first().checkins.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_multiple_rules_same_list(event, item, order, checkin_list):
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
)
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
)
|
||||
order_paid.send(event, order=order)
|
||||
assert order.positions.first().checkins.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_multiple_rules_different_lists(event, item, order, checkin_list):
|
||||
cl2 = event.checkin_lists.create(name="bar")
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
)
|
||||
event.autocheckinrule_set.create(
|
||||
list=cl2,
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
)
|
||||
order_paid.send(event, order=order)
|
||||
assert order.positions.first().checkins.count() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_autodetect_lists(event, item, order, checkin_list):
|
||||
cl2 = event.checkin_lists.create(name="bar", all_products=False)
|
||||
cl2.limit_products.add(item)
|
||||
event.checkin_lists.create(name="baz", all_products=False)
|
||||
|
||||
event.autocheckinrule_set.create(
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
)
|
||||
order_paid.send(event, order=order)
|
||||
|
||||
assert {c.list_id for c in order.positions.first().checkins.all()} == {
|
||||
checkin_list.pk,
|
||||
cl2.pk,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_order_create_via_api_placed(
|
||||
token_client, organizer, event, item, checkin_list
|
||||
):
|
||||
acr = event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PLACED,
|
||||
all_sales_channels=False,
|
||||
)
|
||||
acr.limit_sales_channels.add(organizer.sales_channels.get(identifier="web"))
|
||||
|
||||
q = event.quotas.create(name="Foo", size=None)
|
||||
q.items.add(item)
|
||||
|
||||
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
|
||||
res["positions"][0]["item"] = item.pk
|
||||
res["positions"][0]["answers"] = []
|
||||
resp = token_client.post(
|
||||
"/api/v1/organizers/{}/events/{}/orders/".format(organizer.slug, event.slug),
|
||||
format="json",
|
||||
data=res,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
o = Order.objects.get(code=resp.data["code"])
|
||||
assert o.positions.first().checkins.first().auto_checked_in
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_order_create_via_api_paid(token_client, organizer, event, item, checkin_list):
|
||||
event.autocheckinrule_set.create(
|
||||
list=checkin_list,
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
all_payment_methods=False,
|
||||
limit_payment_methods=["manual"],
|
||||
)
|
||||
q = event.quotas.create(name="Foo", size=None)
|
||||
q.items.add(item)
|
||||
|
||||
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
|
||||
res["positions"][0]["item"] = item.pk
|
||||
res["positions"][0]["answers"] = []
|
||||
res["status"] = "p"
|
||||
res["payment_provider"] = "manual"
|
||||
resp = token_client.post(
|
||||
"/api/v1/organizers/{}/events/{}/orders/".format(organizer.slug, event.slug),
|
||||
format="json",
|
||||
data=res,
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
o = Order.objects.get(code=resp.data["code"])
|
||||
assert o.positions.first().checkins.first().auto_checked_in
|
||||
185
src/tests/plugins/autocheckin/test_control.py
Normal file
185
src/tests/plugins/autocheckin/test_control.py
Normal file
@@ -0,0 +1,185 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import datetime
|
||||
|
||||
from django_scopes import scopes_disabled
|
||||
from tests.base import SoupTest, extract_form_fields
|
||||
|
||||
from pretix.base.models import Event, Item, Organizer, Team, User
|
||||
from pretix.plugins.autocheckin.models import AutoCheckinRule
|
||||
|
||||
|
||||
class AutoCheckinFormTest(SoupTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_user("dummy@dummy.dummy", "dummy")
|
||||
self.orga1 = Organizer.objects.create(name="CCC", slug="ccc")
|
||||
self.orga2 = Organizer.objects.create(name="MRM", slug="mrm")
|
||||
self.event1 = Event.objects.create(
|
||||
organizer=self.orga1,
|
||||
name="30C3",
|
||||
slug="30c3",
|
||||
plugins="pretix.plugins.autocheckin",
|
||||
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
self.item1 = Item.objects.create(
|
||||
event=self.event1, name="Standard", default_price=0, position=1
|
||||
)
|
||||
t = Team.objects.create(
|
||||
organizer=self.orga1,
|
||||
can_change_event_settings=True,
|
||||
can_view_orders=True,
|
||||
can_change_items=True,
|
||||
all_events=True,
|
||||
can_create_events=True,
|
||||
can_change_orders=True,
|
||||
can_change_vouchers=True,
|
||||
)
|
||||
t.members.add(self.user)
|
||||
t = Team.objects.create(
|
||||
organizer=self.orga2,
|
||||
can_change_event_settings=True,
|
||||
can_view_orders=True,
|
||||
can_change_items=True,
|
||||
all_events=True,
|
||||
can_create_events=True,
|
||||
can_change_orders=True,
|
||||
can_change_vouchers=True,
|
||||
)
|
||||
t.members.add(self.user)
|
||||
self.client.login(email="dummy@dummy.dummy", password="dummy")
|
||||
|
||||
def test_create(self):
|
||||
doc = self.get_doc(
|
||||
"/control/event/%s/%s/autocheckin/add" % (self.orga1.slug, self.event1.slug)
|
||||
)
|
||||
form_data = extract_form_fields(doc.select(".container-fluid form")[0])
|
||||
doc = self.post_doc(
|
||||
"/control/event/%s/%s/autocheckin/add"
|
||||
% (self.orga1.slug, self.event1.slug),
|
||||
form_data,
|
||||
)
|
||||
assert doc.select(".alert-success")
|
||||
assert self.event1.autocheckinrule_set.exists()
|
||||
|
||||
def test_delete(self):
|
||||
acr = self.event1.autocheckinrule_set.create(
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
all_payment_methods=False,
|
||||
limit_payment_methods=["manual"],
|
||||
)
|
||||
doc = self.get_doc(
|
||||
"/control/event/%s/%s/autocheckin/%s/delete"
|
||||
% (self.orga1.slug, self.event1.slug, acr.id)
|
||||
)
|
||||
form_data = extract_form_fields(doc.select(".container-fluid form")[0])
|
||||
doc = self.post_doc(
|
||||
"/control/event/%s/%s/autocheckin/%s/delete"
|
||||
% (self.orga1.slug, self.event1.slug, acr.id),
|
||||
form_data,
|
||||
)
|
||||
assert doc.select(".alert-success")
|
||||
with scopes_disabled():
|
||||
assert self.event1.autocheckinrule_set.count() == 0
|
||||
|
||||
def test_item_copy(self):
|
||||
with scopes_disabled():
|
||||
acr = self.event1.autocheckinrule_set.create(
|
||||
mode=AutoCheckinRule.MODE_PAID, all_products=False
|
||||
)
|
||||
acr.limit_products.add(self.item1)
|
||||
|
||||
self.client.post(
|
||||
"/control/event/%s/%s/items/add" % (self.orga1.slug, self.event1.slug),
|
||||
{
|
||||
"name_0": "Intermediate",
|
||||
"default_price": "23.00",
|
||||
"tax_rate": "19.00",
|
||||
"copy_from": str(self.item1.pk),
|
||||
"has_variations": "1",
|
||||
},
|
||||
)
|
||||
with scopes_disabled():
|
||||
acr.refresh_from_db()
|
||||
i_new = Item.objects.get(name__icontains="Intermediate")
|
||||
assert i_new in acr.limit_products.all()
|
||||
|
||||
def test_copy_event(self):
|
||||
with scopes_disabled():
|
||||
acr = self.event1.autocheckinrule_set.create(
|
||||
list=self.event1.checkin_lists.create(name="Test"),
|
||||
mode=AutoCheckinRule.MODE_PAID,
|
||||
all_products=False,
|
||||
all_sales_channels=False,
|
||||
)
|
||||
acr.limit_products.add(self.item1)
|
||||
acr.limit_sales_channels.add(
|
||||
self.orga1.sales_channels.get(identifier="web")
|
||||
)
|
||||
|
||||
self.post_doc(
|
||||
"/control/events/add",
|
||||
{
|
||||
"event_wizard-current_step": "foundation",
|
||||
"event_wizard-prefix": "event_wizard",
|
||||
"foundation-organizer": self.orga2.pk,
|
||||
"foundation-locales": ("en",),
|
||||
},
|
||||
)
|
||||
self.post_doc(
|
||||
"/control/events/add",
|
||||
{
|
||||
"event_wizard-current_step": "basics",
|
||||
"event_wizard-prefix": "event_wizard",
|
||||
"basics-name_0": "33C3",
|
||||
"basics-slug": "33c3",
|
||||
"basics-date_from_0": "2016-12-27",
|
||||
"basics-date_from_1": "10:00:00",
|
||||
"basics-date_to_0": "2016-12-30",
|
||||
"basics-date_to_1": "19:00:00",
|
||||
"basics-location_0": "Hamburg",
|
||||
"basics-currency": "EUR",
|
||||
"basics-tax_rate": "19.00",
|
||||
"basics-locale": "en",
|
||||
"basics-timezone": "Europe/Berlin",
|
||||
},
|
||||
)
|
||||
self.post_doc(
|
||||
"/control/events/add",
|
||||
{
|
||||
"event_wizard-current_step": "copy",
|
||||
"event_wizard-prefix": "event_wizard",
|
||||
"copy-copy_from_event": self.event1.pk,
|
||||
},
|
||||
)
|
||||
|
||||
with scopes_disabled():
|
||||
ev = Event.objects.get(slug="33c3")
|
||||
i_new = ev.items.first()
|
||||
acr_new = ev.autocheckinrule_set.get()
|
||||
|
||||
assert i_new in acr_new.limit_products.all()
|
||||
assert list(acr_new.limit_sales_channels.all()) == [
|
||||
self.orga2.sales_channels.get(identifier="web")
|
||||
]
|
||||
|
||||
assert acr_new.list.event == ev
|
||||
Reference in New Issue
Block a user