mirror of
https://github.com/pretix/pretix.git
synced 2026-10-02 19:24:42 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a883f709d0 |
+2
-2
@@ -92,7 +92,7 @@ dependencies = [
|
|||||||
"redis==7.1.*",
|
"redis==7.1.*",
|
||||||
"reportlab==4.4.*",
|
"reportlab==4.4.*",
|
||||||
"requests==2.32.*",
|
"requests==2.32.*",
|
||||||
"sentry-sdk==2.53.*",
|
"sentry-sdk==2.52.*",
|
||||||
"sepaxml==2.7.*",
|
"sepaxml==2.7.*",
|
||||||
"stripe==7.9.*",
|
"stripe==7.9.*",
|
||||||
"text-unidecode==1.*",
|
"text-unidecode==1.*",
|
||||||
@@ -110,7 +110,7 @@ dev = [
|
|||||||
"aiohttp==3.13.*",
|
"aiohttp==3.13.*",
|
||||||
"coverage",
|
"coverage",
|
||||||
"coveralls",
|
"coveralls",
|
||||||
"fakeredis==2.34.*",
|
"fakeredis==2.33.*",
|
||||||
"flake8==7.3.*",
|
"flake8==7.3.*",
|
||||||
"freezegun",
|
"freezegun",
|
||||||
"isort==7.0.*",
|
"isort==7.0.*",
|
||||||
|
|||||||
@@ -188,15 +188,11 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
|||||||
clist = self.get_object()
|
clist = self.get_object()
|
||||||
if serializer.validated_data.get('nonce'):
|
if serializer.validated_data.get('nonce'):
|
||||||
if kwargs.get('position'):
|
if kwargs.get('position'):
|
||||||
prev = kwargs['position'].all_checkins.filter(
|
prev = kwargs['position'].all_checkins.filter(nonce=serializer.validated_data['nonce']).first()
|
||||||
nonce=serializer.validated_data['nonce'],
|
|
||||||
successful=False
|
|
||||||
).first()
|
|
||||||
else:
|
else:
|
||||||
prev = clist.checkins.filter(
|
prev = clist.checkins.filter(
|
||||||
nonce=serializer.validated_data['nonce'],
|
nonce=serializer.validated_data['nonce'],
|
||||||
raw_barcode=serializer.validated_data['raw_barcode'],
|
raw_barcode=serializer.validated_data['raw_barcode'],
|
||||||
successful=False
|
|
||||||
).first()
|
).first()
|
||||||
if prev:
|
if prev:
|
||||||
# Ignore because nonce is already handled
|
# Ignore because nonce is already handled
|
||||||
|
|||||||
@@ -259,14 +259,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
|||||||
action='pretix.giftcards.transaction.manual',
|
action='pretix.giftcards.transaction.manual',
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
auth=self.request.auth,
|
auth=self.request.auth,
|
||||||
data=merge_dicts(
|
data=merge_dicts(self.request.data, {'id': inst.pk, 'acceptor_id': self.request.organizer.id})
|
||||||
self.request.data,
|
|
||||||
{
|
|
||||||
'id': inst.pk,
|
|
||||||
'acceptor_id': self.request.organizer.id,
|
|
||||||
'acceptor_slug': self.request.organizer.slug
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
@@ -297,11 +290,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
|||||||
action='pretix.giftcards.transaction.manual',
|
action='pretix.giftcards.transaction.manual',
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
auth=self.request.auth,
|
auth=self.request.auth,
|
||||||
data={
|
data={'value': diff, 'acceptor_id': self.request.organizer.id}
|
||||||
'value': diff,
|
|
||||||
'acceptor_id': self.request.organizer.id,
|
|
||||||
'acceptor_slug': self.request.organizer.slug
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return inst
|
return inst
|
||||||
@@ -331,8 +320,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
|||||||
data={
|
data={
|
||||||
'value': value,
|
'value': value,
|
||||||
'text': text,
|
'text': text,
|
||||||
'acceptor_id': self.request.organizer.id,
|
'acceptor_id': self.request.organizer.id
|
||||||
'acceptor_slug': self.request.organizer.slug
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK)
|
return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK)
|
||||||
|
|||||||
@@ -198,7 +198,6 @@ class ParametrizedGiftcardTransactionWebhookEvent(ParametrizedWebhookEvent):
|
|||||||
'notification_id': logentry.pk,
|
'notification_id': logentry.pk,
|
||||||
'issuer_id': logentry.organizer_id,
|
'issuer_id': logentry.organizer_id,
|
||||||
'acceptor_id': logentry.parsed_data.get('acceptor_id'),
|
'acceptor_id': logentry.parsed_data.get('acceptor_id'),
|
||||||
'acceptor_slug': logentry.parsed_data.get('acceptor_slug'),
|
|
||||||
'giftcard': giftcard.pk,
|
'giftcard': giftcard.pk,
|
||||||
'action': logentry.action_type,
|
'action': logentry.action_type,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -651,7 +651,6 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
pgettext('address', 'State'),
|
pgettext('address', 'State'),
|
||||||
_('Voucher'),
|
_('Voucher'),
|
||||||
_('Voucher budget usage'),
|
_('Voucher budget usage'),
|
||||||
_('Voucher tag'),
|
|
||||||
_('Pseudonymization ID'),
|
_('Pseudonymization ID'),
|
||||||
_('Ticket secret'),
|
_('Ticket secret'),
|
||||||
_('Seat ID'),
|
_('Seat ID'),
|
||||||
@@ -770,7 +769,6 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
op.state_for_address or '',
|
op.state_for_address or '',
|
||||||
op.voucher.code if op.voucher else '',
|
op.voucher.code if op.voucher else '',
|
||||||
op.voucher_budget_use if op.voucher_budget_use else '',
|
op.voucher_budget_use if op.voucher_budget_use else '',
|
||||||
op.voucher.tag if op.voucher else '',
|
|
||||||
op.pseudonymization_id,
|
op.pseudonymization_id,
|
||||||
op.secret,
|
op.secret,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class AllowIgnoreQuotaColumn(BooleanColumnMixin, ImportColumn):
|
|||||||
|
|
||||||
class PriceModeColumn(ImportColumn):
|
class PriceModeColumn(ImportColumn):
|
||||||
identifier = 'price_mode'
|
identifier = 'price_mode'
|
||||||
verbose_name = gettext_lazy('Price effect')
|
verbose_name = gettext_lazy('Price mode')
|
||||||
default_value = None
|
default_value = None
|
||||||
initial = 'static:none'
|
initial = 'static:none'
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ class PriceModeColumn(ImportColumn):
|
|||||||
elif value in reverse:
|
elif value in reverse:
|
||||||
return reverse[value]
|
return reverse[value]
|
||||||
else:
|
else:
|
||||||
raise ValidationError(_("Could not parse {value} as a price effect, use one of {options}.").format(
|
raise ValidationError(_("Could not parse {value} as a price mode, use one of {options}.").format(
|
||||||
value=value, options=', '.join(d.keys())
|
value=value, options=', '.join(d.keys())
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ class ValueColumn(DecimalColumnMixin, ImportColumn):
|
|||||||
def clean(self, value, previous_values):
|
def clean(self, value, previous_values):
|
||||||
value = super().clean(value, previous_values)
|
value = super().clean(value, previous_values)
|
||||||
if value and previous_values.get("price_mode") == "none":
|
if value and previous_values.get("price_mode") == "none":
|
||||||
raise ValidationError(_("It is pointless to set a value without a price effect."))
|
raise ValidationError(_("It is pointless to set a value without a price mode."))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def assign(self, value, obj: Voucher, **kwargs):
|
def assign(self, value, obj: Voucher, **kwargs):
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ class Voucher(LoggedModel):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
price_mode = models.CharField(
|
price_mode = models.CharField(
|
||||||
verbose_name=_("Price effect"),
|
verbose_name=_("Price mode"),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
choices=PRICE_MODES,
|
choices=PRICE_MODES,
|
||||||
default='none'
|
default='none'
|
||||||
|
|||||||
@@ -1650,8 +1650,7 @@ class GiftCardPayment(BasePaymentProvider):
|
|||||||
action='pretix.giftcards.transaction.payment',
|
action='pretix.giftcards.transaction.payment',
|
||||||
data={
|
data={
|
||||||
'value': trans.value,
|
'value': trans.value,
|
||||||
'acceptor_id': self.event.organizer.id,
|
'acceptor_id': self.event.organizer.id
|
||||||
'acceptor_slug': self.event.organizer.slug
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except PaymentException as e:
|
except PaymentException as e:
|
||||||
@@ -1683,7 +1682,6 @@ class GiftCardPayment(BasePaymentProvider):
|
|||||||
data={
|
data={
|
||||||
'value': refund.amount,
|
'value': refund.amount,
|
||||||
'acceptor_id': self.event.organizer.id,
|
'acceptor_id': self.event.organizer.id,
|
||||||
'acceptor_slug': self.event.organizer.slug,
|
|
||||||
'text': refund.comment,
|
'text': refund.comment,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -409,6 +409,18 @@ def mail_send_task(self, **kwargs) -> bool:
|
|||||||
outgoing_mail.inflight_since = now()
|
outgoing_mail.inflight_since = now()
|
||||||
outgoing_mail.save(update_fields=["status", "inflight_since"])
|
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 = dict(outgoing_mail.headers)
|
||||||
headers.setdefault('X-PX-Correlation', str(outgoing_mail.guid))
|
headers.setdefault('X-PX-Correlation', str(outgoing_mail.guid))
|
||||||
email = CustomEmail(
|
email = CustomEmail(
|
||||||
|
|||||||
@@ -253,8 +253,7 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
|
|||||||
auth=auth,
|
auth=auth,
|
||||||
data={
|
data={
|
||||||
'value': position.price,
|
'value': position.price,
|
||||||
'acceptor_id': order.event.organizer.id,
|
'acceptor_id': order.event.organizer.id
|
||||||
'acceptor_slug': order.event.organizer.slug
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
@@ -564,7 +563,6 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
|||||||
data={
|
data={
|
||||||
'value': -position.price,
|
'value': -position.price,
|
||||||
'acceptor_id': order.event.organizer.id,
|
'acceptor_id': order.event.organizer.id,
|
||||||
'acceptor_slug': order.event.organizer.slug
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2459,8 +2457,7 @@ class OrderChangeManager:
|
|||||||
auth=self.auth,
|
auth=self.auth,
|
||||||
data={
|
data={
|
||||||
'value': -position.price,
|
'value': -position.price,
|
||||||
'acceptor_id': self.order.event.organizer.id,
|
'acceptor_id': self.order.event.organizer.id
|
||||||
'acceptor_slug': self.order.event.organizer.slug
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2486,8 +2483,7 @@ class OrderChangeManager:
|
|||||||
auth=self.auth,
|
auth=self.auth,
|
||||||
data={
|
data={
|
||||||
'value': -opa.position.price,
|
'value': -opa.position.price,
|
||||||
'acceptor_id': self.order.event.organizer.id,
|
'acceptor_id': self.order.event.organizer.id
|
||||||
'acceptor_slug': self.order.event.organizer.slug
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -3457,7 +3453,6 @@ def signal_listener_issue_giftcards(sender: Event, order: Order, **kwargs):
|
|||||||
data={
|
data={
|
||||||
'value': trans.value,
|
'value': trans.value,
|
||||||
'acceptor_id': order.event.organizer.id,
|
'acceptor_id': order.event.organizer.id,
|
||||||
'acceptor_slug': order.event.organizer.slug
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
any_giftcards = True
|
any_giftcards = True
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import logging
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.db.models import Prefetch, prefetch_related_objects
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.formats import date_format
|
from django.utils.formats import date_format
|
||||||
from django.utils.html import escape, mark_safe
|
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 (
|
from pretix.base.i18n import (
|
||||||
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
|
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
|
||||||
)
|
)
|
||||||
|
from pretix.base.models import EventMetaValue
|
||||||
from pretix.base.reldate import RelativeDateWrapper
|
from pretix.base.reldate import RelativeDateWrapper
|
||||||
from pretix.base.settings import PERSON_NAME_SCHEMES, get_name_parts_localized
|
from pretix.base.settings import PERSON_NAME_SCHEMES, get_name_parts_localized
|
||||||
from pretix.base.signals import (
|
from pretix.base.signals import (
|
||||||
@@ -752,6 +754,11 @@ def base_placeholders(sender, **kwargs):
|
|||||||
name_scheme['sample'][f]
|
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():
|
for k, v in sender.meta_data.items():
|
||||||
ph.append(MarkdownTextPlaceholder(
|
ph.append(MarkdownTextPlaceholder(
|
||||||
'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k],
|
'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k],
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
<h1>{% trans "Not found" %}</h1>
|
<h1>{% trans "Not found" %}</h1>
|
||||||
<p>{% trans "I'm afraid we could not find the the resource you requested." %}</p>
|
<p>{% trans "I'm afraid we could not find the the resource you requested." %}</p>
|
||||||
<p>{{ exception }}</p>
|
<p>{{ exception }}</p>
|
||||||
|
<p class="links">
|
||||||
|
<a id='goback' href='#'>{% trans "Take a step back" %}</a>
|
||||||
|
</p>
|
||||||
{% if request.user.is_staff and not staff_session %}
|
{% if request.user.is_staff and not staff_session %}
|
||||||
<form action="{% url 'control:user.sudo' %}?next={{ request.path|add:"?"|add:request.GET.urlencode|urlencode }}" method="post">
|
<form action="{% url 'control:user.sudo' %}?next={{ request.path|add:"?"|add:request.GET.urlencode|urlencode }}" method="post">
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -144,23 +144,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% trans "If you lose access to your devices, you can use one of the following keys to log in. We recommend to store them in a safe place, e.g. printed out or in a password manager. Every token can be used at most once." %}
|
||||||
If you lose access to your devices, you can use one of your emergency tokens to log in.
|
|
||||||
We recommend to store them in a safe place, e.g. printed out or in a password manager.
|
|
||||||
Every token can be used at most once.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
</p>
|
||||||
{% if static_tokens_device %}
|
<p>{% trans "Unused tokens:" %}</p>
|
||||||
<p>
|
<ul>
|
||||||
{% blocktrans trimmed with generation_date_time=static_tokens_device.created_at %}
|
{% for t in static_tokens %}
|
||||||
You generated your emergency tokens on {{ generation_date_time }}.
|
<li><code>{{ t.token }}</code></li>
|
||||||
{% endblocktrans %}
|
{% endfor %}
|
||||||
</p>
|
</ul>
|
||||||
{% else %}
|
|
||||||
<p>
|
|
||||||
{% trans "You don't have any emergency tokens yet." %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default">
|
<a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default">
|
||||||
<span class="fa fa-refresh"></span>
|
<span class="fa fa-refresh"></span>
|
||||||
{% trans "Generate new emergency tokens" %}
|
{% trans "Generate new emergency tokens" %}
|
||||||
|
|||||||
@@ -1850,8 +1850,7 @@ class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
|||||||
data={
|
data={
|
||||||
'value': value,
|
'value': value,
|
||||||
'text': request.POST.get('text'),
|
'text': request.POST.get('text'),
|
||||||
'acceptor_id': self.request.organizer.id,
|
'acceptor_id': self.request.organizer.id
|
||||||
'acceptor_slug': self.request.organizer.slug
|
|
||||||
},
|
},
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
)
|
)
|
||||||
@@ -1914,8 +1913,7 @@ class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
|||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
data={
|
data={
|
||||||
'value': form.cleaned_data['value'],
|
'value': form.cleaned_data['value'],
|
||||||
'acceptor_id': self.request.organizer.id,
|
'acceptor_id': self.request.organizer.id
|
||||||
'acceptor_slug': self.request.organizer.slug
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return redirect(reverse(
|
return redirect(reverse(
|
||||||
|
|||||||
@@ -49,14 +49,12 @@ from django.db import transaction
|
|||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.http import url_has_allowed_host_and_scheme
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.cache import never_cache
|
|
||||||
from django.views.generic import FormView, ListView, TemplateView, UpdateView
|
from django.views.generic import FormView, ListView, TemplateView, UpdateView
|
||||||
from django_otp.plugins.otp_static.models import StaticDevice
|
from django_otp.plugins.otp_static.models import StaticDevice
|
||||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||||
@@ -87,9 +85,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class RecentAuthenticationRequiredMixin:
|
class RecentAuthenticationRequiredMixin:
|
||||||
max_time = 900
|
max_time = 3600
|
||||||
|
|
||||||
@method_decorator(never_cache)
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
tdelta = time.time() - request.session.get('pretix_auth_login_time', 0)
|
tdelta = time.time() - request.session.get('pretix_auth_login_time', 0)
|
||||||
if tdelta > self.max_time:
|
if tdelta > self.max_time:
|
||||||
@@ -292,13 +289,16 @@ class User2FAMainView(RecentAuthenticationRequiredMixin, TemplateView):
|
|||||||
ctx = super().get_context_data()
|
ctx = super().get_context_data()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ctx['static_tokens_device'] = StaticDevice.objects.get(user=self.request.user, name='emergency')
|
ctx['static_tokens'] = StaticDevice.objects.get(user=self.request.user, name='emergency').token_set.all()
|
||||||
except StaticDevice.MultipleObjectsReturned:
|
except StaticDevice.MultipleObjectsReturned:
|
||||||
ctx['static_tokens_device'] = StaticDevice.objects.filter(
|
ctx['static_tokens'] = StaticDevice.objects.filter(
|
||||||
user=self.request.user, name='emergency'
|
user=self.request.user, name='emergency'
|
||||||
).first()
|
).first().token_set.all()
|
||||||
except StaticDevice.DoesNotExist:
|
except StaticDevice.DoesNotExist:
|
||||||
ctx['static_tokens_device'] = None
|
d = StaticDevice.objects.create(user=self.request.user, name='emergency')
|
||||||
|
for i in range(10):
|
||||||
|
d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890'))
|
||||||
|
ctx['static_tokens'] = d.token_set.all()
|
||||||
|
|
||||||
ctx['devices'] = []
|
ctx['devices'] = []
|
||||||
for dt in REAL_DEVICE_TYPES:
|
for dt in REAL_DEVICE_TYPES:
|
||||||
@@ -631,8 +631,7 @@ class User2FARegenerateEmergencyView(RecentAuthenticationRequiredMixin, Template
|
|||||||
self.request.user.update_session_token()
|
self.request.user.update_session_token()
|
||||||
update_session_auth_hash(self.request, self.request.user)
|
update_session_auth_hash(self.request, self.request.user)
|
||||||
messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe '
|
messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe '
|
||||||
'place in case you lose access to your devices. You will not be able to view them '
|
'place in case you lose access to your devices.'))
|
||||||
'again here.\n\nYour emergency codes:\n- ' + '\n- '.join(t.token for t in d.token_set.all())))
|
|
||||||
return redirect(reverse('control:user.settings.2fa'))
|
return redirect(reverse('control:user.settings.2fa'))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ def set_cookie_without_samesite(request, response, key, *args, **kwargs):
|
|||||||
if not is_secure:
|
if not is_secure:
|
||||||
# https://www.chromestatus.com/feature/5633521622188032
|
# https://www.chromestatus.com/feature/5633521622188032
|
||||||
return
|
return
|
||||||
|
if should_send_same_site_none(request.headers.get('User-Agent', '')):
|
||||||
useragent = request.headers.get('User-Agent', '')
|
|
||||||
|
|
||||||
if should_send_same_site_none(useragent):
|
|
||||||
# Chromium is rolling out SameSite=Lax as a default
|
# Chromium is rolling out SameSite=Lax as a default
|
||||||
# https://www.chromestatus.com/feature/5088147346030592
|
# https://www.chromestatus.com/feature/5088147346030592
|
||||||
# This however breaks all pretix-in-an-iframe things, such as the pretix Widget.
|
# This however breaks all pretix-in-an-iframe things, such as the pretix Widget.
|
||||||
@@ -47,29 +44,8 @@ def set_cookie_without_samesite(request, response, key, *args, **kwargs):
|
|||||||
# This will only work on secure cookies as well
|
# This will only work on secure cookies as well
|
||||||
# https://www.chromestatus.com/feature/5633521622188032
|
# https://www.chromestatus.com/feature/5633521622188032
|
||||||
response.cookies[key]['secure'] = is_secure
|
response.cookies[key]['secure'] = is_secure
|
||||||
|
# CHIPS
|
||||||
if can_send_partitioned_cookie(useragent):
|
response.cookies[key]['Partitioned'] = True
|
||||||
# CHIPS
|
|
||||||
response.cookies[key]['Partitioned'] = True
|
|
||||||
|
|
||||||
|
|
||||||
def can_send_partitioned_cookie(useragent):
|
|
||||||
# Safari currently exhibits a bug where Partitioned cookies (CHIPS) are not
|
|
||||||
# sent back to the originating site after multi-hop cross-site redirects,
|
|
||||||
# breaking SSO login flows in pretix.
|
|
||||||
#
|
|
||||||
# Partitioned cookies were initially introduced in Safari 18.4, removed
|
|
||||||
# again in 18.5 due to a bug, and reintroduced in Safari 26.2, where the
|
|
||||||
# current issue is present.
|
|
||||||
#
|
|
||||||
# Once the Safari issue is fixed, this check should be refined to be
|
|
||||||
# conditional on the affected versions only.
|
|
||||||
#
|
|
||||||
# WebKit issues:
|
|
||||||
#
|
|
||||||
# - https://bugs.webkit.org/show_bug.cgi?id=292975
|
|
||||||
# - https://bugs.webkit.org/show_bug.cgi?id=306194
|
|
||||||
return not is_safari(useragent)
|
|
||||||
|
|
||||||
|
|
||||||
# Based on https://www.chromium.org/updates/same-site/incompatible-clients
|
# Based on https://www.chromium.org/updates/same-site/incompatible-clients
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
<dt>{% trans "Reference code (important):" %}</dt><dd><b>{{ code }}</b></dd>
|
<dt>{% trans "Reference code (important):" %}</dt><dd><b>{{ code }}</b></dd>
|
||||||
<dt>{% trans "Amount:" %}</dt><dd>{{ amount|money:event.currency }}</dd>
|
<dt>{% trans "Amount:" %}</dt><dd>{{ amount|money:event.currency }}</dd>
|
||||||
{% if settings.bank_details_type == "sepa" %}
|
{% if settings.bank_details_type == "sepa" %}
|
||||||
<dt>{% trans "Account holder" %}:</dt><dd>{{ settings.bank_details_sepa_name }}</dd>
|
<dt>{% trans "Account holder" %}:</dt><dd>{{ settings.bank_details_sepa_name }}</dt>
|
||||||
<dt>{% trans "IBAN" %}:</dt><dd>{{ settings.bank_details_sepa_iban|ibanformat }}</dd>
|
<dt>{% trans "IBAN" %}:</dt><dd>{{ settings.bank_details_sepa_iban|ibanformat }}</dt>
|
||||||
<dt>{% trans "BIC" %}:</dt><dd>{{ settings.bank_details_sepa_bic }}</dd>
|
<dt>{% trans "BIC" %}:</dt><dd>{{ settings.bank_details_sepa_bic }}</dt>
|
||||||
<dt>{% trans "Bank" %}:</dt><dd>{{ settings.bank_details_sepa_bank }}</dd>
|
<dt>{% trans "Bank" %}:</dt><dd>{{ settings.bank_details_sepa_bank }}</dt>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
{% if details %}
|
{% if details %}
|
||||||
@@ -38,4 +38,4 @@
|
|||||||
{% if payment_qr_codes %}
|
{% if payment_qr_codes %}
|
||||||
{% include "pretixpresale/event/payment_qr_codes.html" %}
|
{% include "pretixpresale/event/payment_qr_codes.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -786,29 +786,23 @@ class PaypalMethod(BasePaymentProvider):
|
|||||||
else:
|
else:
|
||||||
pp_captured_order = response.result
|
pp_captured_order = response.result
|
||||||
|
|
||||||
|
for purchaseunit in pp_captured_order.purchase_units:
|
||||||
|
for capture in purchaseunit.payments.captures:
|
||||||
|
try:
|
||||||
|
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment, reference=capture.id)
|
||||||
|
except ReferencedPayPalObject.MultipleObjectsReturned:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if capture.status != '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.refresh_from_db()
|
payment.refresh_from_db()
|
||||||
|
|
||||||
any_captures = False
|
|
||||||
all_captures_completed = True
|
|
||||||
for purchaseunit in pp_captured_order.purchase_units:
|
|
||||||
for capture in purchaseunit.payments.captures:
|
|
||||||
try:
|
|
||||||
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment, reference=capture.id)
|
|
||||||
except ReferencedPayPalObject.MultipleObjectsReturned:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if capture.status != 'COMPLETED':
|
|
||||||
all_captures_completed = False
|
|
||||||
else:
|
|
||||||
any_captures = True
|
|
||||||
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
|
|
||||||
|
|
||||||
if pp_captured_order.status != 'COMPLETED':
|
if pp_captured_order.status != 'COMPLETED':
|
||||||
payment.fail(info=pp_captured_order.dict())
|
payment.fail(info=pp_captured_order.dict())
|
||||||
logger.error('Invalid state: %s' % repr(pp_captured_order.dict()))
|
logger.error('Invalid state: %s' % repr(pp_captured_order.dict()))
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ logger = logging.getLogger('pretix.plugins.stripe')
|
|||||||
# - UPI: ✗
|
# - UPI: ✗
|
||||||
# - Netbanking: ✗
|
# - Netbanking: ✗
|
||||||
# - TWINT: ✓
|
# - TWINT: ✓
|
||||||
# - Wero: ✓ (No settings UI yet)
|
|
||||||
#
|
#
|
||||||
# Bank transfers
|
# Bank transfers
|
||||||
# - ACH Bank Transfer: ✗
|
# - ACH Bank Transfer: ✗
|
||||||
@@ -510,15 +509,6 @@ class StripeSettingsHolder(BasePaymentProvider):
|
|||||||
'before they work properly.'),
|
'before they work properly.'),
|
||||||
required=False,
|
required=False,
|
||||||
)),
|
)),
|
||||||
# Disabled for now, since still in closed Beta and only available to dedicated boarded accounts.
|
|
||||||
# ('method_wero',
|
|
||||||
# forms.BooleanField(
|
|
||||||
# label=_('Wero'),
|
|
||||||
# disabled=self.event.currency not in 'EUR',
|
|
||||||
# help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
|
|
||||||
# 'before they work properly.'),
|
|
||||||
# required=False,
|
|
||||||
# )),
|
|
||||||
] + extra_fields + list(super().settings_form_fields.items()) + moto_settings
|
] + extra_fields + list(super().settings_form_fields.items()) + moto_settings
|
||||||
)
|
)
|
||||||
if not self.settings.connect_client_id or self.settings.secret_key:
|
if not self.settings.connect_client_id or self.settings.secret_key:
|
||||||
@@ -1956,15 +1946,3 @@ class StripeMobilePay(StripeRedirectMethod):
|
|||||||
"type": "mobilepay",
|
"type": "mobilepay",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class StripeWero(StripeRedirectMethod):
|
|
||||||
identifier = 'stripe_wero'
|
|
||||||
verbose_name = _('WERO via Stripe')
|
|
||||||
public_name = 'WERO'
|
|
||||||
method = 'wero'
|
|
||||||
confirmation_method = 'automatic'
|
|
||||||
explanation = _(
|
|
||||||
'This payment method is available to European online banking users, whose banking institutions support WERO '
|
|
||||||
'either through their native banking apps or through the WERO wallet app. Please have you app ready.'
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -49,14 +49,14 @@ def register_payment_provider(sender, **kwargs):
|
|||||||
StripeMultibanco, StripePayByBank, StripePayPal, StripePromptPay,
|
StripeMultibanco, StripePayByBank, StripePayPal, StripePromptPay,
|
||||||
StripePrzelewy24, StripeRevolutPay, StripeSEPADirectDebit,
|
StripePrzelewy24, StripeRevolutPay, StripeSEPADirectDebit,
|
||||||
StripeSettingsHolder, StripeSofort, StripeSwish, StripeTwint,
|
StripeSettingsHolder, StripeSofort, StripeSwish, StripeTwint,
|
||||||
StripeWeChatPay, StripeWero,
|
StripeWeChatPay,
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
StripeSettingsHolder, StripeCC, StripeGiropay, StripeIdeal, StripeAlipay, StripeBancontact,
|
StripeSettingsHolder, StripeCC, StripeGiropay, StripeIdeal, StripeAlipay, StripeBancontact,
|
||||||
StripeSofort, StripeEPS, StripeMultibanco, StripePayByBank, StripePrzelewy24, StripePromptPay, StripeRevolutPay,
|
StripeSofort, StripeEPS, StripeMultibanco, StripePayByBank, StripePrzelewy24, StripePromptPay, StripeRevolutPay,
|
||||||
StripeWeChatPay, StripeSEPADirectDebit, StripeAffirm, StripeKlarna, StripePayPal, StripeSwish,
|
StripeWeChatPay, StripeSEPADirectDebit, StripeAffirm, StripeKlarna, StripePayPal, StripeSwish,
|
||||||
StripeTwint, StripeMobilePay, StripeWero
|
StripeTwint, StripeMobilePay
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{% extends "pretixpresale/event/base.html" %}
|
{% extends "pretixpresale/event/base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% block title %}{% trans "Resend order link" %}{% endblock %}
|
{% block title %}{% trans "Resend order links" %}{% endblock %}
|
||||||
{% block custom_header %}
|
{% block custom_header %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>
|
<h2>
|
||||||
{% trans "Resend order link" %}
|
{% trans "Resend order links" %}
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
|
|||||||
@@ -1510,10 +1510,7 @@ class OrderChangeMixin:
|
|||||||
'max_count': iao.max_count,
|
'max_count': iao.max_count,
|
||||||
'iao': iao,
|
'iao': iao,
|
||||||
'items': [i for i in items if not i.require_voucher],
|
'items': [i for i in items if not i.require_voucher],
|
||||||
'items_missing': {
|
'items_missing': {k: v for k, v in current_addon_products_missing.items() if v},
|
||||||
k: v for k, v in current_addon_products_missing.items()
|
|
||||||
if v and k[0].category_id == iao.addon_category_id
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return positions
|
return positions
|
||||||
|
|||||||
@@ -1177,30 +1177,6 @@ def test_store_failed(token_client, organizer, clist, event, order):
|
|||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_store_failed_after_success(token_client, organizer, clist, event, order):
|
|
||||||
with scopes_disabled():
|
|
||||||
p = order.positions.first()
|
|
||||||
p.all_checkins.create(
|
|
||||||
type=Checkin.TYPE_ENTRY,
|
|
||||||
nonce='foobar',
|
|
||||||
successful=True,
|
|
||||||
list=clist,
|
|
||||||
raw_barcode=p.secret
|
|
||||||
)
|
|
||||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/failed_checkins/'.format(
|
|
||||||
organizer.slug, event.slug, clist.pk,
|
|
||||||
), {
|
|
||||||
'raw_barcode': p.secret,
|
|
||||||
'nonce': 'foobar',
|
|
||||||
'position': p.pk,
|
|
||||||
'error_reason': 'unpaid'
|
|
||||||
}, format='json')
|
|
||||||
assert resp.status_code == 201
|
|
||||||
with scopes_disabled():
|
|
||||||
assert Checkin.all.filter(position=p).count() == 2
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_redeem_unknown(token_client, organizer, clist, event, order):
|
def test_redeem_unknown(token_client, organizer, clist, event, order):
|
||||||
resp = _redeem(token_client, organizer, clist, 'unknown_secret', {'force': True})
|
resp = _redeem(token_client, organizer, clist, 'unknown_secret', {'force': True})
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ def test_price_mode_validation(event, item, user):
|
|||||||
import_vouchers.apply(
|
import_vouchers.apply(
|
||||||
args=(event.pk, inputfile_factory().id, settings, 'en', user.pk)
|
args=(event.pk, inputfile_factory().id, settings, 'en', user.pk)
|
||||||
).get()
|
).get()
|
||||||
assert 'It is pointless to set a value without a price effect.' in str(excinfo.value)
|
assert 'It is pointless to set a value without a price mode.' in str(excinfo.value)
|
||||||
|
|
||||||
settings['price_mode'] = 'static:percent'
|
settings['price_mode'] = 'static:percent'
|
||||||
import_vouchers.apply(
|
import_vouchers.apply(
|
||||||
|
|||||||
@@ -339,17 +339,13 @@ class UserSettings2FATest(SoupTest):
|
|||||||
|
|
||||||
def test_gen_emergency(self):
|
def test_gen_emergency(self):
|
||||||
self.client.get('/control/settings/2fa/')
|
self.client.get('/control/settings/2fa/')
|
||||||
assert not StaticDevice.objects.filter(user=self.user, name='emergency').exists()
|
|
||||||
|
|
||||||
self.client.post('/control/settings/2fa/regenemergency')
|
|
||||||
d = StaticDevice.objects.get(user=self.user, name='emergency')
|
d = StaticDevice.objects.get(user=self.user, name='emergency')
|
||||||
assert d.token_set.count() == 10
|
assert d.token_set.count() == 10
|
||||||
old_tokens = set(t.token for t in d.token_set.all())
|
old_tokens = set(t.token for t in d.token_set.all())
|
||||||
|
|
||||||
self.client.post('/control/settings/2fa/regenemergency')
|
self.client.post('/control/settings/2fa/regenemergency')
|
||||||
|
new_tokens = set(t.token for t in d.token_set.all())
|
||||||
d = StaticDevice.objects.get(user=self.user, name='emergency')
|
d = StaticDevice.objects.get(user=self.user, name='emergency')
|
||||||
assert d.token_set.count() == 10
|
assert d.token_set.count() == 10
|
||||||
new_tokens = set(t.token for t in d.token_set.all())
|
|
||||||
assert old_tokens != new_tokens
|
assert old_tokens != new_tokens
|
||||||
|
|
||||||
def test_delete_u2f(self):
|
def test_delete_u2f(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user