forked from CGM_Public/pretix_original
Add identifier field to questions
This commit is contained in:
@@ -142,7 +142,7 @@ class InlineQuestionOptionSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = QuestionOption
|
||||
fields = ('id', 'answer')
|
||||
fields = ('id', 'identifier', 'answer')
|
||||
|
||||
|
||||
class QuestionSerializer(I18nAwareModelSerializer):
|
||||
@@ -151,7 +151,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Question
|
||||
fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position',
|
||||
'ask_during_checkin')
|
||||
'ask_during_checkin', 'identifier')
|
||||
|
||||
|
||||
class QuotaSerializer(I18nAwareModelSerializer):
|
||||
|
||||
@@ -29,10 +29,23 @@ class InvoiceAdddressSerializer(I18nAwareModelSerializer):
|
||||
'vat_id_validated', 'internal_reference')
|
||||
|
||||
|
||||
class AnswerQuestionIdentifierField(serializers.Field):
|
||||
def to_representation(self, instance: QuestionAnswer):
|
||||
return instance.question.identifier
|
||||
|
||||
|
||||
class AnswerQuestionOptionsIdentifierField(serializers.Field):
|
||||
def to_representation(self, instance: QuestionAnswer):
|
||||
return [o.identifier for o in instance.options.all()]
|
||||
|
||||
|
||||
class AnswerSerializer(I18nAwareModelSerializer):
|
||||
question_identifier = AnswerQuestionIdentifierField(source='*', read_only=True)
|
||||
option_identifiers = AnswerQuestionOptionsIdentifierField(source='*', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = QuestionAnswer
|
||||
fields = ('question', 'answer', 'options')
|
||||
fields = ('question', 'answer', 'question_identifier', 'options', 'option_identifiers')
|
||||
|
||||
|
||||
class CheckinSerializer(I18nAwareModelSerializer):
|
||||
|
||||
@@ -50,7 +50,7 @@ class OrderViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
def get_queryset(self):
|
||||
return self.request.event.orders.prefetch_related(
|
||||
'positions', 'positions__checkins', 'positions__item', 'positions__answers', 'positions__answers__options',
|
||||
'fees'
|
||||
'positions__answers__questions', 'fees'
|
||||
).select_related(
|
||||
'invoice_address'
|
||||
)
|
||||
@@ -234,7 +234,7 @@ class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
return OrderPosition.objects.filter(order__event=self.request.event).prefetch_related(
|
||||
'checkins', 'answers', 'answers__options'
|
||||
'checkins', 'answers', 'answers__options', 'answers__question'
|
||||
).select_related(
|
||||
'item', 'order', 'order__event', 'order__event__organizer'
|
||||
)
|
||||
|
||||
59
src/pretix/base/migrations/0085_auto_20180312_1119.py
Normal file
59
src/pretix/base/migrations/0085_auto_20180312_1119.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-03-12 11:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
|
||||
def set_identifiers(apps, schema_editor):
|
||||
Question = apps.get_model('pretixbase', 'Question')
|
||||
QuestionOption = apps.get_model('pretixbase', 'QuestionOption')
|
||||
|
||||
for q in Question.objects.select_related('event'):
|
||||
if not q.identifier:
|
||||
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
|
||||
while True:
|
||||
code = get_random_string(length=8, allowed_chars=charset)
|
||||
if not Question.objects.filter(event=q.event, identifier=code).exists():
|
||||
q.identifier = code
|
||||
q.save()
|
||||
break
|
||||
|
||||
for q in QuestionOption.objects.select_related('question', 'question__event'):
|
||||
if not q.identifier:
|
||||
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
|
||||
while True:
|
||||
code = get_random_string(length=8, allowed_chars=charset)
|
||||
if not QuestionOption.objects.filter(question__event=q.question.event, identifier=code).exists():
|
||||
q.identifier = code
|
||||
q.save()
|
||||
break
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0084_questionoption_position'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='identifier',
|
||||
field=models.CharField(default='', max_length=190),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='questionoption',
|
||||
name='identifier',
|
||||
field=models.CharField(default='', max_length=190),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='locale',
|
||||
field=models.CharField(choices=[('en', 'English'), ('de', 'German'), ('de-informal', 'German (informal)'), ('nl', 'Dutch'), ('da', 'Danish'), ('pt-br', 'Portuguese (Brazil)')], default='en', max_length=50, verbose_name='Language'),
|
||||
),
|
||||
migrations.RunPython(set_identifiers, migrations.RunPython.noop)
|
||||
]
|
||||
@@ -11,6 +11,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import F, Func, Q, Sum
|
||||
from django.utils import formats
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import is_naive, make_aware, now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
@@ -630,6 +631,8 @@ class Question(LoggedModel):
|
||||
:param items: A set of ``Items`` objects that this question should be applied to
|
||||
:param ask_during_checkin: Whether to ask this question during check-in instead of during check-out.
|
||||
:type ask_during_checkin: bool
|
||||
:param identifier: An arbitrary, internal identifier
|
||||
:type identifier: str
|
||||
"""
|
||||
TYPE_NUMBER = "N"
|
||||
TYPE_STRING = "S"
|
||||
@@ -661,6 +664,12 @@ class Question(LoggedModel):
|
||||
question = I18nTextField(
|
||||
verbose_name=_("Question")
|
||||
)
|
||||
identifier = models.CharField(
|
||||
max_length=190,
|
||||
verbose_name=_("Internal identifier"),
|
||||
help_text=_('You can enter any value here to make it easier to match the data with other sources. If you do '
|
||||
'not input one, we will generate one automatically.')
|
||||
)
|
||||
help_text = I18nTextField(
|
||||
verbose_name=_("Help text"),
|
||||
help_text=_("If the question needs to be explained or clarified, do it here!"),
|
||||
@@ -706,7 +715,18 @@ class Question(LoggedModel):
|
||||
if self.event:
|
||||
self.event.cache.clear()
|
||||
|
||||
def clean_identifier(self, code):
|
||||
if Question.objects.filter(event=self.event, identifier=code).exclude(pk=self.pk).exists():
|
||||
raise ValidationError(_('This identifier is already used for a different question.'))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.identifier:
|
||||
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
|
||||
while True:
|
||||
code = get_random_string(length=8, allowed_chars=charset)
|
||||
if not Question.objects.filter(event=self.event, identifier=code).exists():
|
||||
self.identifier = code
|
||||
break
|
||||
super().save(*args, **kwargs)
|
||||
if self.event:
|
||||
self.event.cache.clear()
|
||||
@@ -779,12 +799,23 @@ class Question(LoggedModel):
|
||||
|
||||
class QuestionOption(models.Model):
|
||||
question = models.ForeignKey('Question', related_name='options')
|
||||
identifier = models.CharField(max_length=190)
|
||||
answer = I18nCharField(verbose_name=_('Answer'))
|
||||
position = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.answer)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.identifier:
|
||||
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
|
||||
while True:
|
||||
code = get_random_string(length=8, allowed_chars=charset)
|
||||
if not QuestionOption.objects.filter(question__event=self.question.event, identifier=code).exists():
|
||||
self.identifier = code
|
||||
break
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Question option")
|
||||
verbose_name_plural = _("Question options")
|
||||
|
||||
@@ -41,6 +41,7 @@ class QuestionForm(I18nModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['items'].queryset = self.instance.event.items.all()
|
||||
self.fields['identifier'].required = False
|
||||
|
||||
class Meta:
|
||||
model = Question
|
||||
@@ -51,6 +52,7 @@ class QuestionForm(I18nModelForm):
|
||||
'type',
|
||||
'required',
|
||||
'ask_during_checkin',
|
||||
'identifier',
|
||||
'items'
|
||||
]
|
||||
widgets = {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
{% bootstrap_field form.question layout="control" %}
|
||||
{% bootstrap_field form.help_text layout="control" %}
|
||||
{% bootstrap_field form.type layout="control" %}
|
||||
{% bootstrap_field form.identifier layout="control" %}
|
||||
{% bootstrap_field form.ask_during_checkin layout="control" %}
|
||||
{% bootstrap_field form.required layout="control" %}
|
||||
</fieldset>
|
||||
@@ -54,10 +55,16 @@
|
||||
</div>
|
||||
<div class="row question-option-row">
|
||||
<div class="col-xs-10">
|
||||
<span class="text-muted">
|
||||
{% blocktrans trimmed with id=form.instance.identifier %}
|
||||
Answer option {{ id }}
|
||||
{% endblocktrans %}
|
||||
</span>
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.answer layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-xs-2 text-right">
|
||||
<span> </span><br>
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
@@ -79,9 +86,13 @@
|
||||
</div>
|
||||
<div class="row question-option-row">
|
||||
<div class="col-xs-10">
|
||||
<span class="text-muted">
|
||||
{% trans "New answer option" %}
|
||||
</span>
|
||||
{% bootstrap_field formset.empty_form.answer layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-xs-2 text-right">
|
||||
<span> </span><br>
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
|
||||
@@ -1187,9 +1187,9 @@ def test_quota_availability(token_client, organizer, event, quota, item):
|
||||
|
||||
@pytest.fixture
|
||||
def question(event, item):
|
||||
q = event.questions.create(question="T-Shirt size", type="C")
|
||||
q = event.questions.create(question="T-Shirt size", type="C", identifier="ABC")
|
||||
q.items.add(item)
|
||||
q.options.create(answer="XL")
|
||||
q.options.create(answer="XL", identifier="FOO")
|
||||
return q
|
||||
|
||||
|
||||
@@ -1199,10 +1199,12 @@ TEST_QUESTION_RES = {
|
||||
"required": False,
|
||||
"items": [],
|
||||
"ask_during_checkin": False,
|
||||
"identifier": "ABC",
|
||||
"position": 0,
|
||||
"options": [
|
||||
{
|
||||
"id": 0,
|
||||
"identifier": "FOO",
|
||||
"answer": {"en": "XL"}
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user