Compare commits

..
Author SHA1 Message Date
Lukas Bockstaller 3105b7e8fb raise correct validation error 2026-10-23 13:51:16 +02:00
Lukas Bockstaller 26da8b3aab API: run clean_live() after updates to the event 2026-10-23 13:22:20 +02:00
Raphael Michel e188796a81 Bump Pillow to 13.0.* (#6694) 2026-10-22 15:44:57 +02:00
Lukas Bockstaller 1ca41269cd test: unset EMAIL_HOST_USER and EMAIL_HOST_PASSWORD for smtp tests (#6688)
Harden tests so that they don't fail locally when the config values are set in pretix.cfg
2026-10-22 12:03:57 +02:00
Lukas Bockstaller 9581c08949 put last_seen attribute into the defaults parameter of the get_or_create (#6689) 2026-10-22 12:00:35 +02:00
7 changed files with 48 additions and 81 deletions

No files matched your search

+1 -1
View File
@@ -77,7 +77,7 @@ dependencies = [
"paypal-checkout-serversdk==1.0.*",
"PyJWT==2.15.*",
"phonenumberslite==9.0.*",
"Pillow==12.3.*",
"Pillow==13.0.*",
"pretix-plugin-build",
"protobuf==7.36.*",
"psycopg2-binary",
+1 -1
View File
@@ -76,7 +76,7 @@ class DeviceTokenAuthentication(TokenAuthentication):
except DeviceLastSeen.DoesNotExist:
# First request from device, create model, ignore result. Use get_or_create to be safe
# against concurrent create requests
DeviceLastSeen.objects.get_or_create(device=device, last_seen=now())
DeviceLastSeen.objects.get_or_create(device=device, defaults={"last_seen": now()})
else:
if now() - last_seen_obj.last_seen < timedelta(seconds=10):
# We don't need to know the last seen info of a device to more precision than this,
+12 -3
View File
@@ -35,10 +35,13 @@
import logging
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.core.exceptions import (
PermissionDenied, ValidationError as DjangoValidationError,
)
from django.db import transaction
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.html import strip_tags
from django.utils.translation import gettext as _
from django_countries.serializers import CountryFieldMixin
from pytz import common_timezones
@@ -218,8 +221,6 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
if self.instance is None:
raise ValidationError(_('Events cannot be created as \'live\'. Quotas and payment must be added to the '
'event before sales can go live.'))
else:
self.instance.clean_live()
return value
@cached_property
@@ -429,6 +430,14 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
event.set_active_plugins(plugins)
event.save()
live = validated_data.get('live') or event.live
if live:
event.refresh_from_db()
try:
event.clean_live()
except DjangoValidationError as e:
raise ValidationError({'live': [strip_tags(m) for m in e.messages]})
return event
+1 -24
View File
@@ -153,7 +153,7 @@ class SubEventBulkEditForm(I18nModelForm):
self.fields[k].widget.is_required = False
self.fields[k].required = False
for k in ('date_from', 'date_to', 'date_admission'):
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end'):
self.fields[k + '_day'] = forms.DateField(
label=self._meta.model._meta.get_field(k).verbose_name,
help_text=self._meta.model._meta.get_field(k).help_text,
@@ -167,17 +167,6 @@ class SubEventBulkEditForm(I18nModelForm):
required=False,
)
for k in ('presale_start', 'presale_end'):
self.fields[k + '_rel'] = RelativeDateTimeField(
label=self._meta.model._meta.get_field(k).verbose_name,
help_text=(
self._meta.model._meta.get_field(k).help_text + " "
+ _("If base your relative date on one that is not defined on the event, it will fall back to the event's start.")
),
required=False,
limit_choices=('event__date_from', 'event__date_to'),
)
class Meta:
model = SubEvent
localized_fields = '__all__'
@@ -258,18 +247,6 @@ class SubEventBulkEditForm(I18nModelForm):
)
setattr(obj, k.replace('_time', ''), newval)
fields.add(k.replace('_time', ''))
elif k.endswith('_rel'):
target_key = k[:-4]
for obj in objs:
cval = self.cleaned_data[k]
if cval is None:
newval = None
if not self._meta.model._meta.get_field(target_key, '').null:
continue
else:
newval = cval.datetime(obj)
setattr(obj, target_key, newval)
fields.add(target_key)
else:
fields.add(k)
for obj in objs:
@@ -127,8 +127,28 @@
{% bootstrap_field form.date_admission_time layout="bulkedit_inline" form_group_class="" %}
</div>
</div>
{% bootstrap_field form.presale_start_rel layout="bulkedit" placeholder=datetime_sample %}
{% bootstrap_field form.presale_end_rel layout="bulkedit" placeholder=datetime_sample %}
<div class="form-group">
<label class="col-md-3 control-label" for="{{ form.presale_start_day.id_for_label }}">
{{ form.presale_start_day.label }}
</label>
<div class="col-md-5">
{% bootstrap_field form.presale_start_day layout="bulkedit_inline" form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.presale_start_time layout="bulkedit_inline" form_group_class="" %}
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label" for="{{ form.presale_end_day.id_for_label }}">
{{ form.presale_end_day.label }}
</label>
<div class="col-md-5">
{% bootstrap_field form.presale_end_day layout="bulkedit_inline" form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.presale_end_time layout="bulkedit_inline" form_group_class="" %}
</div>
</div>
</fieldset>
<fieldset>
<legend>{% trans "Item prices" %}</legend>
+7 -50
View File
@@ -44,7 +44,7 @@ from django.db import transaction
from django.db.models import (
Count, Exists, F, OuterRef, Prefetch, ProtectedError, Subquery,
)
from django.db.models.functions import Coalesce, TruncDate, TruncTime, Extract
from django.db.models.functions import Coalesce, TruncDate, TruncTime
from django.forms import inlineformset_factory
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render
@@ -1590,20 +1590,12 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
# as well in all cases we are concerned about
# See also: https://code.djangoproject.com/ticket/31948
k + '_day': TruncDate(k)
for k in ('date_from', 'date_to', 'date_admission')
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
},
**{
k + '_time': TruncTime(k)
for k in ('date_from', 'date_to', 'date_admission')
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
},
**{
k + '_rel_min': Extract(F('date_from') - F(k), 'epoch') / 60
for k in ['presale_start', "presale_end"]
},
**{
k + '_rel_days': Extract(F('date_from__date') - F(f"{k}__date"), 'days')
for k in ['presale_start', "presale_end"]
}
)
fields = {
@@ -1621,6 +1613,10 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
'date_to_time',
'date_admission_day',
'date_admission_time',
'presale_start_day',
'presale_start_time',
'presale_end_day',
'presale_end_time',
}
for k in fields:
existing_values = list(qs.order_by(k).values(k).annotate(c=Count('*')))
@@ -1630,45 +1626,6 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
mixed_values.add(k)
initial[k] = None
for k in ['presale_start', "presale_end"]:
existing_fixed_values = list(qs.order_by(k).values(k).annotate(c=Count('*')))
existing_rel_days_values = list(qs.order_by(f'{k}_rel_days').values(f'{k}_rel_days').annotate(c=Count("*")))
existing_rel_min_values = list(qs.order_by(f'{k}_rel_min').values(f'{k}_rel_min').annotate(c=Count("*")))
if len(existing_fixed_values) == 1:
if existing_fixed_values[0][k]:
initial[f"{k}_rel"] = RelativeDateWrapper(existing_fixed_values[0][k])
else:
initial[f"{k}_rel"] = None
elif len(existing_rel_min_values) == 1:
if rel_min := existing_rel_min_values[0][f'{k}_rel_min']:
initial[f"{k}_rel"] = RelativeDateWrapper(RelativeDate(
days=0,
base_date_name='event__date_from',
time=None,
minutes=abs(rel_min),
is_after=rel_min < 0,
))
else:
initial[f"{k}_rel"] = None
elif len(existing_rel_days_values) == 1:
if rel_days := existing_rel_days_values[0][f'{k}_rel_days']:
initial[f"{k}_rel"] = RelativeDateWrapper(RelativeDate(
days=abs(rel_days),
base_date_name='event__date_from',
time=None,
minutes=None,
is_after=rel_days < 0,
))
else:
initial[f"{k}_rel"] = None
elif len(existing_fixed_values) > 1 or len(existing_rel_days_values) > 1 or len(existing_rel_min_values) > 1:
mixed_values.add(k)
initial[k] = None
kwargs = super().get_form_kwargs()
kwargs['event'] = self.request.event
kwargs['prefix'] = 'bulkedit'
+4
View File
@@ -642,6 +642,8 @@ def assert_mail_connection(res, should_connect, use_ssl):
True, False
])
def test_private_smtp_ip(res, use_ssl, settings):
settings.EMAIL_HOST_USER = ''
settings.EMAIL_HOST_PASSWORD = ''
settings.EMAIL_CUSTOM_SMTP_BACKEND = 'pretix.base.email.CheckPrivateNetworkSmtpBackend'
settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = False
with assert_mail_connection(res=res, should_connect=False, use_ssl=use_ssl), pytest.raises(match="Request to .* blocked"):
@@ -665,6 +667,8 @@ def test_private_smtp_ip(res, use_ssl, settings):
True, False
])
def test_public_smtp_ip(use_ssl, allow_private, settings):
settings.EMAIL_HOST_USER = ''
settings.EMAIL_HOST_PASSWORD = ''
settings.EMAIL_CUSTOM_SMTP_BACKEND = 'pretix.base.email.CheckPrivateNetworkSmtpBackend'
settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = allow_private