Add signal question_form_fields

This commit is contained in:
Raphael Michel
2017-07-02 19:45:26 +02:00
parent 45c17ba949
commit 1fc3307d22
11 changed files with 154 additions and 7 deletions

File diff suppressed because one or more lines are too long

View File

@@ -405,6 +405,8 @@ class AbstractPosition(models.Model):
:type attendee_email: str
:param voucher: A voucher that has been applied to this sale
:type voucher: Voucher
:param meta_info: Additional meta information on the position, JSON-encoded.
:type meta_info: str
"""
item = models.ForeignKey(
Item,
@@ -438,10 +440,21 @@ class AbstractPosition(models.Model):
addon_to = models.ForeignKey(
'self', null=True, blank=True, on_delete=models.CASCADE, related_name='addons'
)
meta_info = models.TextField(
verbose_name=_("Meta information"),
null=True, blank=True
)
class Meta:
abstract = True
@property
def meta_info_data(self):
if self.meta_info:
return json.loads(self.meta_info)
else:
return {}
def cache_answers(self):
"""
Creates two properties on the object.

View File

@@ -201,6 +201,11 @@
<dd>{% if q.answer %}{{ q.answer|linebreaksbr }}{% else %}
<em>{% trans "not answered" %}</em>{% endif %}</dd>
{% endfor %}
{% for q in line.additional_fields %}
<dt>{{ q.question }}</dt>
<dd>{% if q.answer %}{{ q.answer|linebreaksbr }}{% else %}
<em>{% trans "not answered" %}</em>{% endif %}</dd>
{% endfor %}
</dl>
{% endif %}
</div>

View File

@@ -36,6 +36,7 @@ from pretix.control.forms.orders import (
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.presale.signals import question_form_fields
class OrderList(EventPermissionRequiredMixin, ListView):
@@ -141,12 +142,24 @@ class OrderDetail(OrderView):
positions = []
for p in cartpos:
responses = question_form_fields.send(sender=self.request.event, position=p)
p.additional_fields = []
data = p.meta_info_data
for r, response in sorted(responses, key=lambda r: str(r[0])):
for key, value in response.items():
p.additional_fields.append({
'answer': data.get('question_form_data', {}).get(key),
'question': value.label
})
p.has_questions = (
p.additional_fields or
(p.item.admission and self.request.event.settings.attendee_names_asked) or
(p.item.admission and self.request.event.settings.attendee_emails_asked) or
p.item.questions.all()
)
p.cache_answers()
positions.append(p)
positions.sort(key=lambda p: p.sort_key)

View File

@@ -19,7 +19,7 @@ from pretix.presale.forms.checkout import (
)
from pretix.presale.signals import (
checkout_confirm_messages, checkout_flow_steps, contact_form_fields,
order_meta_from_request,
order_meta_from_request, question_form_fields,
)
from pretix.presale.views import CartMixin, get_cart, get_cart_total
from pretix.presale.views.async import AsyncAction
@@ -330,6 +330,13 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
if warn:
messages.warning(request, _('Please fill in answers to all required questions.'))
return False
responses = question_form_fields.send(sender=self.request.event, position=cp)
form_data = cp.meta_info_data.get('question_form_data', {})
for r, response in sorted(responses, key=lambda r: str(r[0])):
for key, value in response.items():
if value.required and not form_data.get(key):
return False
return True
def get_context_data(self, **kwargs):

View File

@@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _
from pretix.base.models import ItemVariation, Question
from pretix.base.models.orders import InvoiceAddress
from pretix.base.templatetags.rich_text import rich_text
from pretix.presale.signals import contact_form_fields
from pretix.presale.signals import contact_form_fields, question_form_fields
class ContactForm(forms.Form):
@@ -89,7 +89,8 @@ class QuestionsForm(forms.Form):
"""
cartpos = self.cartpos = kwargs.pop('cartpos', None)
orderpos = self.orderpos = kwargs.pop('orderpos', None)
item = cartpos.item if cartpos else orderpos.item
pos = cartpos or orderpos
item = pos.item
questions = list(item.questions.all())
event = kwargs.pop('event')
@@ -174,6 +175,14 @@ class QuestionsForm(forms.Form):
field.answer = answers[0]
self.fields['question_%s' % q.id] = field
responses = question_form_fields.send(sender=event, position=pos)
data = pos.meta_info_data
for r, response in sorted(responses, key=lambda r: str(r[0])):
for key, value in response.items():
# We need to be this explicit, since OrderedDict.update does not retain ordering
self.fields[key] = value
value.initial = data.get('question_form_data', {}).get(key)
class AddOnRadioSelect(forms.RadioSelect):
option_template_name = 'pretixpresale/forms/addon_choice_option.html'

View File

@@ -76,7 +76,23 @@ contact_form_fields = EventPluginSignal(
This signals allows you to add form fields to the contact form that is presented during checkout
and by default only asks for the email address. You are supposed to return a dictionary of
form fields with globally unique keys. The validated form results will be saved into the
``contact_form_data`` entry of the order metadata dictionary.
``contact_form_data`` entry of the order's meta_info dictionary.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
question_form_fields = EventPluginSignal(
providing_args=["position"]
)
"""
This signals allows you to add form fields to the questions form that is presented during checkout
and by default asks for the questions configured in the backend. You are supposed to return a dictionary
of form fields with globally unique keys. The validated form results will be saved into the
``question_form_data`` entry of the position's meta_info dictionary.
The ``position`` keyword argument will contain either a ``CartPosition`` object or an ``OrderPosition``
object, depending on whether the form is called as part of the order checkout or for changing an order
later.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""

View File

@@ -17,11 +17,11 @@
{% if line.has_questions %}
<dl>
{% if line.item.admission and event.settings.attendee_names_asked%}
{% if line.item.admission and event.settings.attendee_names_asked %}
<dt>{% trans "Attendee name" %}</dt>
<dd>{% if line.attendee_name %}{{ line.attendee_name }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}</dd>
{% endif %}
{% if line.item.admission and event.settings.attendee_emails_asked%}
{% if line.item.admission and event.settings.attendee_emails_asked %}
<dt>{% trans "Attendee email" %}</dt>
<dd>{% if line.attendee_email %}{{ line.attendee_email }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}</dd>
{% endif %}
@@ -29,6 +29,10 @@
<dt>{{ q.question }}</dt>
<dd>{% if q.answer %}{{ q.answer|linebreaksbr }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}</dd>
{% endfor %}
{% for q in line.additional_answers %}
<dt>{{ q.question }}</dt>
<dd>{% if q.answer %}{{ q.answer|linebreaksbr }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}</dd>
{% endfor %}
</dl>
{% endif %}
</div>

View File

@@ -1,3 +1,4 @@
from collections import defaultdict
from datetime import timedelta
from decimal import Decimal
from itertools import groupby
@@ -8,6 +9,7 @@ from django.utils.timezone import now
from pretix.base.decimal import round_decimal
from pretix.base.models import CartPosition, OrderPosition
from pretix.presale.signals import question_form_fields
class CartMixin:
@@ -38,6 +40,17 @@ class CartMixin:
lcp = list(cartpos)
has_addons = {cp.addon_to.pk for cp in lcp if cp.addon_to}
pos_additional_fields = defaultdict(list)
for cp in lcp:
responses = question_form_fields.send(sender=self.request.event, position=cp)
data = cp.meta_info_data
for r, response in sorted(responses, key=lambda r: str(r[0])):
for key, value in response.items():
pos_additional_fields[cp.pk].append({
'answer': data.get('question_form_data', {}).get(key),
'question': value.label
})
# Group items of the same variation
# We do this by list manipulations instead of a GROUP BY query, as
# Django is unable to join related models in a .values() query
@@ -56,6 +69,7 @@ class CartMixin:
has_attendee_data = pos.item.admission and (
self.request.event.settings.attendee_names_asked
or self.request.event.settings.attendee_emails_asked
or pos_additional_fields.get(pos.pk)
)
addon_penalty = 1 if pos.addon_to else 0
if downloads or pos.pk in has_addons or pos.addon_to:
@@ -75,6 +89,7 @@ class CartMixin:
group.has_questions = answers and k[0] != ""
if answers:
group.cache_answers()
group.additional_answers = pos_additional_fields.get(group.pk)
positions.append(group)
total = sum(p.total for p in positions)

View File

@@ -1,3 +1,4 @@
import json
from collections import defaultdict
from django import forms
@@ -58,6 +59,7 @@ class QuestionsViewMixin:
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
@@ -89,6 +91,16 @@ class QuestionsViewMixin:
)
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):