forked from CGM_Public/pretix_original
Add advanced search to order list
This commit is contained in:
@@ -1,16 +1,22 @@
|
||||
from datetime import datetime, time
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django import forms
|
||||
from django.apps import apps
|
||||
from django.db.models import Exists, F, OuterRef, Q
|
||||
from django.conf import settings
|
||||
from django.db.models import Exists, F, Model, OuterRef, Q, QuerySet
|
||||
from django.db.models.functions import Coalesce, ExtractWeekDay
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.formats import date_format, localize
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import get_current_timezone, make_aware, now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django.utils.translation import gettext, gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.forms.widgets import DatePickerWidget
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.forms.widgets import (
|
||||
DatePickerWidget, SplitDateTimePickerWidget,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Checkin, Event, EventMetaProperty, EventMetaValue, Invoice, InvoiceAddress,
|
||||
Item, Order, OrderPayment, OrderPosition, OrderRefund, Organizer, Question,
|
||||
@@ -19,7 +25,9 @@ from pretix.base.models import (
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.control.forms.widgets import Select2
|
||||
from pretix.control.signals import order_search_filter_q
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.helpers.database import FixedOrderBy, rolledback_transaction
|
||||
from pretix.helpers.dicts import move_to_end
|
||||
from pretix.helpers.i18n import i18ncomp
|
||||
|
||||
PAYMENT_PROVIDERS = []
|
||||
@@ -83,6 +91,38 @@ class FilterForm(forms.Form):
|
||||
else:
|
||||
return self.orders[o]
|
||||
|
||||
def filter_to_strings(self):
|
||||
string = []
|
||||
for k, f in self.fields.items():
|
||||
v = self.cleaned_data.get(k)
|
||||
if v is None or (isinstance(v, (list, str, QuerySet)) and len(v) == 0):
|
||||
continue
|
||||
if k == "saveas":
|
||||
continue
|
||||
|
||||
if isinstance(v, bool):
|
||||
val = _('Yes') if v else _('No')
|
||||
elif isinstance(v, QuerySet):
|
||||
q = ['"' + str(m) + '"' for m in v]
|
||||
if not q:
|
||||
continue
|
||||
val = ' or '.join(q)
|
||||
elif isinstance(v, Model):
|
||||
val = '"' + str(v) + '"'
|
||||
elif isinstance(f, forms.MultipleChoiceField):
|
||||
valdict = dict(f.choices)
|
||||
val = ' or '.join([str(valdict.get(m)) for m in v])
|
||||
elif isinstance(f, forms.ChoiceField):
|
||||
val = str(dict(f.choices).get(v))
|
||||
elif isinstance(v, datetime):
|
||||
val = date_format(v, 'SHORT_DATETIME_FORMAT')
|
||||
elif isinstance(v, Decimal):
|
||||
val = localize(v)
|
||||
else:
|
||||
val = v
|
||||
string.append('{}: {}'.format(f.label, val))
|
||||
return string
|
||||
|
||||
|
||||
class OrderFilterForm(FilterForm):
|
||||
query = forms.CharField(
|
||||
@@ -104,21 +144,29 @@ class OrderFilterForm(FilterForm):
|
||||
label=_('Order status'),
|
||||
choices=(
|
||||
('', _('All orders')),
|
||||
(Order.STATUS_PAID, _('Paid (or canceled with paid fee)')),
|
||||
(Order.STATUS_PENDING, _('Pending')),
|
||||
('o', _('Pending (overdue)')),
|
||||
(Order.STATUS_PENDING + Order.STATUS_PAID, _('Pending or paid')),
|
||||
(Order.STATUS_EXPIRED, _('Expired')),
|
||||
(Order.STATUS_PENDING + Order.STATUS_EXPIRED, _('Pending or expired')),
|
||||
(Order.STATUS_CANCELED, _('Canceled')),
|
||||
('cp', _('Canceled (or with paid fee)')),
|
||||
('na', _('Approved, payment pending')),
|
||||
('pa', _('Approval pending')),
|
||||
('overpaid', _('Overpaid')),
|
||||
('underpaid', _('Underpaid')),
|
||||
('pendingpaid', _('Pending (but fully paid)')),
|
||||
(_('Valid orders'), (
|
||||
(Order.STATUS_PAID, _('Paid (or canceled with paid fee)')),
|
||||
(Order.STATUS_PENDING, _('Pending')),
|
||||
(Order.STATUS_PENDING + Order.STATUS_PAID, _('Pending or paid')),
|
||||
)),
|
||||
(_('Cancellations'), (
|
||||
(Order.STATUS_CANCELED, _('Canceled')),
|
||||
('cp', _('Canceled (or with paid fee)')),
|
||||
('rc', _('Cancellation requested')),
|
||||
)),
|
||||
(_('Payment process'), (
|
||||
(Order.STATUS_EXPIRED, _('Expired')),
|
||||
(Order.STATUS_PENDING + Order.STATUS_EXPIRED, _('Pending or expired')),
|
||||
('o', _('Pending (overdue)')),
|
||||
('overpaid', _('Overpaid')),
|
||||
('underpaid', _('Underpaid')),
|
||||
('pendingpaid', _('Pending (but fully paid)')),
|
||||
)),
|
||||
(_('Approval process'), (
|
||||
('na', _('Approved, payment pending')),
|
||||
('pa', _('Approval pending')),
|
||||
)),
|
||||
('testmode', _('Test mode')),
|
||||
('rc', _('Cancellation requested')),
|
||||
),
|
||||
required=False,
|
||||
)
|
||||
@@ -343,6 +391,237 @@ class EventOrderFilterForm(OrderFilterForm):
|
||||
return qs
|
||||
|
||||
|
||||
class FilterNullBooleanSelect(forms.NullBooleanSelect):
|
||||
def __init__(self, attrs=None):
|
||||
choices = (
|
||||
('unknown', _('All')),
|
||||
('true', _('Yes')),
|
||||
('false', _('No')),
|
||||
)
|
||||
super(forms.NullBooleanSelect, self).__init__(attrs, choices)
|
||||
|
||||
|
||||
class EventOrderExpertFilterForm(EventOrderFilterForm):
|
||||
subevents_from = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(attrs={
|
||||
}),
|
||||
label=pgettext_lazy('subevent', 'All dates starting at or after'),
|
||||
required=False,
|
||||
)
|
||||
subevents_to = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(attrs={
|
||||
}),
|
||||
label=pgettext_lazy('subevent', 'All dates starting before'),
|
||||
required=False,
|
||||
)
|
||||
created_from = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(attrs={
|
||||
}),
|
||||
label=_('Order placed at or after'),
|
||||
required=False,
|
||||
)
|
||||
created_to = forms.SplitDateTimeField(
|
||||
widget=SplitDateTimePickerWidget(attrs={
|
||||
}),
|
||||
label=_('Order placed before'),
|
||||
required=False,
|
||||
)
|
||||
email = forms.CharField(
|
||||
required=False,
|
||||
label=_('E-mail address')
|
||||
)
|
||||
comment = forms.CharField(
|
||||
required=False,
|
||||
label=_('Comment')
|
||||
)
|
||||
locale = forms.ChoiceField(
|
||||
required=False,
|
||||
label=_('Locale'),
|
||||
choices=settings.LANGUAGES
|
||||
)
|
||||
email_known_to_work = forms.NullBooleanField(
|
||||
required=False,
|
||||
widget=FilterNullBooleanSelect,
|
||||
label=_('E-mail address verified'),
|
||||
)
|
||||
total = forms.DecimalField(
|
||||
localize=True,
|
||||
required=False,
|
||||
label=_('Total amount'),
|
||||
)
|
||||
sales_channel = forms.ChoiceField(
|
||||
label=_('Sales channel'),
|
||||
required=False,
|
||||
choices=[('', '')] + [
|
||||
(k, v.verbose_name) for k, v in get_all_sales_channels().items()
|
||||
]
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
del self.fields['query']
|
||||
del self.fields['question']
|
||||
del self.fields['answer']
|
||||
del self.fields['ordering']
|
||||
if not self.event.has_subevents:
|
||||
del self.fields['subevents_from']
|
||||
del self.fields['subevents_to']
|
||||
|
||||
locale_names = dict(settings.LANGUAGES)
|
||||
self.fields['locale'].choices = [('', '')] + [(a, locale_names[a]) for a in self.event.settings.locales]
|
||||
|
||||
move_to_end(self.fields, 'item')
|
||||
move_to_end(self.fields, 'provider')
|
||||
|
||||
self.fields['invoice_address_company'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Invoice address') + ': ' + gettext('Company')
|
||||
)
|
||||
self.fields['invoice_address_name'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Invoice address') + ': ' + gettext('Name')
|
||||
)
|
||||
self.fields['invoice_address_street'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Invoice address') + ': ' + gettext('Address')
|
||||
)
|
||||
self.fields['invoice_address_zipcode'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Invoice address') + ': ' + gettext('ZIP code'),
|
||||
help_text=_('Exact matches only')
|
||||
)
|
||||
self.fields['invoice_address_city'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Invoice address') + ': ' + gettext('City'),
|
||||
help_text=_('Exact matches only')
|
||||
)
|
||||
self.fields['invoice_address_country'] = forms.ChoiceField(
|
||||
required=False,
|
||||
label=gettext('Invoice address') + ': ' + gettext('Country'),
|
||||
choices=[('', '')] + list(CachedCountries())
|
||||
)
|
||||
self.fields['attendee_name'] = forms.CharField(
|
||||
required=False,
|
||||
label=_('Attendee name')
|
||||
)
|
||||
self.fields['attendee_email'] = forms.CharField(
|
||||
required=False,
|
||||
label=_('Attendee e-mail address')
|
||||
)
|
||||
self.fields['attendee_address_company'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Attendee address') + ': ' + gettext('Company')
|
||||
)
|
||||
self.fields['attendee_address_street'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Attendee address') + ': ' + gettext('Address')
|
||||
)
|
||||
self.fields['attendee_address_zipcode'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Attendee address') + ': ' + gettext('ZIP code'),
|
||||
help_text=_('Exact matches only')
|
||||
)
|
||||
self.fields['attendee_address_city'] = forms.CharField(
|
||||
required=False,
|
||||
label=gettext('Attendee address') + ': ' + gettext('City'),
|
||||
help_text=_('Exact matches only')
|
||||
)
|
||||
self.fields['attendee_address_country'] = forms.ChoiceField(
|
||||
required=False,
|
||||
label=gettext('Attendee address') + ': ' + gettext('Country'),
|
||||
choices=[('', '')] + list(CachedCountries())
|
||||
)
|
||||
self.fields['ticket_secret'] = forms.CharField(
|
||||
label=_('Ticket secret'),
|
||||
required=False
|
||||
)
|
||||
for q in self.event.questions.all():
|
||||
self.fields['question_{}'.format(q.pk)] = forms.CharField(
|
||||
label=q.question,
|
||||
required=False,
|
||||
help_text=_('Exact matches only')
|
||||
)
|
||||
|
||||
def filter_qs(self, qs):
|
||||
fdata = self.cleaned_data
|
||||
qs = super().filter_qs(qs)
|
||||
|
||||
if fdata.get('subevents_from'):
|
||||
qs = qs.filter(
|
||||
all_positions__subevent__date_from__gte=fdata.get('subevents_from'),
|
||||
all_positions__canceled=False
|
||||
).distinct()
|
||||
if fdata.get('subevents_to'):
|
||||
qs = qs.filter(
|
||||
all_positions__subevent__date_from__lt=fdata.get('subevents_to'),
|
||||
all_positions__canceled=False
|
||||
).distinct()
|
||||
if fdata.get('email'):
|
||||
qs = qs.filter(
|
||||
email__icontains=fdata.get('email')
|
||||
)
|
||||
if fdata.get('created_from'):
|
||||
qs = qs.filter(datetime__gte=fdata.get('created_from'))
|
||||
if fdata.get('created_to'):
|
||||
qs = qs.filter(datetime__gte=fdata.get('created_to'))
|
||||
if fdata.get('comment'):
|
||||
qs = qs.filter(comment__icontains=fdata.get('comment'))
|
||||
if fdata.get('sales_channel'):
|
||||
qs = qs.filter(sales_channel=fdata.get('sales_channel'))
|
||||
if fdata.get('total'):
|
||||
qs = qs.filter(total=fdata.get('total'))
|
||||
if fdata.get('email_known_to_work') is not None:
|
||||
qs = qs.filter(email_known_to_work=fdata.get('email_known_to_work'))
|
||||
if fdata.get('locale'):
|
||||
qs = qs.filter(locale=fdata.get('locale'))
|
||||
if fdata.get('invoice_address_company'):
|
||||
qs = qs.filter(invoice_address__company__icontains=fdata.get('invoice_address_company'))
|
||||
if fdata.get('invoice_address_name'):
|
||||
qs = qs.filter(invoice_address__name_cached__icontains=fdata.get('invoice_address_name'))
|
||||
if fdata.get('invoice_address_street'):
|
||||
qs = qs.filter(invoice_address__street__icontains=fdata.get('invoice_address_street'))
|
||||
if fdata.get('invoice_address_zipcode'):
|
||||
qs = qs.filter(invoice_address__zipcode__iexact=fdata.get('invoice_address_zipcode'))
|
||||
if fdata.get('invoice_address_city'):
|
||||
qs = qs.filter(invoice_address__city__iexact=fdata.get('invoice_address_city'))
|
||||
if fdata.get('invoice_address_country'):
|
||||
qs = qs.filter(invoice_address__country=fdata.get('invoice_address_country'))
|
||||
if fdata.get('attendee_name'):
|
||||
qs = qs.filter(
|
||||
all_positions__attendee_name_cached__icontains=fdata.get('attendee_name')
|
||||
)
|
||||
if fdata.get('attendee_address_company'):
|
||||
qs = qs.filter(
|
||||
all_positions__company__icontains=fdata.get('attendee_address_company')
|
||||
).distinct()
|
||||
if fdata.get('attendee_address_street'):
|
||||
qs = qs.filter(
|
||||
all_positions__street__icontains=fdata.get('attendee_address_street')
|
||||
).distinct()
|
||||
if fdata.get('attendee_address_city'):
|
||||
qs = qs.filter(
|
||||
all_positions__city__iexact=fdata.get('attendee_address_city')
|
||||
).distinct()
|
||||
if fdata.get('attendee_address_country'):
|
||||
qs = qs.filter(
|
||||
all_positions__country=fdata.get('attendee_address_country')
|
||||
).distinct()
|
||||
if fdata.get('ticket_secret'):
|
||||
qs = qs.filter(
|
||||
all_positions__secret__icontains=fdata.get('ticket_secret')
|
||||
).distinct()
|
||||
for q in self.event.questions.all():
|
||||
if fdata.get(f'question_{q.pk}'):
|
||||
answers = QuestionAnswer.objects.filter(
|
||||
question_id=q.pk,
|
||||
orderposition__order_id=OuterRef('pk'),
|
||||
answer__iexact=fdata.get(f'question_{q.pk}')
|
||||
)
|
||||
qs = qs.annotate(**{f'q_{q.pk}': Exists(answers)}).filter(**{f'q_{q.pk}': True})
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class OrderSearchFilterForm(OrderFilterForm):
|
||||
orders = {'code': 'code', 'email': 'email', 'total': 'total',
|
||||
'datetime': 'datetime', 'status': 'status',
|
||||
|
||||
@@ -176,7 +176,7 @@ def get_event_navigation(request: HttpRequest):
|
||||
'event': request.event.slug,
|
||||
'organizer': request.event.organizer.slug,
|
||||
}),
|
||||
'active': url.url_name in ('event.orders', 'event.order') or "event.order." in url.url_name,
|
||||
'active': url.url_name in ('event.orders', 'event.order', 'event.orders.search') or "event.order." in url.url_name,
|
||||
},
|
||||
{
|
||||
'label': _('Overview'),
|
||||
|
||||
@@ -323,3 +323,19 @@ this is not an Event signal and will be called even if your plugin is not active
|
||||
event if the search is performed within an event, and ``None`` otherwise. The search query will be passed as
|
||||
``query``.
|
||||
"""
|
||||
|
||||
order_search_forms = EventPluginSignal(
|
||||
providing_args=['request']
|
||||
)
|
||||
"""
|
||||
This signal allows you to return additional forms that should be rendered in the advanced order search.
|
||||
You are passed ``request`` argument and are expected to return an instance of a form class that you bind
|
||||
yourself when appropriate. Your form will be executed as part of the standard validation and rendering
|
||||
cycle and rendered using default bootstrap styles.
|
||||
|
||||
You are required to set ``prefix`` on your form instance. You are required to implement a ``filter_qs(queryset)``
|
||||
method on your form that returns a new, filtered query set. You are required to implement a ``filter_to_strings()``
|
||||
method on your form that returns a list of strings describing the currently active filters.
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% block title %}{% trans "Orders" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Orders" %}</h1>
|
||||
{% if not filter_form.filtered and orders|length == 0 %}
|
||||
{% if not filter_form.filtered and orders|length == 0 and not filter_strings %}
|
||||
<div class="empty-collection">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
@@ -21,57 +21,72 @@
|
||||
{% trans "Take your shop live" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% eventurl request.event "presale:event.index" %}" class="btn btn-primary btn-lg">
|
||||
<a href="{% eventurl request.event "presale:event.index" %}" class="btn btn-primary btn-lg" target="_blank">
|
||||
{% trans "Go to the ticket shop" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row filter-form">
|
||||
<form class="col-md-2 col-xs-12"
|
||||
action="{% url "control:event.orders.go" event=request.event.slug organizer=request.event.organizer.slug %}">
|
||||
<div class="input-group">
|
||||
<input type="text" name="code" class="form-control" placeholder="{% trans "Order code" %}" autofocus>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary" type="submit">{% trans "Go!" %}</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<form class="" action="" method="get">
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.status layout='inline' %}
|
||||
</div>
|
||||
{% if request.event.has_subevents %}
|
||||
<div class="col-md-1 col-xs-6">
|
||||
{% bootstrap_field filter_form.item layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.subevent layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-1 col-xs-6">
|
||||
{% bootstrap_field filter_form.provider layout='inline' %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.item layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.provider layout='inline' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.query layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
<button class="btn btn-primary btn-block" type="submit">
|
||||
<span class="fa fa-filter"></span>
|
||||
<span class="hidden-md">
|
||||
{% trans "Filter" %}
|
||||
{% if filter_strings %}
|
||||
<p>
|
||||
<span class="fa fa-filter"></span>
|
||||
{% trans "Search query:" %}
|
||||
{{ filter_strings|join:" · " }}
|
||||
·
|
||||
<a href="{% url "control:event.orders.search" event=request.event.slug organizer=request.event.organizer.slug %}?{{ request.META.QUERY_STRING }}">
|
||||
<span class="fa fa-edit"></span>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<div class="row filter-form">
|
||||
<form class="col-md-2 col-xs-12"
|
||||
action="{% url "control:event.orders.go" event=request.event.slug organizer=request.event.organizer.slug %}">
|
||||
<div class="input-group">
|
||||
<input type="text" name="code" class="form-control" placeholder="{% trans "Order code" %}" autofocus>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary" type="submit">{% trans "Go!" %}</button>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form class="" action="" method="get">
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.status layout='inline' %}
|
||||
</div>
|
||||
{% if request.event.has_subevents %}
|
||||
<div class="col-md-1 col-xs-6">
|
||||
{% bootstrap_field filter_form.item layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.subevent layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-1 col-xs-6">
|
||||
{% bootstrap_field filter_form.provider layout='inline' %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.item layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.provider layout='inline' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% bootstrap_field filter_form.query layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-1 col-xs-6">
|
||||
<button class="btn btn-primary btn-block" type="submit">
|
||||
<span class="fa fa-filter"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-1 col-xs-6">
|
||||
<a href="{% url "control:event.orders.search" event=request.event.slug organizer=request.event.organizer.slug %}" class="btn btn-default btn-block" type="submit" data-toggle="tooltip" title="{% trans "Advanced search" %}">
|
||||
<span class="fa fa-cog"></span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if filter_form.is_valid and filter_form.cleaned_data.question %}
|
||||
<p class="text-muted">
|
||||
<span class="fa fa-filter"></span>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% load urlreplace %}
|
||||
{% load money %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Order search" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Order search" %}</h1>
|
||||
<form class="form-horizontal" action="{% url "control:event.orders" event=request.event.slug organizer=request.event.organizer.slug %}" method="get">
|
||||
{% for f in forms %}
|
||||
{% bootstrap_form_errors f layout='control' %}
|
||||
{% for field in f %}
|
||||
{% bootstrap_field field layout='control' %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Search" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -283,6 +283,7 @@ urlpatterns = [
|
||||
url(r'^orders/refunds/$', orders.RefundList.as_view(), name='event.orders.refunds'),
|
||||
url(r'^orders/go$', orders.OrderGo.as_view(), name='event.orders.go'),
|
||||
url(r'^orders/$', orders.OrderList.as_view(), name='event.orders'),
|
||||
url(r'^orders/search$', orders.OrderSearch.as_view(), name='event.orders.search'),
|
||||
url(r'^dangerzone/$', event.DangerZone.as_view(), name='event.dangerzone'),
|
||||
url(r'^cancel/$', orders.EventCancel.as_view(), name='event.cancel'),
|
||||
url(r'^shredder/$', shredder.StartShredView.as_view(), name='event.shredder.start'),
|
||||
|
||||
@@ -74,7 +74,8 @@ from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.base.views.mixins import OrderQuestionsViewMixin
|
||||
from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.control.forms.filter import (
|
||||
EventOrderFilterForm, OverviewFilterForm, RefundFilterForm,
|
||||
EventOrderExpertFilterForm, EventOrderFilterForm, OverviewFilterForm,
|
||||
RefundFilterForm,
|
||||
)
|
||||
from pretix.control.forms.orders import (
|
||||
CancelForm, CommentForm, ConfirmPaymentForm, EventCancelForm, ExporterForm,
|
||||
@@ -84,6 +85,7 @@ from pretix.control.forms.orders import (
|
||||
OrderRefundForm, OtherOperationsForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import order_search_forms
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.presale.signals import question_form_fields
|
||||
@@ -91,7 +93,31 @@ from pretix.presale.signals import question_form_fields
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OrderList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
class OrderSearchMixin:
|
||||
def get_forms(self):
|
||||
f = [
|
||||
EventOrderExpertFilterForm(
|
||||
data=self.request.GET,
|
||||
event=self.request.event,
|
||||
prefix='expert',
|
||||
)
|
||||
]
|
||||
for recv, resp in order_search_forms.send(sender=self.request.event, request=self.request):
|
||||
f.append(resp)
|
||||
return f
|
||||
|
||||
|
||||
class OrderSearch(OrderSearchMixin, EventPermissionRequiredMixin, TemplateView):
|
||||
template_name = 'pretixcontrol/orders/search.html'
|
||||
permission = 'can_view_orders'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['forms'] = self.get_forms()
|
||||
return ctx
|
||||
|
||||
|
||||
class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
model = Order
|
||||
context_object_name = 'orders'
|
||||
template_name = 'pretixcontrol/orders/index.html'
|
||||
@@ -105,12 +131,21 @@ class OrderList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
|
||||
for f in self.get_forms():
|
||||
if any(k.startswith(f.prefix) for k in self.request.GET.keys()) and f.is_valid():
|
||||
qs = f.filter_qs(qs)
|
||||
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['filter_form'] = self.filter_form
|
||||
|
||||
ctx['filter_strings'] = []
|
||||
for f in self.get_forms():
|
||||
if any(k.startswith(f.prefix) for k in self.request.GET.keys()) and f.is_valid():
|
||||
ctx['filter_strings'] += f.filter_to_strings()
|
||||
|
||||
# Only compute this annotations for this page (query optimization)
|
||||
s = OrderPosition.objects.filter(
|
||||
order=OuterRef('pk')
|
||||
|
||||
Reference in New Issue
Block a user