Compare commits

...

10 Commits

Author SHA1 Message Date
Ruud Hendrickx
99e9690d48 Translations: Update Dutch (Belgium)
Currently translated at 71.3% (4465 of 6257 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl_BE/

powered by weblate
2026-03-09 14:24:17 +01:00
Hijiri Umemoto
e63e82e854 Translations: Update Japanese
Currently translated at 100.0% (6257 of 6257 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2026-03-09 14:24:17 +01:00
argonimos
c662e627d5 Translations: Update German
Currently translated at 100.0% (6257 of 6257 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2026-03-09 14:24:17 +01:00
Mie Frydensbjerg
f2121c7853 Translations: Update Danish
Currently translated at 44.7% (2800 of 6257 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/da/

powered by weblate
2026-03-09 14:24:17 +01:00
Raphael Michel
3ce6dbf798 Mail: Remove redundant SQL queries (#5896)
On my local test event, this saved 75 queries on sending an email due to
an N+1 query problem in the metadata querying.
2026-03-09 13:53:20 +01:00
dependabot[bot]
43b91af5e6 Update sentry-sdk requirement from ==2.53.* to ==2.54.* (#5947)
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.53.0...2.54.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.54.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 13:53:00 +01:00
dependabot[bot]
034d6b997e Bump minimatch from 3.0.4 to 3.1.5 in /src/pretix/static/npm_dir (#5937)
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.5.
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.5)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 13:52:50 +01:00
dependabot[bot]
345ad35fcf Update protobuf requirement from ==6.33.* to ==7.34.* (#5945)
Updates the requirements on [protobuf](https://github.com/protocolbuffers/protobuf) to permit the latest version.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Commits](https://github.com/protocolbuffers/protobuf/commits)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-version: 7.34.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 13:52:44 +01:00
Raphael Michel
347337e76f Invoice generation: Add way for renderers to signal they are not ready (#5905) 2026-03-09 13:52:11 +01:00
Lukas Bockstaller
c07ba31307 API: add organizer-level orderpositions endpoint (#5848)
* initial implementation

* handle permissions

* split out organizer list endpoint

* remove left over empty lines

* revert import changes

* tidying up

* revert no longer needed test changes

* revert no longer needed test changes

* Apply suggestions from code review

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

* add event to api response

* prefetch

* handle auth

* document event

* bump querycounts for prefetches

* Use existing Permission Denied Error Message

---------

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-03-06 11:55:38 +01:00
15 changed files with 315 additions and 121 deletions

View File

@@ -1719,6 +1719,56 @@ List of all order positions
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/orderpositions/
Returns a list of all order positions within all events of a given organizer (with sufficient access permissions).
The supported query parameters and output format of this endpoint are almost identical to those of the list endpoint
within an event.
The only changes are that responses also contain the ``event`` attribute in each result and that the 'pdf_data'
parameter is not supported.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/orderpositions/ 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
X-Page-Generated: 2017-12-01T10:00:00Z
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id:": 23442
"event": "sampleconf",
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
...
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
Fetching individual positions
-----------------------------

View File

@@ -77,7 +77,7 @@ dependencies = [
"phonenumberslite==9.0.*",
"Pillow==12.1.*",
"pretix-plugin-build",
"protobuf==6.33.*",
"protobuf==7.34.*",
"psycopg2-binary",
"pycountry",
"pycparser==3.0",
@@ -92,7 +92,7 @@ dependencies = [
"redis==7.1.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.53.*",
"sentry-sdk==2.54.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",

View File

@@ -637,6 +637,14 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
return entry
class OrganizerOrderPositionSerializer(OrderPositionSerializer):
event = SlugRelatedField(slug_field='slug', read_only=True)
class Meta(OrderPositionSerializer.Meta):
fields = OrderPositionSerializer.Meta.fields + ('event',)
read_only_fields = OrderPositionSerializer.Meta.read_only_fields + ('event',)
class RequireAttentionField(serializers.Field):
def to_representation(self, instance: OrderPosition):
return instance.require_checkin_attention

View File

@@ -67,6 +67,7 @@ orga_router.register(r'invoices', order.InvoiceViewSet)
orga_router.register(r'scheduled_exports', exporters.ScheduledOrganizerExportViewSet)
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
orga_router.register(r'transactions', order.OrganizerTransactionViewSet)
orga_router.register(r'orderpositions', order.OrganizerOrderPositionViewSet, basename='orderpositions')
team_router = routers.DefaultRouter()
team_router.register(r'members', organizer.TeamMemberViewSet)
@@ -83,7 +84,7 @@ event_router.register(r'discounts', discount.DiscountViewSet)
event_router.register(r'quotas', item.QuotaViewSet)
event_router.register(r'vouchers', voucher.VoucherViewSet)
event_router.register(r'orders', order.EventOrderViewSet)
event_router.register(r'orderpositions', order.OrderPositionViewSet)
event_router.register(r'orderpositions', order.EventOrderPositionViewSet)
event_router.register(r'transactions', order.TransactionViewSet)
event_router.register(r'invoices', order.InvoiceViewSet)
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')

View File

@@ -57,9 +57,10 @@ from pretix.api.serializers.order import (
BlockedTicketSecretSerializer, InvoiceSerializer, OrderCreateSerializer,
OrderPaymentCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, OrganizerTransactionSerializer,
PriceCalcSerializer, PrintLogSerializer, RevokedTicketSecretSerializer,
SimulatedOrderSerializer, TransactionSerializer,
OrderRefundSerializer, OrderSerializer, OrganizerOrderPositionSerializer,
OrganizerTransactionSerializer, PriceCalcSerializer, PrintLogSerializer,
RevokedTicketSecretSerializer, SimulatedOrderSerializer,
TransactionSerializer,
)
from pretix.api.serializers.orderchange import (
BlockNameSerializer, OrderChangeOperationSerializer,
@@ -1065,8 +1066,7 @@ with scopes_disabled():
}
class OrderPositionViewSet(viewsets.ModelViewSet):
serializer_class = OrderPositionSerializer
class OrderPositionViewSetMixin:
queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
ordering = ('order__datetime', 'positionid')
@@ -1087,8 +1087,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
ctx['pdf_data'] = False
ctx['check_quotas'] = self.request.query_params.get('check_quotas', 'true').lower() == 'true'
return ctx
@@ -1097,9 +1096,8 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
qs = OrderPosition.all
else:
qs = OrderPosition.objects
qs = qs.filter(order__event=self.request.event)
if self.request.query_params.get('pdf_data', 'false').lower() == 'true':
qs = qs.filter(order__event__organizer=self.request.organizer)
if self.request.query_params.get('pdf_data', 'false').lower() == 'true' and getattr(self.request, 'event', None):
prefetch_related_objects([self.request.organizer], 'meta_properties')
prefetch_related_objects(
[self.request.event],
@@ -1154,9 +1152,9 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
qs = qs.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.select_related("device")),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
'answers', 'answers__options', 'answers__question', 'order__event', 'order__event__organizer'
).select_related(
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
'item', 'order', 'seat'
)
return qs
@@ -1168,6 +1166,45 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
return prov
raise NotFound('Unknown output provider.')
class OrganizerOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrganizerOrderPositionSerializer
def get_queryset(self):
qs = super().get_queryset()
perm = self.permission if self.request.method in SAFE_METHODS else self.write_permission
if isinstance(self.request.auth, (TeamAPIToken, Device)):
auth_obj = self.request.auth
elif self.request.user.is_authenticated:
auth_obj = self.request.user
else:
raise PermissionDenied("Unknown authentication scheme")
qs = qs.filter(
order__event__in=auth_obj.get_events_with_permission(perm, request=self.request).filter(
organizer=self.request.organizer
)
)
return qs
class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet):
serializer_class = OrderPositionSerializer
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
return ctx
def get_queryset(self):
qs = super().get_queryset()
qs = qs.filter(order__event=self.request.event)
return qs
@action(detail=True, methods=['POST'], url_name='price_calc')
def price_calc(self, request, *args, **kwargs):
"""

View File

@@ -148,6 +148,10 @@ class NumberedCanvas(Canvas):
self.restoreState()
class InvoiceNotReadyException(Exception):
pass
class BaseInvoiceRenderer:
"""
This is the base class for all invoice renderers.

View File

@@ -51,6 +51,7 @@ from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString
from pretix.base.i18n import language
from pretix.base.invoicing.pdf import InvoiceNotReadyException
from pretix.base.invoicing.transmission import (
get_transmission_types, transmission_providers,
)
@@ -504,7 +505,7 @@ def generate_invoice(order: Order, trigger_pdf=True):
return invoice
@app.task(base=TransactionAwareTask)
@app.task(base=TransactionAwareTask, throws=(InvoiceNotReadyException,))
def invoice_pdf_task(invoice: int):
with scopes_disabled():
i = Invoice.objects.get(pk=invoice)

View File

@@ -409,6 +409,18 @@ def mail_send_task(self, **kwargs) -> bool:
outgoing_mail.inflight_since = now()
outgoing_mail.save(update_fields=["status", "inflight_since"])
# Performance optimization, saves database queries later on if we resolve the known relationships
if outgoing_mail.event_id:
assert outgoing_mail.event.organizer_id == outgoing_mail.organizer.pk
outgoing_mail.event.organizer = outgoing_mail.organizer
if outgoing_mail.order_id:
assert outgoing_mail.order.event_id == outgoing_mail.event_id
outgoing_mail.order.event = outgoing_mail.event
outgoing_mail.order.organizer = outgoing_mail.organizer
if outgoing_mail.orderposition_id:
assert outgoing_mail.orderposition.order_id == outgoing_mail.order_id
outgoing_mail.orderposition.order = outgoing_mail.order
headers = dict(outgoing_mail.headers)
headers.setdefault('X-PX-Correlation', str(outgoing_mail.guid))
email = CustomEmail(

View File

@@ -24,6 +24,7 @@ import logging
from datetime import timedelta
from decimal import Decimal
from django.db.models import Prefetch, prefetch_related_objects
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.html import escape, mark_safe
@@ -35,6 +36,7 @@ from pretix.base.forms.widgets import format_placeholders_help_text
from pretix.base.i18n import (
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
)
from pretix.base.models import EventMetaValue
from pretix.base.reldate import RelativeDateWrapper
from pretix.base.settings import PERSON_NAME_SCHEMES, get_name_parts_localized
from pretix.base.signals import (
@@ -752,6 +754,11 @@ def base_placeholders(sender, **kwargs):
name_scheme['sample'][f]
))
prefetch_related_objects(
[sender],
Prefetch('meta_values', queryset=EventMetaValue.objects.select_related("property"), to_attr="meta_values_cached")
)
prefetch_related_objects([sender.organizer], Prefetch('meta_properties'))
for k, v in sender.meta_data.items():
ph.append(MarkdownTextPlaceholder(
'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k],

View File

@@ -4,16 +4,16 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-24 11:50+0000\n"
"PO-Revision-Date: 2026-02-19 22:00+0000\n"
"PO-Revision-Date: 2026-03-05 20:00+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/da/"
">\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/"
"da/>\n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16\n"
"X-Generator: Weblate 5.16.1\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -34285,7 +34285,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/voucher.html:293
#, python-format
msgid "minimum amount to order: %(num)s"
msgstr ""
msgstr "Minimumsbestilling: %(num)s"
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:76
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:160

View File

@@ -5,8 +5,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-24 11:50+0000\n"
"PO-Revision-Date: 2026-02-24 12:07+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"PO-Revision-Date: 2026-03-07 23:00+0000\n"
"Last-Translator: argonimos <jonas@pfeiffer-wagner.de>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/"
"de/>\n"
"Language: de\n"
@@ -14,7 +14,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16\n"
"X-Generator: Weblate 5.16.2\n"
"X-Poedit-Bookmarks: -1,-1,904,-1,-1,-1,-1,-1,-1,-1\n"
#: pretix/_base_settings.py:87
@@ -3845,7 +3845,7 @@ msgstr "Restbetrag"
#, python-brace-format
msgctxt "invoice"
msgid "Invoice period: {daterange}"
msgstr "Rechungsperiode: {daterange}"
msgstr "Rechnungsperiode: {daterange}"
#: pretix/base/invoicing/pdf.py:1039
msgctxt "invoice"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-24 11:50+0000\n"
"PO-Revision-Date: 2026-03-02 10:00+0000\n"
"PO-Revision-Date: 2026-03-09 12:52+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.16.1\n"
"X-Generator: Weblate 5.16.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -11448,7 +11448,7 @@ msgstr ""
"{event}のご注文が完了しました。無料製品のみのご注文のため、\n"
"お支払いは不要です。\n"
"\n"
"注文の詳細の変更やステータス確認は、以下のURLから行えます\n"
"注文の詳細の変更やステータス確認は、以下のURLから行えます\n"
"{url}\n"
"\n"
"よろしくお願いいたします。\n"
@@ -11750,7 +11750,7 @@ msgstr ""
"{event}のご注文のお支払いを受け取りました。\n"
"\n"
"残念ながら、受け取った金額は必要な全額よりも少ないです。\n"
"したがって、追加の**{pending_sum}**の支払いが不足しているため、\n"
"したがって、追加の **{pending_sum}** の支払いが不足しているため、\n"
"ご注文は未払いと見なされます。\n"
"\n"
"お支払い情報やご注文の状況は、以下のURLでご確認いただけます。\n"
@@ -18428,7 +18428,7 @@ msgid ""
"Do you really want to grant the application <strong>%(application)s</strong> "
"access to your pretix account?"
msgstr ""
"本当にアプリケーション<strong>%(application)s</strong>にPretixアカウントへの"
"本当にアプリケーション<strong>%(application)s</strong>にpretixアカウントへの"
"アクセスを許可しますか?"
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html:24
@@ -24692,7 +24692,7 @@ msgstr "顧客履歴"
#: pretix/control/templates/pretixcontrol/organizers/customer_anonymize.html:11
#, python-format
msgid "Anonymize customer #%(id)s"
msgstr "顧客のID #%(id)s を匿名化"
msgstr "顧客 #%(id)s を匿名化"
#: pretix/control/templates/pretixcontrol/organizers/customer_anonymize.html:16
msgid "Are you sure you want to anonymize this customer account?"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-24 11:50+0000\n"
"PO-Revision-Date: 2026-03-04 16:57+0000\n"
"PO-Revision-Date: 2026-03-09 12:52+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch (Belgium) <https://translate.pretix.eu/projects/pretix/"
"pretix/nl_BE/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.1\n"
"X-Generator: Weblate 5.16.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -26981,6 +26981,10 @@ msgid ""
"the affected data in your legislation, e.g. for reasons of taxation. In many "
"countries, you need to keep some data in the live system in case of an audit."
msgstr ""
"Het is uw eigen verantwoordelijkheid om te controleren of u de gegevens "
"volgens uw wetgeving mag verwijderen, bijvoorbeeld om fiscale redenen. In "
"veel landen moet u bepaalde gegevens in het livesysteem bewaren voor het "
"geval er een audit plaatsvindt."
#: pretix/control/templates/pretixcontrol/shredder/index.html:32
msgid ""
@@ -26988,81 +26992,87 @@ msgid ""
"to store it offline. Some kinds of data (such as some payment information) "
"as well as historical log data cannot be downloaded at the moment."
msgstr ""
"U kunt voor de meeste categorieën de gegevens gedeeltelijk downloaden om ze "
"offline op te slaan. Sommige soorten gegevens (bijvoorbeeld sommige "
"betalingsinformatie) en historische loggegevens kunnen momenteel niet worden "
"gedownload."
#: pretix/control/templates/pretixcontrol/shredder/index.html:46
msgid "Data selection"
msgstr ""
msgstr "Gegevensselectie"
#: pretix/control/templates/pretixcontrol/shredder/index.html:63
msgid ""
"We recommend not to remove this data because you might need it in case of a "
"tax audit."
msgstr ""
"We raden aan om deze gegevens niet te verwijderen, omdat u ze mogelijk nodig "
"hebt bij een belastingaudit."
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:10
msgctxt "subevent"
msgid "Create multiple dates"
msgstr ""
msgstr "Meerdere datums aanmaken"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:35
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:146
msgid "Repetition rule"
msgstr ""
msgstr "Regel voor herhaling"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:81
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:192
#, python-format
msgid "Repeat every %(interval)s %(freq)s, starting at %(start)s."
msgstr ""
msgstr "Herhaal ieder(e) %(interval)s %(freq)s, beginnend op %(start)s."
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:258
msgctxt "subevent"
msgid "Preview"
msgstr ""
msgstr "Voorbeeldweergave"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:265
msgctxt "subevent"
msgid "Times"
msgstr ""
msgstr "Tijden"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:339
msgid "Start of first slot"
msgstr ""
msgstr "Begin van eerste tijdsslot"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:345
msgid "End of time slots"
msgstr ""
msgstr "Einde van tijdsslots"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:351
msgid "Length of slots"
msgstr ""
msgstr "Lengte van tijdsslots"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:360
msgid "Break between slots"
msgstr ""
msgstr "Pauze tussen tijdsslots"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:370
msgid "Create"
msgstr ""
msgstr "Aanmaken"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:377
msgid "Add a single time slot"
msgstr ""
msgstr "Eén tijdsslot toevoegen"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:379
msgid "Add many time slots"
msgstr ""
msgstr "Meerdere tijdsslots toevoegen"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:481
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:266
#: pretix/control/templates/pretixcontrol/subevents/detail.html:124
msgid "Add a new quota"
msgstr ""
msgstr "Nieuw quotum toevoegen"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:485
#: pretix/control/templates/pretixcontrol/subevents/detail.html:128
msgid "Product settings"
msgstr ""
msgstr "Productinstellingen"
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:487
#: pretix/control/templates/pretixcontrol/subevents/detail.html:130
@@ -27070,6 +27080,8 @@ msgid ""
"These settings are optional, if you leave them empty, the default values "
"from the product settings will be used."
msgstr ""
"Deze instellingen zijn optioneel. Als u deze instellingen leeg laat, zullen "
"de standaardwaarden uit de productinstellingen worden gebruikt."
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:523
#: pretix/control/templates/pretixcontrol/subevents/detail.html:166

View File

@@ -2776,9 +2776,9 @@
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -5642,9 +5642,9 @@
"optional": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"

View File

@@ -34,7 +34,7 @@ from stripe import error
from tests.plugins.stripe.test_checkout import apple_domain_create
from tests.plugins.stripe.test_provider import MockedCharge
from pretix.base.models import InvoiceAddress, Order, OrderPosition
from pretix.base.models import InvoiceAddress, Order, OrderPosition, Team
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
@@ -180,6 +180,41 @@ def order2(event2, item2):
return o
@pytest.fixture
@scopes_disabled()
def team2(organizer, event2):
team2 = Team.objects.create(
organizer=organizer,
name="Test-Team 2",
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_change_orders=True,
can_manage_customers=True,
can_manage_reusable_media=True,
can_change_organizer_settings=True,
)
team2.limit_events.add(event2)
team2.save()
return team2
@pytest.fixture
@scopes_disabled()
def limited_token_client(client, team2):
team2.can_view_orders = True
team2.can_view_vouchers = True
team2.save()
t = team2.tokens.create(name='Foo')
client.credentials(HTTP_AUTHORIZATION='Token ' + t.token)
return client
TEST_ORDERPOSITION_RES = {
"id": 1,
"order": "FOO",
@@ -987,8 +1022,64 @@ def test_refund_cancel(token_client, organizer, event, order):
assert resp.status_code == 400
@pytest.mark.parametrize(
"endpoint_template, response_code",
[('/api/v1/organizers/{}/events/{}/orderpositions/', 403), ('/api/v1/organizers/{}/orderpositions/', 200)]
)
@pytest.mark.django_db
def test_orderposition_list(token_client, organizer, device, event, order, item, subevent, subevent2, question, django_assert_num_queries):
def test_orderposition_list_limited_read(
endpoint_template, response_code, limited_token_client, organizer, device, event, order, item, subevent, subevent2, question
):
endpoint = endpoint_template.format(organizer.slug, event.slug)
i2 = copy.copy(item)
i2.pk = None
i2.save()
with scopes_disabled():
var = item.variations.create(value="Children")
res = copy.copy(TEST_ORDERPOSITION_RES)
op = order.positions.first()
op.variation = var
op.save()
res["id"] = op.pk
res["item"] = item.pk
res["variation"] = var.pk
res["answers"][0]["question"] = question.pk
res["print_logs"][0]["id"] = op.print_logs.first().pk
res["print_logs"][0]["device_id"] = device.device_id
resp = limited_token_client.get(endpoint)
assert resp.status_code == response_code
if response_code == 200:
assert resp.json() == {'count': 0, 'next': None, 'previous': None, 'results': []}
else:
assert resp.json() == {'detail': 'You do not have permission to perform this action.'}
@pytest.mark.parametrize(
("endpoint_template", "endpoint_type"),
[
('/api/v1/organizers/{}/events/{}/orderpositions/', "event"),
('/api/v1/organizers/{}/orderpositions/', "organizer")
],
)
@pytest.mark.django_db
def test_orderposition_list(
endpoint_template,
endpoint_type,
token_client,
organizer,
device,
event,
order,
item,
subevent,
subevent2,
question,
django_assert_num_queries
):
endpoint = endpoint_template.format(organizer.slug, event.slug)
i2 = copy.copy(item)
i2.pk = None
i2.save()
@@ -1005,88 +1096,64 @@ def test_orderposition_list(token_client, organizer, device, event, order, item,
res["answers"][0]["question"] = question.pk
res["print_logs"][0]["id"] = op.print_logs.first().pk
res["print_logs"][0]["device_id"] = device.device_id
if endpoint_type == "organizer":
res["event"] = event.slug
resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint)
assert resp.status_code == 200
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?order__status=n'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?order__status=n')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?order__status=p'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?order__status=p')
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?item={}'.format(organizer.slug, event.slug, item.pk))
resp = token_client.get(endpoint + '?item={}'.format(item.pk))
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?item__in={},{}'.format(
organizer.slug, event.slug, item.pk, i2.pk
))
resp = token_client.get(endpoint + '?item__in={},{}'.format(item.pk, i2.pk))
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?item={}'.format(organizer.slug, event.slug, i2.pk))
resp = token_client.get(endpoint + '?item={}'.format(i2.pk))
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?variation={}'.format(organizer.slug, event.slug, var.pk))
resp = token_client.get(endpoint + '?variation={}'.format(var.pk))
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?variation={}'.format(organizer.slug, event.slug, var2.pk))
resp = token_client.get(endpoint + '?variation={}'.format(var2.pk))
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=Peter'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?attendee_name=Peter')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=peter'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?attendee_name=peter')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=Mark'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?attendee_name=Mark')
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?secret=z3fsn8jyufm5kpk768q69gkbyr5f4h6w'.format(
organizer.slug, event.slug))
resp = token_client.get(endpoint + '?secret=z3fsn8jyufm5kpk768q69gkbyr5f4h6w')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?secret=abc123'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?secret=abc123')
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?pseudonymization_id=ABCDEFGHKL'.format(
organizer.slug, event.slug))
resp = token_client.get(endpoint + '?pseudonymization_id=ABCDEFGHKL')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?pseudonymization_id=FOO'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?pseudonymization_id=FOO')
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?search=FO'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?search=FO')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?search=z3fsn8j'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?search=z3fsn8j')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?search=Peter'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?search=Peter')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?search=5f4h6w'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?search=5f4h6w')
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?order=FOO'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?order=FOO')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?order=BAR'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?order=BAR')
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?has_checkin=false'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?has_checkin=false')
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?has_checkin=true'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?has_checkin=true')
assert [] == resp.data['results']
with scopes_disabled():
@@ -1103,33 +1170,28 @@ def test_orderposition_list(token_client, organizer, device, event, order, item,
'gate': None,
'type': 'entry'
}]
with django_assert_num_queries(16):
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?has_checkin=true'.format(organizer.slug, event.slug)
)
if '/events/' in endpoint:
with django_assert_num_queries(18):
resp = token_client.get(endpoint + '?has_checkin=true')
else:
with django_assert_num_queries(17):
resp = token_client.get(endpoint + '?has_checkin=true')
assert [res] == resp.data['results']
op.subevent = subevent
op.save()
res['subevent'] = subevent.pk
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?subevent={}'.format(organizer.slug, event.slug, subevent.pk))
resp = token_client.get(endpoint + '?subevent={}'.format(subevent.pk))
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?subevent__in={},{}'.format(organizer.slug, event.slug,
subevent.pk, subevent2.pk))
resp = token_client.get(endpoint + '?subevent__in={},{}'.format(subevent.pk, subevent2.pk))
assert [res] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?subevent={}'.format(organizer.slug, event.slug,
subevent.pk + 1))
resp = token_client.get(endpoint + '?subevent={}'.format(subevent.pk + 1))
assert [] == resp.data['results']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?include_canceled_positions=false'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?include_canceled_positions=false')
assert len(resp.data['results']) == 1
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?include_canceled_positions=true'.format(organizer.slug, event.slug))
resp = token_client.get(endpoint + '?include_canceled_positions=true')
assert len(resp.data['results']) == 2