Compare commits

...
Author SHA1 Message Date
Raphael Michel d1ba5c298d Create regression tests 2026-03-30 19:16:17 +02:00
Raphael Michel c6c48537dd Do not create cart session on view with active session 2026-03-30 19:16:03 +02:00
Raphael Michel ab08dea9f7 Skip useless code paths in CartMixin 2026-03-30 18:58:10 +02:00
Raphael Michel 4d15731528 Do not create useless cart session accessing invoice address 2026-03-30 18:57:45 +02:00
Raphael Michel a2cef22ea8 Bump version to 2026.4.0.dev0 2026-03-30 15:01:39 +02:00
Raphael Michel 3843448812 Bump version to 2026.3.0 2026-03-30 15:01:30 +02:00
Kara Engelhardt 49893ca9df Fix crash in mail_send_task for nonexistant mails 2026-03-30 14:57:56 +02:00
Raphael Michel 4eade5070e Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6287 of 6287 strings)

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

powered by weblate
2026-03-30 14:01:13 +02:00
Raphael Michel 32b1997208 Translations: Update German
Currently translated at 100.0% (6287 of 6287 strings)

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

powered by weblate
2026-03-30 14:01:13 +02:00
Raphael Michel eaf4a310f6 Translations: Update wordlist 2026-03-30 13:59:37 +02:00
Raphael Michel 8dc0f7c1b2 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2026-03-30 13:26:02 +02:00
CVZ-es dd3e6c4692 Translations: Update Spanish
Currently translated at 100.0% (256 of 256 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/es/

powered by weblate
2026-03-30 13:21:45 +02:00
64 changed files with 17072 additions and 15589 deletions
+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2026.3.0.dev0"
__version__ = "2026.4.0.dev0"
+1 -1
View File
@@ -411,7 +411,7 @@ def mail_send_task(self, **kwargs) -> bool:
try:
outgoing_mail = OutgoingMail.objects.select_for_update(of=OF_SELF).get(pk=outgoing_mail)
except OutgoingMail.DoesNotExist:
logger.info(f"Ignoring job for non existing email {outgoing_mail.guid}")
logger.info(f"Ignoring job for non existing email {outgoing_mail}")
return False
if outgoing_mail.status == OutgoingMail.STATUS_INFLIGHT:
logger.info(f"Ignoring job for inflight email {outgoing_mail.guid}")
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
+4
View File
@@ -32,6 +32,7 @@ ausgecheckt
ausgeklappt
auswahl
Authentication
Authenticator
Authenticator-App
Autorisierungscode
Autorisierungs-Endpunktes
@@ -130,6 +131,7 @@ Eingangsscan
Einlassbuchung
Einlassdatum
Einlasskontrolle
Einmalpasswörter
einzuchecken
email
E-Mail-Renderer
@@ -163,6 +165,7 @@ Explorer
FA
Favicon
F-Droid
freeOTP
Footer
Footer-Link
Footer-Text
@@ -557,6 +560,7 @@ Zahlungs-ID
Zahlungspflichtig
Zehnerkarten
Zeitbasiert
zeitbasierte
Zeitslotbuchung
Zimpler
ZIP-Datei
File diff suppressed because it is too large Load Diff
@@ -32,6 +32,7 @@ ausgecheckt
ausgeklappt
auswahl
Authentication
Authenticator
Authenticator-App
Autorisierungscode
Autorisierungs-Endpunktes
@@ -130,6 +131,7 @@ Eingangsscan
Einlassbuchung
Einlassdatum
Einlasskontrolle
Einmalpasswörter
einzuchecken
email
E-Mail-Renderer
@@ -163,6 +165,7 @@ Explorer
FA
Favicon
F-Droid
freeOTP
Footer
Footer-Link
Footer-Text
@@ -557,6 +560,7 @@ Zahlungs-ID
Zahlungspflichtig
Zehnerkarten
Zeitbasiert
zeitbasierte
Zeitslotbuchung
Zimpler
ZIP-Datei
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"POT-Creation-Date: 2026-03-30 11:25+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
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
"PO-Revision-Date: 2026-03-30 03:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/es/>\n"
@@ -329,7 +329,7 @@ msgstr "Pedido no aprobado"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
msgid "Checked-in Tickets"
msgstr "Registro de código QR"
msgstr "Billetes registrados"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:69
msgid "Valid Tickets"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
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
+3
View File
@@ -12,6 +12,7 @@ anonymized
Auth
authentification
authenticator
Authenticator
automatical
availabilities
backend
@@ -22,6 +23,7 @@ barcodes
Bcc
BCC
BezahlCode
biometric
BLIK
blocklist
BN
@@ -56,6 +58,7 @@ EPS
eps
favicon
filetype
freeOTP
frontend
frontpage
Galician
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+26 -10
View File
@@ -70,18 +70,21 @@ def cached_invoice_address(request):
# do not create a session, if we don't have a session we also don't have an invoice address ;)
request._checkout_flow_invoice_address = InvoiceAddress()
return request._checkout_flow_invoice_address
cs = cart_session(request)
iapk = cs.get('invoice_address')
if not iapk:
cs = cart_session(request, create=False)
if cs is None:
request._checkout_flow_invoice_address = InvoiceAddress()
else:
try:
with scopes_disabled():
request._checkout_flow_invoice_address = InvoiceAddress.objects.get(
pk=iapk, order__isnull=True
)
except InvoiceAddress.DoesNotExist:
iapk = cs.get('invoice_address')
if not iapk:
request._checkout_flow_invoice_address = InvoiceAddress()
else:
try:
with scopes_disabled():
request._checkout_flow_invoice_address = InvoiceAddress.objects.get(
pk=iapk, order__isnull=True
)
except InvoiceAddress.DoesNotExist:
request._checkout_flow_invoice_address = InvoiceAddress()
return request._checkout_flow_invoice_address
@@ -111,6 +114,12 @@ class CartMixin:
return cached_invoice_address(self.request)
def get_cart(self, answers=False, queryset=None, order=None, downloads=False, payments=None):
if not self.request.session.session_key and not order:
# The user has not even a session ID yet, so they can't have a cart and we can save a lot of work
return {
'positions': [],
# Other keys are not used on non-checkout pages
}
if queryset is not None:
prefetch = []
if answers:
@@ -166,7 +175,8 @@ class CartMixin:
else:
fees = []
if not order:
if not order and lcp:
# Do not re-round for empty cart (useless) or confirmed order (incorrect)
apply_rounding(self.request.event.settings.tax_rounding, self.invoice_address, self.request.event.currency, [*lcp, *fees])
total = sum([c.price for c in lcp]) + sum([f.value for f in fees])
@@ -277,6 +287,12 @@ class CartMixin:
}
def current_selected_payments(self, positions, fees, invoice_address, *, warn=False):
from pretix.presale.views.cart import get_or_create_cart_id
if not get_or_create_cart_id(self.request, create=False):
# No active cart ID, no payments there
return []
raw_payments = copy.deepcopy(self.cart_session.get('payments', []))
fees = [f for f in fees if f.fee_type != OrderFee.FEE_TYPE_PAYMENT] # we re-compute these here
+4 -2
View File
@@ -417,7 +417,7 @@ def get_or_create_cart_id(request, create=True):
return new_id
def cart_session(request):
def cart_session(request, create=True):
"""
Before pretix 1.8.0, all checkout-related information (like the entered email address) was stored
in the user's regular session dictionary. This led to data interference and leaks for example if a
@@ -428,7 +428,9 @@ def cart_session(request):
active cart session sub-dictionary for read and write access.
"""
request.session.modified = True
cart_id = get_or_create_cart_id(request)
cart_id = get_or_create_cart_id(request, create=create)
if not cart_id and not create:
return None
return request.session['carts'][cart_id]
+29
View File
@@ -36,6 +36,7 @@
import datetime
import re
from decimal import Decimal
from importlib import import_module
from json import loads
from zoneinfo import ZoneInfo
@@ -80,6 +81,34 @@ class EventMiddlewareTest(EventTestMixin, SoupTest):
doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertIn(str(self.event.name), doc.find("h1").text)
def test_no_session_cookie_set_on_event_index_view(self):
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertEqual(resp.status_code, 200)
assert settings.SESSION_COOKIE_NAME not in self.client.cookies
def test_no_cart_session_added_on_event_index_view(self):
# Make sure a session is present by doing a cart op on another event
event2 = Event.objects.create(
organizer=self.orga, name='30C3b', slug='30c3b',
date_from=datetime.datetime(now().year + 1, 12, 26, 14, 0, tzinfo=datetime.timezone.utc),
live=True,
)
self.client.post('/%s/%s/cart/add' % (self.orga.slug, event2.slug), {
'item_%d' % 1337: '1', # item does not need to exist
'ajax': 1
})
assert settings.SESSION_COOKIE_NAME in self.client.cookies
# Visit shop, make sure no session is created
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertEqual(resp.status_code, 200)
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
session = SessionStore(self.client.cookies[settings.SESSION_COOKIE_NAME].value).load()
assert set(session.keys()) == {
f"current_cart_event_{event2.pk}", "carts"
}
def test_not_found(self):
resp = self.client.get('/%s/%s/' % ('foo', 'bar'))
self.assertEqual(resp.status_code, 404)