Check-in: Show more information (#3576)

* Check-in: Show more information

* Add change notes

* Rebase migration

* Add "expand" option to checkinrpc

* REmove accidental file

* Docs fixes

* REbase migration

* Rebase migration

* Fix typo

* REbase migration

* Make web-checkin look more like new android checkin
This commit is contained in:
Raphael Michel
2023-11-28 14:52:12 +01:00
committed by GitHub
parent ab28086779
commit 8a3b313cb6
35 changed files with 346 additions and 41 deletions
+8 -1
View File
@@ -31,6 +31,7 @@ Checking a ticket in
This endpoint supports passing multiple check-in lists to perform a multi-event scan. However, each check-in list
passed needs to be from a distinct event.
:query string expand: Expand a field inside the ``position`` object into a full object. Currently ``subevent``, ``item``, ``variation``, and ``answers.question`` are supported. Can be passed multiple times.
:<json string secret: Scanned QR code corresponding to the ``secret`` attribute of a ticket.
:<json string source_type: Type of source the ``secret`` was obtained form. Defaults to ``"barcode"``.
:<json array lists: List of check-in list IDs to search on. No two check-in lists may be from the same event.
@@ -63,6 +64,7 @@ Checking a ticket in
``checkin_attention`` flag set. (3) If ``attendee_name`` is empty, it may automatically fall
back to values from a parent product or from invoice addresses.
:>json boolean require_attention: Whether or not the ``require_attention`` flag is set on the item or order.
:>json list checkin_texts: List of additional texts to show to the user.
:>json object list: Excerpt of information about the matching :ref:`check-in list <rest-checkinlists>` (if any was found),
including the attributes ``id``, ``name``, ``event``, ``subevent``, and ``include_pending``.
:>json object questions: List of questions to be answered for check-in, only set on status ``"incomplete"``.
@@ -103,6 +105,7 @@ Checking a ticket in
},
"require_attention": false,
"checkin_texts": [],
"list": {
"id": 1,
"name": "Default check-in list",
@@ -125,6 +128,7 @@ Checking a ticket in
},
"require_attention": false,
"checkin_texts": [],
"list": {
"id": 1,
"name": "Default check-in list",
@@ -142,6 +146,7 @@ Checking a ticket in
"position": 1,
"identifier": "WY3TP9SL",
"ask_during_checkin": true,
"show_during_checkin": true,
"options": [
{
"id": 1,
@@ -178,7 +183,8 @@ Checking a ticket in
"status": "error",
"reason": "invalid",
"reason_explanation": null,
"require_attention": false
"require_attention": false,
"checkin_texts": []
}
**Example error response (known, but invalid ticket)**:
@@ -193,6 +199,7 @@ Checking a ticket in
"reason": "unpaid",
"reason_explanation": null,
"require_attention": false,
"checkin_texts": [],
"list": {
"id": 1,
"name": "Default check-in list",
+2 -1
View File
@@ -498,7 +498,7 @@ Order position endpoints
``attendee_name,positionid``
:query string order: Only return positions of the order with the given order code
:query string search: Fuzzy search matching the attendee name, order code, invoice address name as well as to the beginning of the secret.
:query string expand: Expand a field into a full object. Currently only ``subevent``, ``item``, and ``variation`` are supported. Can be passed multiple times.
:query string expand: Expand a field into a full object. Currently ``subevent``, ``item``, ``variation``, and ``answers.question`` are supported. Can be passed multiple times.
:query integer item: Only return positions with the purchased item matching the given ID.
:query integer item__in: Only return positions with the purchased item matching one of the given comma-separated IDs.
:query integer variation: Only return positions with the purchased item variation matching the given ID.
@@ -707,6 +707,7 @@ Order position endpoints
"position": 1,
"identifier": "WY3TP9SL",
"ask_during_checkin": true,
"show_during_checkin": true,
"options": [
{
"id": 1,
+10
View File
@@ -29,6 +29,8 @@ position integer An integer, use
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if such
a variation is being scanned.
checkin_text string Text that will be shown if a ticket of this type is
scanned (or ``null``).
require_approval boolean If ``true``, orders with this variation will need to be
approved by the event organizer before they can be
paid.
@@ -58,6 +60,8 @@ meta_data object Values set for
.. versionchanged:: 2023.10
The ``free_price_suggestion`` attribute has been added.
The ``checkin_text`` attribute has been added.
Endpoints
---------
@@ -94,6 +98,7 @@ Endpoints
},
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -119,6 +124,7 @@ Endpoints
},
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -179,6 +185,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -218,6 +225,7 @@ Endpoints
"default_price": "10.00",
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -248,6 +256,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
@@ -309,6 +318,7 @@ Endpoints
"free_price_suggestion": null,
"active": false,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_hidden": false,
+20
View File
@@ -74,6 +74,8 @@ max_per_order integer This product
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if such
a product is being scanned.
checkin_text string Text that will be shown if a ticket of this type is
scanned (or ``null``).
original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
require_approval boolean If ``true``, orders with this product will need to be
@@ -137,6 +139,8 @@ variations list of objects A list with o
├ checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if such
a variation is being scanned.
├ checkin_text string Text that will be shown if a ticket of this type is
scanned (or ``null``).
├ require_approval boolean If ``true``, orders with this variation will need to be
approved by the event organizer before they can be
paid.
@@ -205,6 +209,7 @@ meta_data object Values set fo
.. versionchanged:: 2023.10
The ``checkin_text`` and ``variations[x].checkin_text`` attributes have been added.
The ``free_price_suggestion`` and ``variations[x].free_price_suggestion`` attributes have been added.
.. versionchanged:: 2023.10
@@ -283,6 +288,7 @@ Endpoints
"min_per_order": null,
"max_per_order": null,
"checkin_attention": false,
"checkin_text": null,
"has_variations": false,
"generate_tickets": null,
"allow_waitinglist": true,
@@ -312,6 +318,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -331,6 +338,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -421,6 +429,7 @@ Endpoints
"min_per_order": null,
"max_per_order": null,
"checkin_attention": false,
"checkin_text": null,
"has_variations": false,
"require_approval": false,
"require_bundling": false,
@@ -447,6 +456,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -466,6 +476,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -537,6 +548,7 @@ Endpoints
"min_per_order": null,
"max_per_order": null,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_bundling": false,
"require_membership": false,
@@ -562,6 +574,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -581,6 +594,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -640,6 +654,7 @@ Endpoints
"allow_waitinglist": true,
"show_quota_left": null,
"checkin_attention": false,
"checkin_text": null,
"has_variations": true,
"require_approval": false,
"require_bundling": false,
@@ -666,6 +681,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -685,6 +701,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -775,6 +792,7 @@ Endpoints
"min_per_order": null,
"max_per_order": null,
"checkin_attention": false,
"checkin_text": null,
"has_variations": true,
"require_approval": false,
"require_bundling": false,
@@ -801,6 +819,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
@@ -820,6 +839,7 @@ Endpoints
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"require_membership": false,
"require_membership_types": [],
+11
View File
@@ -46,6 +46,8 @@ custom_followup_at date Internal date f
checkin_attention boolean If ``true``, the check-in app should show a warning
that this ticket requires special attention if a ticket
of this order is scanned.
checkin_text string Text that will be shown if a ticket of this order is
scanned (or ``null``).
invoice_address object Invoice address information (can be ``null``)
├ last_modified datetime Last modification date of the address
├ company string Customer company name
@@ -135,6 +137,10 @@ last_modified datetime Last modificati
The ``event`` attribute has been added. The organizer-level endpoint has been added.
.. versionchanged:: 2023.10
The ``checkin_text`` attribute has been added.
.. versionchanged:: 2023.9
The ``customer`` query parameter has been added.
@@ -318,6 +324,7 @@ List of all orders
"comment": "",
"custom_followup_at": null,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"valid_if_pending": false,
"invoice_address": {
@@ -539,6 +546,7 @@ Fetching individual orders
"comment": "",
"custom_followup_at": null,
"checkin_attention": false,
"checkin_text": null,
"require_approval": false,
"valid_if_pending": false,
"invoice_address": {
@@ -709,6 +717,8 @@ Updating order fields
* ``checkin_attention``
* ``checkin_text``
* ``locale``
* ``comment``
@@ -924,6 +934,7 @@ Creating orders
* ``comment`` (optional)
* ``custom_followup_at`` (optional)
* ``checkin_attention`` (optional)
* ``checkin_text`` (optional)
* ``require_approval`` (optional)
* ``valid_if_pending`` (optional)
* ``invoice_address`` (optional)
+11
View File
@@ -44,6 +44,8 @@ identifier string An arbitrary st
ask_during_checkin boolean If ``true``, this question will not be asked while
buying the ticket, but will show up when redeeming
the ticket instead.
show_during_checkin boolean If ``true``, the answer to the question will be shown
during check-in (if the check-in client supports it).
hidden boolean If ``true``, the question will only be shown in the
backend.
print_on_invoice boolean If ``true``, the question will only be shown on
@@ -77,6 +79,10 @@ dependency_value string An old version
for one value. **Deprecated.**
===================================== ========================== =======================================================
.. versionchanged:: 2023.8
The ``show_during_checkin`` attribute has been added.
Endpoints
---------
@@ -115,6 +121,7 @@ Endpoints
"position": 1,
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"show_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"valid_number_min": null,
@@ -194,6 +201,7 @@ Endpoints
"position": 1,
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"show_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"valid_number_min": null,
@@ -257,6 +265,7 @@ Endpoints
"items": [1, 2],
"position": 1,
"ask_during_checkin": false,
"show_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"dependency_question": null,
@@ -293,6 +302,7 @@ Endpoints
"position": 1,
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"show_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"dependency_question": null,
@@ -376,6 +386,7 @@ Endpoints
"position": 2,
"identifier": "WY3TP9SL",
"ask_during_checkin": false,
"show_during_checkin": false,
"hidden": false,
"print_on_invoice": false,
"dependency_question": null,
+7 -4
View File
@@ -61,7 +61,7 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price', 'free_price_suggestion', 'require_approval',
'require_membership', 'require_membership_types', 'require_membership_hidden',
'checkin_attention', 'available_from', 'available_until',
'checkin_attention', 'checkin_text', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher', 'meta_data')
def __init__(self, *args, **kwargs):
@@ -85,7 +85,7 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price', 'free_price_suggestion', 'require_approval',
'require_membership', 'require_membership_types', 'require_membership_hidden',
'checkin_attention', 'available_from', 'available_until',
'checkin_attention', 'checkin_text', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher', 'meta_data')
def __init__(self, *args, **kwargs):
@@ -237,7 +237,7 @@ class ItemSerializer(I18nAwareModelSerializer):
'default_price', 'free_price', 'free_price_suggestion', 'tax_rate', 'tax_rule', 'admission',
'personalized', 'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'allow_waitinglist',
'issue_giftcard', 'meta_data',
@@ -440,7 +440,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
class Meta:
model = Question
fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position',
'ask_during_checkin', 'identifier', 'dependency_question', 'dependency_values',
'ask_during_checkin', 'show_during_checkin', 'identifier', 'dependency_question', 'dependency_values',
'hidden', 'dependency_value', 'print_on_invoice', 'help_text', 'valid_number_min',
'valid_number_max', 'valid_date_min', 'valid_date_max', 'valid_datetime_min', 'valid_datetime_max',
'valid_string_length_max', 'valid_file_portrait')
@@ -486,6 +486,9 @@ class QuestionSerializer(I18nAwareModelSerializer):
if full_data.get('ask_during_checkin') and full_data.get('type') in Question.ASK_DURING_CHECKIN_UNSUPPORTED:
raise ValidationError(_('This type of question cannot be asked during check-in.'))
if full_data.get('show_during_checkin') and full_data.get('type') in Question.SHOW_DURING_CHECKIN_UNSUPPORTED:
raise ValidationError(_('This type of question cannot be shown during check-in.'))
Question.clean_items(event, full_data.get('items'))
return data
+10 -7
View File
@@ -44,7 +44,7 @@ from pretix.api.serializers import CompatibleJSONField
from pretix.api.serializers.event import SubEventSerializer
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.item import (
InlineItemVariationSerializer, ItemSerializer,
InlineItemVariationSerializer, ItemSerializer, QuestionSerializer,
)
from pretix.base.channels import get_all_sales_channels
from pretix.base.decimal import round_decimal
@@ -585,6 +585,9 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
if 'variation' in self.context['expand']:
self.fields['variation'] = InlineItemVariationSerializer(read_only=True)
if 'answers.question' in self.context['expand']:
self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True)
class OrderPaymentTypeField(serializers.Field):
# TODO: Remove after pretix 2.2
@@ -715,7 +718,7 @@ class OrderSerializer(I18nAwareModelSerializer):
fields = (
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer', 'valid_if_pending'
)
read_only_fields = (
@@ -771,8 +774,8 @@ class OrderSerializer(I18nAwareModelSerializer):
def update(self, instance, validated_data):
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'email', 'locale', 'phone',
'valid_if_pending']
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'checkin_text', 'email', 'locale',
'phone', 'valid_if_pending']
if 'invoice_address' in validated_data:
iadata = validated_data.pop('invoice_address')
@@ -1036,9 +1039,9 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
class Meta:
model = Order
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts',
'force', 'send_email', 'simulate', 'customer', 'custom_followup_at', 'require_approval',
'valid_if_pending')
'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date',
'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at',
'require_approval', 'valid_if_pending')
def validate_payment_provider(self, pp):
if pp is None:
+7
View File
@@ -536,6 +536,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'reason': Checkin.REASON_ALREADY_REDEEMED,
'reason_explanation': None,
'require_attention': False,
'checkin_texts': [],
'__warning': 'Compatibility hack active due to detected old pretixSCAN version',
}, status=400)
except: # we don't care e.g. about invalid version numbers
@@ -547,6 +548,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'reason': Checkin.REASON_INVALID,
'reason_explanation': None,
'require_attention': False,
'checkin_texts': [],
'list': MiniCheckinListSerializer(checkinlists[0]).data,
}, status=404)
elif revoked_matches and force:
@@ -576,6 +578,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'reason': Checkin.REASON_REVOKED,
'reason_explanation': None,
'require_attention': False,
'checkin_texts': [],
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, revoked_matches[
0].event)).data,
'list': MiniCheckinListSerializer(list_by_event[revoked_matches[0].event_id]).data,
@@ -631,6 +634,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'reason': Checkin.REASON_AMBIGUOUS,
'reason_explanation': None,
'require_attention': op.require_checkin_attention,
'checkin_texts': op.checkin_texts,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=400)
@@ -679,6 +683,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
return Response({
'status': 'incomplete',
'require_attention': op.require_checkin_attention,
'checkin_texts': op.checkin_texts,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'questions': [
QuestionSerializer(q).data for q in e.questions
@@ -709,6 +714,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'reason': e.code,
'reason_explanation': e.reason,
'require_attention': op.require_checkin_attention,
'checkin_texts': op.checkin_texts,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=400)
@@ -716,6 +722,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
return Response({
'status': 'ok',
'require_attention': op.require_checkin_attention,
'checkin_texts': op.checkin_texts,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=201)
+10
View File
@@ -827,6 +827,16 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
}
)
if 'checkin_text' in self.request.data and serializer.instance.checkin_text != self.request.data.get('checkin_text'):
serializer.instance.log_action(
'pretix.event.order.checkin_text',
user=self.request.user,
auth=self.request.auth,
data={
'new_value': self.request.data.get('checkin_text')
}
)
if 'valid_if_pending' in self.request.data and serializer.instance.valid_if_pending != self.request.data.get('valid_if_pending'):
serializer.instance.log_action(
'pretix.event.order.valid_if_pending',
+3
View File
@@ -88,6 +88,7 @@ class ItemDataExporter(ListExporter):
_("Minimum amount per order"),
_("Maximum amount per order"),
_("Requires special attention"),
_("Check-in text"),
_("Original price"),
_("This product is a gift card"),
_("Require a valid membership"),
@@ -162,6 +163,7 @@ class ItemDataExporter(ListExporter):
i.min_per_order if i.min_per_order is not None else "",
i.max_per_order if i.max_per_order is not None else "",
_("Yes") if i.checkin_attention else "",
i.checkin_text or "",
v.original_price or i.original_price or "",
_("Yes") if i.issue_giftcard else "",
_("Yes") if i.require_membership or v.require_membership else "",
@@ -206,6 +208,7 @@ class ItemDataExporter(ListExporter):
i.min_per_order if i.min_per_order is not None else "",
i.max_per_order if i.max_per_order is not None else "",
_("Yes") if i.checkin_attention else "",
i.checkin_text or "",
i.original_price or "",
_("Yes") if i.issue_giftcard else "",
_("Yes") if i.require_membership else "",
+3
View File
@@ -96,6 +96,7 @@ class JSONExporter(BaseExporter):
'min_per_order': item.min_per_order,
'max_per_order': item.max_per_order,
'checkin_attention': item.checkin_attention,
'checkin_text': item.checkin_text,
'original_price': item.original_price,
'issue_giftcard': item.issue_giftcard,
'meta_data': item.meta_data,
@@ -110,6 +111,7 @@ class JSONExporter(BaseExporter):
'description': str(variation.description),
'position': variation.position,
'checkin_attention': variation.checkin_attention,
'checkin_text': variation.checkin_text,
'require_approval': variation.require_approval,
'require_membership': variation.require_membership,
'sales_channels': variation.sales_channels,
@@ -164,6 +166,7 @@ class JSONExporter(BaseExporter):
'custom_followup_at': order.custom_followup_at,
'require_approval': order.require_approval,
'checkin_attention': order.checkin_attention,
'checkin_text': order.checkin_text,
'sales_channel': order.sales_channel,
'expires': order.expires,
'datetime': order.datetime,
+2
View File
@@ -275,6 +275,7 @@ class OrderListExporter(MultiSheetListExporter):
headers.append(_('Invoice numbers'))
headers.append(_('Sales channel'))
headers.append(_('Requires special attention'))
headers.append(_('Check-in text'))
headers.append(_('Comment'))
headers.append(_('Follow-up date'))
headers.append(_('Positions'))
@@ -384,6 +385,7 @@ class OrderListExporter(MultiSheetListExporter):
row.append(order.invoice_numbers)
row.append(order.sales_channel)
row.append(_('Yes') if order.checkin_attention else _('No'))
row.append(order.checkin_text or "")
row.append(order.comment or "")
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
row.append(order.pcnt)
@@ -0,0 +1,32 @@
# Generated by Django 4.2.4 on 2023-09-06 09:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0252_logentry_organizer"),
]
operations = [
migrations.AddField(
model_name="item",
name="checkin_text",
field=models.TextField(null=True),
),
migrations.AddField(
model_name="itemvariation",
name="checkin_text",
field=models.TextField(null=True),
),
migrations.AddField(
model_name="order",
name="checkin_text",
field=models.TextField(null=True),
),
migrations.AddField(
model_name="question",
name="show_during_checkin",
field=models.BooleanField(default=False),
),
]
+20
View File
@@ -336,6 +336,8 @@ class Item(LoggedModel):
:type min_per_order: int
:param checkin_attention: Requires special attention at check-in
:type checkin_attention: bool
:param checkin_text: Additional text to show at check-in
:type checkin_text: bool
:param original_price: The item's "original" price. Will not be used for any calculations, will just be shown.
:type original_price: decimal.Decimal
:param require_approval: If set to ``True``, orders containing this product can only be processed and paid after approved by an administrator
@@ -566,6 +568,11 @@ class Item(LoggedModel):
'attention. You can use this for example for student tickets to indicate to the person at '
'check-in that the student ID card still needs to be checked.')
)
checkin_text = models.TextField(
verbose_name=_('Check-in text'),
null=True, blank=True,
help_text=_('This text will be shown by the check-in app if a ticket of this type is scanned.')
)
original_price = models.DecimalField(
verbose_name=_('Original price'),
blank=True, null=True,
@@ -1096,6 +1103,11 @@ class ItemVariation(models.Model):
'attention. You can use this for example for student tickets to indicate to the person at '
'check-in that the student ID card still needs to be checked.')
)
checkin_text = models.TextField(
verbose_name=_('Check-in text'),
null=True, blank=True,
help_text=_('This text will be shown by the check-in app if a ticket of this type is scanned.')
)
objects = ScopedManager(organizer='item__event__organizer')
@@ -1450,6 +1462,8 @@ class Question(LoggedModel):
:param items: A set of ``Items`` objects that this question should be applied to
:param ask_during_checkin: Whether to ask this question during check-in instead of during check-out.
:type ask_during_checkin: bool
:param show_during_checkin: Whether to show the answer to this question during check-in.
:type show_during_checkin: bool
:param hidden: Whether to only show the question in the backend
:type hidden: bool
:param identifier: An arbitrary, internal identifier
@@ -1487,6 +1501,7 @@ class Question(LoggedModel):
)
UNLOCALIZED_TYPES = [TYPE_DATE, TYPE_TIME, TYPE_DATETIME]
ASK_DURING_CHECKIN_UNSUPPORTED = []
SHOW_DURING_CHECKIN_UNSUPPORTED = [TYPE_FILE]
event = models.ForeignKey(
Event,
@@ -1538,6 +1553,11 @@ class Question(LoggedModel):
help_text=_('Not supported by all check-in apps for all question types.'),
default=False
)
show_during_checkin = models.BooleanField(
verbose_name=_('Show answer during check-in'),
help_text=_('Not supported by all check-in apps for all question types.'),
default=False
)
hidden = models.BooleanField(
verbose_name=_('Hidden question'),
help_text=_('This question will only show up in the backend.'),
+16
View File
@@ -244,6 +244,11 @@ class Order(LockModel, LoggedModel):
'special attention. This will not show any details or custom message, so you need to brief your '
'check-in staff how to handle these cases.')
)
checkin_text = models.TextField(
verbose_name=_('Check-in text'),
null=True, blank=True,
help_text=_('This text will be shown by the check-in app if a ticket of this order is scanned.')
)
expiry_reminder_sent = models.BooleanField(
default=False
)
@@ -2425,6 +2430,17 @@ class OrderPosition(AbstractPosition):
return True
return False
@cached_property
def checkin_texts(self):
texts = []
if self.order.checkin_text:
texts.append(self.order.checkin_text)
if self.variation_id and self.variation.checkin_text:
texts.append(self.variation.checkin_text)
if self.item.checkin_text:
texts.append(self.item.checkin_text)
return texts
@property
def checkins(self):
"""
+14
View File
@@ -133,6 +133,14 @@ class QuestionForm(I18nModelForm):
return val
def clean_show_during_checkin(self):
val = self.cleaned_data.get('show_during_checkin')
if val and self.cleaned_data.get('type') in Question.SHOW_DURING_CHECKIN_UNSUPPORTED:
raise ValidationError(_('This type of question cannot be shown during check-in.'))
return val
def clean_identifier(self):
val = self.cleaned_data.get('identifier')
Question._clean_identifier(self.instance.event, val, self.instance)
@@ -155,6 +163,7 @@ class QuestionForm(I18nModelForm):
'type',
'required',
'ask_during_checkin',
'show_during_checkin',
'hidden',
'identifier',
'items',
@@ -379,6 +388,7 @@ class ItemCreateForm(I18nModelForm):
'max_per_order',
'generate_tickets',
'checkin_attention',
'checkin_text',
'free_price',
'original_price',
'sales_channels',
@@ -703,6 +713,7 @@ class ItemUpdateForm(I18nModelForm):
'max_per_order',
'min_per_order',
'checkin_attention',
'checkin_text',
'generate_tickets',
'original_price',
'require_bundling',
@@ -751,6 +762,7 @@ class ItemUpdateForm(I18nModelForm):
'show_quota_left': ShowQuotaNullBooleanSelect(),
'max_per_order': forms.widgets.NumberInput(attrs={'min': 0}),
'min_per_order': forms.widgets.NumberInput(attrs={'min': 0}),
'checkin_text': forms.TextInput(),
}
@@ -869,6 +881,7 @@ class ItemVariationForm(I18nModelForm):
'require_membership_hidden',
'require_membership_types',
'checkin_attention',
'checkin_text',
'available_from',
'available_until',
'sales_channels',
@@ -884,6 +897,7 @@ class ItemVariationForm(I18nModelForm):
'require_membership_types': forms.CheckboxSelectMultiple(attrs={
'class': 'scrolling-multiple-choice'
}),
'checkin_text': forms.TextInput(),
}
def clean(self):
+2 -1
View File
@@ -265,12 +265,13 @@ class ExporterForm(forms.Form):
class CommentForm(I18nModelForm):
class Meta:
model = Order
fields = ['comment', 'checkin_attention', 'custom_followup_at']
fields = ['comment', 'checkin_attention', 'checkin_text', 'custom_followup_at']
widgets = {
'comment': forms.Textarea(attrs={
'rows': 3,
'class': 'helper-width-100',
}),
'checkin_text': forms.TextInput(),
'custom_followup_at': DatePickerWidget(),
}
+1
View File
@@ -407,6 +407,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.order.custom_followup_at': _('The order\'s follow-up date has been updated.'),
'pretix.event.order.checkin_attention': _('The order\'s flag to require attention at check-in has been '
'toggled.'),
'pretix.event.order.checkin_text': _('The order\'s check-in text has been changed.'),
'pretix.event.order.pretix.event.order.valid_if_pending': _('The order\'s flag to be considered valid even if '
'unpaid has been toggled.'),
'pretix.event.order.payment.changed': _('A new payment {local_id} has been started instead of the previous one.'),
@@ -85,11 +85,31 @@
{% endif %}
{% endif %}
{% if result.position %}
{% if result.position.require_attention %}
{% if result.require_attention %}
<p>
<span class="fa fa-info-circle fa-fw"></span> {% trans "Special attention required" %}
<strong>
<span class="fa fa-info-circle text-info fa-fw"></span>
{% trans "Special attention required" %}
</strong>
</p>
{% endif %}
{% for t in result.checkin_texts %}
<p>
<span class="fa fa-info-circle text-muted fa-fw"></span>
{{ t }}
</p>
{% endfor %}
{% if result.position_object %}
{% for a in result.position_object.answers.all %}
{% if a.question.show_during_checkin %}
<p>
<span class="fa fa-question-circle text-muted fa-fw"></span>
<strong>{{ a.question.question }}</strong>
{{ a }}
</p>
{% endif %}
{% endfor %}
{% endif %}
<p>
<span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=result.position.order %}">
@@ -108,6 +108,7 @@
</div>
{% endif %}
{% bootstrap_field form.checkin_attention layout="control" %}
{% bootstrap_field form.checkin_text layout="control" %}
</div>
</details>
{% endfor %}
@@ -206,6 +207,7 @@
</div>
{% endif %}
{% bootstrap_field formset.empty_form.checkin_attention layout="control" %}
{% bootstrap_field formset.empty_form.checkin_text layout="control" %}
</div>
</details>
{% endescapescript %}
@@ -202,6 +202,7 @@
<fieldset>
<legend>{% trans "Check-in & Validity" %}</legend>
{% bootstrap_field form.checkin_attention layout="control" %}
{% bootstrap_field form.checkin_text layout="control" %}
{% bootstrap_field form.validity_mode layout="control" %}
<div data-display-dependency="#{{ form.validity_mode.id_for_label }}" data-display-dependency-value="fixed">
{% bootstrap_field form.validity_fixed_from layout="control" %}
@@ -129,6 +129,7 @@
{% bootstrap_field form.help_text layout="control" %}
{% bootstrap_field form.identifier layout="control" %}
{% bootstrap_field form.ask_during_checkin layout="control" %}
{% bootstrap_field form.show_during_checkin layout="control" %}
{% bootstrap_field form.hidden layout="control" %}
{% bootstrap_field form.print_on_invoice layout="control" %}
@@ -997,6 +997,7 @@
{% bootstrap_field comment_form.comment show_help=True show_label=False %}
{% bootstrap_field comment_form.custom_followup_at %}
{% bootstrap_field comment_form.checkin_attention show_help=True show_label=False %}
{% bootstrap_field comment_form.checkin_text show_help=True show_label=False %}
{% if "can_change_orders" in request.eventpermset %}
<button class="btn btn-default">
{% trans "Update comment" %}
+4 -1
View File
@@ -551,9 +551,12 @@ class CheckInListSimulator(EventPermissionRequiredMixin, FormView):
gate=form.cleaned_data.get("gate"),
).data
if self.result.get("position"):
op = OrderPosition.objects.get(pk=self.result["position"]["id"])
self.result["position_object"] = op
if form.cleaned_data["checkin_type"] == Checkin.TYPE_ENTRY and self.list.rules and self.result.get("position")\
and (self.result["status"] in ("ok", "incomplete") or self.result["reason"] == "rules"):
op = OrderPosition.objects.get(pk=self.result["position"]["id"])
rule_data = LazyRuleVars(op, self.list, form.cleaned_data["datetime"], form.cleaned_data.get("gate"))
rule_graph = _logic_annotate_for_graphic_explain(self.list.rules, op.subevent or self.list.event, rule_data,
form.cleaned_data["datetime"])
+9 -2
View File
@@ -513,7 +513,8 @@ class OrderDetail(OrderView):
ctx['comment_form'] = CommentForm(initial={
'comment': self.order.comment,
'custom_followup_at': self.order.custom_followup_at,
'checkin_attention': self.order.checkin_attention
'checkin_attention': self.order.checkin_attention,
'checkin_text': self.order.checkin_text,
})
ctx['display_locale'] = dict(settings.LANGUAGES)[self.object.locale or self.request.event.settings.locale]
@@ -747,7 +748,13 @@ class OrderComment(OrderView):
self.order.log_action('pretix.event.order.checkin_attention', user=self.request.user, data={
'new_value': form.cleaned_data.get('checkin_attention')
})
self.order.save(update_fields=['checkin_attention', 'comment', 'custom_followup_at'])
if form.cleaned_data.get('checkin_text') != self.order.checkin_text:
self.order.checkin_text = form.cleaned_data.get('checkin_text')
self.order.log_action('pretix.event.order.checkin_text', user=self.request.user, data={
'new_value': form.cleaned_data.get('checkin_text')
})
self.order.save(update_fields=['checkin_attention', 'checkin_text', 'comment', 'custom_followup_at'])
self.order.refresh_from_db()
messages.success(self.request, _('The comment has been updated.'))
else:
@@ -25,22 +25,32 @@
{{ checkError }}
</div>
<div :class="'check-result-status check-result-' + checkResultColor">
{{ checkResultText }}
</div>
<div class="panel-body" v-if="checkResult.position">
<div class="details">
<h4>{{ checkResult.position.order }}-{{ checkResult.position.positionid }} {{ checkResult.position.attendee_name }}</h4>
<strong v-if="checkResult.reason_explanation">{{ checkResult.reason_explanation }}<br></strong>
<span>{{ checkResultItemvar }}</span><br>
<span v-if="checkResultSubevent">{{ checkResultSubevent }}<br></span>
<span class="secret">{{ checkResult.position.secret }}</span>
<span v-if="checkResult.position.seat"><br>{{ checkResult.position.seat.name }}</span>
</div>
<div class="check-result-text">{{ checkResultText }}</div>
<div class="check-result-item">{{ checkResultItemvar }}</div>
<div class="check-result-reason" v-if="checkResult.reason_explanation">{{ checkResult.reason_explanation }}</div>
</div>
<div class="attention" v-if="checkResult && checkResult.require_attention">
<span class="fa fa-warning"></span>
{{ $root.strings['check.attention'] }}
</div>
<div class="panel-body" v-if="checkResult.position">
<div class="details">
<code>{{ checkResult.position.order }}-{{ checkResult.position.positionid }}</code>
<h4>{{ checkResult.position.attendee_name }}</h4>
<span v-if="checkResultSubevent">{{ checkResultSubevent }}<br></span>
<span class="secret">{{ checkResult.position.secret }}</span>
<span v-if="checkResult.position.seat"><br>{{ checkResult.position.seat.name }}</span>
<span v-for="a in checkResult.position.answers">
<span v-if="a.question.show_during_checkin">
<br>
<strong>{{ a.question.question | i18nstring_localize }}:</strong>
{{ a.answer | answer(a.question, $root.timezone, $root.strings) }}
</span>
</span>
<strong v-for="t in checkResult.checkin_texts"><br>{{ t }}</strong>
</div>
</div>
</div>
<div v-else-if="searchResults !== null" class="panel panel-primary search-results">
@@ -296,6 +306,24 @@ export default {
}
},
},
filters: {
i18nstring_localize (value) {
return i18nstring_localize(value);
},
answer (value, question, timezone, strings) {
if (question.type === "B" && value === "True") {
return strings['yes']
} else if (question.type === "B" && value === "False") {
return strings['no']
} else if (question.type === "W" && !!value) {
return moment(value).tz(timezone).format("L LT")
} else if (question.type === "D" && !!value) {
return moment(value).format("L")
} else {
return value
}
},
},
methods: {
selectResult(res) {
this.check(res.id, false, false, false, false)
@@ -341,7 +369,7 @@ export default {
this.$refs.input.blur()
})
let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation'
let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question'
if (untrusted) {
url += '&untrusted_input=true'
}
@@ -399,7 +427,7 @@ export default {
} else if (data.status === 'error' && data.reason === 'invalid' && fallbackToSearch) {
this.startSearch(false)
} else {
this.clearTimeout = window.setTimeout(this.clear, 1000 * 20)
this.clearTimeout = window.setTimeout(this.clear, 1000 * 30)
this.fetchStatus()
}
})
@@ -407,7 +435,7 @@ export default {
this.checkLoading = false
this.checkResult = {}
this.checkError = reason.toString()
this.clearTimeout = window.setTimeout(this.clear, 1000 * 20)
this.clearTimeout = window.setTimeout(this.clear, 1000 * 30)
})
},
globalKeydown(e) {
@@ -487,13 +515,13 @@ export default {
} else {
this.searchError = data
}
this.clearTimeout = window.setTimeout(this.clear, 1000 * 20)
this.clearTimeout = window.setTimeout(this.clear, 1000 * 30)
})
.catch(reason => {
this.searchLoading = false
this.searchResults = []
this.searchError = reason
this.clearTimeout = window.setTimeout(this.clear, 1000 * 20)
this.clearTimeout = window.setTimeout(this.clear, 1000 * 30)
})
},
searchNext() {
@@ -510,12 +538,12 @@ export default {
} else {
this.searchError = data
}
this.clearTimeout = window.setTimeout(this.clear, 1000 * 20)
this.clearTimeout = window.setTimeout(this.clear, 1000 * 30)
})
.catch(reason => {
this.searchLoading = false
this.searchError = reason
this.clearTimeout = window.setTimeout(this.clear, 1000 * 20)
this.clearTimeout = window.setTimeout(this.clear, 1000 * 30)
})
},
switchType() {
@@ -66,6 +66,8 @@ window.vapp = new Vue({
'status.checkin': gettext('Checked-in Tickets'),
'status.position': gettext('Valid Tickets'),
'status.inside': gettext('Currently inside'),
'yes': gettext('Yes'),
'no': gettext('No'),
},
event_name: document.querySelector('#app').attributes['data-event-name'].value,
timezone: document.body.attributes['data-timezone'].value,
@@ -96,11 +96,9 @@ a.searchresult, .check-result {
.check-result-status {
height: 30vh;
max-height: 200px;
font-size: 35px;
text-transform: uppercase;
font-weight: bold;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
@@ -117,6 +115,21 @@ a.searchresult, .check-result {
&.check-result-purple {
background: $brand-primary;
}
.check-result-text {
font-size: 35px;
font-weight: bold;
margin-bottom: 10px;
}
.check-result-item {
font-size: 28px;
}
}
.check-result .panel-body code {
float: right;
background: none;
font-size: 18px;
line-height: 1.1;
padding: 0;
}
.attention {
@@ -137,7 +150,7 @@ a.searchresult, .check-result {
@-webkit-keyframes blinking {
0%, 49% {
background-color: $brand-primary;
background-color: $brand-info;
color: white;
}
50%, 100% {
@@ -25,6 +25,7 @@
data-pretixlocale="{{ request.LANGUAGE_CODE }}" data-timezone="{{ request.event.settings.timezone }}">
<div
data-api-lists="{% url "api-v1:checkinlist-list" event=request.event.slug organizer=request.organizer.slug %}"
data-api-questions="{% url "api-v1:question-list" event=request.event.slug organizer=request.organizer.slug %}"
data-event-name="{{ request.event.name }}"
id="app"></div>
{% compress js %}
+28
View File
@@ -471,6 +471,21 @@ def test_require_product(token_client, organizer, clist, event, order):
assert resp.data['reason'] == 'product'
@pytest.mark.django_db
def test_checkin_texts(token_client, organizer, clist, event, order):
with scopes_disabled():
p = order.positions.first()
p.item.checkin_text = 'A'
p.item.save()
order.checkin_text = 'B'
order.save()
resp = _redeem(token_client, organizer, clist, p.secret, {})
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
assert resp.data['checkin_texts'] == ['B', 'A']
@pytest.mark.django_db
def test_require_paid(token_client, organizer, clist, event, order):
with scopes_disabled():
@@ -683,6 +698,19 @@ def test_question_upload(token_client, organizer, clist, event, order, question)
assert order.positions.first().answers.get(question=question[0]).file
@pytest.mark.django_db
def test_question_expand(token_client, organizer, clist, event, order, question):
with scopes_disabled():
p = order.positions.first()
question[0].save()
p.answers.create(question=question[0], answer="3")
resp = _redeem(token_client, organizer, clist, p.secret, {"answers": {question[0].pk: ""}}, query="?expand=answers.question")
assert resp.status_code == 201
assert resp.data["status"] == "ok"
assert resp.data["position"]["answers"][0]["question"]["question"]["en"] == "Size"
@pytest.mark.django_db
def test_store_failed(token_client, organizer, clist, event, order):
with scopes_disabled():
+18
View File
@@ -279,6 +279,7 @@ TEST_ITEM_RES = {
"hidden_if_available": None,
"hidden_if_item_available": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": False,
"require_approval": False,
"variations": [],
@@ -392,6 +393,7 @@ def test_item_detail_variations(token_client, organizer, event, team, item):
"description": None,
"position": 0,
"checkin_attention": False,
"checkin_text": None,
"require_approval": False,
"require_membership": False,
"require_membership_hidden": False,
@@ -478,6 +480,7 @@ def test_item_create(token_client, organizer, event, item, category, taxrule, me
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"has_variations": True,
"require_membership_types": [membership_type.pk],
"meta_data": {
@@ -556,6 +559,7 @@ def test_item_create_with_variation(token_client, organizer, event, item, catego
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"variations": [
{
@@ -566,6 +570,7 @@ def test_item_create_with_variation(token_client, organizer, event, item, catego
"active": True,
"require_approval": True,
"checkin_attention": False,
"checkin_text": None,
"require_membership": False,
"require_membership_hidden": False,
"require_membership_types": [],
@@ -618,6 +623,7 @@ def test_item_create_giftcard_validation(token_client, organizer, event, item, c
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"addons": []
},
@@ -650,6 +656,7 @@ def test_item_create_giftcard_validation(token_client, organizer, event, item, c
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"addons": []
},
@@ -687,6 +694,7 @@ def test_item_create_with_addon(token_client, organizer, event, item, category,
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"addons": [
{
@@ -733,6 +741,7 @@ def test_item_create_with_addon(token_client, organizer, event, item, category,
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"addons": [
{
@@ -777,6 +786,7 @@ def test_item_create_with_addon(token_client, organizer, event, item, category,
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"addons": [
{
@@ -821,6 +831,7 @@ def test_item_create_with_addon(token_client, organizer, event, item, category,
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"addons": [
{
@@ -873,6 +884,7 @@ def test_item_create_with_bundle(token_client, organizer, event, item, category,
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"bundles": [
{
@@ -919,6 +931,7 @@ def test_item_create_with_bundle(token_client, organizer, event, item, category,
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"bundles": [
{
@@ -960,6 +973,7 @@ def test_item_create_with_bundle(token_client, organizer, event, item, category,
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"bundles": [
{
@@ -1190,6 +1204,7 @@ def test_item_file_upload(token_client, organizer, event, item):
"min_per_order": None,
"max_per_order": None,
"checkin_attention": False,
"checkin_text": None,
"has_variations": True,
"meta_data": {
"day": "Wednesday"
@@ -1307,6 +1322,7 @@ TEST_VARIATIONS_RES = {
"default_price": None,
"price": "23.00",
"checkin_attention": False,
"checkin_text": None,
"require_approval": False,
"require_membership": False,
"require_membership_hidden": False,
@@ -1329,6 +1345,7 @@ TEST_VARIATIONS_UPDATE = {
"position": 1,
"default_price": "20.0",
"checkin_attention": False,
"checkin_text": None,
"require_approval": False,
"require_membership": False,
"require_membership_hidden": False,
@@ -2056,6 +2073,7 @@ TEST_QUESTION_RES = {
"required": False,
"items": [],
"ask_during_checkin": False,
"show_during_checkin": False,
"hidden": False,
"print_on_invoice": False,
"identifier": "ABC",
+3
View File
@@ -257,6 +257,7 @@ def test_order_update_allowed_fields(token_client, organizer, event, order):
'valid_if_pending': True,
'custom_followup_at': '2021-06-12',
'checkin_attention': True,
'checkin_text': 'foobar',
'email': 'foo@bar.com',
'phone': '+4962219999',
'locale': 'de',
@@ -280,6 +281,7 @@ def test_order_update_allowed_fields(token_client, organizer, event, order):
assert order.comment == 'Here is a comment'
assert order.custom_followup_at.isoformat() == '2021-06-12'
assert order.checkin_attention
assert order.checkin_text == 'foobar'
assert order.email == 'foo@bar.com'
assert order.phone == '+4962219999'
assert order.locale == 'de'
@@ -294,6 +296,7 @@ def test_order_update_allowed_fields(token_client, organizer, event, order):
assert order.all_logentries().get(action_type='pretix.event.order.comment')
assert order.all_logentries().get(action_type='pretix.event.order.custom_followup_at')
assert order.all_logentries().get(action_type='pretix.event.order.checkin_attention')
assert order.all_logentries().get(action_type='pretix.event.order.checkin_text')
assert order.all_logentries().get(action_type='pretix.event.order.contact.changed')
assert order.all_logentries().get(action_type='pretix.event.order.phone.changed')
assert order.all_logentries().get(action_type='pretix.event.order.locale.changed')
+1
View File
@@ -392,6 +392,7 @@ def test_order_create_simulate(token_client, organizer, event, item, quota, ques
],
'downloads': [],
'checkin_attention': False,
'checkin_text': None,
'payments': [],
'refunds': [],
'require_approval': False,
+1
View File
@@ -288,6 +288,7 @@ TEST_ORDER_RES = {
"comment": "",
"custom_followup_at": None,
"checkin_attention": False,
"checkin_text": None,
"invoice_address": {
"last_modified": "2017-12-01T10:00:00Z",
"is_business": False,