Compare commits

..

1 Commits

Author SHA1 Message Date
Lukas Bockstaller 51dc1b6029 add missing slug fields 2026-02-24 10:13:03 +01:00
97 changed files with 16582 additions and 21897 deletions
-50
View File
@@ -1719,56 +1719,6 @@ 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
-----------------------------
+2 -2
View File
@@ -77,7 +77,7 @@ dependencies = [
"phonenumberslite==9.0.*",
"Pillow==12.1.*",
"pretix-plugin-build",
"protobuf==7.34.*",
"protobuf==6.33.*",
"psycopg2-binary",
"pycountry",
"pycparser==3.0",
@@ -92,7 +92,7 @@ dependencies = [
"redis==7.1.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.54.*",
"sentry-sdk==2.53.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
+1 -1
View File
@@ -19,4 +19,4 @@
# 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/>.
#
__version__ = "2026.3.0.dev0"
__version__ = "2026.2.0.dev0"
-21
View File
@@ -19,7 +19,6 @@
# 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 json
import logging
import os
from collections import Counter, defaultdict
@@ -637,14 +636,6 @@ 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
@@ -1224,18 +1215,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
raise ValidationError('The given payment provider is not known.')
return pp
def validate_payment_info(self, info):
if info:
try:
obj = json.loads(info)
except ValueError:
raise ValidationError('payment_info must be valid JSON.')
if not isinstance(obj, dict):
# only objects are allowed
raise ValidationError('payment_info must be a JSON object.')
return info
def validate_expires(self, expires):
if expires < now():
raise ValidationError('Expiration date must be in the future.')
+1 -2
View File
@@ -365,10 +365,9 @@ class TeamInviteSerializer(serializers.ModelSerializer):
def _send_invite(self, instance):
mail(
instance.email,
_('Account invitation'),
_('pretix account invitation'),
'pretixcontrol/email/invitation.txt',
{
'instance': settings.PRETIX_INSTANCE_NAME,
'user': self,
'organizer': self.context['organizer'].name,
'team': instance.team.name,
+1 -2
View File
@@ -67,7 +67,6 @@ 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)
@@ -84,7 +83,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.EventOrderPositionViewSet)
event_router.register(r'orderpositions', order.OrderPositionViewSet)
event_router.register(r'transactions', order.TransactionViewSet)
event_router.register(r'invoices', order.InvoiceViewSet)
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')
+12 -49
View File
@@ -57,10 +57,9 @@ from pretix.api.serializers.order import (
BlockedTicketSecretSerializer, InvoiceSerializer, OrderCreateSerializer,
OrderPaymentCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, OrganizerOrderPositionSerializer,
OrganizerTransactionSerializer, PriceCalcSerializer, PrintLogSerializer,
RevokedTicketSecretSerializer, SimulatedOrderSerializer,
TransactionSerializer,
OrderRefundSerializer, OrderSerializer, OrganizerTransactionSerializer,
PriceCalcSerializer, PrintLogSerializer, RevokedTicketSecretSerializer,
SimulatedOrderSerializer, TransactionSerializer,
)
from pretix.api.serializers.orderchange import (
BlockNameSerializer, OrderChangeOperationSerializer,
@@ -1066,7 +1065,8 @@ with scopes_disabled():
}
class OrderPositionViewSetMixin:
class OrderPositionViewSet(viewsets.ModelViewSet):
serializer_class = OrderPositionSerializer
queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
ordering = ('order__datetime', 'positionid')
@@ -1087,7 +1087,8 @@ class OrderPositionViewSetMixin:
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['pdf_data'] = False
ctx['event'] = self.request.event
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
ctx['check_quotas'] = self.request.query_params.get('check_quotas', 'true').lower() == 'true'
return ctx
@@ -1096,8 +1097,9 @@ class OrderPositionViewSetMixin:
qs = OrderPosition.all
else:
qs = OrderPosition.objects
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):
qs = qs.filter(order__event=self.request.event)
if self.request.query_params.get('pdf_data', 'false').lower() == 'true':
prefetch_related_objects([self.request.organizer], 'meta_properties')
prefetch_related_objects(
[self.request.event],
@@ -1152,9 +1154,9 @@ class OrderPositionViewSetMixin:
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', 'order__event', 'order__event__organizer'
'answers', 'answers__options', 'answers__question',
).select_related(
'item', 'order', 'seat'
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
)
return qs
@@ -1166,45 +1168,6 @@ class OrderPositionViewSetMixin:
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):
"""
+2 -5
View File
@@ -216,10 +216,7 @@ class OutboundSyncProvider:
try:
mapped_objects = self.sync_order(sq.order)
actions_taken = [res and res.sync_info.get("action", "") for res_list in mapped_objects.values() for res in res_list]
should_write_logentry = any(action not in (None, "nothing_to_do") for action in actions_taken)
logger.info('Synced order %s to %s, actions: %r, log: %r', sq.order.code, sq.sync_provider, actions_taken, should_write_logentry)
if should_write_logentry:
if not all(all(not res or res.sync_info.get("action", "") == "nothing_to_do" for res in res_list) for res_list in mapped_objects.values()):
sq.order.log_action("pretix.event.order.data_sync.success", {
"provider": self.identifier,
"objects": {
@@ -240,7 +237,7 @@ class OutboundSyncProvider:
sq.set_sync_error("exceeded", e.messages, e.full_message)
else:
logger.info(
f"Could not sync order {sq.order.code} to {sq.sync_provider} "
f"Could not sync order {sq.order.code} to {type(self).__name__} "
f"(transient error, attempt #{sq.failed_attempts}, next {sq.not_before})",
exc_info=True,
)
+1 -10
View File
@@ -42,8 +42,6 @@ from django.utils.html import escape
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import gettext_lazy as _
from pretix.helpers.format import PlainHtmlAlternativeString
def replace_arabic_numbers(inp):
if not isinstance(inp, str):
@@ -63,18 +61,11 @@ def replace_arabic_numbers(inp):
return inp.translate(table)
def format_placeholder_help_text(placeholder_name, sample_value):
if isinstance(sample_value, PlainHtmlAlternativeString):
sample_value = sample_value.plain
title = (_("Sample: %s") % sample_value) if sample_value else ""
return ('<button type="button" class="content-placeholder" title="%s">{%s}</button>' % (escape(title), escape(placeholder_name)))
def format_placeholders_help_text(placeholders, event=None):
placeholders = [(k, v.render_sample(event) if event else v) for k, v in placeholders.items()]
placeholders.sort(key=lambda x: x[0])
phs = [
format_placeholder_help_text(k, v)
'<button type="button" class="content-placeholder" title="%s">{%s}</button>' % (escape(_("Sample: %s") % v) if v else "", escape(k))
for k, v in placeholders
]
return _('Available placeholders: {list}').format(
-4
View File
@@ -148,10 +148,6 @@ class NumberedCanvas(Canvas):
self.restoreState()
class InvoiceNotReadyException(Exception):
pass
class BaseInvoiceRenderer:
"""
This is the base class for all invoice renderers.
+1 -4
View File
@@ -346,8 +346,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
{
'user': self,
'messages': msg,
'url': build_absolute_uri('control:user.settings'),
'instance': settings.PRETIX_INSTANCE_NAME,
'url': build_absolute_uri('control:user.settings')
},
event=None,
user=self,
@@ -392,7 +391,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
'user': self,
'reason': msg,
'code': code,
'instance': settings.PRETIX_INSTANCE_NAME,
},
event=None,
user=self,
@@ -432,7 +430,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
mail(
self.email, _('Password recovery'), 'pretixcontrol/email/forgot.txt',
{
'instance': settings.PRETIX_INSTANCE_NAME,
'user': self,
'url': (build_absolute_uri('control:auth.forgot.recover')
+ '?id=%d&token=%s' % (self.id, default_token_generator.make_token(self)))
+1 -1
View File
@@ -86,7 +86,7 @@ class OrderSyncQueue(models.Model):
def set_sync_error(self, failure_mode, messages, full_message):
logger.exception(
f"Could not sync order {self.order.code} to {self.sync_provider} ({failure_mode})"
f"Could not sync order {self.order.code} to {type(self).__name__} ({failure_mode})"
)
self.order.log_action(f"pretix.event.order.data_sync.failed.{failure_mode}", {
"provider": self.sync_provider,
+2 -3
View File
@@ -181,11 +181,10 @@ class WaitingListEntry(LoggedModel):
block_quota=True,
item_id=self.item_id,
subevent_id=self.subevent_id,
waitinglistentries__isnull=False,
seat__isnull=True
waitinglistentries__isnull=False
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
free_seats = num_free_seats_for_product - num_valid_vouchers_for_product
if free_seats < 1:
if not free_seats:
raise WaitingListException(_('No seat with this product is currently available.'))
if '@' not in self.email:
+1 -2
View File
@@ -51,7 +51,6 @@ 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,
)
@@ -505,7 +504,7 @@ def generate_invoice(order: Order, trigger_pdf=True):
return invoice
@app.task(base=TransactionAwareTask, throws=(InvoiceNotReadyException,))
@app.task(base=TransactionAwareTask)
def invoice_pdf_task(invoice: int):
with scopes_disabled():
i = Invoice.objects.get(pk=invoice)
-12
View File
@@ -409,18 +409,6 @@ 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(
-7
View File
@@ -24,7 +24,6 @@ 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
@@ -36,7 +35,6 @@ 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 (
@@ -754,11 +752,6 @@ 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],
-1
View File
@@ -176,7 +176,6 @@ def shred(self, event: Event, fileid: str, confirm_code: str, user: int=None, lo
_('Data shredding completed'),
'pretixbase/email/shred_completed.txt',
{
'instance': settings.PRETIX_INSTANCE_NAME,
'user': user,
'organizer': event.organizer.name,
'event': str(event.name),
-3
View File
@@ -12,9 +12,6 @@
<meta charset="utf-8">
<link rel="icon" href="{% static "pretixbase/img/favicon.ico" %}">
{% block custom_header %}{% endblock %}
{% if css_theme %}
<link rel="stylesheet" type="text/css" href="{{ css_theme }}" />
{% endif %}
</head>
<body>
<div class="container">
@@ -13,5 +13,5 @@ Start time: {{ start_time }} (new data added after this time might not have been
Best regards,
Your {{ instance }} team
Your pretix team
{% endblocktrans %}
@@ -1,34 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix 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 import template
from django.utils.html import mark_safe
register = template.Library()
@register.filter("anon_email")
def anon_email(value):
"""Replaces @ with [at] and . with [dot] for anonymization."""
if not isinstance(value, str):
return value
value = value.replace("@", "[at]").replace(".", "[dot]")
return mark_safe(''.join(['&#{0};'.format(ord(char)) for char in value]))
+1 -1
View File
@@ -423,7 +423,7 @@ def resolve_timeframe_to_dates_inclusive(ref_dt, frame, timezone) -> Tuple[Optio
raise ValueError(f"Invalid timeframe '{frame}'")
def resolve_timeframe_to_datetime_start_inclusive_end_exclusive(ref_dt, frame, timezone) -> Tuple[Optional[datetime], Optional[datetime]]:
def resolve_timeframe_to_datetime_start_inclusive_end_exclusive(ref_dt, frame, timezone) -> Tuple[Optional[date], Optional[date]]:
"""
Given a serialized timeframe, evaluate it relative to `ref_dt` and return a tuple of datetimes
where the first element ist the first possible datetime within the timeframe and the second
-1
View File
@@ -518,7 +518,6 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
'The order requires approval before it can continue to be processed.'),
'pretix.event.order.approved': _('The order has been approved.'),
'pretix.event.order.denied': _('The order has been denied (comment: "{comment}").'),
'pretix.event.order.vatid.validated': _('The customer VAT ID has been verified.'),
'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" '
'to "{new_email}".'),
'pretix.event.order.contact.confirmed': _(
@@ -9,5 +9,5 @@ Please do never give this code to another person. Our support team will never as
If this code was not requested by you, please contact us immediately.
Best regards,
Your {{ instance }} team
Your pretix team
{% endblocktrans %}
@@ -5,5 +5,5 @@ you requested a new password. Please go to the following page to reset your pass
{{ url }}
Best regards,
Your {{ instance }} team
{% endblocktrans %}
Your pretix team
{% endblocktrans %}
@@ -1,6 +1,6 @@
{% load i18n %}{% blocktrans with url=url|safe %}Hello,
you have been invited to a team on {{ instance }}, a platform to perform event
you have been invited to a team on pretix, a platform to perform event
ticket sales.
Organizer: {{ organizer }}
@@ -13,5 +13,5 @@ If you do not want to join, you can safely ignore or delete this email.
Best regards,
Your {{ instance }} team
Your pretix team
{% endblocktrans %}
@@ -1,6 +1,6 @@
{% load i18n %}{% blocktrans with url=url|safe messages=messages|safe %}Hello,
this is to inform you that the account information of your {{ instance }} account has been
this is to inform you that the account information of your pretix account has been
changed. In particular, the following changes have been performed:
{{ messages }}
@@ -12,5 +12,5 @@ You can review and change your account settings here:
{{ url }}
Best regards,
Your {{ instance }} team
Your pretix team
{% endblocktrans %}
+4 -8
View File
@@ -870,15 +870,11 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
)
except ValueError:
msgs[self.supported_locale[idx]] = format_html(
'<div class="alert alert-danger">{}</div>',
PlaceholderValidator.error_message
)
msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
PlaceholderValidator.error_message)
except KeyError as e:
msgs[self.supported_locale[idx]] = format_html(
'<div class="alert alert-danger">{}</div>',
_('Invalid placeholder: {%(value)s}') % {'value': e.args[0]}
)
msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
_('Invalid placeholder: {%(value)s}') % {'value': e.args[0]})
return JsonResponse({
'item': preview_item,
+3 -11
View File
@@ -1641,17 +1641,9 @@ class OrderCheckVATID(OrderView):
try:
normalized_id = validate_vat_id(ia.vat_id, str(ia.country))
with transaction.atomic():
ia.vat_id_validated = True
ia.vat_id = normalized_id
ia.save()
self.order.log_action(
'pretix.event.order.vatid.validated',
data={
'vat_id': normalized_id,
},
user=self.request.user,
)
ia.vat_id_validated = True
ia.vat_id = normalized_id
ia.save()
except VATIDFinalError as e:
messages.error(self.request, e.message)
except VATIDTemporaryError:
+1 -2
View File
@@ -1039,10 +1039,9 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
def _send_invite(self, instance):
mail(
instance.email,
_('Account invitation'),
_('pretix account invitation'),
'pretixcontrol/email/invitation.txt',
{
'instance': settings.PRETIX_INSTANCE_NAME,
'user': self,
'organizer': self.request.organizer.name,
'team': instance.team.name,
+2 -3
View File
@@ -280,12 +280,11 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
block_quota=True,
item_id=wle.item_id,
subevent=wle.subevent_id,
waitinglistentries__isnull=False,
seat__isnull=True
waitinglistentries__isnull=False
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
free_seats = num_free_seats_for_product - num_valid_vouchers_for_product
wle.availability = (
Quota.AVAILABILITY_GONE if free_seats < 1 else wle.availability[0],
Quota.AVAILABILITY_GONE if free_seats == 0 else wle.availability[0],
min(free_seats, wle.availability[1]) if wle.availability[1] is not None else free_seats,
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-24 11:50+0000\n"
"POT-Creation-Date: 2026-02-20 13:01+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-03-02 21:00+0000\n"
"Last-Translator: Sandra Rial Pérez <sandrarial@gestiontickets.online>\n"
"PO-Revision-Date: 2025-12-03 23:00+0000\n"
"Last-Translator: sandra r <sandrarial@gestiontickets.online>\n"
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
"js/gl/>\n"
"Language: gl\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.14.3\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -162,12 +162,12 @@ msgstr "Pedidos pagados"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (ordered)"
msgstr "Asistentes (ordenados)"
msgstr ""
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (paid)"
msgstr "Asistentes (de pago)"
msgstr ""
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
msgid "Total revenue"
@@ -732,8 +732,8 @@ msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr ""
"Os artigos do teu carro xa non están reservados para ti. Podes completar o "
"teu pedido sempre que estean dispoñibles."
"Os artigos da túa cesta xa non están reservados para ti. Aínda podes "
"completar o teu pedido mentres estean dispoñibles."
#: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+17 -23
View File
@@ -802,37 +802,31 @@ class PaypalMethod(BasePaymentProvider):
all_captures_completed = False
else:
any_captures = True
# Payment has at least one capture, but it is not yet completed
if any_captures and not all_captures_completed:
if not (any_captures and all_captures_completed):
messages.warning(request, _('PayPal has not yet approved the payment. We will inform you as '
'soon as the payment completed.'))
payment.info = json.dumps(pp_captured_order.dict())
payment.state = OrderPayment.PAYMENT_STATE_PENDING
payment.save()
return
# Payment has at least one capture and all captures are completed
elif any_captures and all_captures_completed:
if pp_captured_order.status != 'COMPLETED':
payment.fail(info=pp_captured_order.dict())
logger.error('Invalid state: %s' % repr(pp_captured_order.dict()))
raise PaymentException(
_('We were unable to process your payment. See below for details on how to proceed.')
)
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED:
logger.warning('PayPal success event even though order is already marked as paid')
return
if pp_captured_order.status != 'COMPLETED':
payment.fail(info=pp_captured_order.dict())
logger.error('Invalid state: %s' % repr(pp_captured_order.dict()))
raise PaymentException(
_('We were unable to process your payment. See below for details on how to proceed.')
)
try:
payment.info = json.dumps(pp_captured_order.dict())
payment.save(update_fields=['info'])
payment.confirm()
except Quota.QuotaExceededException as e:
raise PaymentException(str(e))
# Payment has not any captures yet - so it's probably in created status
else:
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED:
logger.warning('PayPal success event even though order is already marked as paid')
return
try:
payment.info = json.dumps(pp_captured_order.dict())
payment.save(update_fields=['info'])
payment.confirm()
except Quota.QuotaExceededException as e:
raise PaymentException(str(e))
finally:
if 'payment_paypal_oid' in request.session:
del request.session['payment_paypal_oid']
@@ -842,7 +836,7 @@ class PaypalMethod(BasePaymentProvider):
try:
if (
payment.info
and payment.info_data['purchase_units'][0]['payments']['captures'][0]['status'] == 'PENDING'
and payment.info_data['purchase_units'][0]['payments']['captures'][0]['status'] == 'pending'
):
retry = False
except (KeyError, IndexError):
@@ -6,7 +6,6 @@
{% load eventurl %}
{% load safelink %}
{% load rich_text %}
{% load anonymize_email %}
{% block thetitle %}
{% if messages %}
{{ messages|join:" " }} ::
@@ -220,7 +219,7 @@
{% endblock %}
{% block footernav %}
{% if request.event.settings.contact_mail %}
<li><a href="{{ 'mailto:'|add:request.event.settings.contact_mail|anon_email }}" target="_blank" rel="noopener">{% trans "Contact" %}</a></li>
<li><a href="mailto:{{ request.event.settings.contact_mail }}" target="_blank" rel="noopener">{% trans "Contact" %}</a></li>
{% endif %}
{% if request.event.settings.privacy_url %}
<li><a href="{% safelink request.event.settings.privacy_url %}" target="_blank" rel="noopener">{% trans "Privacy policy" %}</a></li>
@@ -21,5 +21,4 @@
<script type="text/javascript" src="{% static "pretixpresale/js/ui/cart.js" %}"></script>
<script type="text/javascript" src="{% static "pretixpresale/js/ui/iframe.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/deanonymize_email.js" %}"></script>
{% endcompress %}
@@ -5,7 +5,6 @@
{% load thumb %}
{% load eventurl %}
{% load safelink %}
{% load anonymize_email %}
{% block thetitle %}
{% block title %}{% endblock %}{% if url_name != "organizer.index" %} :: {% endif %}{{ organizer.name }}
{% endblock %}
@@ -98,7 +97,7 @@
{% endblock %}
{% block footernav %}
{% if not request.event and request.organizer.settings.contact_mail %}
<li><a href="{{ 'mailto:'|add:request.organizer.settings.contact_mail|anon_email }}" target="_blank" rel="noopener">{% trans "Contact" %}</a></li>
<li><a href="mailto:{{ request.organizer.settings.contact_mail }}" target="_blank" rel="noopener">{% trans "Contact" %}</a></li>
{% endif %}
{% if not request.event and request.organizer.settings.privacy_url %}
<li><a href="{% safelink request.organizer.settings.privacy_url %}" target="_blank" rel="noopener">{% trans "Privacy policy" %}</a></li>
+1
View File
@@ -393,6 +393,7 @@ class TokenView(View):
if grant.code_challenge_method == "S256":
expected_challenge = base64.urlsafe_b64encode(hashlib.sha256(request.POST["code_verifier"].encode()).digest()).decode().rstrip("=")
print(grant.code_challenge, expected_challenge)
if expected_challenge != grant.code_challenge:
return JsonResponse({
"error": "invalid_grant",
-4
View File
@@ -211,10 +211,6 @@ USE_X_FORWARDED_HOST = config.getboolean('pretix', 'trust_x_forwarded_host', fal
REQUEST_ID_HEADER = config.get('pretix', 'request_id_header', fallback=False)
if REQUEST_ID_HEADER in config.cp.BOOLEAN_STATES:
raise ImproperlyConfigured(
"request_id_header should be set to a header name, not a boolean value."
)
if config.getboolean('pretix', 'trust_x_forwarded_proto', fallback=False):
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+6 -6
View File
@@ -2776,9 +2776,9 @@
}
},
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -5642,9 +5642,9 @@
"optional": true
},
"minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
@@ -1,7 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('a[href^="mailto:"]').forEach(function(link) {
// Replace [at] with @ and the [dot] with . in both the href and the displayed text (if needed)
link.href = link.href.replace('[at]', '@').replace('[dot]', '.');
link.textContent = link.textContent.replace('[at]', '@').replace('[dot]', '.');
});
});
-35
View File
@@ -895,41 +895,6 @@ def test_order_create_payment_info_optional(token_client, organizer, event, item
assert json.loads(p.info) == res['payment_info']
@pytest.mark.django_db
def test_order_create_payment_info_valid_object(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['answers'][0]['question'] = question.pk
res["payment_info"] = [{"should": "fail"}]
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
res['payment_info'] = {
'foo': {
'bar': [1, 2],
'test': False
}
}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
o = Order.objects.get(code=resp.data['code'])
p = o.payments.first()
assert p.provider == "banktransfer"
assert p.amount == o.total
assert json.loads(p.info) == res['payment_info']
@pytest.mark.django_db
def test_order_create_position_secret_optional(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
+67 -129
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, Team
from pretix.base.models import InvoiceAddress, Order, OrderPosition
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
@@ -180,41 +180,6 @@ 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",
@@ -1022,64 +987,8 @@ 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_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)
def test_orderposition_list(token_client, organizer, device, event, order, item, subevent, subevent2, question, django_assert_num_queries):
i2 = copy.copy(item)
i2.pk = None
i2.save()
@@ -1096,64 +1005,88 @@ def test_orderposition_list(
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(endpoint)
resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?order__status=n')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?order__status=n'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?order__status=p')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?order__status=p'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?item={}'.format(item.pk))
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?item={}'.format(organizer.slug, event.slug, item.pk))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?item__in={},{}'.format(item.pk, i2.pk))
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?item__in={},{}'.format(
organizer.slug, event.slug, item.pk, i2.pk
))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?item={}'.format(i2.pk))
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?item={}'.format(organizer.slug, event.slug, i2.pk))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?variation={}'.format(var.pk))
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?variation={}'.format(organizer.slug, event.slug, var.pk))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?variation={}'.format(var2.pk))
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?variation={}'.format(organizer.slug, event.slug, var2.pk))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?attendee_name=Peter')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=Peter'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?attendee_name=peter')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=peter'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?attendee_name=Mark')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=Mark'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?secret=z3fsn8jyufm5kpk768q69gkbyr5f4h6w')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?secret=z3fsn8jyufm5kpk768q69gkbyr5f4h6w'.format(
organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?secret=abc123')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?secret=abc123'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?pseudonymization_id=ABCDEFGHKL')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?pseudonymization_id=ABCDEFGHKL'.format(
organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?pseudonymization_id=FOO')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?pseudonymization_id=FOO'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?search=FO')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?search=FO'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?search=z3fsn8j')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?search=z3fsn8j'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?search=Peter')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?search=Peter'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?search=5f4h6w')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?search=5f4h6w'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?order=FOO')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?order=FOO'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?order=BAR')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?order=BAR'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?has_checkin=false')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?has_checkin=false'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?has_checkin=true')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?has_checkin=true'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
with scopes_disabled():
@@ -1170,28 +1103,33 @@ def test_orderposition_list(
'gate': None,
'type': 'entry'
}]
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')
with django_assert_num_queries(16):
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?has_checkin=true'.format(organizer.slug, event.slug)
)
assert [res] == resp.data['results']
op.subevent = subevent
op.save()
res['subevent'] = subevent.pk
resp = token_client.get(endpoint + '?subevent={}'.format(subevent.pk))
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?subevent={}'.format(organizer.slug, event.slug, subevent.pk))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?subevent__in={},{}'.format(subevent.pk, subevent2.pk))
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?subevent__in={},{}'.format(organizer.slug, event.slug,
subevent.pk, subevent2.pk))
assert [res] == resp.data['results']
resp = token_client.get(endpoint + '?subevent={}'.format(subevent.pk + 1))
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?subevent={}'.format(organizer.slug, event.slug,
subevent.pk + 1))
assert [] == resp.data['results']
resp = token_client.get(endpoint + '?include_canceled_positions=false')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?include_canceled_positions=false'.format(organizer.slug, event.slug))
assert len(resp.data['results']) == 1
resp = token_client.get(endpoint + '?include_canceled_positions=true')
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/orderpositions/?include_canceled_positions=true'.format(organizer.slug, event.slug))
assert len(resp.data['results']) == 2
+3 -75
View File
@@ -29,8 +29,6 @@ from pretix.base.models import (
Event, Item, ItemVariation, Organizer, Quota, Team, User, Voucher,
WaitingListEntry,
)
from pretix.base.models.seating import Seat, SeatingPlan
from pretix.base.models.waitinglist import WaitingListException
from pretix.control.views.dashboards import waitinglist_widgets
@@ -57,11 +55,11 @@ def env():
WaitingListEntry.objects.create(
event=event, item=item1, email='success@example.org', voucher=v
)
v = Voucher.objects.create(item=item2, event=event, block_quota=True, redeemed=0, valid_until=now() - timedelta(days=5))
v = Voucher.objects.create(item=item1, event=event, block_quota=True, redeemed=0, valid_until=now() - timedelta(days=5))
WaitingListEntry.objects.create(
event=event, item=item2, email='expired@example.org', voucher=v
)
v = Voucher.objects.create(item=item2, event=event, block_quota=True, redeemed=0, valid_until=now() + timedelta(days=5))
v = Voucher.objects.create(item=item1, event=event, block_quota=True, redeemed=0, valid_until=now() + timedelta(days=5))
WaitingListEntry.objects.create(
event=event, item=item2, email='valid@example.org', voucher=v
)
@@ -347,75 +345,5 @@ def test_dashboard(client, env):
quota.items.add(env['item1'])
w = waitinglist_widgets(env['event'])
assert '2' in w[0]['content']
assert '1' in w[0]['content']
assert '5' in w[1]['content']
@pytest.mark.django_db
def test_waitinglist_seat_calc(client, env):
item = env['item1']
event = env['event']
wle = env['wle']
SeatingPlan.objects.create(
name="Plan", organizer=event.organizer, layout="{}"
)
event.seat_category_mappings.create(
layout_category='Stalls', product=item
)
for i in range(2):
event.seats.create(seat_number=f"A{i}", product=item, seat_guid=f"A{i}")
quota = Quota.objects.create(event=event, size=10)
quota.items.add(item)
client.login(email='dummy@dummy.dummy', password='dummy')
# Calculated availability should not be more than number of available seats
response = client.get('/control/event/dummy/dummy/waitinglist/')
assert len(response.context['entries']) == 5
for entry in response.context['entries']:
assert entry.availability == (Quota.AVAILABILITY_OK, 2)
# Sending out a voucher reduces availability by 1
with scopes_disabled():
wle.send_voucher()
voucher = wle.voucher
assert voucher
response = client.get('/control/event/dummy/dummy/waitinglist/')
assert len(response.context['entries']) == 4
for entry in response.context['entries']:
assert entry.availability == (Quota.AVAILABILITY_OK, 1)
# Assigning a seat to a voucher does not decrease availability further
with scopes_disabled():
voucher.seat = Seat.objects.get(seat_guid="A0")
voucher.save()
response = client.get('/control/event/dummy/dummy/waitinglist/')
assert len(response.context['entries']) == 4
for entry in response.context['entries']:
assert entry.availability == (Quota.AVAILABILITY_OK, 1)
with scopes_disabled():
wle2 = WaitingListEntry.objects.filter(item=item, voucher__isnull=True).first()
wle2.send_voucher()
# Overbooking is handled correctly
# Regression test for calculation that used `not free_seats` instead of `free_seats < 1`
with scopes_disabled():
# Block seat
seat = Seat.objects.get(seat_guid="A1")
seat.blocked = True
seat.save()
response = client.get('/control/event/dummy/dummy/waitinglist/')
assert len(response.context['entries']) == 3
for entry in response.context['entries']:
assert entry.availability == (Quota.AVAILABILITY_GONE, -1)
with scopes_disabled(), pytest.raises(WaitingListException):
wle3 = WaitingListEntry.objects.filter(item=item, voucher__isnull=True).first()
wle3.send_voucher()