diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py
index ecb4a22b4d..7f8183b5fc 100644
--- a/src/pretix/control/forms/filter.py
+++ b/src/pretix/control/forms/filter.py
@@ -7,7 +7,8 @@ from django.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.models import (
- Checkin, Event, Invoice, Item, Order, OrderPosition, Organizer, SubEvent,
+ Checkin, Event, Invoice, Item, Order, OrderPosition, Organizer, Question,
+ QuestionAnswer, SubEvent,
)
from pretix.base.signals import register_payment_providers
from pretix.control.forms.widgets import Select2
@@ -99,6 +100,7 @@ class OrderFilterForm(FilterForm):
('p', _('Paid')),
('n', _('Pending')),
('o', _('Pending (overdue)')),
+ ('np', _('Pending or paid')),
('e', _('Expired')),
('ne', _('Pending or expired')),
('c', _('Canceled')),
@@ -145,6 +147,8 @@ class OrderFilterForm(FilterForm):
s = fdata.get('status')
if s == 'o':
qs = qs.filter(status=Order.STATUS_PENDING, expires__lt=now().replace(hour=0, minute=0, second=0))
+ elif s == 'np':
+ qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
elif s == 'ne':
qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
else:
@@ -175,11 +179,19 @@ class EventOrderFilterForm(OrderFilterForm):
required=False,
empty_label=pgettext_lazy('subevent', 'All dates')
)
+ question = forms.ModelChoiceField(
+ queryset=Question.objects.none(),
+ required=False,
+ )
+ answer = forms.CharField(
+ required=False
+ )
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
super().__init__(*args, **kwargs)
self.fields['item'].queryset = self.event.items.all()
+ self.fields['question'].queryset = self.event.questions.all()
self.fields['provider'].choices += [(k, v.verbose_name) for k, v
in self.event.get_payment_providers().items()]
@@ -209,6 +221,31 @@ class EventOrderFilterForm(OrderFilterForm):
if fdata.get('subevent'):
qs = qs.filter(positions__subevent=fdata.get('subevent'))
+ if fdata.get('question') and fdata.get('answer') is not None:
+ q = fdata.get('question')
+
+ if q.type == Question.TYPE_FILE:
+ answers = QuestionAnswer.objects.filter(
+ orderposition__order_id=OuterRef('pk'),
+ question_id=q.pk,
+ file__isnull=False
+ )
+ qs = qs.annotate(has_answer=Exists(answers)).filter(has_answer=True)
+ elif q.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
+ answers = QuestionAnswer.objects.filter(
+ question_id=q.pk,
+ orderposition__order_id=OuterRef('pk'),
+ options__pk=fdata.get('answer')
+ )
+ qs = qs.annotate(has_answer=Exists(answers)).filter(has_answer=True)
+ else:
+ answers = QuestionAnswer.objects.filter(
+ question_id=q.pk,
+ orderposition__order_id=OuterRef('pk'),
+ answer__iexact=fdata.get('answer')
+ )
+ qs = qs.annotate(has_answer=Exists(answers)).filter(has_answer=True)
+
return qs
diff --git a/src/pretix/control/templates/pretixcontrol/items/question.html b/src/pretix/control/templates/pretixcontrol/items/question.html
index c3095792c1..a27308f5ff 100644
--- a/src/pretix/control/templates/pretixcontrol/items/question.html
+++ b/src/pretix/control/templates/pretixcontrol/items/question.html
@@ -74,7 +74,9 @@
{% for stat in stats %}
|
- {{ stat.answer }}
+
+ {{ stat.answer }}
+
|
{{ stat.count }} |
diff --git a/src/pretix/control/templates/pretixcontrol/orders/index.html b/src/pretix/control/templates/pretixcontrol/orders/index.html
index e8a023981c..d66334125b 100644
--- a/src/pretix/control/templates/pretixcontrol/orders/index.html
+++ b/src/pretix/control/templates/pretixcontrol/orders/index.html
@@ -72,6 +72,18 @@
+ {% if filter_form.is_valid and filter_form.cleaned_data.question %}
+
+
+ {% blocktrans trimmed with question=filter_form.cleaned_data.question.question %}
+ List filtered by answers to question "{{ question }}".
+ {% endblocktrans %}
+
+
+ {% trans "Remove filter" %}
+
+
+ {% endif %}
diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py
index fe51ae0cda..0dd4f4a171 100644
--- a/src/pretix/control/views/item.py
+++ b/src/pretix/control/views/item.py
@@ -416,13 +416,14 @@ class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingV
qs = [
{
'answer': ugettext('File uploaded'),
- 'count': qs.filter(file__isnull=False).count()
+ 'count': qs.filter(file__isnull=False).count(),
}
]
elif self.object.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
qs = qs.order_by('options').values('options', 'options__answer') \
.annotate(count=Count('id')).order_by('-count')
for a in qs:
+ a['alink'] = a['options']
a['answer'] = str(a['options__answer'])
del a['options__answer']
elif self.object.type in (Question.TYPE_TIME, Question.TYPE_DATE, Question.TYPE_DATETIME):
@@ -430,12 +431,14 @@ class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingV
qs_model = qs
qs = qs.values('answer').annotate(count=Count('id')).order_by('-count')
for a, a_model in zip(qs, qs_model):
+ a['alink'] = a['answer']
a['answer'] = str(a_model)
else:
qs = qs.order_by('answer').values('answer').annotate(count=Count('id')).order_by('-count')
if self.object.type == Question.TYPE_BOOLEAN:
for a in qs:
+ a['alink'] = a['answer']
a['answer'] = ugettext('Yes') if a['answer'] == 'True' else ugettext('No')
a['answer_bool'] = a['answer'] == 'True'
diff --git a/src/tests/control/test_orders.py b/src/tests/control/test_orders.py
index a5cb9c8bff..b5a6694b81 100644
--- a/src/tests/control/test_orders.py
+++ b/src/tests/control/test_orders.py
@@ -9,8 +9,8 @@ from django_countries.fields import Country
from tests.base import SoupTest
from pretix.base.models import (
- Event, InvoiceAddress, Item, Order, OrderPosition, Organizer, Quota, Team,
- User,
+ Event, InvoiceAddress, Item, Order, OrderPosition, Organizer, Question,
+ QuestionAnswer, Quota, Team, User,
)
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice,
@@ -86,6 +86,25 @@ def test_order_list(client, env):
response = client.get('/control/event/dummy/dummy/orders/?status=o')
assert 'FOO' in response.rendered_content
+ q = Question.objects.create(event=env[0], question="Q", type="N", required=True)
+ q.items.add(env[3])
+ op = env[2].positions.first()
+ qa = QuestionAnswer.objects.create(question=q, orderposition=op, answer="12")
+ response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=12' % q.pk)
+ assert 'FOO' in response.rendered_content
+ response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=13' % q.pk)
+ assert 'FOO' not in response.rendered_content
+
+ q.type = "C"
+ q.save()
+ qo1 = q.options.create(answer="Foo")
+ qo2 = q.options.create(answer="Bar")
+ qa.options.add(qo1)
+ response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=%d' % (q.pk, qo1.pk))
+ assert 'FOO' in response.rendered_content
+ response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=%d' % (q.pk, qo2.pk))
+ assert 'FOO' not in response.rendered_content
+
@pytest.mark.django_db
def test_order_detail(client, env):