Files
pretix_cgo/src/pretix/base/views/mixins.py
T
2020-10-26 09:30:16 +01:00

286 lines
12 KiB
Python

import json
from collections import OrderedDict
from decimal import Decimal
from django import forms
from django.core.files.uploadedfile import UploadedFile
from django.db.models import Prefetch, QuerySet
from django.utils.functional import cached_property
from pretix.base.forms.questions import (
BaseInvoiceAddressForm, BaseInvoiceNameForm, BaseQuestionsForm,
)
from pretix.base.models import (
CartPosition, InvoiceAddress, OrderPosition, Question, QuestionAnswer,
QuestionOption,
)
from pretix.presale.signals import contact_form_fields_overrides
class BaseQuestionsViewMixin:
form_class = BaseQuestionsForm
all_optional = False
@staticmethod
def _keyfunc(pos):
# Sort addons after the item they are an addon to
if isinstance(pos, OrderPosition):
i = pos.addon_to.positionid if pos.addon_to else pos.positionid
else:
i = pos.addon_to.pk if pos.addon_to else pos.pk
addon_penalty = 1 if pos.addon_to else 0
return i, addon_penalty, pos.pk
@cached_property
def _positions_for_questions(self):
raise NotImplementedError()
def get_question_override_sets(self, position):
return []
@cached_property
def forms(self):
"""
A list of forms with one form for each cart position that has questions
the user can answer. All forms have a custom prefix, so that they can all be
submitted at once.
"""
formlist = []
for cr in self._positions_for_questions:
cartpos = cr if isinstance(cr, CartPosition) else None
orderpos = cr if isinstance(cr, OrderPosition) else None
form = self.form_class(event=self.request.event,
prefix=cr.id,
cartpos=cartpos,
orderpos=orderpos,
all_optional=self.all_optional,
data=(self.request.POST if self.request.method == 'POST' else None),
files=(self.request.FILES if self.request.method == 'POST' else None))
form.pos = cartpos or orderpos
form.show_copy_answers_to_addon_button = form.pos.addon_to and (
set(form.pos.addon_to.item.questions.all()) & set(form.pos.item.questions.all()) or
(form.pos.addon_to.item.admission and form.pos.item.admission and (
self.request.event.settings.attendee_names_asked or
self.request.event.settings.attendee_emails_asked or
self.request.event.settings.attendee_company_asked or
self.request.event.settings.attendee_addresses_asked
))
)
override_sets = self.get_question_override_sets(cr)
for overrides in override_sets:
for question_name, question_field in form.fields.items():
if hasattr(question_field, 'question'):
if question_field.question.identifier in overrides:
if 'initial' in overrides[question_field.question.identifier]:
question_field.initial = overrides[question_field.question.identifier]['initial']
if 'disabled' in overrides[question_field.question.identifier]:
question_field.disabled = overrides[question_field.question.identifier]['disabled']
else:
if question_name in overrides:
if 'initial' in overrides[question_name]:
question_field.initial = overrides[question_name]['initial']
if 'disabled' in overrides[question_name]:
question_field.disabled = overrides[question_name]['disabled']
if len(form.fields) > 0:
formlist.append(form)
return formlist
@cached_property
def formdict(self):
storage = OrderedDict()
for f in self.forms:
pos = f.pos
if pos.addon_to_id:
if pos.addon_to not in storage:
storage[pos.addon_to] = []
storage[pos.addon_to].append(f)
else:
if pos not in storage:
storage[pos] = []
storage[pos].append(f)
return storage
def save(self):
failed = False
for form in self.forms:
meta_info = form.pos.meta_info_data
# Every form represents a CartPosition or OrderPosition with questions attached
if not form.is_valid():
failed = True
else:
# This form was correctly filled, so we store the data as
# answers to the questions / in the CartPosition object
for k, v in form.cleaned_data.items():
if k == 'attendee_name_parts':
form.pos.attendee_name_parts = v if v else None
elif k == 'attendee_email':
form.pos.attendee_email = v if v != '' else None
elif k == 'company':
form.pos.company = v if v != '' else None
elif k == 'street':
form.pos.street = v if v != '' else None
elif k == 'zipcode':
form.pos.zipcode = v if v != '' else None
elif k == 'city':
form.pos.city = v if v != '' else None
elif k == 'country':
form.pos.country = v if v != '' else None
elif k == 'state':
form.pos.state = v if v != '' else None
elif k.startswith('question_'):
field = form.fields[k]
if hasattr(field, 'answer'):
# We already have a cached answer object, so we don't
# have to create a new one
if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) \
or (isinstance(v, QuerySet) and not v.exists()):
if field.answer.file:
field.answer.file.delete()
field.answer.delete()
else:
self._save_to_answer(field, field.answer, v)
field.answer.save()
elif v != '' and v is not None:
answer = QuestionAnswer(
cartposition=(form.pos if isinstance(form.pos, CartPosition) else None),
orderposition=(form.pos if isinstance(form.pos, OrderPosition) else None),
question=field.question,
)
self._save_to_answer(field, answer, v)
answer.save()
else:
meta_info.setdefault('question_form_data', {})
if v is None:
if k in meta_info['question_form_data']:
del meta_info['question_form_data'][k]
else:
meta_info['question_form_data'][k] = v
form.pos.meta_info = json.dumps(meta_info)
form.pos.save()
return not failed
def _save_to_answer(self, field, answer, value):
if isinstance(field, forms.ModelMultipleChoiceField):
answstr = ", ".join([str(o) for o in value])
if not answer.pk:
answer.save()
else:
answer.options.clear()
answer.answer = answstr
answer.options.add(*value)
elif isinstance(field, forms.ModelChoiceField):
if not answer.pk:
answer.save()
else:
answer.options.clear()
answer.options.add(value)
answer.answer = value.answer
elif isinstance(field, forms.FileField):
if isinstance(value, UploadedFile):
answer.file.save(value.name, value)
answer.answer = 'file://' + value.name
else:
answer.answer = value
class OrderQuestionsViewMixin(BaseQuestionsViewMixin):
invoice_form_class = BaseInvoiceAddressForm
invoice_name_form_class = BaseInvoiceNameForm
only_user_visible = True
all_optional = False
@cached_property
def _positions_for_questions(self):
return self.positions
@cached_property
def positions(self):
qqs = self.request.event.questions.all()
if self.only_user_visible:
qqs = qqs.filter(ask_during_checkin=False, hidden=False)
return list(self.order.positions.select_related(
'item', 'variation'
).prefetch_related(
Prefetch('answers',
QuestionAnswer.objects.prefetch_related('options'),
to_attr='answerlist'),
Prefetch('item__questions',
qqs.prefetch_related(
Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch(
# This prefetch statement is utter bullshit, but it actually prevents Django from doing
# a lot of queries since ModelChoiceIterator stops trying to be clever once we have
# a prefetch lookup on this query...
'question',
Question.objects.none(),
to_attr='dummy'
)))
).select_related('dependency_question'),
to_attr='questions_to_ask')
))
@cached_property
def invoice_address(self):
try:
return self.order.invoice_address
except InvoiceAddress.DoesNotExist:
return InvoiceAddress(order=self.order)
@cached_property
def address_asked(self):
return self.request.event.settings.invoice_address_asked and (
self.order.total != Decimal('0.00') or not self.request.event.settings.invoice_address_not_asked_free
)
@cached_property
def _contact_override_sets(self):
override_sets = [
resp for recv, resp in contact_form_fields_overrides.send(
self.request.event,
request=self.request,
order=self.order,
)
]
for override in override_sets:
for k in override:
# We don't want initial values to be modified, they should come from the order directly
override[k].pop('initial', None)
return override_sets
@cached_property
def invoice_form(self):
if not self.address_asked and self.request.event.settings.invoice_name_required:
f = self.invoice_name_form_class(
data=self.request.POST if self.request.method == "POST" else None,
event=self.request.event,
instance=self.invoice_address, validate_vat_id=False,
all_optional=self.all_optional
)
elif self.address_asked:
f = self.invoice_form_class(
data=self.request.POST if self.request.method == "POST" else None,
event=self.request.event,
instance=self.invoice_address, validate_vat_id=False,
all_optional=self.all_optional,
)
else:
f = forms.Form(data=self.request.POST if self.request.method == "POST" else None)
override_sets = self._contact_override_sets
for overrides in override_sets:
for fname, val in overrides.items():
if 'disabled' in val and fname in f.fields:
f.fields[fname].disabled = val['disabled']
return f
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['order'] = self.order
ctx['formgroups'] = self.formdict.items()
ctx['invoice_form'] = self.invoice_form
ctx['invoice_address_asked'] = self.address_asked
return ctx