mirror of
https://github.com/pretix/pretix.git
synced 2026-09-14 16:24:43 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7aaca873e3 | ||
|
|
0254788124 |
+2
-2
@@ -52,7 +52,7 @@ dependencies = [
|
||||
"django-markup",
|
||||
"django-oauth-toolkit==2.3.*",
|
||||
"django-otp==1.7.*",
|
||||
"django-phonenumber-field==8.5.*",
|
||||
"django-phonenumber-field==8.4.*",
|
||||
"django-querytagger==0.0.3",
|
||||
"django-redis==7.0.*",
|
||||
"django-scopes==2.1.*",
|
||||
@@ -98,7 +98,7 @@ dependencies = [
|
||||
"sepaxml==2.7.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
"tlds>=2026072401",
|
||||
"tlds>=2026041800",
|
||||
"tqdm==4.*",
|
||||
"ua-parser==1.0.*",
|
||||
"vobject==0.9.*",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -51,18 +52,10 @@ class BaseSecurityProfile:
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def priority(self) -> int:
|
||||
"""
|
||||
Priority for ordering, higher will come first.
|
||||
"""
|
||||
return 100
|
||||
|
||||
|
||||
class FullAccessSecurityProfile(BaseSecurityProfile):
|
||||
identifier = 'full'
|
||||
verbose_name = _('Full device access (reading and changing orders and gift cards, reading of products and settings)')
|
||||
priority = 1000
|
||||
|
||||
def is_allowed(self, request):
|
||||
return True
|
||||
@@ -202,15 +195,13 @@ def get_all_security_profiles():
|
||||
if _ALL_PROFILES:
|
||||
return _ALL_PROFILES
|
||||
|
||||
types = []
|
||||
types = OrderedDict()
|
||||
for recv, ret in register_device_security_profile.send(None):
|
||||
if isinstance(ret, (list, tuple)):
|
||||
for r in ret:
|
||||
types.append(r)
|
||||
types[r.identifier] = r
|
||||
else:
|
||||
types.append(ret)
|
||||
types.sort(key=lambda el: el.priority, reverse=True)
|
||||
types = {r.identifier: r for r in types}
|
||||
types[ret.identifier] = ret
|
||||
_ALL_PROFILES = types
|
||||
return types
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ from rest_framework.exceptions import (
|
||||
NotFound, PermissionDenied, ValidationError,
|
||||
)
|
||||
from rest_framework.generics import get_object_or_404
|
||||
from rest_framework.mixins import UpdateModelMixin
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.auth.permission import EventCRUDPermission
|
||||
@@ -712,7 +711,7 @@ class SeatFilter(FilterSet):
|
||||
fields = ('zone_name', 'row_name', 'row_label', 'seat_number', 'seat_label', 'seat_guid', 'blocked',)
|
||||
|
||||
|
||||
class SeatViewSet(ConditionalListView, UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
class SeatViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer_class = SeatSerializer
|
||||
queryset = Seat.objects.none()
|
||||
write_permission = 'event.settings.general:write'
|
||||
|
||||
@@ -40,12 +40,11 @@ from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
|
||||
from ..exporter import MultiSheetListExporter, OrganizerLevelExportMixin
|
||||
from ..models import Membership
|
||||
from ..exporter import ListExporter, OrganizerLevelExportMixin
|
||||
from ..signals import register_multievent_data_exporters
|
||||
|
||||
|
||||
class CustomerListExporter(OrganizerLevelExportMixin, MultiSheetListExporter):
|
||||
class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
|
||||
identifier = 'customerlist'
|
||||
verbose_name = gettext_lazy('Customer accounts')
|
||||
category = pgettext_lazy('export_category', 'Customer accounts')
|
||||
@@ -55,20 +54,13 @@ class CustomerListExporter(OrganizerLevelExportMixin, MultiSheetListExporter):
|
||||
def get_required_organizer_permission(cls) -> str:
|
||||
return 'organizer.customers:write'
|
||||
|
||||
@property
|
||||
def sheets(self):
|
||||
return (
|
||||
('customers', _('Customers')),
|
||||
('memberships', _('Memberships')),
|
||||
)
|
||||
|
||||
@property
|
||||
def additional_form_fields(self):
|
||||
return OrderedDict(
|
||||
[]
|
||||
)
|
||||
|
||||
def iterate_customers(self, form_data):
|
||||
def iterate_list(self, form_data):
|
||||
qs = self.organizer.customers.prefetch_related('provider')
|
||||
|
||||
headers = [
|
||||
@@ -117,52 +109,6 @@ class CustomerListExporter(OrganizerLevelExportMixin, MultiSheetListExporter):
|
||||
]
|
||||
yield row
|
||||
|
||||
def iterate_memberships(self, form_data):
|
||||
qs = Membership.objects.filter(
|
||||
customer__organizer=self.organizer
|
||||
).prefetch_related('membership_type').select_related('customer', 'granted_in', 'granted_in__order')
|
||||
|
||||
headers = [
|
||||
_('Customer ID'),
|
||||
_('External identifier'),
|
||||
_('Email'),
|
||||
_('Test mode'),
|
||||
_('Canceled'),
|
||||
_('Membership type'),
|
||||
_('Purchase ticket'),
|
||||
_('Start date'),
|
||||
_('Start time'),
|
||||
_('End date'),
|
||||
_('End time'),
|
||||
_('Name'),
|
||||
]
|
||||
name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme]
|
||||
if name_scheme and len(name_scheme['fields']) > 1:
|
||||
for k, label, w in name_scheme['fields']:
|
||||
headers.append(_('Name') + ': ' + str(label))
|
||||
yield headers
|
||||
|
||||
tz = get_current_timezone()
|
||||
for obj in qs:
|
||||
row = [
|
||||
obj.customer.identifier,
|
||||
obj.customer.external_identifier,
|
||||
obj.customer.email or '',
|
||||
_('Yes') if obj.testmode else _('No'),
|
||||
_('Yes') if obj.canceled else _('No'),
|
||||
str(obj.membership_type.name),
|
||||
f'{obj.granted_in.order.code}-{obj.granted_in.positionid}' if obj.granted_in else None,
|
||||
obj.date_start.astimezone(tz).strftime('%Y-%m-%d'),
|
||||
obj.date_start.astimezone(tz).strftime('%H:%M'),
|
||||
obj.date_end.astimezone(tz).strftime('%Y-%m-%d'),
|
||||
obj.date_end.astimezone(tz).strftime('%H:%M'),
|
||||
obj.attendee_name or '',
|
||||
]
|
||||
if name_scheme and len(name_scheme['fields']) > 1:
|
||||
for k, label, w in name_scheme['fields']:
|
||||
row.append(obj.attendee_name_parts.get(k, ''))
|
||||
yield row
|
||||
|
||||
def get_filename(self):
|
||||
return '{}_customers'.format(self.organizer.slug)
|
||||
|
||||
|
||||
@@ -1121,7 +1121,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
for q in question_cache.values():
|
||||
if q.required and q.type == Question.TYPE_BOOLEAN:
|
||||
if 'question_%d' % q.pk in d and d['question_%d' % q.pk] is False:
|
||||
d['question_%d' % q.pk] = None
|
||||
del d['question_%d' % q.pk]
|
||||
|
||||
return d
|
||||
|
||||
|
||||
@@ -497,16 +497,8 @@ class RejectInvalidInputMiddleware(MiddlewareMixin):
|
||||
if "\x00" in request.META['QUERY_STRING'] or "%00" in request.META['QUERY_STRING']:
|
||||
raise BadRequest("Invalid characters in input.")
|
||||
if request.method in ('POST', 'PUT', 'PATCH') and request.content_type == "application/x-www-form-urlencoded":
|
||||
try:
|
||||
post_data = request.POST.lists()
|
||||
except BadRequest:
|
||||
# Reading request.POST wasn't possible, probably an invalid charset. Django will crash once we actually
|
||||
# use request.POST, but if we don't, let's not crash it (required for some weird payment provider
|
||||
# webhooks, e.g. computop).
|
||||
pass
|
||||
else:
|
||||
if any("\x00" in value for key, value_list in post_data for value in value_list):
|
||||
raise BadRequest("Invalid characters in input.")
|
||||
if any("\x00" in value for key, value_list in request.POST.lists() for value in value_list):
|
||||
raise BadRequest("Invalid characters in input.")
|
||||
|
||||
|
||||
class CustomCommonMiddleware(CommonMiddleware):
|
||||
|
||||
@@ -2072,17 +2072,6 @@ class OrderPayment(models.Model):
|
||||
"""
|
||||
return '{}-P-{}'.format(self.order.code, self.local_id)
|
||||
|
||||
@property
|
||||
def global_id(self):
|
||||
"""
|
||||
The global ID of this payment, constructed by the organizer slug, event slug, and the full id.
|
||||
"""
|
||||
return "{organizer}-{event}-{full_id}".format(
|
||||
organizer=self.order.organizer.slug.upper(),
|
||||
event=self.order.event.slug.upper(),
|
||||
full_id=self.full_id,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.local_id:
|
||||
self.local_id = (self.order.payments.aggregate(m=Max('local_id'))['m'] or 0) + 1
|
||||
@@ -2283,17 +2272,6 @@ class OrderRefund(models.Model):
|
||||
"""
|
||||
return '{}-R-{}'.format(self.order.code, self.local_id)
|
||||
|
||||
@property
|
||||
def global_id(self):
|
||||
"""
|
||||
The global ID of this refund, constructed by the organizer slug, event slug, and the full id.
|
||||
"""
|
||||
return "{organizer}-{event}-{full_id}".format(
|
||||
organizer=self.order.organizer.slug.upper(),
|
||||
event=self.order.event.slug.upper(),
|
||||
full_id=self.full_id,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.local_id:
|
||||
self.local_id = (self.order.refunds.aggregate(m=Max('local_id'))['m'] or 0) + 1
|
||||
|
||||
@@ -562,7 +562,9 @@ class OrderPositionChangeForm(forms.Form):
|
||||
if instance.addon_to_id:
|
||||
del self.fields['operation_split']
|
||||
|
||||
if not instance.seat and not instance._seat_allowed:
|
||||
if not instance.seat and not (
|
||||
instance.item.seat_category_mappings.filter(subevent=instance.subevent).exists()
|
||||
):
|
||||
del self.fields['seat']
|
||||
|
||||
choices = [
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import functools
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
@@ -70,15 +69,6 @@ OVERVIEW_BANLIST = [
|
||||
]
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=32)
|
||||
def object_id_to_string(model_class, **kwargs):
|
||||
# The cache is thread-local and persists requests, but it's small enough that we can accept that
|
||||
try:
|
||||
return str(model_class.objects.get(**kwargs))
|
||||
except model_class.DoesNotExist:
|
||||
return "?"
|
||||
|
||||
|
||||
class OrderChangeLogEntryType(OrderLogEntryType):
|
||||
prefix = _('The order has been changed:')
|
||||
|
||||
@@ -103,10 +93,10 @@ class OrderItemChanged(OrderChangeLogEntryType):
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
old_item = str(event.items.get(pk=data['old_item']))
|
||||
if data['old_variation']:
|
||||
old_item += ' - ' + str(object_id_to_string(ItemVariation, item__event_id=event.pk, pk=data['old_variation']))
|
||||
old_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['old_variation']))
|
||||
new_item = str(event.items.get(pk=data['new_item']))
|
||||
if data['new_variation']:
|
||||
new_item += ' - ' + str(object_id_to_string(ItemVariation, item__event_id=event.pk, pk=data['new_variation']))
|
||||
new_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['new_variation']))
|
||||
return _('Position #{posid}: {old_item} ({old_price}) changed to {new_item} ({new_price}).').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_item=old_item, new_item=new_item,
|
||||
@@ -163,14 +153,14 @@ class OrderTaxRuleChanged(OrderChangeLogEntryType):
|
||||
if 'positionid' in data:
|
||||
return _('Tax rule of position #{posid} changed from {old_rule} to {new_rule}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_rule=object_id_to_string(TaxRule, pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
||||
new_rule=object_id_to_string(TaxRule, pk=data['new_taxrule']),
|
||||
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
||||
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
|
||||
)
|
||||
elif 'fee' in data:
|
||||
return _('Tax rule of fee #{fee} changed from {old_rule} to {new_rule}.').format(
|
||||
fee=data.get('fee', '?'),
|
||||
old_rule=object_id_to_string(TaxRule, pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
||||
new_rule=object_id_to_string(TaxRule, pk=data['new_taxrule']),
|
||||
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
||||
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
|
||||
)
|
||||
|
||||
|
||||
@@ -214,7 +204,7 @@ class OrderCanceled(OrderChangeLogEntryType):
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
old_item = str(event.items.get(pk=data['old_item']))
|
||||
if data['old_variation']:
|
||||
old_item += ' - ' + object_id_to_string(ItemVariation, pk=data['old_variation'])
|
||||
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
||||
return _('Position #{posid} ({old_item}, {old_price}) canceled.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_item=old_item,
|
||||
@@ -229,7 +219,7 @@ class OrderPositionAdded(OrderChangeLogEntryType):
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
item = str(event.items.get(pk=data['item']))
|
||||
if data['variation']:
|
||||
item += ' - ' + object_id_to_string(ItemVariation, item__event_id=event.pk, pk=data['variation'])
|
||||
item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['variation']))
|
||||
if data['addon_to']:
|
||||
addon_to = OrderPosition.objects.get(order__event=event, pk=data['addon_to'])
|
||||
return _('Position #{posid} created: {item} ({price}) as an add-on to position #{addon_to}.').format(
|
||||
@@ -293,7 +283,7 @@ class OrderChangedSplit(OrderChangeLogEntryType):
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
old_item = str(event.items.get(pk=data['old_item']))
|
||||
if data['old_variation']:
|
||||
old_item += ' - ' + object_id_to_string(ItemVariation, pk=data['old_variation'])
|
||||
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
||||
url = reverse('control:event.order', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
@@ -349,7 +339,6 @@ class OrderChangedSplitFrom(OrderLogEntryType):
|
||||
'pretix.event.checkin.reverted': _('The check-in of position #{posid} on list "{list}" has been reverted.'),
|
||||
})
|
||||
class CheckinErrorLogEntryType(OrderLogEntryType):
|
||||
|
||||
def display(self, logentry: LogEntry, data):
|
||||
return self.display_plain(self.plain, logentry, data)
|
||||
|
||||
@@ -364,7 +353,10 @@ class CheckinErrorLogEntryType(OrderLogEntryType):
|
||||
event = logentry.event
|
||||
|
||||
if 'list' in data and event:
|
||||
data['list'] = object_id_to_string(CheckinList, event_id=event.id, pk=data['list'])
|
||||
try:
|
||||
data['list'] = event.checkin_lists.get(pk=data.get('list')).name
|
||||
except CheckinList.DoesNotExist:
|
||||
data['list'] = _("(unknown)")
|
||||
else:
|
||||
data['list'] = _("(unknown)")
|
||||
|
||||
|
||||
@@ -833,7 +833,7 @@
|
||||
<strong>{% trans "Pending total" %}</strong>
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
|
||||
<strong>{{ pending_sum|money:event.currency }}</strong>
|
||||
<strong>{{ order.pending_sum|money:event.currency }}</strong>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -251,7 +251,7 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if e.voucher %}
|
||||
<a href="{% url "control:event.voucher" organizer=request.event.organizer.slug event=request.event.slug voucher=e.voucher.pk %}?next={{ request.get_full_path|urlencode }}">
|
||||
<a href="{% url "control:event.voucher" organizer=request.event.organizer.slug event=request.event.slug voucher=e.voucher.pk %}">
|
||||
{{ e.voucher }}
|
||||
</a>
|
||||
{% elif not e.voucher and e.availability.0 == 100 and e.availability.1|default_if_none:"none" != "none" %}
|
||||
|
||||
@@ -484,17 +484,10 @@ class CheckinListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Checkin.all.filter(
|
||||
list_id__in=self.request.event.checkin_lists.values_list('id', flat=True),
|
||||
list__event=self.request.event,
|
||||
).select_related(
|
||||
'position', 'position__order', 'position__item', 'position__variation', 'position__subevent'
|
||||
).prefetch_related(
|
||||
# For events with huge numbers of check-ins, prefetch_related is a lot more efficient than
|
||||
# select_related, since it just needs to query one page size of positions instead of joining
|
||||
# multiple huge tables during result computation
|
||||
Prefetch(
|
||||
'position',
|
||||
queryset=OrderPosition.all.select_related(
|
||||
'order', 'item', 'variation', 'subevent',
|
||||
)
|
||||
),
|
||||
'list', 'gate', 'device'
|
||||
)
|
||||
if self.filter_form.is_valid():
|
||||
|
||||
@@ -1568,7 +1568,7 @@ class WidgetSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
|
||||
return ctx
|
||||
|
||||
|
||||
class QuickSetupView(EventPermissionRequiredMixin, FormView):
|
||||
class QuickSetupView(FormView):
|
||||
template_name = 'pretixcontrol/event/quick_setup.html'
|
||||
permission = 'event.settings.general:write'
|
||||
form_class = QuickSetupForm
|
||||
|
||||
@@ -81,7 +81,7 @@ from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedFile, CachedTicket, Checkin, GiftCard, Invoice, InvoiceAddress, Item,
|
||||
ItemVariation, LogEntry, Order, QuestionAnswer, Quota,
|
||||
ScheduledEventExport, SeatCategoryMapping, generate_secret,
|
||||
ScheduledEventExport, generate_secret,
|
||||
)
|
||||
from pretix.base.models.orders import (
|
||||
CancellationRequest, OrderFee, OrderPayment, OrderPosition, OrderRefund,
|
||||
@@ -564,11 +564,10 @@ class OrderDetail(OrderView):
|
||||
})
|
||||
ctx['display_locale'] = dict(settings.LANGUAGES)[self.object.locale or self.request.event.settings.locale]
|
||||
|
||||
pending_sum = self.order.pending_sum
|
||||
ctx['overpaid'] = pending_sum * -1
|
||||
ctx['overpaid'] = self.order.pending_sum * -1
|
||||
ctx['download_buttons'] = self.download_buttons
|
||||
ctx['payment_refund_sum'] = self.order.payment_refund_sum
|
||||
ctx['pending_sum'] = pending_sum
|
||||
ctx['pending_sum'] = self.order.pending_sum
|
||||
ctx['uncancelled_invoice'] = self.order.invoices.exclude(
|
||||
Exists(self.order.invoices.filter(refers=OuterRef('pk'), is_cancellation=True))
|
||||
).exclude(is_cancellation=True).first()
|
||||
@@ -602,9 +601,8 @@ class OrderDetail(OrderView):
|
||||
).prefetch_related(
|
||||
'item__questions', 'issued_gift_cards', 'owned_gift_cards', 'linked_media',
|
||||
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')),
|
||||
Prefetch('all_checkins', queryset=Checkin.all.select_related('list', 'gate').order_by('datetime')),
|
||||
Prefetch('all_checkins', queryset=Checkin.all.select_related('list').order_by('datetime')),
|
||||
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device').order_by('datetime')),
|
||||
Prefetch('subevent', queryset=self.request.event.subevents.all()),
|
||||
).order_by('positionid')
|
||||
|
||||
positions = []
|
||||
@@ -1987,37 +1985,20 @@ class OrderChange(OrderView):
|
||||
def fees(self):
|
||||
fees = list(self.order.fees.all())
|
||||
for f in fees:
|
||||
f.form = OrderFeeChangeForm(
|
||||
prefix='of-{}'.format(f.pk),
|
||||
instance=f,
|
||||
data=self.request.POST if self.request.method == "POST" else None
|
||||
)
|
||||
f.form = OrderFeeChangeForm(prefix='of-{}'.format(f.pk), instance=f,
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
return fees
|
||||
|
||||
@cached_property
|
||||
def positions(self):
|
||||
positions = list(self.order.positions.select_related(
|
||||
'item', 'item__tax_rule', 'used_membership', 'used_membership__membership_type', 'tax_rule',
|
||||
'seat',
|
||||
).prefetch_related(
|
||||
Prefetch(
|
||||
'subevent',
|
||||
queryset=self.request.event.subevents.all(),
|
||||
),
|
||||
'granted_memberships',
|
||||
'issued_gift_cards',
|
||||
'addons',
|
||||
).annotate(
|
||||
_seat_allowed=Exists(SeatCategoryMapping.objects.filter(subevent=OuterRef("subevent"), product=OuterRef("item")))
|
||||
))
|
||||
'seat', 'subevent',
|
||||
).prefetch_related('granted_memberships', 'issued_gift_cards'))
|
||||
for p in positions:
|
||||
p.form = OrderPositionChangeForm(
|
||||
prefix='op-{}'.format(p.pk),
|
||||
instance=p,
|
||||
items=self.items,
|
||||
initial={'seat': p.seat.seat_guid if p.seat else None},
|
||||
data=self.request.POST if self.request.method == "POST" else None
|
||||
)
|
||||
p.form = OrderPositionChangeForm(prefix='op-{}'.format(p.pk), instance=p, items=self.items,
|
||||
initial={'seat': p.seat.seat_guid if p.seat else None},
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
return positions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
@@ -46,7 +46,6 @@ from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import BadRequest, PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.crypto import get_random_string
|
||||
@@ -96,20 +95,6 @@ class RecentAuthenticationRequiredMixin:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
tdelta = time.time() - request.session.get('pretix_auth_login_time', 0)
|
||||
if tdelta > self.max_time:
|
||||
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||
# It's not useful to return a 302 redirect on a XMLHttpRequest request, because
|
||||
# the XMLHttpRequest is unable to detect redirects.
|
||||
return HttpResponse(
|
||||
"Authentication required",
|
||||
status=401,
|
||||
headers={
|
||||
# Appending ?next= is handled by client, because it should be the top-level context url,
|
||||
# not the URL called in the background
|
||||
"X-Login-Url": reverse('control:user.reauth')
|
||||
}
|
||||
)
|
||||
|
||||
return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ from django.shortcuts import redirect, render
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import format_html
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -351,8 +350,6 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return self.request.GET.get("next")
|
||||
return reverse('control:event.vouchers', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
|
||||
@@ -28,7 +28,7 @@ from django.contrib.auth import login as auth_login
|
||||
from django.contrib.gis import geoip2
|
||||
from django.core.cache import cache
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.timezone import localtime, now, override
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_countries.fields import Country
|
||||
from geoip2.errors import AddressNotFoundError
|
||||
@@ -167,13 +167,13 @@ def handle_login_source(user, request):
|
||||
})
|
||||
if user.known_login_sources.count() > 1:
|
||||
# Do not send on first login or first login after introduction of this feature:
|
||||
with language(user.locale), override(user.timezone):
|
||||
with language(user.locale):
|
||||
mail(
|
||||
user.email,
|
||||
_('New sign-in to your account'),
|
||||
'pretixcontrol/email/login_notice.txt',
|
||||
{
|
||||
'when': date_format(localtime(src.last_seen), 'DATETIME_FORMAT'),
|
||||
'when': date_format(src.last_seen, 'DATETIME_FORMAT'),
|
||||
'agent': src.agent_type,
|
||||
'os': src.os_type,
|
||||
# ua-parser returns "Other" for unidentified desktop devices.
|
||||
|
||||
@@ -4,16 +4,16 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-07 09:30+0000\n"
|
||||
"PO-Revision-Date: 2026-07-27 17:00+0000\n"
|
||||
"PO-Revision-Date: 2026-06-20 17:00+0000\n"
|
||||
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\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 2026.7.1\n"
|
||||
"X-Generator: Weblate 2026.6.1\n"
|
||||
|
||||
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html
|
||||
@@ -177,7 +177,7 @@ msgstr "Spansk (Latinamerika)"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Thai"
|
||||
msgstr "Thai"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Turkish"
|
||||
@@ -436,7 +436,7 @@ msgstr ""
|
||||
#: pretix/api/serializers/organizer.py pretix/control/views/organizer.py
|
||||
#, python-format
|
||||
msgid "You've been invited to join %(organizer)s"
|
||||
msgstr "Du er blevet inviteret til at tilslutte dig %(organizer)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/serializers/organizer.py pretix/control/views/organizer.py
|
||||
msgid "This user already has been invited for this team."
|
||||
@@ -458,8 +458,9 @@ msgid "Medium connected to other event"
|
||||
msgstr "Medie er tilknyttet et andet arrangement"
|
||||
|
||||
#: pretix/api/views/checkin.py
|
||||
#, fuzzy
|
||||
msgid "You cannot exchange a medium for a medium."
|
||||
msgstr "Du kan ikke bytte en medium mod en anden medium."
|
||||
msgstr "Du kan ikke annullere denne bestilling."
|
||||
|
||||
#: pretix/api/views/oauth.py pretix/control/logdisplay.py
|
||||
#, python-brace-format
|
||||
@@ -4055,32 +4056,43 @@ msgid "Users"
|
||||
msgstr "Brugere"
|
||||
|
||||
#: pretix/base/models/auth.py
|
||||
#, fuzzy
|
||||
#| msgid "Changes to your account at {organizer}"
|
||||
msgid "Changes to your account"
|
||||
msgstr "Ændringer på din konto"
|
||||
msgstr "Ændringer på din konto hos {organizer}"
|
||||
|
||||
#: pretix/base/models/auth.py
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid ""
|
||||
#| "to confirm changing your email address from {old_email}\n"
|
||||
#| "to {new_email}, use the following code:"
|
||||
msgid ""
|
||||
"To change your email address from {old_email} to {new_email}, use the "
|
||||
"following code:"
|
||||
msgstr ""
|
||||
"Hvis du vil ændre din e-mailadresse fra {old_email} til {new_email}, skal du "
|
||||
"bruge følgende kode:"
|
||||
"For at bekræfte ændringen af din e-mailadresse fra {old_email}\n"
|
||||
"til {new_email} skal du bruge følgende kode:"
|
||||
|
||||
#: pretix/base/models/auth.py
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid ""
|
||||
#| "to confirm changing your email address from {old_email}\n"
|
||||
#| "to {new_email}, use the following code:"
|
||||
msgid ""
|
||||
"To verify your email address {email} on {instance}, use the following code:"
|
||||
msgstr ""
|
||||
"Brug følgende kode til at bekræfte din e-mailadresse {email} på {instance}:"
|
||||
"For at bekræfte ændringen af din e-mailadresse fra {old_email}\n"
|
||||
"til {new_email} skal du bruge følgende kode:"
|
||||
|
||||
#: pretix/base/models/auth.py
|
||||
#, fuzzy
|
||||
msgid "Your confirmation code"
|
||||
msgstr "Din bekræftelseskode"
|
||||
msgstr "Bekræftelsestekst"
|
||||
|
||||
#: pretix/base/models/auth.py
|
||||
#, fuzzy
|
||||
msgid "Reset your password"
|
||||
msgstr "Nulstil din adgangskode"
|
||||
msgstr "Nulstil adgangskode"
|
||||
|
||||
#: pretix/base/models/checkin.py
|
||||
msgid "All products (including newly created ones)"
|
||||
@@ -4241,16 +4253,22 @@ msgid "Check-in annulled"
|
||||
msgstr "Check-in annulleret"
|
||||
|
||||
#: pretix/base/models/checkin.py
|
||||
#, fuzzy
|
||||
#| msgid "Ticket already used"
|
||||
msgid "Ticket already exchanged"
|
||||
msgstr "Billetten er allerede ombyttet"
|
||||
msgstr "Billet allerede i brug"
|
||||
|
||||
#: pretix/base/models/checkin.py
|
||||
#, fuzzy
|
||||
#| msgid "Reusable media"
|
||||
msgid "Reusable medium invalid"
|
||||
msgstr "Genanvendeligt medium er ugyldigt"
|
||||
msgstr "Genanvendelige medier"
|
||||
|
||||
#: pretix/base/models/checkin.py
|
||||
#, fuzzy
|
||||
#| msgid "Reusable media type"
|
||||
msgid "Reusable medium already exists"
|
||||
msgstr "Det genanvendelige medium findes allerede"
|
||||
msgstr "Genanvendelig medietype"
|
||||
|
||||
#: pretix/base/models/customers.py
|
||||
msgid "Provider name"
|
||||
@@ -4825,8 +4843,10 @@ msgstr ""
|
||||
"begge dele."
|
||||
|
||||
#: pretix/base/models/event.py
|
||||
#, fuzzy
|
||||
#| msgid "The bundled item must belong to the same event as the item."
|
||||
msgid "Property and event must belong to the same organizer."
|
||||
msgstr "Ejendommen og arrangementet skal tilhøre den samme arrangør."
|
||||
msgstr "Artikelpakken skal tilhøre samme arrangement som selve artiklen."
|
||||
|
||||
#: pretix/base/models/event.py pretix/base/models/organizer.py
|
||||
msgid "Link text"
|
||||
@@ -5062,40 +5082,42 @@ msgid "Show product with info on why it’s unavailable"
|
||||
msgstr "Vis produktet med oplysninger om, hvorfor det ikke er tilgængeligt"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
#, fuzzy
|
||||
#| msgid "Don't use re-usable media, use regular one-off tickets"
|
||||
msgid "Don't use reusable media, use regular one-off tickets"
|
||||
msgstr "Brug ikke genanvendelige medier, men almindelige engangsbilletter"
|
||||
msgstr "Brug ikke genanvendelige medier, brug almindelige engangsbilletter"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
msgid "Require a previously unknown medium to be newly added"
|
||||
msgstr "Påkræv, at et førhen ukendt medie bliver tilføjet fornylig"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
#, fuzzy
|
||||
#| msgid "Require an existing medium to be re-used"
|
||||
msgid "Require an existing medium to be reused, replacing any previous tickets"
|
||||
msgstr ""
|
||||
"Påkræv, at et eksisterende medium genbruges, hvorved eventuelle tidligere "
|
||||
"billetter erstattes"
|
||||
msgstr "Påkræv genanvendelse af et eksisterende medie"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
#, fuzzy
|
||||
#| msgid "Require either an existing or a new medium to be used"
|
||||
msgid ""
|
||||
"Require either an existing or a new medium to be used, replacing any "
|
||||
"previous tickets"
|
||||
msgstr ""
|
||||
"Påkræv, at der anvendes enten et eksisterende eller et nyt medium, som "
|
||||
"erstatter eventuelle tidligere billetter"
|
||||
msgstr "Påkræv, at der enten bruges et eksisterende eller et nyt medie"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
#, fuzzy
|
||||
#| msgid "Require an existing medium to be re-used"
|
||||
msgid "Require an existing medium to be reused, adding to any previous tickets"
|
||||
msgstr ""
|
||||
"Påkræv, at et eksisterende medium genbruges og tilføjes samtlige tidligere "
|
||||
"billetter"
|
||||
msgstr "Påkræv genanvendelse af et eksisterende medie"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
#, fuzzy
|
||||
#| msgid "Require either an existing or a new medium to be used"
|
||||
msgid ""
|
||||
"Require either an existing or a new medium to be used, adding to any "
|
||||
"previous tickets"
|
||||
msgstr ""
|
||||
"Påkræv, at der anvendes enten et eksisterende eller et nyt medie, hvilket "
|
||||
"tilføjes til eventuelle tidligere billetter"
|
||||
msgstr "Påkræv, at der enten bruges et eksisterende eller et nyt medie"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
msgid "Category"
|
||||
|
||||
@@ -8,16 +8,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-07 09:30+0000\n"
|
||||
"PO-Revision-Date: 2026-07-27 17:00+0000\n"
|
||||
"PO-Revision-Date: 2024-09-23 18:00+0000\n"
|
||||
"Last-Translator: Albizuri <oier@puntu.eus>\n"
|
||||
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"eu/>\n"
|
||||
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix/eu/"
|
||||
">\n"
|
||||
"Language: eu\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 2026.7.1\n"
|
||||
"X-Generator: Weblate 5.7.2\n"
|
||||
|
||||
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html
|
||||
@@ -13559,8 +13559,10 @@ msgstr ""
|
||||
#: pretix/base/templates/pretixbase/email/order_details.html
|
||||
#: pretix/presale/templates/pretixpresale/event/base.html
|
||||
#: pretix/presale/templates/pretixpresale/organizers/base.html
|
||||
#, fuzzy
|
||||
#| msgid "Contact:"
|
||||
msgid "Contact"
|
||||
msgstr "Harremanetarako"
|
||||
msgstr "Kontaktua:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/shred_completed.txt
|
||||
#, fuzzy, python-format
|
||||
@@ -18283,7 +18285,7 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html
|
||||
#: pretix/presale/templates/pretixpresale/organizers/index.html
|
||||
msgid "Tickets"
|
||||
msgstr "Sarrerak"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/navigation.py
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html
|
||||
@@ -33874,11 +33876,12 @@ msgid "Show full-size image of %(item)s"
|
||||
msgstr "Erakutsi %(item)s-ren irudi osoa"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
|
||||
#, python-format
|
||||
#, fuzzy, python-format
|
||||
#| msgid "Upcoming events"
|
||||
msgid "%(amount)s× in your cart"
|
||||
msgid_plural "%(amount)s× in your cart"
|
||||
msgstr[0] "%(amount)s× zure saskian"
|
||||
msgstr[1] "%(amount)s× zure saskian"
|
||||
msgstr[0] "Hurrengo ekitaldiak"
|
||||
msgstr[1] "Hurrengo ekitaldiak"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher.html
|
||||
@@ -34122,14 +34125,18 @@ msgstr ""
|
||||
"Kontuan izan oraindik zure ordainketaren zain gaudela prozesua osatzeko."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Please bookmark or save the link to this exact page if you want to access "
|
||||
#| "your order later. We also sent you an email containing the link to the "
|
||||
#| "address you specified."
|
||||
msgid ""
|
||||
"Please bookmark or save the link to this exact page if you want to access "
|
||||
"your order later. We also sent you an email to the address you specified "
|
||||
"containing the link to this page."
|
||||
msgstr ""
|
||||
"Mesedez, gorde orri honetarako esteka zure eskaera geroago ikusi nahi "
|
||||
"baduzu. E-mail bat ere bidali dizugu zehaztu duzun helbidera, orrialde "
|
||||
"honetarako estekarekin."
|
||||
"baduzu. E-mail bat ere bidali dizugu, zehaztutako helbidera lotura duena."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html
|
||||
#, fuzzy
|
||||
|
||||
@@ -111,6 +111,7 @@ class Paypal(BasePaymentProvider):
|
||||
('client_id',
|
||||
forms.CharField(
|
||||
label=_('Client ID'),
|
||||
max_length=80,
|
||||
min_length=80,
|
||||
help_text=format_html(
|
||||
'<a target="_blank" rel="noopener" href="{docs_url}">{text}</a>',
|
||||
|
||||
@@ -1164,22 +1164,6 @@ def test_event_update_seating(token_client, organizer, event, item, seatingplan)
|
||||
assert all(seat['product'] == item.pk for seat in resp.data['results'])
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_no_create_or_delete_seats(token_client, organizer, event, item, seatingplan):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/seats/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
'blocked': False,
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 405
|
||||
resp = token_client.delete(
|
||||
'/api/v1/organizers/{}/events/{}/seats/3/'.format(organizer.slug, event.slug),
|
||||
)
|
||||
assert resp.status_code == 405
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_update_seating_invalid_product(token_client, organizer, event, item, seatingplan):
|
||||
resp = token_client.patch(
|
||||
|
||||
@@ -90,7 +90,6 @@ event_urls = [
|
||||
"delete/",
|
||||
"dangerzone/",
|
||||
"cancel/",
|
||||
"quickstart/",
|
||||
"settings/",
|
||||
"settings/plugins",
|
||||
"settings/payment",
|
||||
@@ -312,7 +311,6 @@ event_permission_urls = [
|
||||
("event.settings.general:write", "live/", 200, HTTP_GET),
|
||||
("event.settings.general:write", "delete/", 200, HTTP_GET),
|
||||
("event.settings.general:write", "dangerzone/", 200, HTTP_GET),
|
||||
("event.settings.general:write", "quickstart/", 200, HTTP_GET),
|
||||
("event.settings.general:write", "settings/", 200, HTTP_GET),
|
||||
# ("event.settings.payment:write", "settings/payment", 200, HTTP_GET), GET allowed also with other permissions
|
||||
("event.settings.payment:write", "settings/payment", 200, HTTP_POST),
|
||||
|
||||
Reference in New Issue
Block a user