Compare commits

...

3 Commits

Author SHA1 Message Date
Lukas Bockstaller
07f38819a6 uses propper form validation 2025-12-04 10:15:41 +01:00
Lukas Bockstaller
b4dd2145ff move form out of views.py 2025-12-04 09:33:51 +01:00
Lukas Bockstaller
1c26036976 fix linting and formatting 2025-12-04 09:16:02 +01:00
2 changed files with 119 additions and 122 deletions

View File

@@ -47,7 +47,10 @@ from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as __, gettext_lazy as _
from django.utils.timezone import now
from django.utils.translation import (
gettext as __, gettext_lazy as _, pgettext_lazy,
)
from django_scopes.forms import (
SafeModelChoiceField, SafeModelMultipleChoiceField,
)
@@ -56,8 +59,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, Question,
QuestionOption, Quota,
Item, ItemCategory, ItemProgramTime, ItemVariation, Order, OrderPosition,
Question, QuestionOption, Quota, SubEvent,
)
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
from pretix.base.signals import item_copy_data
@@ -272,6 +275,100 @@ class QuestionOptionForm(I18nModelForm):
]
class QuestionFilterForm(forms.Form):
STATUS_VARIANTS = [
("", _("All orders")),
("p", _("Paid")),
("pv", _("Paid or confirmed")),
("n", _("Pending")),
("np", _("Pending or paid")),
("o", _("Pending (overdue)")),
("e", _("Expired")),
("ne", _("Pending or expired")),
("c", _("Canceled"))
]
status = forms.ChoiceField(
choices=STATUS_VARIANTS,
widget=forms.Select(
attrs={
'class': 'form-control',
}
),
required=False,
)
item = forms.ChoiceField(
choices=[],
widget=forms.Select(
attrs={'class': 'form-control'}
),
required=False
)
subevent = forms.ModelChoiceField(
queryset=SubEvent.objects.none(),
required=False,
empty_label=pgettext_lazy('subevent', 'All dates')
)
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
super().__init__(*args, **kwargs)
self.initial['status'] = "np"
self.fields['item'].choices = [('', _('All products'))] + [(item.id, item.name) for item in Item.objects.filter(event=self.event)]
if self.event.has_subevents:
self.fields["subevent"].queryset = self.event.subevents.all()
self.fields['subevent'].widget = Select2(
attrs={
'class': 'form-control simple-subevent-choice',
'data-model-select2': 'event',
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
}),
'data-placeholder': pgettext_lazy('subevent', 'All dates')
}
)
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
else:
del self.fields['subevent']
def order_position_queryset(self):
fdata = self.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"])
s = fdata.get("status", "np")
if s != "":
if s == 'o':
opqs = opqs.filter(order__status=Order.STATUS_PENDING,
order__expires__lt=now().replace(hour=0, minute=0, second=0))
elif s == 'np':
opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
elif s == 'pv':
opqs = opqs.filter(
Q(order__status=Order.STATUS_PAID) |
Q(order__status=Order.STATUS_PENDING, order__valid_if_pending=True)
)
elif s == 'ne':
opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
else:
opqs = opqs.filter(order__status=s)
if s not in (Order.STATUS_CANCELED, ""):
opqs = opqs.filter(canceled=False)
if fdata.get("item", "") != "":
i = fdata.get("item", "")
opqs = opqs.filter(item_id__in=(i,))
return opqs
class QuotaForm(I18nModelForm):
itemvars = forms.MultipleChoiceField(
label=_("Products"),

View File

@@ -37,23 +37,17 @@ import json
from collections import OrderedDict, namedtuple
from itertools import groupby
from json.decoder import JSONDecodeError
import pprint
from django import forms
from django.contrib import messages
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.exceptions import PermissionDenied
from django.core.files import File
from django.db import transaction
from django.db.models import (
Count, Exists, F, OuterRef, Prefetch, ProtectedError, Q,
)
from pretix.base.exporter import ListExporter
from django.dispatch import receiver
from pretix.base.signals import register_data_exporters
from django.forms import Select
from django.forms.fields import MultipleChoiceField
from django.forms.models import inlineformset_factory, ModelMultipleChoiceField, ModelChoiceField
from django.forms.models import inlineformset_factory
from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
)
@@ -61,7 +55,7 @@ from django.shortcuts import redirect
from django.urls import resolve, reverse
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy as _, pgettext_lazy
from django.utils.translation import gettext, gettext_lazy as _
from django.views.decorators.http import require_http_methods
from django.views.generic import ListView
from django.views.generic.detail import DetailView, SingleObjectMixin
@@ -71,30 +65,29 @@ from pretix.api.serializers.item import (
ItemAddOnSerializer, ItemBundleSerializer, ItemProgramTimeSerializer,
ItemVariationSerializer,
)
from pretix.base.exporter import ListExporter
from pretix.base.forms import I18nFormSet
from pretix.base.models import (
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, Order,
OrderPosition, Question, QuestionAnswer, QuestionOption, Quota,
SeatCategoryMapping, Voucher,
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, Question,
QuestionAnswer, QuestionOption, Quota, SeatCategoryMapping, Voucher, OrderPosition,
)
from pretix.base.models.event import Event
from pretix.base.models.event import SubEvent
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.services.tickets import invalidate_cache
from pretix.base.signals import quota_availability
from pretix.base.signals import quota_availability, register_data_exporters
from pretix.control.forms.item import (
CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm,
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemProgramTimeForm,
ItemProgramTimeFormSet, ItemUpdateForm, ItemVariationForm,
ItemVariationsFormSet, QuestionForm, QuestionOptionForm, QuotaForm,
ItemVariationsFormSet, QuestionFilterForm, QuestionForm,
QuestionOptionForm, QuotaForm,
)
from pretix.control.permissions import (
EventPermissionRequiredMixin, event_permission_required,
)
from pretix.control.signals import item_forms, item_formsets
from pretix.helpers.models import modelcopy
from ..forms.widgets import Select2, Select2Multiple
from ...helpers.compat import CompatDeleteView
from . import ChartContainingView, CreateView, PaginationMixin, UpdateView
@@ -615,6 +608,7 @@ class QuestionDelete(EventPermissionRequiredMixin, CompatDeleteView):
'event': self.request.event.slug,
})
class QuestionMixin:
@cached_property
def formset(self):
@@ -668,102 +662,6 @@ class QuestionMixin:
ctx['formset'] = self.formset
return ctx
class QuestionFilterForm(forms.Form):
STATUS_VARIANTS = [
("", _("All orders")),
("p", _("Paid")),
("pv", _("Paid or confirmed")),
("n", _("Pending")),
("np", _("Pending or paid")),
("o", _("Pending (overdue)")),
("e", _("Expired")),
("ne", _("Pending or expired")),
("c", _("Canceled"))
]
status = forms.ChoiceField(
choices=STATUS_VARIANTS,
widget=forms.Select(
attrs={
'class': 'form-control',
}
),
required=False,
)
item = forms.ChoiceField(
choices=[],
widget=forms.Select(
attrs={'class': 'form-control'}
),
required=False
)
subevent = forms.ModelChoiceField(
queryset=SubEvent.objects.none(),
required=False,
empty_label=pgettext_lazy('subevent', 'All dates')
)
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
super().__init__(*args, **kwargs)
self.initial['status']="np"
self.fields['item'].choices = [('', _('All products'))] + [(item.id, item.name) for item in Item.objects.filter(event=self.event)]
if self.event.has_subevents:
self.fields["subevent"].queryset = self.event.subevents.all()
self.fields['subevent'].widget = Select2(
attrs={
'class': 'form-control simple-subevent-choice',
'data-model-select2': 'event',
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
}),
'data-placeholder': pgettext_lazy('subevent', 'All dates')
}
)
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
else:
del self.fields['subevent']
def is_valid(self) -> bool:
return True
def orderPositionQuerySet(self):
fdata = self.data
opqs = OrderPosition.objects.filter(
order__event=self.event,
)
if (fdata.get('subevent', "") != "") & (fdata.get('subevent', "") != None):
opqs = opqs.filter(subevent=fdata["subevent"])
s = fdata.get("status", "np")
if s != "":
if s == 'o':
opqs = opqs.filter(order__status=Order.STATUS_PENDING,
order__expires__lt=now().replace(hour=0, minute=0, second=0))
elif s == 'np':
opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
elif s == 'pv':
opqs = opqs.filter(
Q(order__status=Order.STATUS_PAID) |
Q(order__status=Order.STATUS_PENDING, order__valid_if_pending=True)
)
elif s == 'ne':
opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
else:
opqs = opqs.filter(order__status=s)
if s not in (Order.STATUS_CANCELED, ""):
opqs = opqs.filter(canceled=False)
if fdata.get("item", "") != "":
i = fdata.get("item", "")
opqs = opqs.filter(item_id__in=(i,))
return opqs
class QuestionAnswerExporter(ListExporter):
identifier = 'question_answer_exporter'
@@ -778,7 +676,7 @@ class QuestionAnswerExporter(ListExporter):
forms.ModelChoiceField(
label=_('Question'),
queryset=Question.objects.filter(event=self.event),
),
),
**QuestionFilterForm(event=self.event).fields
}
@@ -787,7 +685,7 @@ class QuestionAnswerExporter(ListExporter):
def iterate_list(self, form_data):
question = Question.objects.filter(event=self.event).get(pk=form_data['question'])
opqs = QuestionFilterForm(event=self.event, data=form_data).orderPositionQuerySet()
opqs = QuestionFilterForm(event=self.event, data=form_data).order_position_queryset()
qs = QuestionAnswer.objects.filter(
question=question, orderposition__isnull=False,
@@ -816,19 +714,19 @@ class QuestionAnswerExporter(ListExporter):
yield row
@receiver(register_data_exporters, dispatch_uid="exporter_questions_exporter")
def register_data_exporter(sender, **kwargs):
return QuestionAnswerExporter
class QuestionView(EventPermissionRequiredMixin, ChartContainingView, DetailView):
model = Question
template_name = 'pretixcontrol/items/question.html'
permission = 'can_change_items'
template_name_field = 'question'
def get_answer_statistics(self):
opqs = QuestionFilterForm(self.request.GET, event=self.request.event).orderPositionQuerySet()
def get_answer_statistics(self, opqs: OrderPosition):
qs = QuestionAnswer.objects.filter(
question=self.object, orderposition__isnull=False,
)
@@ -879,9 +777,11 @@ class QuestionView(EventPermissionRequiredMixin, ChartContainingView, DetailView
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx['items'] = self.object.items.all()
stats = self.get_answer_statistics()
ctx['stats'], ctx['total'] = stats
ctx['form'] = QuestionFilterForm(data=self.request.GET, event=self.request.event)
if ctx['form'].is_valid():
opqs = ctx['form'].order_position_queryset()
stats = self.get_answer_statistics(opqs)
ctx['stats'], ctx['total'] = stats
return ctx
def get_object(self, queryset=None) -> Question: