Fix #190 and #472 -- Change of questions within pretix control

This commit is contained in:
Raphael Michel
2018-01-26 12:43:47 +01:00
parent 083c94403b
commit 1ee6e31538
19 changed files with 732 additions and 546 deletions

View File

@@ -4,7 +4,7 @@ from decimal import Decimal
from django.contrib import messages
from django.db import transaction
from django.db.models import Prefetch, Sum
from django.db.models import Sum
from django.http import FileResponse, Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
@@ -14,11 +14,9 @@ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import TemplateView, View
from pretix.base.models import (
CachedTicket, Invoice, Order, OrderPosition, Question, QuestionOption,
)
from pretix.base.models import CachedTicket, Invoice, Order, OrderPosition
from pretix.base.models.orders import (
CachedCombinedTicket, InvoiceAddress, OrderFee, QuestionAnswer,
CachedCombinedTicket, OrderFee, QuestionAnswer,
)
from pretix.base.payment import PaymentException
from pretix.base.services.invoices import (
@@ -29,12 +27,12 @@ from pretix.base.services.tickets import (
get_cachedticket_for_order, get_cachedticket_for_position,
)
from pretix.base.signals import allow_ticket_download, register_ticket_outputs
from pretix.base.views.mixins import OrderQuestionsViewMixin
from pretix.helpers.safedownload import check_token
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
from pretix.presale.forms.checkout import InvoiceAddressForm
from pretix.presale.forms.checkout import InvoiceAddressForm, QuestionsForm
from pretix.presale.views import CartMixin, EventViewMixin
from pretix.presale.views.async import AsyncAction
from pretix.presale.views.questions import QuestionsViewMixin
from pretix.presale.views.robots import NoSearchIndexViewMixin
@@ -425,48 +423,11 @@ class OrderInvoiceCreate(EventViewMixin, OrderDetailMixin, View):
@method_decorator(xframe_options_exempt, 'dispatch')
class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, TemplateView):
class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, TemplateView):
form_class = QuestionsForm
invoice_form_class = InvoiceAddressForm
template_name = "pretixpresale/event/order_modify.html"
@cached_property
def _positions_for_questions(self):
return self.positions
@cached_property
def positions(self):
return list(self.order.positions.select_related(
'item', 'variation'
).prefetch_related(
Prefetch('answers',
QuestionAnswer.objects.prefetch_related('options'),
to_attr='answerlist'),
Prefetch('item__questions',
Question.objects.filter(ask_during_checkin=False).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'
)))
),
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 invoice_form(self):
return InvoiceAddressForm(data=self.request.POST if self.request.method == "POST" else None,
event=self.request.event,
instance=self.invoice_address, validate_vat_id=False)
def post(self, request, *args, **kwargs):
failed = not self.save() or not self.invoice_form.is_valid()
if failed:
@@ -497,13 +458,6 @@ class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, Template
return redirect(self.get_order_url())
return super().dispatch(request, *args, **kwargs)
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
return ctx
@method_decorator(xframe_options_exempt, 'dispatch')
class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView):

View File

@@ -1,29 +1,14 @@
import json
from collections import OrderedDict
from django import forms
from django.core.files.uploadedfile import UploadedFile
from django.db.models import Prefetch
from django.utils.functional import cached_property
from pretix.base.models import (
CartPosition, OrderPosition, Question, QuestionAnswer, QuestionOption,
)
from pretix.base.models import Question, QuestionAnswer, QuestionOption
from pretix.base.views.mixins import BaseQuestionsViewMixin
from pretix.presale.forms.checkout import QuestionsForm
from pretix.presale.views import get_cart
class QuestionsViewMixin:
@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
class QuestionsViewMixin(BaseQuestionsViewMixin):
form_class = QuestionsForm
@cached_property
def _positions_for_questions(self):
@@ -48,112 +33,3 @@ class QuestionsViewMixin:
to_attr='questions_to_ask')
)
return sorted(list(cart), key=self._keyfunc)
@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 = QuestionsForm(event=self.request.event,
prefix=cr.id,
cartpos=cartpos,
orderpos=orderpos,
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
if len(form.fields) > 0:
formlist.append(form)
return formlist
@cached_property
def formdict(self):
storage = OrderedDict()
for f in self.forms:
pos = f.cartpos or f.orderpos
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':
form.pos.attendee_name = v if v != '' else None
form.pos.save()
elif k == 'attendee_email':
form.pos.attendee_email = v if v != '' else None
form.pos.save()
elif k.startswith('question_') and v is not None:
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):
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 != '':
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(update_fields=['meta_info'])
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