factor out subevent.py

This commit is contained in:
Lukas Bockstaller
2025-12-08 16:20:20 +01:00
parent 1a2ee155bd
commit e076f4bbd9
3 changed files with 195 additions and 185 deletions
+188
View File
@@ -0,0 +1,188 @@
from ast import literal_eval
from collections import namedtuple
from datetime import datetime
from typing import Union
from django import forms
from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.forms.widgets import SplitDateTimePickerWidget
from pretix.base.models import Event
from pretix.control.forms import SplitDateTimeField
from pretix.control.forms.widgets import Select2
SubEventSelection = namedtuple(
typename='SubEventSelection',
field_names=['selection', 'subevents', 'start', 'end', ],
defaults=['subevent', None, None, None],
)
subeventselectionparts = namedtuple(
typename='subeventselectionparts',
field_names=['selection', 'subevents', 'start', 'end']
)
class SubEventSelectionWrapper:
def __init__(self, data: Union[None, SubEventSelection]):
self.data = data
def get_queryset(self, event: Event):
if self.data.selection == 'subevent':
if self.data.subevents is None:
return event.subevents.all()
else:
return event.subevents.filter(pk=self.data.subevents)
elif self.data.selection == 'timerange':
if self.data.start and self.data.end:
return event.subevents.filter(date_from__lte=self.data.start,
date_from__gte=self.data.end)
elif self.data.start:
return event.subevents.filter(date_from__gte=self.data.start)
elif self.data.end:
return event.subevents.filter(date_from__lte=self.data.end)
return event.subevents.all()
def to_string(self) -> str:
if self.data:
if self.data.selection == 'subevent':
return 'SUBEVENT/pk/{}'.format(self.data.subevents.pk)
elif self.data.selection == 'timerange':
if self.data.start and self.data.end:
return 'SUBEVENT/range/{}/{}'.format(self.data.start.isoformat(), self.data.end.isoformat())
elif self.data.start:
return 'SUBEVENT/from/{}'.format(self.data.start)
elif self.data.end:
return 'SUBEVENT/to/{}'.format(self.data.end)
return 'SUBEVENT'
@classmethod
def from_string(cls, input: str):
data = SubEventSelection(selection='subevent')
if input.startswith('SUBEVENT'):
parts = input.split('/')
if len(parts) == 1:
data = SubEventSelection(selection='subevent')
elif parts[1] == 'pk':
data = SubEventSelection(
selection='subevent',
subevents=literal_eval(parts[2])
)
elif parts[1] == 'range':
data = SubEventSelection(
selection="timerange",
start=datetime.fromisoformat(parts[2]),
end=datetime.fromisoformat(parts[3]),
)
elif parts[1] == 'from':
data = SubEventSelection(
selection="timerange",
start=datetime.fromisoformat(parts[2]),
)
elif parts[1] == 'to':
data = SubEventSelection(
selection="timerange",
end=datetime.fromisoformat(parts[3]),
)
return SubEventSelectionWrapper(
data=data
)
class SubeventSelectionWidget(forms.MultiWidget):
template_name = 'pretixcontrol/forms/widgets/subeventselection.html'
parts = SubEventSelection
def __init__(self, event: Event, status_choices, subevent_choices, *args, **kwargs):
widgets = subeventselectionparts(
selection=forms.RadioSelect(
choices=status_choices,
),
subevents=Select2(
attrs={
'class': 'simple-subevent-choice',
'data-model-select2': 'event',
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
}),
'data-placeholder': pgettext_lazy('subevent', 'All dates')
},
),
start=SplitDateTimePickerWidget(),
end=SplitDateTimePickerWidget(),
)
widgets.subevents.choices = subevent_choices
super().__init__(widgets=widgets, *args, **kwargs)
def decompress(self, value):
if isinstance(value, str):
value = SubEventSelectionWrapper.from_string(value)
if isinstance(value, subeventselectionparts):
return value
return subeventselectionparts(selection='subevent', start=None, end=None, subevents=None)
class SubeventSelectionField(forms.MultiValueField):
widget = SubeventSelectionWidget
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
choices = [
("subevent", _("Subevent")),
("timerange", _("Timerange"))
]
fields = SubEventSelection(
selection=forms.ChoiceField(
choices=choices,
required=True,
),
subevents=forms.ModelChoiceField(
required=False,
queryset=self.event.subevents,
empty_label=pgettext_lazy('subevent', 'All dates')
),
start=SplitDateTimeField(
required=False,
),
end=SplitDateTimeField(
required=False,
),
)
kwargs['widget'] = SubeventSelectionWidget(
event=self.event,
status_choices=choices,
subevent_choices=fields.subevents.widget.choices,
)
super().__init__(
fields=fields, require_all_fields=False, *args, **kwargs
)
def compress(self, data_list):
if not data_list:
return None
return SubEventSelectionWrapper(data=SubEventSelection(*data_list)).to_string()
def clean(self, value):
data = subeventselectionparts(*value)
if data.selection == "timerange":
if (data.start != ["", ""] and data.end != ["", ""]) and data.end < data.start:
raise ValidationError(_("The end date must be after the start date."))
if (data.start == ["", ""]) and (data.end == ["", ""]):
raise ValidationError(_('At least one of start and end must be specified.'))
return super().clean(value)
+6 -184
View File
@@ -33,12 +33,8 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # 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. # License for the specific language governing permissions and limitations under the License.
import copy import copy
import datetime
import os import os
from ast import literal_eval
from collections import namedtuple
from decimal import Decimal from decimal import Decimal
from typing import Union
from urllib.parse import urlencode from urllib.parse import urlencode
from django import forms from django import forms
@@ -52,9 +48,7 @@ from django.utils.functional import cached_property
from django.utils.html import escape, format_html from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ( from django.utils.translation import gettext as __, gettext_lazy as _
gettext as __, gettext_lazy as _, pgettext_lazy,
)
from django_scopes.forms import ( from django_scopes.forms import (
SafeModelChoiceField, SafeModelMultipleChoiceField, SafeModelChoiceField, SafeModelMultipleChoiceField,
) )
@@ -63,11 +57,14 @@ from i18nfield.forms import I18nFormField, I18nTextarea
from pretix.base.forms import I18nFormSet, I18nMarkdownTextarea, I18nModelForm from pretix.base.forms import I18nFormSet, I18nMarkdownTextarea, I18nModelForm
from pretix.base.forms.widgets import DatePickerWidget from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import ( from pretix.base.models import (
Event, Item, ItemCategory, ItemProgramTime, ItemVariation, Order, Item, ItemCategory, ItemProgramTime, ItemVariation, Order, OrderPosition,
OrderPosition, Question, QuestionOption, Quota Question, QuestionOption, Quota,
) )
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
from pretix.base.signals import item_copy_data from pretix.base.signals import item_copy_data
from pretix.base.subevent import (
SubeventSelectionField, SubEventSelectionWrapper,
)
from pretix.control.forms import ( from pretix.control.forms import (
ButtonGroupRadioSelect, ExtFileField, ItemMultipleChoiceField, ButtonGroupRadioSelect, ExtFileField, ItemMultipleChoiceField,
SalesChannelCheckboxSelectMultiple, SplitDateTimeField, SalesChannelCheckboxSelectMultiple, SplitDateTimeField,
@@ -279,181 +276,6 @@ class QuestionOptionForm(I18nModelForm):
] ]
SubEventSelection = namedtuple(
typename='SubEventSelection',
field_names=['selection', 'subevents', 'start', 'end', ],
defaults=['subevent', None, None, None],
)
subeventselectionparts = namedtuple(
typename='subeventselectionparts',
field_names=['selection', 'subevents', 'start', 'end']
)
class SubEventSelectionWrapper:
def __init__(self, data: Union[None, SubEventSelection]):
self.data = data
def get_queryset(self, event: Event):
if self.data.selection == 'subevent':
if self.data.subevents is None:
return event.subevents.all()
else:
return event.subevents.filter(pk=self.data.subevents)
elif self.data.selection == 'timerange':
if self.data.start and self.data.end:
return event.subevents.filter(date_from__lte=self.data.start,
date_from__gte=self.data.end)
elif self.data.start:
return event.subevents.filter(date_from__gte=self.data.start)
elif self.data.end:
return event.subevents.filter(date_from__lte=self.data.end)
return event.subevents.all()
def to_string(self) -> str:
if self.data:
if self.data.selection == 'subevent':
return 'SUBEVENT/pk/{}'.format(self.data.subevents.pk)
elif self.data.selection == 'timerange':
if self.data.start and self.data.end:
return 'SUBEVENT/range/{}/{}'.format(self.data.start.isoformat(), self.data.end.isoformat())
elif self.data.start:
return 'SUBEVENT/from/{}'.format(self.data.start)
elif self.data.end:
return 'SUBEVENT/to/{}'.format(self.data.end)
return 'SUBEVENT'
@classmethod
def from_string(cls, input: str):
data = SubEventSelection(selection='subevent')
if input.startswith('SUBEVENT'):
parts = input.split('/')
if len(parts) == 1:
data = SubEventSelection(selection='subevent')
elif parts[1] == 'pk':
data = SubEventSelection(
selection='subevent',
subevents=literal_eval(parts[2])
)
elif parts[1] == 'range':
data = SubEventSelection(
selection="timerange",
start=datetime.datetime.fromisoformat(parts[2]),
end=datetime.datetime.fromisoformat(parts[3]),
)
elif parts[1] == 'from':
data = SubEventSelection(
selection="timerange",
start=datetime.datetime.fromisoformat(parts[2]),
)
elif parts[1] == 'to':
data = SubEventSelection(
selection="timerange",
end=datetime.datetime.fromisoformat(parts[3]),
)
return SubEventSelectionWrapper(
data=data
)
class SubeventSelectionWidget(forms.MultiWidget):
template_name = 'pretixcontrol/forms/widgets/subeventselection.html'
parts = SubEventSelection
def __init__(self, event: Event, status_choices, subevent_choices, *args, **kwargs):
widgets = subeventselectionparts(
selection=forms.RadioSelect(
choices=status_choices,
),
subevents=Select2(
attrs={
'class': 'simple-subevent-choice',
'data-model-select2': 'event',
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
}),
'data-placeholder': pgettext_lazy('subevent', 'All dates')
},
),
start=SplitDateTimePickerWidget(),
end=SplitDateTimePickerWidget(),
)
widgets.subevents.choices = subevent_choices
super().__init__(widgets=widgets, *args, **kwargs)
def decompress(self, value):
if isinstance(value, str):
value = SubEventSelectionWrapper.from_string(value)
if isinstance(value, subeventselectionparts):
return value
return subeventselectionparts(selection='subevent', start=None, end=None, subevents=None)
class SubeventSelectionField(forms.MultiValueField):
widget = SubeventSelectionWidget
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
choices = [
("subevent", _("Subevent")),
("timerange", _("Timerange"))
]
fields = SubEventSelection(
selection=forms.ChoiceField(
choices=choices,
required=True,
),
subevents=forms.ModelChoiceField(
required=False,
queryset=self.event.subevents,
empty_label=pgettext_lazy('subevent', 'All dates')
),
start=SplitDateTimeField(
required=False,
),
end=SplitDateTimeField(
required=False,
),
)
kwargs['widget'] = SubeventSelectionWidget(
event=self.event,
status_choices=choices,
subevent_choices=fields.subevents.widget.choices,
)
super().__init__(
fields=fields, require_all_fields=False, *args, **kwargs
)
def compress(self, data_list):
if not data_list:
return None
return SubEventSelectionWrapper(data=SubEventSelection(*data_list)).to_string()
def clean(self, value):
data = subeventselectionparts(*value)
if data.selection == "timerange":
if (data.start != ["", ""] and data.end != ["", ""]) and data.end < data.start:
raise ValidationError(_("The end date must be after the start date."))
if (data.start == ["", ""]) and (data.end == ["", ""]):
raise ValidationError(_('At least one of start and end must be specified.'))
return super().clean(value)
class QuestionFilterForm(forms.Form): class QuestionFilterForm(forms.Form):
STATUS_VARIANTS = [ STATUS_VARIANTS = [
("", _("All orders")), ("", _("All orders")),
+1 -1
View File
@@ -82,7 +82,7 @@ from pretix.control.forms.item import (
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemProgramTimeForm, ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemProgramTimeForm,
ItemProgramTimeFormSet, ItemUpdateForm, ItemVariationForm, ItemProgramTimeFormSet, ItemUpdateForm, ItemVariationForm,
ItemVariationsFormSet, QuestionFilterForm, QuestionForm, ItemVariationsFormSet, QuestionFilterForm, QuestionForm,
QuestionOptionForm, QuotaForm QuestionOptionForm, QuotaForm,
) )
from pretix.control.permissions import ( from pretix.control.permissions import (
EventPermissionRequiredMixin, event_permission_required, EventPermissionRequiredMixin, event_permission_required,