add SubEventSelectionWrapper and filtering by datetime

This commit is contained in:
Lukas Bockstaller
2025-12-08 16:12:23 +01:00
parent 9743d7ae52
commit 1a2ee155bd
4 changed files with 147 additions and 48 deletions

View File

@@ -33,9 +33,12 @@
# 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.
import copy
import datetime
import os
from ast import literal_eval
from collections import namedtuple
from decimal import Decimal
from typing import Union
from urllib.parse import urlencode
from django import forms
@@ -60,8 +63,8 @@ from i18nfield.forms import I18nFormField, I18nTextarea
from pretix.base.forms import I18nFormSet, I18nMarkdownTextarea, I18nModelForm
from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import (
Item, ItemCategory, ItemProgramTime, ItemVariation, Order, OrderPosition,
Question, QuestionOption, Quota, SubEvent, Event
Event, Item, ItemCategory, ItemProgramTime, ItemVariation, Order,
OrderPosition, Question, QuestionOption, Quota
)
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
from pretix.base.signals import item_copy_data
@@ -275,19 +278,97 @@ class QuestionOptionForm(I18nModelForm):
'answer',
]
subeventSelectionParts = namedtuple('subeventWidgetParts', ['selection', 'startDateTime', 'endDateTime', 'subevents'])
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 = subeventSelectionParts
parts = SubEventSelection
def __init__(self, event: Event, status_choices, subevent_choices, *args, **kwargs):
widgets = subeventSelectionParts(
widgets = subeventselectionparts(
selection=forms.RadioSelect(
choices=status_choices,
),
startDateTime=SplitDateTimePickerWidget(),
endDateTime=SplitDateTimePickerWidget(),
subevents=Select2(
attrs={
'class': 'simple-subevent-choice',
@@ -298,20 +379,27 @@ class SubeventSelectionWidget(forms.MultiWidget):
}),
'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 value:
return value
return ['subevent', "", ""]
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')
@@ -320,23 +408,22 @@ class SubeventSelectionField(forms.MultiValueField):
("timerange", _("Timerange"))
]
fields = subeventSelectionParts(
fields = SubEventSelection(
selection=forms.ChoiceField(
choices=choices,
required=True,
initial="subevent",
),
startDateTime=SplitDateTimeField(
required=False,
),
endDateTime=SplitDateTimeField(
required=False,
),
subevents=forms.ModelChoiceField(
required=False,
queryset=self.event.subevents,
empty_label = pgettext_lazy('subevent', 'All dates')
)
empty_label=pgettext_lazy('subevent', 'All dates')
),
start=SplitDateTimeField(
required=False,
),
end=SplitDateTimeField(
required=False,
),
)
kwargs['widget'] = SubeventSelectionWidget(
@@ -352,7 +439,19 @@ class SubeventSelectionField(forms.MultiValueField):
def compress(self, data_list):
if not data_list:
return None
return subeventSelectionParts(*data_list)
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):
@@ -377,6 +476,7 @@ class QuestionFilterForm(forms.Form):
),
required=False,
label=_("Status"),
initial="np",
)
item = forms.ChoiceField(
choices=[],
@@ -395,27 +495,19 @@ class QuestionFilterForm(forms.Form):
self.fields['subevent_selection'] = SubeventSelectionField(
event=self.event,
label=_("Subevents"),
help_text=_(" Select the subevents that should be included in the statistics either by subevent or by the timerange in which they occur.")
help_text=_("Select the subevents that should be included in the statistics either by subevent or by the timerange in which they occur."),
)
self.initial['status'] = "np"
self.fields['item'].choices = [('', _('All products'))] + [(item.id, item.name) for item in
Item.objects.filter(event=self.event)]
def clean(self):
super().clean()
import pprint
pprint.pprint(self.cleaned_data)
def order_position_queryset(self):
fdata = self.data
def filter_qs(self):
fdata = self.cleaned_data
opqs = OrderPosition.objects.filter(
order__event=self.event,
)
if (fdata.get('subevent', "") != "") & (fdata.get('subevent', "") is not None):
opqs = opqs.filter(subevent=fdata["subevent"])
sub_event_qs = SubEventSelectionWrapper.from_string(fdata['subevent_selection']).get_queryset(self.event)
opqs = opqs.filter(subevent__in=sub_event_qs)
s = fdata.get("status", "np")
if s != "":

View File

@@ -11,19 +11,19 @@
{% if selopt.value == "subevent" %}
{% with widget.subwidgets.3 as widget %}
{% with widget.subwidgets.1 as widget %}
{% include widget.template_name %}
{% endwith %}
{% elif selopt.value == "timerange" %}
{% with widget.subwidgets.1 as widget %}
{% with widget.subwidgets.2 as widget %}
{% include widget.template_name %}
{% endwith %}
<span class="spacer">{% trans "until" %}</span>
{% with widget.subwidgets.2 as widget %}
{% with widget.subwidgets.3 as widget %}
{% include widget.template_name %}
{% endwith %}

View File

@@ -5,7 +5,7 @@
{% load formset_tags %}
{% block title %}{% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %}{% endblock %}
{% block inside %}
{% for e in form.errors %}
{% for e in form.errors.values %}
<div class="alert alert-danger has-error">
{{ e }}
</div>

View File

@@ -68,8 +68,9 @@ from pretix.api.serializers.item import (
from pretix.base.exporter import ListExporter
from pretix.base.forms import I18nFormSet
from pretix.base.models import (
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, Question,
QuestionAnswer, QuestionOption, Quota, SeatCategoryMapping, Voucher, OrderPosition,
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation,
OrderPosition, Question, QuestionAnswer, QuestionOption, Quota,
SeatCategoryMapping, Voucher,
)
from pretix.base.models.event import SubEvent
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
@@ -81,7 +82,7 @@ from pretix.control.forms.item import (
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemProgramTimeForm,
ItemProgramTimeFormSet, ItemUpdateForm, ItemVariationForm,
ItemVariationsFormSet, QuestionFilterForm, QuestionForm,
QuestionOptionForm, QuotaForm,
QuestionOptionForm, QuotaForm
)
from pretix.control.permissions import (
EventPermissionRequiredMixin, event_permission_required,
@@ -777,12 +778,18 @@ class QuestionView(EventPermissionRequiredMixin, ChartContainingView, DetailView
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx['items'] = self.object.items.all()
ctx['form'] = QuestionFilterForm(
data=self.request.GET,
event=self.request.event
)
if self.request.GET:
ctx['form'] = QuestionFilterForm(
data=self.request.GET,
event=self.request.event,
)
else:
ctx['form'] = QuestionFilterForm(
event=self.request.event,
)
if ctx['form'].is_valid():
opqs = ctx['form'].order_position_queryset()
opqs = ctx['form'].filter_qs()
stats = self.get_answer_statistics(opqs)
ctx['stats'], ctx['total'] = stats
return ctx