Compare commits

..
10 changed files with 108 additions and 232 deletions
+12
View File
@@ -222,3 +222,15 @@ class HistoryPasswordValidator:
user.historic_passwords.filter(
pk__in=user.historic_passwords.order_by("-created")[self.history_length:].values_list("pk", flat=True),
).delete()
def has_event_access_permission(request, permission='can_change_event_settings'):
return (
request.user.is_authenticated and
request.user.has_event_permission(request.organizer, request.event, permission, request=request)
) or (
getattr(request, 'event_access_user', None) and
request.event_access_user.is_authenticated and
request.event_access_user.has_event_permission(request.organizer, request.event, permission,
session_key=request.event_access_parent_session_key)
)
@@ -1,23 +0,0 @@
# Generated by Django 4.2.17 on 2025-01-13 14:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0274_tax_codes"),
]
operations = [
migrations.AlterField(
model_name="question",
name="valid_number_max",
field=models.DecimalField(decimal_places=6, max_digits=30, null=True),
),
migrations.AlterField(
model_name="question",
name="valid_number_min",
field=models.DecimalField(decimal_places=6, max_digits=30, null=True),
),
]
+2 -2
View File
@@ -1718,10 +1718,10 @@ class Question(LoggedModel):
'Question', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions'
)
dependency_values = MultiStringField(default=[])
valid_number_min = models.DecimalField(decimal_places=6, max_digits=30, null=True, blank=True,
valid_number_min = models.DecimalField(decimal_places=6, max_digits=16, null=True, blank=True,
verbose_name=_('Minimum value'),
help_text=_('Currently not supported in our apps and during check-in'))
valid_number_max = models.DecimalField(decimal_places=6, max_digits=30, null=True, blank=True,
valid_number_max = models.DecimalField(decimal_places=6, max_digits=16, null=True, blank=True,
verbose_name=_('Maximum value'),
help_text=_('Currently not supported in our apps and during check-in'))
valid_date_min = models.DateField(null=True, blank=True,
+65 -169
View File
@@ -185,104 +185,43 @@ BEFORE_AFTER_CHOICE = (
)
reldatetimeparts = namedtuple('reldatetimeparts', (
"status", # 0
"absolute", # 1
"rel_days_number", # 2
"rel_mins_relationto", # 3
"rel_days_timeofday", # 4
"rel_mins_number", # 5
"rel_days_relationto", # 6
"rel_mins_relation", # 7
"rel_days_relation" # 8
))
reldatetimeparts.indizes = reldatetimeparts(*range(9))
class RelativeDateTimeWidget(forms.MultiWidget):
template_name = 'pretixbase/forms/widgets/reldatetime.html'
parts = reldatetimeparts
def __init__(self, *args, **kwargs):
self.status_choices = kwargs.pop('status_choices')
base_choices = kwargs.pop('base_choices')
widgets = reldatetimeparts(
status=forms.RadioSelect(choices=self.status_choices),
absolute=forms.DateTimeInput(
widgets = (
forms.RadioSelect(choices=self.status_choices),
forms.DateTimeInput(
attrs={'class': 'datetimepicker'}
),
rel_days_number=forms.NumberInput(),
rel_mins_relationto=forms.Select(choices=base_choices),
rel_days_timeofday=forms.TimeInput(attrs={'placeholder': _('Time'), 'class': 'timepickerfield'}),
rel_mins_number=forms.NumberInput(),
rel_days_relationto=forms.Select(choices=base_choices),
rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
forms.NumberInput(),
forms.Select(choices=base_choices),
forms.TimeInput(attrs={'placeholder': _('Time'), 'class': 'timepickerfield'}),
forms.NumberInput(),
forms.Select(choices=base_choices),
forms.Select(choices=BEFORE_AFTER_CHOICE),
forms.Select(choices=BEFORE_AFTER_CHOICE),
)
super().__init__(widgets=widgets, *args, **kwargs)
def decompress(self, value):
if isinstance(value, str):
value = RelativeDateWrapper.from_string(value)
if isinstance(value, reldatetimeparts):
return value
if not value:
return reldatetimeparts(
status="unset",
absolute=None,
rel_days_number=1,
rel_mins_relationto="date_from",
rel_days_timeofday=None,
rel_mins_number=0,
rel_days_relationto="date_from",
rel_mins_relation="before",
rel_days_relation="before"
)
return ['unset', None, 1, 'date_from', None, 0, "date_from", "before", "before"]
elif isinstance(value.data, (datetime.datetime, datetime.date)):
return reldatetimeparts(
status="absolute",
absolute=value.data,
rel_days_number=1,
rel_mins_relationto="date_from",
rel_days_timeofday=None,
rel_mins_number=0,
rel_days_relationto="date_from",
rel_mins_relation="before",
rel_days_relation="before"
)
return ['absolute', value.data, 1, 'date_from', None, 0, "date_from", "before", "before"]
elif value.data.minutes is not None:
return reldatetimeparts(
status="relative_minutes",
absolute=None,
rel_days_number=None,
rel_mins_relationto=value.data.base_date_name,
rel_days_timeofday=None,
rel_mins_number=value.data.minutes,
rel_days_relationto=value.data.base_date_name,
rel_mins_relation="after" if value.data.is_after else "before",
rel_days_relation="after" if value.data.is_after else "before"
)
return reldatetimeparts(
status="relative",
absolute=None,
rel_days_number=value.data.days,
rel_mins_relationto=value.data.base_date_name,
rel_days_timeofday=value.data.time,
rel_mins_number=0,
rel_days_relationto=value.data.base_date_name,
rel_mins_relation="after" if value.data.is_after else "before",
rel_days_relation="after" if value.data.is_after else "before"
)
return ['relative_minutes', None, None, value.data.base_date_name, None, value.data.minutes, value.data.base_date_name,
"after" if value.data.is_after else "before", "after" if value.data.is_after else "before"]
return ['relative', None, value.data.days, value.data.base_date_name, value.data.time, 0, value.data.base_date_name,
"after" if value.data.is_after else "before", "after" if value.data.is_after else "before"]
def get_context(self, name, value, attrs):
ctx = super().get_context(name, value, attrs)
ctx['required'] = self.status_choices[0][0] == 'unset'
ctx['rendered_subwidgets'] = self.parts(*(
self._render(w['template_name'], {**ctx, 'widget': w})
for w in ctx['widget']['subwidgets']
))._asdict()
return ctx
@@ -300,36 +239,36 @@ class RelativeDateTimeField(forms.MultiValueField):
choices = BASE_CHOICES
if not kwargs.get('required', True):
status_choices.insert(0, ('unset', _('Not set')))
fields = reldatetimeparts(
status=forms.ChoiceField(
fields = (
forms.ChoiceField(
choices=status_choices,
required=True
),
absolute=forms.DateTimeField(
forms.DateTimeField(
required=False
),
rel_days_number=forms.IntegerField(
forms.IntegerField(
required=False
),
rel_mins_relationto=forms.ChoiceField(
forms.ChoiceField(
choices=choices,
required=False
),
rel_days_timeofday=forms.TimeField(
forms.TimeField(
required=False,
),
rel_mins_number=forms.IntegerField(
forms.IntegerField(
required=False
),
rel_days_relationto=forms.ChoiceField(
forms.ChoiceField(
choices=choices,
required=False
),
rel_mins_relation=forms.ChoiceField(
forms.ChoiceField(
choices=BEFORE_AFTER_CHOICE,
required=False
),
rel_days_relation=forms.ChoiceField(
forms.ChoiceField(
choices=BEFORE_AFTER_CHOICE,
required=False
),
@@ -343,36 +282,32 @@ class RelativeDateTimeField(forms.MultiValueField):
)
def set_event(self, event):
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = [
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
]
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = [
self.widget.widgets[3].choices = [
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
]
def compress(self, data_list):
if not data_list:
return None
data = reldatetimeparts(*data_list)
if data.status == 'absolute':
return RelativeDateWrapper(data.absolute)
elif data.status == 'unset':
if data_list[0] == 'absolute':
return RelativeDateWrapper(data_list[1])
elif data_list[0] == 'unset':
return None
elif data.status == 'relative_minutes':
elif data_list[0] == 'relative_minutes':
return RelativeDateWrapper(RelativeDate(
days=0,
base_date_name=data.rel_mins_relationto,
base_date_name=data_list[3],
time=None,
minutes=data.rel_mins_number,
is_after=data.rel_mins_relation == "after",
minutes=data_list[5],
is_after=data_list[7] == "after",
))
else:
return RelativeDateWrapper(RelativeDate(
days=data.rel_days_number,
base_date_name=data.rel_days_relationto,
time=data.rel_days_timeofday,
days=data_list[2],
base_date_name=data_list[6],
time=data_list[4],
minutes=None,
is_after=data.rel_days_relation == "after",
is_after=data_list[8] == "after",
))
def has_changed(self, initial, data):
@@ -381,41 +316,29 @@ class RelativeDateTimeField(forms.MultiValueField):
return super().has_changed(initial, data)
def clean(self, value):
data = reldatetimeparts(*value)
if data.status == 'absolute' and not data.absolute:
if value[0] == 'absolute' and not value[1]:
raise ValidationError(self.error_messages['incomplete'])
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
elif value[0] == 'relative' and (value[2] is None or not value[3]):
raise ValidationError(self.error_messages['incomplete'])
elif data.status == 'relative_minutes' and (data.rel_mins_number is None or not data.rel_mins_relationto):
elif value[0] == 'relative_minutes' and (value[5] is None or not value[3]):
raise ValidationError(self.error_messages['incomplete'])
return super().clean(value)
reldateparts = namedtuple('reldateparts', (
"status", # 0
"absolute", # 1
"rel_days_number", # 2
"rel_days_relationto", # 3
"rel_days_relation", # 4
))
reldateparts.indizes = reldateparts(*range(5))
class RelativeDateWidget(RelativeDateTimeWidget):
template_name = 'pretixbase/forms/widgets/reldate.html'
parts = reldateparts
def __init__(self, *args, **kwargs):
self.status_choices = kwargs.pop('status_choices')
widgets = reldateparts(
status=forms.RadioSelect(choices=self.status_choices),
absolute=forms.DateInput(
widgets = (
forms.RadioSelect(choices=self.status_choices),
forms.DateInput(
attrs={'class': 'datepickerfield'}
),
rel_days_number=forms.NumberInput(),
rel_days_relationto=forms.Select(choices=kwargs.pop('base_choices')),
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
forms.NumberInput(),
forms.Select(choices=kwargs.pop('base_choices')),
forms.Select(choices=BEFORE_AFTER_CHOICE),
)
forms.MultiWidget.__init__(self, widgets=widgets, *args, **kwargs)
@@ -423,30 +346,10 @@ class RelativeDateWidget(RelativeDateTimeWidget):
if isinstance(value, str):
value = RelativeDateWrapper.from_string(value)
if not value:
return reldateparts(
status="unset",
absolute=None,
rel_days_number=1,
rel_days_relationto="date_from",
rel_days_relation="before"
)
if isinstance(value, reldateparts):
return value
return ['unset', None, 1, 'date_from', 'before']
elif isinstance(value.data, (datetime.datetime, datetime.date)):
return reldateparts(
status="absolute",
absolute=value.data,
rel_days_number=1,
rel_days_relationto="date_from",
rel_days_relation="before"
)
return reldateparts(
status="relative",
absolute=None,
rel_days_number=value.data.days,
rel_days_relationto=value.data.base_date_name,
rel_days_relation="after" if value.data.is_after else "before"
)
return ['absolute', value.data, 1, 'date_from', 'before']
return ['relative', None, value.data.days, value.data.base_date_name, "after" if value.data.is_after else "before"]
class RelativeDateField(RelativeDateTimeField):
@@ -458,22 +361,22 @@ class RelativeDateField(RelativeDateTimeField):
]
if not kwargs.get('required', True):
status_choices.insert(0, ('unset', _('Not set')))
fields = reldateparts(
status=forms.ChoiceField(
fields = (
forms.ChoiceField(
choices=status_choices,
required=True
),
absolute=forms.DateField(
forms.DateField(
required=False
),
rel_days_number=forms.IntegerField(
forms.IntegerField(
required=False
),
rel_days_relationto=forms.ChoiceField(
forms.ChoiceField(
choices=BASE_CHOICES,
required=False
),
rel_days_relation=forms.ChoiceField(
forms.ChoiceField(
choices=BEFORE_AFTER_CHOICE,
required=False
),
@@ -484,35 +387,28 @@ class RelativeDateField(RelativeDateTimeField):
self, fields=fields, require_all_fields=False, *args, **kwargs
)
def set_event(self, event):
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = [
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
]
def compress(self, data_list):
if not data_list:
return None
data = reldateparts(*data_list)
if data.status == 'absolute':
return RelativeDateWrapper(data.absolute)
elif data.status == 'unset':
if data_list[0] == 'absolute':
return RelativeDateWrapper(data_list[1])
elif data_list[0] == 'unset':
return None
else:
return RelativeDateWrapper(RelativeDate(
days=data.rel_days_number,
base_date_name=data.rel_days_relationto,
days=data_list[2],
base_date_name=data_list[3],
time=None, minutes=None,
is_after=data.rel_days_relation == "after"
is_after=data_list[4] == "after"
))
def clean(self, value):
data = reldateparts(*value)
if data.status == 'absolute' and not data.absolute:
if value[0] == 'absolute' and not value[1]:
raise ValidationError(self.error_messages['incomplete'])
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
elif value[0] == 'relative' and (value[2] is None or not value[3]):
raise ValidationError(self.error_messages['incomplete'])
return forms.MultiValueField.clean(self, value)
return super().clean(value)
class ModelRelativeDateTimeField(models.CharField):
@@ -9,11 +9,12 @@
{{ selopt.label }}
</label>
{% if selopt.value == "absolute" %}
{{ rendered_subwidgets.absolute }}
{% include widget.subwidgets.1.template_name with widget=widget.subwidgets.1 %}
{% elif selopt.value == "relative" %}
{% blocktrans trimmed with number=rendered_subwidgets.rel_days_number relation=rendered_subwidgets.rel_days_relation relation_to=rendered_subwidgets.rel_days_relationto %}
{{ number }} days {{ relation }} {{ relation_to }}
{% endblocktrans %}
{% include widget.subwidgets.2.template_name with widget=widget.subwidgets.2 %}
{% trans "days" %}
{% include widget.subwidgets.4.template_name with widget=widget.subwidgets.4 %}
{% include widget.subwidgets.3.template_name with widget=widget.subwidgets.3 %}
{% endif %}
</div>
{% endfor %}
@@ -9,15 +9,19 @@
{{ selopt.label }}
</label>
{% if selopt.value == "absolute" %}
{{ rendered_subwidgets.absolute }}
{% include widget.subwidgets.1.template_name with widget=widget.subwidgets.1 %}
{% elif selopt.value == "relative_minutes" %}
{% blocktrans trimmed with number=rendered_subwidgets.rel_mins_number relation=rendered_subwidgets.rel_mins_relation relation_to=rendered_subwidgets.rel_mins_relationto %}
{{ number }} minutes {{ relation }} {{ relation_to }}
{% endblocktrans %}
{% include widget.subwidgets.5.template_name with widget=widget.subwidgets.5 %}
{% trans "minutes" %}
{% include widget.subwidgets.7.template_name with widget=widget.subwidgets.7 %}
{% include widget.subwidgets.3.template_name with widget=widget.subwidgets.3 %}
{% elif selopt.value == "relative" %}
{% blocktrans trimmed with number=rendered_subwidgets.rel_days_number relation=rendered_subwidgets.rel_days_relation relation_to=rendered_subwidgets.rel_days_relationto time_of_day=rendered_subwidgets.rel_days_timeofday %}
{{ number }} days {{ relation }} {{ relation_to }} at {{ time_of_day }}
{% endblocktrans %}
{% include widget.subwidgets.2.template_name with widget=widget.subwidgets.2 %}
{% trans "days" %}
{% include widget.subwidgets.8.template_name with widget=widget.subwidgets.8 %}
{% include widget.subwidgets.6.template_name with widget=widget.subwidgets.6 %}
{% trans "at" %}
{% include widget.subwidgets.4.template_name with widget=widget.subwidgets.4 %}
{% endif %}
</div>
{% endfor %}
+3 -15
View File
@@ -25,13 +25,15 @@ from contextlib import contextmanager
from dateutil.parser import parse
from django.utils.timezone import now
from pretix.base.auth import has_event_access_permission
timemachine_now_var = contextvars.ContextVar('timemachine_now', default=None)
@contextmanager
def time_machine_now_assigned_from_request(request):
if hasattr(request, 'event') and f'timemachine_now_dt:{request.event.pk}' in request.session and \
request.event.testmode and has_time_machine_permission(request, request.event):
request.event.testmode and has_event_access_permission(request):
request.now_dt = parse(request.session[f'timemachine_now_dt:{request.event.pk}'])
request.now_dt_is_fake = True
else:
@@ -70,17 +72,3 @@ def time_machine_now_assigned(now_dt):
yield
finally:
timemachine_now_var.set(None)
def has_time_machine_permission(request, event):
permission = 'can_change_event_settings'
return (
request.user.is_authenticated and
request.user.has_event_permission(request.organizer, request.event, permission, request=request)
) or (
getattr(request, 'event_access_user', None) and
request.event_access_user.is_authenticated and
request.event_access_user.has_event_permission(request.organizer, request.event, permission,
session_key=request.event_access_parent_session_key)
)
+4 -4
View File
@@ -4,8 +4,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
"PO-Revision-Date: 2025-01-14 09:56+0000\n"
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
"PO-Revision-Date: 2024-11-04 13:44+0000\n"
"Last-Translator: Katrine Tella <ktl@hjoerring.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/da/"
">\n"
"Language: da\n"
@@ -13,7 +13,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.9.2\n"
"X-Generator: Weblate 5.8.1\n"
#: pretix/_base_settings.py:79
msgid "English"
@@ -20841,7 +20841,7 @@ msgstr "Nej, gå tilbage"
#: pretix/control/templates/pretixcontrol/order/approve.html:25
msgid "Yes, approve order"
msgstr "Ja, godkend bestilling"
msgstr "Ja, annullér bestilling"
#: pretix/control/templates/pretixcontrol/order/cancel.html:6
#: pretix/control/templates/pretixcontrol/order/cancel.html:10
+3 -4
View File
@@ -63,6 +63,7 @@ from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView
from pretix.base.auth import has_event_access_permission
from pretix.base.forms.widgets import SplitDateTimePickerWidget
from pretix.base.models import (
ItemVariation, Quota, SalesChannel, SeatCategoryMapping, Voucher,
@@ -73,9 +74,7 @@ from pretix.base.models.items import (
)
from pretix.base.services.placeholders import PlaceholderContext
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.timemachine import (
has_time_machine_permission, time_machine_now,
)
from pretix.base.timemachine import time_machine_now
from pretix.helpers.compat import date_fromisocalendar
from pretix.helpers.formats.en.formats import (
SHORT_MONTH_DAY_FORMAT, WEEK_FORMAT,
@@ -963,7 +962,7 @@ class EventTimeMachine(EventViewMixin, TemplateView):
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
if not has_time_machine_permission(request, request.event):
if not has_event_access_permission(request):
raise PermissionDenied(_('You are not allowed to access time machine mode.'))
if not request.event.testmode:
raise PermissionDenied(_('This feature is only available in test mode.'))
+3 -4
View File
@@ -60,6 +60,7 @@ from django.utils.translation import gettext, gettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import ListView, TemplateView, View
from pretix.base.auth import has_event_access_permission
from pretix.base.models import (
CachedTicket, Checkin, GiftCard, Invoice, Order, OrderPosition, Quota,
TaxRule,
@@ -205,10 +206,8 @@ class TicketPageMixin:
ctx['download_buttons'] = self.download_buttons
ctx['backend_user'] = (
self.request.user.is_authenticated
and self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_orders', request=self.request)
)
ctx['backend_user'] = has_event_access_permission(self.request, 'can_view_orders')
return ctx
@cached_property