diff --git a/src/pretix/base/i18n.py b/src/pretix/base/i18n.py index d131eab435..0a649221f4 100644 --- a/src/pretix/base/i18n.py +++ b/src/pretix/base/i18n.py @@ -225,10 +225,10 @@ class I18nFormField(forms.MultiValueField): 'max_length': kwargs.pop('max_length', None), } self.langcodes = kwargs.pop('langcodes', [l[0] for l in settings.LANGUAGES]) - self.one_required = kwargs['required'] + self.one_required = kwargs.get('required', True) kwargs['required'] = False kwargs['widget'] = kwargs['widget']( - langcodes=self.langcodes, field=self + langcodes=self.langcodes, field=self, **kwargs.pop('widget_kwargs', {}) ) defaults.update(**kwargs) for lngcode in self.langcodes: diff --git a/src/pretix/base/migrations/0018_auto_20160326_1104.py b/src/pretix/base/migrations/0018_auto_20160326_1104.py new file mode 100644 index 0000000000..f0be799575 --- /dev/null +++ b/src/pretix/base/migrations/0018_auto_20160326_1104.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-03-26 11:04 +from __future__ import unicode_literals + +import django.db.models.deletion +from django.db import migrations, models + +import pretix.base.i18n + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0017_auto_20160324_1615'), + ] + + operations = [ + migrations.CreateModel( + name='QuestionOption', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('answer', pretix.base.i18n.I18nCharField(verbose_name='Answer')), + ], + ), + migrations.AlterField( + model_name='item', + name='free_price', + field=models.BooleanField(default=False, help_text='If this option is active, your users can choose the price themselves. The price configured above is then interpreted as the minimum price a user has to enter. You could use this e.g. to collect additional donations for your event.', verbose_name='Free price input'), + ), + migrations.AlterField( + model_name='question', + name='type', + field=models.CharField(choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No'), ('C', 'Choose one from a list'), ('M', 'Choose multiple from a list')], max_length=5, verbose_name='Question type'), + ), + migrations.AddField( + model_name='questionoption', + name='question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Question'), + ), + migrations.AddField( + model_name='questionanswer', + name='option', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='pretixbase.QuestionOption'), + ), + ] diff --git a/src/pretix/base/migrations/0019_auto_20160326_1139.py b/src/pretix/base/migrations/0019_auto_20160326_1139.py new file mode 100644 index 0000000000..2d572976dc --- /dev/null +++ b/src/pretix/base/migrations/0019_auto_20160326_1139.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-03-26 11:39 +from __future__ import unicode_literals + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0018_auto_20160326_1104'), + ] + + operations = [ + migrations.RemoveField( + model_name='questionanswer', + name='option', + ), + migrations.AddField( + model_name='questionanswer', + name='options', + field=models.ManyToManyField(blank=True, null=True, related_name='answers', to='pretixbase.QuestionOption'), + ), + migrations.AlterField( + model_name='questionoption', + name='question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='pretixbase.Question'), + ), + ] diff --git a/src/pretix/base/models/__init__.py b/src/pretix/base/models/__init__.py index 18981e26a3..9ccd18d3a9 100644 --- a/src/pretix/base/models/__init__.py +++ b/src/pretix/base/models/__init__.py @@ -3,7 +3,8 @@ from .base import CachedFile, cachedfile_name from .event import Event, EventLock, EventPermission, EventSetting from .invoices import Invoice, InvoiceLine, invoice_filename from .items import ( - Item, ItemCategory, ItemVariation, Question, Quota, itempicture_upload_to, + Item, ItemCategory, ItemVariation, Question, QuestionOption, Quota, + itempicture_upload_to, ) from .log import LogEntry from .orders import ( @@ -19,5 +20,5 @@ __all__ = [ 'BaseRestriction', 'Quota', 'Order', 'CachedTicket', 'QuestionAnswer', 'AbstractPosition', 'OrderPosition', 'CartPosition', 'EventSetting', 'OrganizerSetting', 'EventLock', 'cachedfile_name', 'itempicture_upload_to', 'generate_secret', 'Voucher', 'LogEntry', 'InvoiceAddress', 'generate_position_secret', 'InvoiceLine', - 'Invoice', 'invoice_filename' + 'Invoice', 'invoice_filename', 'QuestionOption' ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 6e2bc7aad1..1d74681d6f 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -302,6 +302,7 @@ class Question(LoggedModel): * a one-line string (``TYPE_STRING``) * a multi-line string (``TYPE_TEXT``) * a boolean (``TYPE_BOOLEAN``) + * a multiple choice option (``TYPE_CHOICE`` and ``TYPE_CHOICE_MULTIPLE``) :param event: The event this question belongs to :type event: Event @@ -317,11 +318,15 @@ class Question(LoggedModel): TYPE_STRING = "S" TYPE_TEXT = "T" TYPE_BOOLEAN = "B" + TYPE_CHOICE = "C" + TYPE_CHOICE_MULTIPLE = "M" TYPE_CHOICES = ( (TYPE_NUMBER, _("Number")), (TYPE_STRING, _("Text (one line)")), (TYPE_TEXT, _("Multiline text")), (TYPE_BOOLEAN, _("Yes/No")), + (TYPE_CHOICE, _("Choose one from a list")), + (TYPE_CHOICE_MULTIPLE, _("Choose multiple from a list")) ) event = models.ForeignKey( @@ -366,6 +371,14 @@ class Question(LoggedModel): self.event.get_cache().clear() +class QuestionOption(models.Model): + question = models.ForeignKey('Question', related_name='options') + answer = I18nCharField(verbose_name=_('Answer')) + + def __str__(self): + return str(self.answer) + + class Quota(LoggedModel): """ A quota is a "pool of tickets". It is there to limit the number of items diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index b7360998da..5f41bf2aca 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -12,7 +12,7 @@ from typing import List, Union from ..decimal import round_decimal from .base import CachedFile, LoggedModel from .event import Event -from .items import Item, ItemVariation, Question, Quota +from .items import Item, ItemVariation, Question, QuestionOption, Quota def generate_secret(): @@ -286,6 +286,9 @@ class QuestionAnswer(models.Model): question = models.ForeignKey( Question, related_name='answers' ) + options = models.ManyToManyField( + QuestionOption, related_name='answers', blank=True + ) answer = models.TextField() def __str__(self): diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 95d5986004..ab7a4084fb 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -5,8 +5,9 @@ from django.forms import BooleanField, ModelMultipleChoiceField from django.utils.translation import ugettext as __, ugettext_lazy as _ from pretix.base.forms import I18nModelForm +from pretix.base.i18n import I18nFormField, I18nTextarea from pretix.base.models import ( - Item, ItemCategory, ItemVariation, Question, Quota, + Item, ItemCategory, ItemVariation, Question, QuestionOption, Quota, ) @@ -20,6 +21,12 @@ class CategoryForm(I18nModelForm): class QuestionForm(I18nModelForm): + question = I18nFormField( + label=_("Question"), + widget_kwargs={'attrs': {'rows': 5}}, + widget=I18nTextarea + ) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['items'].queryset = self.instance.event.items.all() @@ -34,10 +41,20 @@ class QuestionForm(I18nModelForm): 'items' ] widgets = { - 'items': forms.CheckboxSelectMultiple + 'items': forms.CheckboxSelectMultiple, } +class QuestionOptionForm(I18nModelForm): + + class Meta: + model = QuestionOption + localized_fields = '__all__' + fields = [ + 'answer', + ] + + class QuotaForm(I18nModelForm): def __init__(self, **kwargs): diff --git a/src/pretix/control/templates/pretixcontrol/items/question.html b/src/pretix/control/templates/pretixcontrol/items/question.html index 061a38dada..c849f32c0c 100644 --- a/src/pretix/control/templates/pretixcontrol/items/question.html +++ b/src/pretix/control/templates/pretixcontrol/items/question.html @@ -1,6 +1,7 @@ {% extends "pretixcontrol/items/base.html" %} {% load i18n %} {% load bootstrap3 %} +{% load formset_tags %} {% block title %}{% trans "Question" %}{% endblock %} {% block inside %}

{% trans "Question" %}

@@ -23,6 +24,59 @@ accepted. If you want to allow both options, do not make this field required. {% endblocktrans %} +
+ {% trans "Answer options" %} + +
+ {{ formset.management_form }} + {% bootstrap_formset_errors formset %} +
+ {% for form in formset %} +
+
+ {{ form.id }} + {% bootstrap_field form.DELETE form_group_class="" layout="inline" %} +
+
+
+ {% bootstrap_form_errors form %} + {% bootstrap_field form.answer layout='inline' form_group_class="" %} +
+
+ +
+
+
+ {% endfor %} +
+ +

+ +

+
+