mirror of
https://github.com/pretix/pretix.git
synced 2026-08-07 10:17:49 +00:00
Add global order search
This commit is contained in:
@@ -189,6 +189,13 @@
|
||||
{% trans "Organizers" %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'control:search.orders' %}"
|
||||
{% if url_name == "search.orders" %}class="active"{% endif %}>
|
||||
<i class="fa fa-search fa-fw"></i>
|
||||
{% trans "Order search" %}
|
||||
</a>
|
||||
</li>
|
||||
{% for nav in nav_global %}
|
||||
<li>
|
||||
<a href="{{ nav.url }}" {% if nav.active %}class="active"{% endif %}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% load urlreplace %}
|
||||
{% block title %}{% trans "Order search" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Order search" %}</h1>
|
||||
<form class="form-inline" action="" method="get">
|
||||
<div class="input-group search-line">
|
||||
<input type="text" name="query" class="form-control" placeholder="{% trans "Search for..." %}"
|
||||
value="{{ request.GET.query }}" autofocus>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary" type="submit">{% trans "Search" %}</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Order code" %}
|
||||
<a href="?{% url_replace request 'ordering' '-code' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'code' %}"><i class="fa fa-caret-up"></i></a></th>
|
||||
<th>{% trans "Event" %}
|
||||
<a href="?{% url_replace request 'ordering' '-event' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'event' %}"><i class="fa fa-caret-up"></i></a></th>
|
||||
<th>{% trans "User" %}
|
||||
<a href="?{% url_replace request 'ordering' '-email' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'email' %}"><i class="fa fa-caret-up"></i></a></th>
|
||||
<th>{% trans "Order total" %}
|
||||
<a href="?{% url_replace request 'ordering' '-total' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'total' %}"><i class="fa fa-caret-up"></i></a></th>
|
||||
<th>{% trans "Order date" %}
|
||||
<a href="?{% url_replace request 'ordering' '-datetime' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'datetime' %}"><i class="fa fa-caret-up"></i></a></th>
|
||||
<th>{% trans "Status" %}
|
||||
<a href="?{% url_replace request 'ordering' '-status' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'status' %}"><i class="fa fa-caret-up"></i></a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for o in orders %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
<a href="{% url "control:event.order" event=o.event.slug organizer=o.event.organizer.slug code=o.code %}">
|
||||
{{ o.event.slug|upper }}-{{ o.code }}
|
||||
</a>
|
||||
</strong>
|
||||
</td>
|
||||
<td>{{ o.event.name }}</td>
|
||||
<td>{{ o.email }}</td>
|
||||
<td>{{ o.total|floatformat:2 }} {{ o.event.currency }}</td>
|
||||
<td>{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>
|
||||
<td>{% include "pretixcontrol/orders/fragment_order_status.html" with order=o %}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center"><em>
|
||||
{% trans "We couldn't find any orders that you have access to and that match your search query." %}
|
||||
</em></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
@@ -2,7 +2,7 @@ from django.conf.urls import include, url
|
||||
|
||||
from pretix.control.views import (
|
||||
auth, checkin, dashboards, event, global_settings, item, main, orders,
|
||||
organizer, typeahead, user, vouchers, waitinglist,
|
||||
organizer, search, typeahead, user, vouchers, waitinglist,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@@ -46,6 +46,7 @@ urlpatterns = [
|
||||
url(r'^events/$', main.EventList.as_view(), name='events'),
|
||||
url(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
|
||||
url(r'^events/typeahead/$', typeahead.event_list, name='events.typeahead'),
|
||||
url(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'),
|
||||
url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
|
||||
url(r'^$', dashboards.event_index, name='event.index'),
|
||||
url(r'^live/$', event.EventLive.as_view(), name='event.live'),
|
||||
|
||||
@@ -55,6 +55,8 @@ class OrderList(EventPermissionRequiredMixin, ListView):
|
||||
qs = qs.filter(
|
||||
Q(email__icontains=u) | Q(positions__attendee_name__icontains=u)
|
||||
| Q(positions__attendee_email__icontains=u)
|
||||
| Q(invoice_address__name__icontains=u)
|
||||
| Q(invoice_address__company__icontains=u)
|
||||
)
|
||||
if self.request.GET.get("status", "") != "":
|
||||
s = self.request.GET.get("status", "")
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
from django.db.models import Q
|
||||
from django.views.generic import ListView
|
||||
|
||||
from pretix.base.models import Order
|
||||
|
||||
|
||||
class OrderSearch(ListView):
|
||||
model = Order
|
||||
context_object_name = 'orders'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/search/orders.html'
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Order.objects.all()
|
||||
if not self.request.user.is_superuser:
|
||||
qs = qs.filter(
|
||||
Q(event__organizer_id__in=self.request.user.teams.filter(
|
||||
all_events=True, can_view_orders=True).values_list('organizer', flat=True))
|
||||
| Q(event_id__in=self.request.user.teams.filter(
|
||||
can_view_orders=True).values_list('limit_events__id', flat=True))
|
||||
)
|
||||
|
||||
if self.request.GET.get("query", "") != "":
|
||||
u = self.request.GET.get("query", "")
|
||||
if "-" in u:
|
||||
code = (Q(event__slug__icontains=u.split("-")[0])
|
||||
& Q(code__icontains=Order.normalize_code(u.split("-")[1])))
|
||||
else:
|
||||
code = Q(code__icontains=Order.normalize_code(u))
|
||||
qs = qs.filter(
|
||||
code
|
||||
| Q(email__icontains=u)
|
||||
| Q(positions__attendee_name__icontains=u)
|
||||
| Q(positions__attendee_email__icontains=u)
|
||||
| Q(invoice_address__name__icontains=u)
|
||||
| Q(invoice_address__company__icontains=u)
|
||||
)
|
||||
print(qs.query)
|
||||
|
||||
if self.request.GET.get("ordering", "") != "":
|
||||
p = self.request.GET.get("ordering", "")
|
||||
p_admissable = ('event', '-event', '-code', 'code', '-email', 'email', '-total', 'total', '-datetime',
|
||||
'datetime', '-status', 'status')
|
||||
if p in p_admissable:
|
||||
qs = qs.order_by(p)
|
||||
|
||||
return qs.distinct().prefetch_related('event', 'event__organizer')
|
||||
@@ -142,3 +142,12 @@ pre.mail-preview {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-line {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.input-group-btn {
|
||||
width: 1% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from django.utils.timezone import now
|
||||
from tests.base import SoupTest
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, InvoiceAddress, Item, Order, OrderPosition, Organizer, Team, User,
|
||||
)
|
||||
|
||||
|
||||
class OrderSearchTest(SoupTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
||||
self.orga1 = Organizer.objects.create(name='CCC', slug='ccc')
|
||||
self.event1 = Event.objects.create(
|
||||
organizer=self.orga1, name='30C3', slug='30c3',
|
||||
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
|
||||
plugins='pretix.plugins.banktransfer,tests.testdummy'
|
||||
)
|
||||
self.event2 = Event.objects.create(
|
||||
organizer=self.orga1, name='31C3', slug='31c3',
|
||||
date_from=datetime.datetime(2014, 12, 26, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
|
||||
o1 = Order.objects.create(
|
||||
code='FO1A', event=self.event1, email='dummy1@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
)
|
||||
InvoiceAddress.objects.create(order=o1, company="Test Ltd.", name="Peter Miller")
|
||||
ticket1 = Item.objects.create(event=self.event1, name='Early-bird ticket',
|
||||
category=None, default_price=23,
|
||||
admission=True)
|
||||
OrderPosition.objects.create(
|
||||
order=o1,
|
||||
item=ticket1,
|
||||
variation=None,
|
||||
price=Decimal("14"),
|
||||
attendee_name="Peter",
|
||||
attendee_email="att@att.com"
|
||||
)
|
||||
|
||||
o2 = Order.objects.create(
|
||||
code='FO2', event=self.event2, email='dummy2@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
)
|
||||
ticket2 = Item.objects.create(event=self.event1, name='Early-bird ticket',
|
||||
category=None, default_price=23,
|
||||
admission=True)
|
||||
OrderPosition.objects.create(
|
||||
order=o2,
|
||||
item=ticket2,
|
||||
variation=None,
|
||||
price=Decimal("14"),
|
||||
attendee_name="Mark"
|
||||
)
|
||||
|
||||
self.team = Team.objects.create(organizer=self.orga1, can_view_orders=True)
|
||||
self.team.members.add(self.user)
|
||||
self.team.limit_events.add(self.event1)
|
||||
|
||||
self.client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
def test_team_limit_event(self):
|
||||
resp = self.client.get('/control/search/orders/').rendered_content
|
||||
assert 'FO1' in resp
|
||||
assert 'FO2' not in resp
|
||||
|
||||
def test_team_limit_event_wrong_permission(self):
|
||||
self.team.can_view_orders = False
|
||||
self.team.save()
|
||||
resp = self.client.get('/control/search/orders/').rendered_content
|
||||
assert 'FO1' not in resp
|
||||
assert 'FO2' not in resp
|
||||
|
||||
def test_team_all_events(self):
|
||||
self.team.all_events = True
|
||||
self.team.save()
|
||||
resp = self.client.get('/control/search/orders/').rendered_content
|
||||
assert 'FO1' in resp
|
||||
assert 'FO2' in resp
|
||||
|
||||
def test_team_all_events_wrong_permission(self):
|
||||
self.team.all_events = True
|
||||
self.team.can_view_orders = False
|
||||
self.team.save()
|
||||
resp = self.client.get('/control/search/orders/').rendered_content
|
||||
assert 'FO1' not in resp
|
||||
assert 'FO2' not in resp
|
||||
|
||||
def test_team_none(self):
|
||||
self.team.members.clear()
|
||||
resp = self.client.get('/control/search/orders/').rendered_content
|
||||
assert 'FO1' not in resp
|
||||
assert 'FO2' not in resp
|
||||
|
||||
def test_suberuser(self):
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
self.team.members.clear()
|
||||
resp = self.client.get('/control/search/orders/').rendered_content
|
||||
assert 'FO1' in resp
|
||||
assert 'FO2' in resp
|
||||
|
||||
def test_filter_email(self):
|
||||
resp = self.client.get('/control/search/orders/?query=dummy1@dummy').rendered_content
|
||||
assert 'FO1' in resp
|
||||
resp = self.client.get('/control/search/orders/?query=dummynope').rendered_content
|
||||
assert 'FO1' not in resp
|
||||
|
||||
def test_filter_attendee_name(self):
|
||||
resp = self.client.get('/control/search/orders/?query=Pete').rendered_content
|
||||
assert 'FO1' in resp
|
||||
resp = self.client.get('/control/search/orders/?query=Mark').rendered_content
|
||||
assert 'FO1' not in resp
|
||||
|
||||
def test_filter_attendee_email(self):
|
||||
resp = self.client.get('/control/search/orders/?query=att.com').rendered_content
|
||||
assert 'FO1' in resp
|
||||
resp = self.client.get('/control/search/orders/?query=nope.com').rendered_content
|
||||
assert 'FO1' not in resp
|
||||
|
||||
def test_filter_invoice_address(self):
|
||||
resp = self.client.get('/control/search/orders/?query=Ltd').rendered_content
|
||||
assert 'FO1' in resp
|
||||
resp = self.client.get('/control/search/orders/?query=Miller').rendered_content
|
||||
assert 'FO1' in resp
|
||||
|
||||
def test_filter_code(self):
|
||||
resp = self.client.get('/control/search/orders/?query=FO1').rendered_content
|
||||
assert '30C3-FO1' in resp
|
||||
resp = self.client.get('/control/search/orders/?query=30c3-FO1').rendered_content
|
||||
assert '30C3-FO1' in resp
|
||||
resp = self.client.get('/control/search/orders/?query=30C3-fO1A').rendered_content
|
||||
assert '30C3-FO1' in resp
|
||||
resp = self.client.get('/control/search/orders/?query=30C3-fo14').rendered_content
|
||||
assert '30C3-FO1' in resp
|
||||
resp = self.client.get('/control/search/orders/?query=31c3-FO1').rendered_content
|
||||
assert '30C3-FO1' not in resp
|
||||
resp = self.client.get('/control/search/orders/?query=FO2').rendered_content
|
||||
assert '30C3-FO1' not in resp
|
||||
Reference in New Issue
Block a user