Compare commits

..

1 Commits

Author SHA1 Message Date
Martin Gross
582b8d815e PPv2: Handle payment execution/capture calls properly even if no captures are present yet. 2026-02-19 17:45:52 +01:00
77 changed files with 120600 additions and 140727 deletions

View File

@@ -113,7 +113,7 @@ dev = [
"fakeredis==2.34.*",
"flake8==7.3.*",
"freezegun",
"isort==8.0.*",
"isort==7.0.*",
"pep8-naming==0.15.*",
"potypo",
"pytest-asyncio>=0.24",

View File

@@ -183,7 +183,6 @@ class ParametrizedGiftcardWebhookEvent(ParametrizedWebhookEvent):
return {
'notification_id': logentry.pk,
'issuer_id': logentry.organizer_id,
'issuer_slug': logentry.organizer.slug,
'giftcard': giftcard.pk,
'action': logentry.action_type,
}
@@ -198,7 +197,6 @@ class ParametrizedGiftcardTransactionWebhookEvent(ParametrizedWebhookEvent):
return {
'notification_id': logentry.pk,
'issuer_id': logentry.organizer_id,
'issuer_slug': logentry.organizer.slug,
'acceptor_id': logentry.parsed_data.get('acceptor_id'),
'acceptor_slug': logentry.parsed_data.get('acceptor_slug'),
'giftcard': giftcard.pk,
@@ -475,7 +473,7 @@ def register_default_webhook_events(sender, **kwargs):
),
ParametrizedGiftcardTransactionWebhookEvent(
'pretix.giftcards.transaction.*',
_('Gift card used in transaction'),
_('Gift card used in transcation'),
)
)

View File

@@ -389,7 +389,7 @@ def mail_send_task(self, **kwargs) -> bool:
# mail_send_task(self, *, outgoing_mail)
with scopes_disabled():
mail_send(**kwargs)
return False
return
else:
raise ValueError("Unknown arguments")
@@ -443,24 +443,15 @@ def mail_send_task(self, **kwargs) -> bool:
content = ct.file.read()
args.append((name, content, ct.type))
attach_size += len(content)
except Exception as e:
except Exception:
# This sometimes fails e.g. with FileNotFoundError. We haven't been able to figure out
# why (probably some race condition with ticket cache invalidation?), so retry later.
try:
logger.exception(f'Could not attach tickets to email {outgoing_mail.guid}, will retry')
retry_after = 60
outgoing_mail.error = "Tickets not ready"
outgoing_mail.error_detail = str(e)
outgoing_mail.sent = now()
outgoing_mail.status = OutgoingMail.STATUS_AWAITING_RETRY
outgoing_mail.retry_after = now() + timedelta(seconds=retry_after)
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after",
"actual_attachments"])
self.retry(max_retries=5, countdown=retry_after)
self.retry(max_retries=5, countdown=60)
except MaxRetriesExceededError:
# Well then, something is really wrong, let's send it without attachment before we
# don't send at all
logger.exception(f'Too many retries attaching tickets to email {outgoing_mail.guid}, skip attachment')
logger.exception(f'Could not attach tickets to email {outgoing_mail.guid}')
pass
if attach_size * 1.37 < settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT - 1024 * 1024:

View File

@@ -19,44 +19,17 @@
# 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 forms
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django_scopes.forms import SafeModelChoiceField
from phonenumber_field.formfields import PhoneNumberField
from pretix.base.forms import I18nModelForm
from pretix.base.forms.questions import (
NamePartsFormField, WrappedPhoneNumberPrefixWidget,
)
from pretix.base.models import WaitingListEntry
from pretix.control.forms.widgets import Select2
class WaitingListEntryEditForm(I18nModelForm):
itemvar = forms.ChoiceField(
error_messages={
'invalid_choice': _("Select a valid choice.")
}
)
class WaitingListEntryTransferForm(I18nModelForm):
def __init__(self, *args, **kwargs):
self.instance = kwargs.get('instance', None)
initial = kwargs.get('initial', {})
choices = []
if self.instance and self.instance.pk and 'itemvar' not in initial:
if self.instance.variation is not None:
initial['itemvar'] = f'{self.instance.item.pk}-{self.instance.variation.pk}'
if self.instance.variation.active is False:
choices.append((initial['itemvar'], str(self.instance.variation)))
else:
initial['itemvar'] = self.instance.item.pk
if self.instance.item.active is False:
choices.append((initial['itemvar'], str(self.instance)))
kwargs['initial'] = initial
super().__init__(*args, **kwargs)
if self.event.has_subevents:
@@ -72,73 +45,12 @@ class WaitingListEntryEditForm(I18nModelForm):
}
)
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
else:
del self.fields['subevent']
if self.event.settings.waiting_list_names_asked:
self.fields['name_parts'] = NamePartsFormField(
max_length=255,
required=self.event.settings.waiting_list_names_required,
scheme=self.event.organizer.settings.name_scheme,
titles=self.event.organizer.settings.name_scheme_titles,
label=_('Name'),
)
else:
del self.fields['name_parts']
if not self.event.settings.waiting_list_phones_asked:
del self.fields['phone']
items = self.event.items.filter(active=True).prefetch_related(
'variations'
)
for item in items:
if len(item.variations.all()) > 0:
for variation in item.variations.all():
if variation.active:
choices.append(
('{}-{}'.format(item.pk, variation.pk), '{} - {}'.format(str(item), str(variation)))
)
else:
choices.append(('{}'.format(item.pk), str(item)))
self.fields['itemvar'].label = _("Product")
self.fields['itemvar'].help_text = _("Only includes active products.")
self.fields['itemvar'].required = True
self.fields['itemvar'].choices = choices
def clean(self):
cleaned_data = super().clean()
if self.instance.voucher is not None:
raise forms.ValidationError(_('A voucher for this waiting list entry was already sent out.'))
itemvar = cleaned_data.get('itemvar')
if itemvar:
self.instance.item = self.event.items.get(pk=itemvar.split('-')[0])
if '-' in itemvar:
self.instance.variation = self.instance.item.variations.get(pk=itemvar.split('-')[1])
if ((self.instance.item and not self.instance.item.active) or
(self.instance.variation and not self.instance.variation.active)):
self.add_error('itemvar', _('The selected product is not active.'))
return cleaned_data
class Meta:
model = WaitingListEntry
fields = [
'email',
'name_parts',
'phone',
'subevent',
]
field_classes = {
'subevent': SafeModelChoiceField,
'email': forms.EmailField,
'phone': PhoneNumberField,
}
widgets = {
'phone': WrappedPhoneNumberPrefixWidget,
}

View File

@@ -19,14 +19,6 @@
</ul>
<br>
{% endif %}
{% if possible_cookie_problem %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
It looks like your browser is not accepting our cookie and you need to log in repeatedly. Please
check if your browser is set to block cookies, or delete all existing cookies and retry.
{% endblocktrans %}
</div>
{% endif %}
{% csrf_token %}
{% bootstrap_form form %}
<div class="form-group buttons">

View File

@@ -1,33 +0,0 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Edit entry" %}{% endblock %}
{% block content %}
<h1>{% trans "Edit entry" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% if form.subevent %}
{% bootstrap_field form.subevent layout="control" %}
{% endif %}
{% bootstrap_field form.email layout="control" %}
{% if form.name_parts %}
{% bootstrap_field form.name_parts layout="control" %}
{% endif %}
{% if form.phone %}
{% bootstrap_field form.phone layout="control" %}
{% endif %}
{% bootstrap_field form.itemvar layout="control" %}
<div class="form-group submit-group">
<a href="{% url "control:event.orders.waitinglist" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -124,7 +124,6 @@
</option>
{% endfor %}
</select>
<input name="search" type="text" placeholder="{% trans "Search" %}" class="form-control" value="{{ request.GET.search }}">
{% if request.event.has_subevents %}
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
@@ -268,13 +267,13 @@
data-toggle="tooltip" title="{% trans "Move to the end of the list" %}">
<span class="fa fa-thumbs-down"></span>
</button>
<a href="{% url "control:event.orders.waitinglist.edit" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}"
class="btn btn-default btn-sm" title="{% trans "Edit entry" %}"
data-toggle="tooltip">
<i class="fa fa-edit" aria-hidden="true"></i>
</a>
{% if request.event.has_subevents %}
<a href="{% url "control:event.orders.waitinglist.transfer" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}"
class="btn btn-default btn-sm" title="{% trans "Transfer to other date" context "subevent" %}"
data-toggle="tooltip">
<i class="fa fa-calendar" aria-hidden="true"></i>
</a>
{% endif %}
<a href="{% url "control:event.orders.waitinglist.delete" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}?next={{ request.get_full_path|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% else %}
<button class="btn btn-default btn-sm disabled">

View File

@@ -0,0 +1,23 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Transfer entry" %}{% endblock %}
{% block content %}
<h1>{% trans "Transfer entry" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<p>{% blocktrans trimmed context "subevent" %}
Please select the date to which the following waiting list entry should be
transferred: <strong>{{ entry }}</strong>?
{% endblocktrans %}</p>
{% bootstrap_field form.subevent layout="control" %}
<div class="form-group submit-group">
<a href="{% url "control:event.orders.waitinglist" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-primary btn-save">
{% trans "Transfer" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -480,8 +480,8 @@ urlpatterns = [
re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'),
re_path(r'^waitinglist/(?P<entry>\d+)/delete$', waitinglist.EntryDelete.as_view(),
name='event.orders.waitinglist.delete'),
re_path(r'^waitinglist/(?P<entry>\d+)/edit$', waitinglist.EntryEdit.as_view(),
name='event.orders.waitinglist.edit'),
re_path(r'^waitinglist/(?P<entry>\d+)/transfer$', waitinglist.EntryTransfer.as_view(),
name='event.orders.waitinglist.transfer'),
re_path(r'^checkins/$', checkin.CheckinListView.as_view(), name='event.orders.checkins'),
re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'),
re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'),

View File

@@ -149,8 +149,6 @@ def login(request):
return process_login(request, form.user_cache, form.cleaned_data.get('keep_logged_in', False))
else:
form = LoginForm(backend=backend, request=request)
# Detect redirection loop (usually means cookie not accepted)
ctx['possible_cookie_problem'] = request.path in request.headers.get("Referer", "")
ctx['form'] = form
ctx['can_register'] = settings.PRETIX_REGISTRATION
ctx['can_reset'] = settings.PRETIX_PASSWORD_RESET

View File

@@ -630,14 +630,9 @@ class User2FARegenerateEmergencyView(RecentAuthenticationRequiredMixin, Template
])
self.request.user.update_session_token()
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 '
'place in case you lose access to your devices. You will not be able to view them '
'again here.\n\nYour emergency codes:\n{tokens}').format(
tokens='- ' + '\n- '.join(t.token for t in d.token_set.all())
)
)
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 '
'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'))

View File

@@ -53,7 +53,7 @@ from pretix.base.models import Item, LogEntry, Quota, WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException
from pretix.base.services.waitinglist import assign_automatically
from pretix.base.views.tasks import AsyncAction
from pretix.control.forms.waitinglist import WaitingListEntryEditForm
from pretix.control.forms.waitinglist import WaitingListEntryTransferForm
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.views import PaginationMixin
@@ -138,17 +138,6 @@ class WaitingListQuerySetMixin:
elif force_filtered and '__ALL' not in self.request_data:
qs = qs.none()
if self.request_data.get("search", "") != "":
s = self.request_data.get("search", "")
search_q = Q(email__icontains=s)
if self.request.event.settings.waiting_list_names_asked:
search_q = search_q | Q(name_cached__icontains=s)
if self.request.event.settings.waiting_list_phones_asked:
search_q = search_q | Q(phone__icontains=s)
qs = qs.filter(search_q)
return qs
@@ -249,7 +238,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['items'] = Item.objects.filter(event=self.request.event)
ctx['filtered'] = any(param in self.request.GET for param in ("status", "item", "search"))
ctx['filtered'] = ("status" in self.request.GET or "item" in self.request.GET)
itemvar_cache = {}
quota_cache = {}
@@ -401,20 +390,25 @@ class EntryDelete(EventPermissionRequiredMixin, CompatDeleteView):
})
class EntryEdit(EventPermissionRequiredMixin, UpdateView):
class EntryTransfer(EventPermissionRequiredMixin, UpdateView):
model = WaitingListEntry
template_name = 'pretixcontrol/waitinglist/edit.html'
template_name = 'pretixcontrol/waitinglist/transfer.html'
permission = 'can_change_orders'
form_class = WaitingListEntryEditForm
form_class = WaitingListEntryTransferForm
context_object_name = 'entry'
def dispatch(self, request, *args, **kwargs):
if not self.request.event.has_subevents:
raise Http404(_("This is not an event series."))
return super().dispatch(request, *args, **kwargs)
def get_object(self, queryset=None) -> WaitingListEntry:
return get_object_or_404(WaitingListEntry, pk=self.kwargs['entry'], event=self.request.event, voucher__isnull=True)
@transaction.atomic
def form_valid(self, form):
messages.success(self.request, _('The waitinglist entry has been transferred.'))
if form.has_changed():
messages.success(self.request, _('The waitinglist entry has been changed.'))
self.object.log_action(
'pretix.event.orders.waitinglist.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data

View File

@@ -34,7 +34,10 @@ def set_cookie_without_samesite(request, response, key, *args, **kwargs):
if not is_secure:
# https://www.chromestatus.com/feature/5633521622188032
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
# https://www.chromestatus.com/feature/5088147346030592
# This however breaks all pretix-in-an-iframe things, such as the pretix Widget.
@@ -44,8 +47,29 @@ def set_cookie_without_samesite(request, response, key, *args, **kwargs):
# This will only work on secure cookies as well
# https://www.chromestatus.com/feature/5633521622188032
response.cookies[key]['secure'] = is_secure
# CHIPS
response.cookies[key]['Partitioned'] = True
if can_send_partitioned_cookie(useragent):
# 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

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

View File

@@ -265,7 +265,6 @@ Objekt-IDs
Offline-Scan
OK
Online-Banking
Online-Banking-Nutzer
Onlinebanking
Onlinebanking-Zugangsdaten
Open
@@ -540,8 +539,6 @@ WeChat-Zahlung
Weiterleitungs-URIs
Weiterleitungs-URL
Weiterleitungs-URLs
WERO
WERO-App
WhatsApp
Widget
Widget-Code

File diff suppressed because it is too large Load Diff

View File

@@ -265,7 +265,6 @@ Objekt-IDs
Offline-Scan
OK
Online-Banking
Online-Banking-Nutzer
Onlinebanking
Onlinebanking-Zugangsdaten
Open
@@ -540,8 +539,6 @@ WeChat-Zahlung
Weiterleitungs-URIs
Weiterleitungs-URL
Weiterleitungs-URLs
WERO
WERO-App
WhatsApp
Widget
Widget-Code

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-20 13:01+0000\n"
"POT-Creation-Date: 2026-01-26 13:20+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

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

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-02-23 10:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"PO-Revision-Date: 2026-02-12 20:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
"js/ja/>\n"
"Language: ja\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.16\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -256,7 +256,7 @@ msgstr "承認保留中"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
msgid "Redeemed"
msgstr "引き換え済み"
msgstr "使用済"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
msgid "Cancel"

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

View File

@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-02-19 22:00+0000\n"
"PO-Revision-Date: 2026-02-05 23:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
"nl/>\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -674,7 +674,7 @@ msgstr "Zoekopdracht"
#: pretix/static/pretixcontrol/js/ui/main.js:461
msgid "All"
msgstr "Allemaal"
msgstr "Alle"
#: pretix/static/pretixcontrol/js/ui/main.js:462
msgid "None"

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

View File

@@ -187,7 +187,6 @@ webhooks
webserver
Wechat
WeChat
WERO
WhatsApp
whitespace
xlsx

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -791,17 +791,16 @@ class PaypalMethod(BasePaymentProvider):
any_captures = False
all_captures_completed = True
for purchaseunit in pp_captured_order.purchase_units:
if hasattr(purchaseunit, 'payments'):
for capture in purchaseunit.payments.captures:
try:
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment, reference=capture.id)
except ReferencedPayPalObject.MultipleObjectsReturned:
pass
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 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.'))

View File

@@ -9,7 +9,7 @@
<script type="text/plain" id="stripe_payment_intent_action_type">{{ payment_intent_action_type }}</script>
<script type="text/plain" id="stripe_payment_intent_client_secret">{{ payment_intent_client_secret }}</script>
{% if payment_intent_next_action_redirect_url %}
<script type="text/plain" id="stripe_payment_intent_next_action_redirect_url">{{ payment_intent_next_action_redirect_url|safe }}</script>
<script type="text/plain" id="stripe_payment_intent_next_action_redirect_url">{{ payment_intent_next_action_redirect_url }}</script>
{% endif %}
{% if payment_intent_redirect_action_handling %}
<script type="text/plain" id="stripe_payment_intent_redirect_action_handling">{{ payment_intent_redirect_action_handling }}</script>

View File

@@ -19,6 +19,19 @@
# 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/>.
#
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
#
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
#
# This file contains Apache-licensed contributions copyrighted by: Daniel
#
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# 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.
from datetime import timedelta
import pytest
@@ -26,8 +39,7 @@ from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.base.models import (
Event, Item, ItemVariation, Organizer, Quota, Team, User, Voucher,
WaitingListEntry,
Event, Item, Organizer, Quota, Team, User, Voucher, WaitingListEntry,
)
from pretix.control.views.dashboards import waitinglist_widgets
@@ -40,12 +52,11 @@ def env():
date_from=now(), plugins='pretix.plugins.banktransfer,tests.testdummy'
)
event.settings.set('ticketoutput_testdummy__enabled', True)
event.settings.set('waiting_list_names_asked', False)
event.settings.set('waiting_list_names_required', False)
event.settings.set('waiting_list_phones_asked', False)
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
item1 = Item.objects.create(event=event, name="Ticket", default_price=23, admission=True, allow_waitinglist=True)
item2 = Item.objects.create(event=event, name="Ticket", default_price=23, admission=True)
item1 = Item.objects.create(event=event, name="Ticket", default_price=23,
admission=True)
item2 = Item.objects.create(event=event, name="Ticket", default_price=23,
admission=True)
for i in range(5):
WaitingListEntry.objects.create(
@@ -67,16 +78,7 @@ def env():
t = Team.objects.create(organizer=o, can_view_orders=True, can_change_orders=True)
t.members.add(user)
t.limit_events.add(event)
wle = WaitingListEntry.objects.filter(item=item1).first()
variation = ItemVariation.objects.create(item=item1)
return {
"event": event,
"item1": item1,
"wle": wle,
"variation": variation,
}
return event, user, o, item1
@pytest.mark.django_db
@@ -120,7 +122,7 @@ def test_list(client, env):
assert 'foo0@bar.com' not in response.content.decode()
assert 'valid@example.org' not in response.content.decode()
response = client.get('/control/event/dummy/dummy/waitinglist/?item=%d' % env['item1'].pk)
response = client.get('/control/event/dummy/dummy/waitinglist/?item=%d' % env[3].pk)
assert 'item2@example.org' not in response.content.decode()
assert 'foo0@bar.com' in response.content.decode()
@@ -189,161 +191,11 @@ def test_delete_bulk(client, env):
WaitingListEntry.objects.get(id=wle.id)
@pytest.mark.django_db
def test_edit_settings(client, env):
event = env['event']
wle = env['wle']
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id)
assert ['email', 'itemvar'] == list(response.context_data['form'].fields.keys())
event.settings.set('waiting_list_names_asked', True)
response = client.get('/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id)
assert 'name_parts' in list(response.context_data['form'].fields.keys())
event.settings.set('waiting_list_names_required', True)
response = client.get('/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id)
assert response.context_data['form'].fields['name_parts'].required is True
event.settings.set('waiting_list_phones_asked', True)
response = client.get('/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id)
assert 'phone' in list(response.context_data['form'].fields.keys())
@pytest.mark.django_db
def test_edit_itemvariation(client, env):
item = env['item1']
variation = env['variation']
wle = env['wle']
client.login(email='dummy@dummy.dummy', password='dummy')
itemvar = f"{item.pk}-{variation.pk}"
client.post(
'/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id,
data={
"email": f"1_{wle.email}",
"itemvar": itemvar
}
)
wle.refresh_from_db()
assert wle.variation == variation
@pytest.mark.django_db
def test_edit_validations_only_valid_item(client, env):
item = env['item1']
wle = env['wle']
client.login(email='dummy@dummy.dummy', password='dummy')
itemvar = f"{item.pk + 10000}"
response = client.post(
'/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id,
data={
"email": f"1_{wle.email}",
"itemvar": itemvar
}
)
assert response.context_data['form'].errors['itemvar'] == ["Select a valid choice."]
@pytest.mark.django_db
def test_edit_validations_only_valid_variation(client, env):
item = env['item1']
wle = env['wle']
variation = env['variation']
client.login(email='dummy@dummy.dummy', password='dummy')
itemvar = f"{item.pk}-{variation.pk + 1}"
response = client.post(
'/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id,
data={
"email": f"1_{wle.email}",
"itemvar": itemvar
}
)
assert response.context_data['form'].errors['itemvar'] == ["Select a valid choice."]
@pytest.mark.django_db
def test_edit_validations_inactive_item(client, env):
item = env['item1']
wle = env['wle']
item.active = False
item.save()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post(
'/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id,
data={
"email": f"1_{wle.email}",
"itemvar": f"{item.pk}"
}
)
assert response.context_data['form'].errors['itemvar'] == ["The selected product is not active."]
@pytest.mark.django_db
def test_edit_validations_inactive_variation(client, env):
item = env['item1']
wle = env['wle']
variation = env['variation']
wle.variation = variation
wle.save()
variation.active = False
variation.save()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post(
'/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id,
data={
"email": f"1_{wle.email}",
"itemvar": f"{item.pk}-{variation.pk}"
}
)
assert response.context_data['form'].errors['itemvar'] == ["The selected product is not active."]
@pytest.mark.django_db
def test_edit_voucher_send_out(client, env):
event = env['event']
item = env['item1']
wle = env['wle']
quota = Quota.objects.create(event=event, size=100)
quota.items.add(item)
client.login(email='dummy@dummy.dummy', password='dummy')
with scopes_disabled():
wle.send_voucher()
response = client.post(
'/control/event/dummy/dummy/waitinglist/%s/edit' % wle.id,
data={
"email": f"1_{wle.email}",
"itemvar": item.pk
},
follow=True
)
assert response.status_code == 404
@pytest.mark.django_db
def test_dashboard(client, env):
with scopes_disabled():
quota = Quota.objects.create(name="Test", size=2, event=env['event'])
quota.items.add(env['item1'])
w = waitinglist_widgets(env['event'])
quota = Quota.objects.create(name="Test", size=2, event=env[0])
quota.items.add(env[3])
w = waitinglist_widgets(env[0])
assert '1' in w[0]['content']
assert '5' in w[1]['content']