From 07d8a3d76556feb3e883d717bf94fe2a80e3eb89 Mon Sep 17 00:00:00 2001 From: Felix Rindt Date: Sat, 3 Mar 2018 20:36:30 +0100 Subject: [PATCH] Fix #774 -- Make question options sortable (#786) * add position field * add question option sorting logic * add meta class to question option for sorting * regenerate migration * add template content and view mechanics * Rename migration after rebase & update dependency --- .../0084_questionoption_position.py | 41 +++++++++++++++++++ src/pretix/base/models/items.py | 13 ++++-- .../pretixcontrol/items/question_edit.html | 10 +++++ src/pretix/control/views/item.py | 31 ++++++-------- src/requirements/production.txt | 2 +- src/setup.py | 2 +- 6 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 src/pretix/base/migrations/0084_questionoption_position.py diff --git a/src/pretix/base/migrations/0084_questionoption_position.py b/src/pretix/base/migrations/0084_questionoption_position.py new file mode 100644 index 0000000000..6ffe8950af --- /dev/null +++ b/src/pretix/base/migrations/0084_questionoption_position.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.9 on 2018-03-03 16:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +def set_position(apps, schema_editor): + Question = apps.get_model('pretixbase', 'Question') + for q in Question.objects.all(): + for i, option in enumerate(q.options.all()): + option.position = i + option.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0083_auto_20180228_2102'), + ] + + operations = [ + migrations.AlterModelOptions( + name='questionoption', + options={'ordering': ('position', 'id'), 'verbose_name': 'Question option', 'verbose_name_plural': 'Question options'}, + ), + migrations.AddField( + model_name='questionoption', + name='position', + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name='question', + name='position', + field=models.PositiveIntegerField(default=0, verbose_name='Position'), + ), + migrations.RunPython( + set_position, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index e7828c2e8a..39761ee4a6 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -682,8 +682,9 @@ class Question(LoggedModel): blank=True, help_text=_('This question will be asked to buyers of the selected products') ) - position = models.IntegerField( - default=0 + position = models.PositiveIntegerField( + default=0, + verbose_name=_("Position") ) ask_during_checkin = models.BooleanField( verbose_name=_('Ask during check-in instead of in the ticket buying process'), @@ -779,10 +780,16 @@ class Question(LoggedModel): class QuestionOption(models.Model): question = models.ForeignKey('Question', related_name='options') answer = I18nCharField(verbose_name=_('Answer')) + position = models.IntegerField(default=0) def __str__(self): return str(self.answer) + class Meta: + verbose_name = _("Question option") + verbose_name_plural = _("Question options") + ordering = ('position', 'id') + class Quota(LoggedModel): """ @@ -799,7 +806,7 @@ class Quota(LoggedModel): Please read the documentation section on quotas carefully before doing anything with quotas. This might confuse you otherwise. - http://docs.pretix.eu/en/latest/development/concepts.html#restriction-by-number + https://docs.pretix.eu/en/latest/development/concepts.html#quotas The AVAILABILITY_* constants represent various states of a quota allowing its items/variations to be up for sale. diff --git a/src/pretix/control/templates/pretixcontrol/items/question_edit.html b/src/pretix/control/templates/pretixcontrol/items/question_edit.html index e4cdfd0a6e..b963a079d4 100644 --- a/src/pretix/control/templates/pretixcontrol/items/question_edit.html +++ b/src/pretix/control/templates/pretixcontrol/items/question_edit.html @@ -50,6 +50,7 @@
{{ form.id }} {% bootstrap_field form.DELETE form_group_class="" layout="inline" %} + {% bootstrap_field form.ORDER form_group_class="" layout="inline" %}
@@ -57,6 +58,10 @@ {% bootstrap_field form.answer layout='inline' form_group_class="" %}
+ +
@@ -70,12 +75,17 @@
{{ formset.empty_form.id }} {% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %} + {% bootstrap_field formset.empty_form.ORDER form_group_class="" layout="inline" %}
{% bootstrap_field formset.empty_form.answer layout='inline' form_group_class="" %}
+ +
diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py index bd887bb10f..5353f4592a 100644 --- a/src/pretix/control/views/item.py +++ b/src/pretix/control/views/item.py @@ -338,7 +338,7 @@ class QuestionMixin: formsetclass = inlineformset_factory( Question, QuestionOption, form=QuestionOptionForm, formset=I18nFormSet, - can_order=False, can_delete=True, extra=0 + can_order=True, can_delete=True, extra=0 ) return formsetclass(self.request.POST if self.request.method == "POST" else None, queryset=(QuestionOption.objects.filter(question=self.object) @@ -358,30 +358,25 @@ class QuestionMixin: ) form.instance.delete() form.instance.pk = None - elif form.has_changed(): - form.instance.question = obj - form.save() + + forms = self.formset.ordered_forms + [ + ef for ef in self.formset.extra_forms + if ef not in self.formset.ordered_forms and ef not in self.formset.deleted_forms + ] + for i, form in enumerate(forms): + form.instance.position = i + form.instance.question = obj + created = not form.instance.pk + form.save() + if form.has_changed(): change_data = {k: form.cleaned_data.get(k) for k in form.changed_data} change_data['id'] = form.instance.pk obj.log_action( + 'pretix.event.question.option.added' if created else 'pretix.event.question.option.changed', user=self.request.user, data=change_data ) - for form in self.formset.extra_forms: - if not form.has_changed(): - continue - if self.formset._should_delete_form(form): - continue - form.instance.question = obj - form.save() - change_data = {k: form.cleaned_data.get(k) for k in form.changed_data} - change_data['id'] = form.instance.pk - obj.log_action( - 'pretix.event.question.option.added', - user=self.request.user, data=change_data - ) - return True return False diff --git a/src/requirements/production.txt b/src/requirements/production.txt index 3f870c381e..5dd5b869f3 100644 --- a/src/requirements/production.txt +++ b/src/requirements/production.txt @@ -4,7 +4,7 @@ djangorestframework==3.6.* python-dateutil pytz django-bootstrap3==8.2.* -django-formset-js-improved==0.5.0.1 +django-formset-js-improved==0.5.0.2 django-compressor==2.1.1 django-hierarkey==1.0.*,>=1.0.3 django-filter==1.0.* diff --git a/src/setup.py b/src/setup.py index 3714842deb..3f40eda78f 100644 --- a/src/setup.py +++ b/src/setup.py @@ -69,7 +69,7 @@ setup( 'python-dateutil==2.4.*', 'pytz', 'django-bootstrap3==8.2.*', - 'django-formset-js-improved==0.5.0.1', + 'django-formset-js-improved==0.5.0.2', 'django-compressor==2.1', 'django-hierarkey==1.0.*,>=1.0.2', 'django-filter==1.0.*',