Add payment search page (#2335)

Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
ser8phin
2021-12-15 16:06:43 +01:00
committed by GitHub
parent c8a830ecde
commit eccba09452
6 changed files with 704 additions and 5 deletions

View File

@@ -811,6 +811,213 @@ class OrderSearchFilterForm(OrderFilterForm):
)
class OrderPaymentSearchFilterForm(forms.Form):
orders = {'id': 'id', 'local_id': 'local_id', 'state': 'state', 'amount': 'amount', 'order': 'order',
'created': 'created', 'payment_date': 'payment_date', 'provider': 'provider', 'info': 'info',
'fee': 'fee'}
query = forms.CharField(
label=_('Search for…'),
widget=forms.TextInput(attrs={
'placeholder': _('Search for…'),
'autofocus': 'autofocus'
}),
required=False,
)
event = forms.ModelChoiceField(
label=_('Event'),
queryset=Event.objects.none(),
required=False,
widget=Select2(
attrs={
'data-model-select2': 'event',
'data-select2-url': reverse_lazy('control:events.typeahead'),
'data-placeholder': _('All events')
}
)
)
organizer = forms.ModelChoiceField(
label=_('Organizer'),
queryset=Organizer.objects.none(),
required=False,
empty_label=_('All organizers'),
widget=Select2(
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse_lazy('control:organizers.select2'),
'data-placeholder': _('All organizers')
}
),
)
state = forms.ChoiceField(
label=_('Status'),
required=False,
choices=[('', _('All payments'))] + list(OrderPayment.PAYMENT_STATES),
)
provider = forms.ChoiceField(
label=_('Payment provider'),
choices=[
('', _('All payment providers')),
],
required=False,
)
created_from = forms.DateField(
label=_('Payment created from'),
required=False,
widget=DatePickerWidget,
)
created_until = forms.DateField(
label=_('Payment created until'),
required=False,
widget=DatePickerWidget,
)
completed_from = forms.DateField(
label=_('Paid from'),
required=False,
widget=DatePickerWidget,
)
completed_until = forms.DateField(
label=_('Paid until'),
required=False,
widget=DatePickerWidget,
)
amount = forms.CharField(
label=_('Amount'),
required=False,
widget=forms.NumberInput(attrs={
'placeholder': _('Amount'),
}),
)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super().__init__(*args, **kwargs)
self.fields['ordering'] = forms.ChoiceField(
choices=sum([
[(a, a), ('-' + a, '-' + a)]
for a in self.orders.keys()
], []),
required=False
)
if self.request.user.has_active_staff_session(self.request.session.session_key):
self.fields['organizer'].queryset = Organizer.objects.all()
self.fields['event'].queryset = Event.objects.all()
else:
self.fields['organizer'].queryset = Organizer.objects.filter(
pk__in=self.request.user.teams.values_list('organizer', flat=True)
)
self.fields['event'].queryset = self.request.user.get_events_with_permission('can_view_orders')
self.fields['provider'].choices += get_all_payment_providers()
def filter_qs(self, qs):
fdata = self.cleaned_data
if fdata.get('created_from'):
date_start = make_aware(datetime.combine(
fdata.get('created_from'),
time(hour=0, minute=0, second=0, microsecond=0)
), get_current_timezone())
qs = qs.filter(created__gte=date_start)
if fdata.get('created_until'):
date_end = make_aware(datetime.combine(
fdata.get('created_until') + timedelta(days=1),
time(hour=0, minute=0, second=0, microsecond=0)
), get_current_timezone())
qs = qs.filter(created__lt=date_end)
if fdata.get('completed_from'):
date_start = make_aware(datetime.combine(
fdata.get('completed_from'),
time(hour=0, minute=0, second=0, microsecond=0)
), get_current_timezone())
qs = qs.filter(payment_date__gte=date_start)
if fdata.get('completed_until'):
date_end = make_aware(datetime.combine(
fdata.get('completed_until') + timedelta(days=1),
time(hour=0, minute=0, second=0, microsecond=0)
), get_current_timezone())
qs = qs.filter(payment_date__lt=date_end)
if fdata.get('event'):
qs = qs.filter(order__event=fdata.get('event'))
if fdata.get('organizer'):
qs = qs.filter(order__event__organizer=fdata.get('organizer'))
if fdata.get('state'):
qs = qs.filter(state=fdata.get('state'))
if fdata.get('provider'):
qs = qs.filter(provider=fdata.get('provider'))
if fdata.get('query'):
u = fdata.get('query')
matching_invoices = Invoice.objects.filter(
Q(invoice_no__iexact=u)
| Q(invoice_no__iexact=u.zfill(5))
| Q(full_invoice_no__iexact=u)
).values_list('order_id', flat=True)
matching_invoice_addresses = InvoiceAddress.objects.filter(
Q(
Q(name_cached__icontains=u) | Q(company__icontains=u)
)
).values_list('order_id', flat=True)
if "-" in u:
code = (Q(event__slug__icontains=u.rsplit("-", 1)[0])
& Q(code__icontains=Order.normalize_code(u.rsplit("-", 1)[1])))
else:
code = Q(code__icontains=Order.normalize_code(u))
matching_orders = Order.objects.filter(
Q(
code
| Q(email__icontains=u)
| Q(comment__icontains=u)
)
).values_list('id', flat=True)
mainq = (
Q(order__id__in=matching_invoices)
| Q(order__id__in=matching_invoice_addresses)
| Q(order__id__in=matching_orders)
)
qs = qs.filter(mainq)
if fdata.get('amount'):
amount = fdata.get('amount')
def is_decimal(value):
result = True
parts = value.split('.', maxsplit=1)
for part in parts:
result = result & part.isdecimal()
return result
if is_decimal(amount):
qs = qs.filter(amount=Decimal(amount))
if fdata.get('ordering'):
p = self.cleaned_data.get('ordering')
if p.startswith('-') and p not in self.orders:
qs = qs.order_by('-' + self.orders[p[1:]])
else:
qs = qs.order_by(self.orders[p])
else:
qs = qs.order_by('-created')
return qs
class SubEventFilterForm(FilterForm):
orders = {
'date_from': 'date_from',

View File

@@ -343,10 +343,24 @@ def get_global_navigation(request):
'icon': 'group',
},
{
'label': _('Order search'),
'label': _('Search'),
'url': reverse('control:search.orders'),
'active': 'search.orders' in url.url_name,
'active': False,
'icon': 'search',
'children': [
{
'label': _('Orders'),
'url': reverse('control:search.orders'),
'active': 'search.orders' in url.url_name,
'icon': 'search',
},
{
'label': _('Payments'),
'url': reverse('control:search.payments'),
'active': 'search.payments' in url.url_name,
'icon': 'search',
},
]
},
{
'label': _('User settings'),

View File

@@ -0,0 +1,164 @@
{% extends "pretixcontrol/base.html" %}
{% load i18n %}
{% load eventurl %}
{% load urlreplace %}
{% load money %}
{% load bootstrap3 %}
{% block title %}{% trans "Payment search" %}{% endblock %}
{% block content %}
<h1>{% trans "Payment search" %}</h1>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Filter" %}
</h3>
</div>
<form class="panel-body filter-form" action="" method="get">
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<div class="row">
<div class="col-md-6 col-sm-12 col-xs-12">
{% bootstrap_field filter_form.query %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.completed_from %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.completed_until %}
</div>
</div>
<div class="row">
<div class="col-md-3 col-sm-3 col-xs-4">
{% bootstrap_field filter_form.amount %}
</div>
<div class="col-md-5 col-sm-5 col-xs-8">
{% bootstrap_field filter_form.provider %}
</div>
<div class="col-md-4 col-sm-4 col-xs-12">
{% bootstrap_field filter_form.state %}
</div>
</div>
</div>
<div class="col-md-12 col-sm-12 col-xs-12">
<div class="row">
<div class="col-md-6 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.organizer %}
</div>
<div class="col-md-6 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.event %}
</div>
</div>
<div class="row">
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.created_from %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.created_until %}
</div>
</div>
</div>
</div>
<div class="text-right flip">
<button class="btn btn-primary btn-lg" type="submit">
<span class="fa fa-filter"></span>
{% trans "Filter" %}
</button>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>
{% trans "Payment ID" %}
</th>
<th>
{% trans "Order" %}
<a href="?{% url_replace request 'ordering' '-order' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'order' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Start date" %}
<a href="?{% url_replace request 'ordering' '-created' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'created' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Confirmation date" %}
<a href="?{% url_replace request 'ordering' '-payment_date' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'payment_date' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Payment provider" %}
<a href="?{% url_replace request 'ordering' '-provider' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'provider' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th class="text-right flip">
{% trans "Amount" %}
<a href="?{% url_replace request 'ordering' '-amount' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'amount' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th class="text-right flip">
{% trans "Status" %}
<a href="?{% url_replace request 'ordering' '-state' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'state' %}"><i class="fa fa-caret-up"></i></a>
</th>
</tr>
</thead>
<tbody>
{% for p in payments %}
<tr>
<td>{{ p.full_id }}</td>
<td>
<strong>
<a href="{% url "control:event.order" event=p.order.event.slug organizer=p.order.event.organizer.slug code=p.order.code %}">
{{ p.order.full_code }}</a>
</strong>
{% if p.order.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>
{% if p.migrated %}
<span class="label label-default" data-toggle="tooltip"
title="{% trans "This payment was created with an older version of pretix, therefore accurate data might not be available." %}">
{% trans "MIGRATED" %}
</span>
{% else %}
{{ p.created|date:"SHORT_DATETIME_FORMAT" }}
{% endif %}
</td>
<td>{{ p.payment_date|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>{{ p.payment_provider.verbose_name }}</td>
<td class="text-right flip">{{ p.amount|money:p.order.event.currency }}</td>
<td class="text-right flip">
<span class="label label-{% if p.state == "created" or p.state == "pending" %}warning{% elif p.state == "confirmed" %}success{% else %}danger{% endif %}">
{{ p.get_state_display }}
</span>
</td>
</tr>
{% if staff_session %}
<tr>
<td colspan="1"></td>
<td colspan="6">
<a href="" class="btn btn-default btn-xs" data-expandpayment data-id="{{ p.pk }}">
<span class="fa-eye fa fa-fw"></span>
{% trans "Inspect" %}
</a>
</td>
</tr>
{% endif %}
{% empty %}
<tr>
<td colspan="7" class="text-center"><em>
{% trans "We couldn't find any payments that you have access to and that match your search query." %}
</em></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination_huge.html" %}
{% endblock %}

View File

@@ -194,6 +194,7 @@ urlpatterns = [
re_path(r'^events/typeahead/$', typeahead.event_list, name='events.typeahead'),
re_path(r'^events/typeahead/meta/$', typeahead.meta_values, name='events.meta.typeahead'),
re_path(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'),
re_path(r'^search/payments/$', search.PaymentSearch.as_view(), name='search.payments'),
re_path(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
re_path(r'^$', dashboards.event_index, name='event.index'),
re_path(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'),

View File

@@ -25,8 +25,10 @@ from django.utils.functional import cached_property
from django.views.generic import ListView
from pretix.base.models import Order, OrderPosition
from pretix.base.models.orders import CancellationRequest
from pretix.control.forms.filter import OrderSearchFilterForm
from pretix.base.models.orders import CancellationRequest, OrderPayment
from pretix.control.forms.filter import (
OrderPaymentSearchFilterForm, OrderSearchFilterForm,
)
from pretix.control.views import LargeResultSetPaginator, PaginationMixin
@@ -136,3 +138,73 @@ class OrderSearch(PaginationMixin, ListView):
).prefetch_related(
'event', 'event__organizer'
).select_related('invoice_address')
class PaymentSearch(PaginationMixin, ListView):
model = OrderPayment
paginator_class = LargeResultSetPaginator
context_object_name = 'payments'
template_name = 'pretixcontrol/search/payments.html'
@cached_property
def filter_form(self):
return OrderPaymentSearchFilterForm(data=self.request.GET, request=self.request)
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx['filter_form'] = self.filter_form
return ctx
def get_queryset(self):
qs = OrderPayment.objects.using(settings.DATABASE_REPLICA)
if not self.request.user.has_active_staff_session(self.request.session.session_key):
qs = qs.filter(
Q(order__event_id__in=self.request.user.get_events_with_permission('can_view_orders').values_list('id', flat=True))
)
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
if self.filter_form.cleaned_data.get('query'):
"""
We need to work around a bug in PostgreSQL's (and likely MySQL's) query plan optimizer here.
The database lacks statistical data to predict how common our search filter is and therefore
assumes that it is cheaper to first ORDER *all* orders in the system (since we got an index on
datetime), then filter out with a full scan until OFFSET/LIMIT condition is fulfilled. If we
look for something rare (such as an email address used once within hundreds of thousands of
orders, this ends up to be pathologically slow.
For some search queries on pretix.eu, we see search times of >30s, just due to the ORDER BY and
LIMIT clause. Without them. the query runs in roughly 0.6s. This heuristical approach tries to
detect these cases and rewrite the query as a nested subquery that strongly suggests sorting
before filtering. However, since even that fails in some cases because PostgreSQL thinks it knows
better, we literally force it by evaluating the subquery explicitly. We only do this for n<=200,
to avoid memory leaks and problems with maximum parameter count on SQLite. In cases where the
search query yields lots of results, this will actually be slower since it requires two queries,
sorry.
Phew.
"""
page = self.kwargs.get(self.page_kwarg) or self.request.GET.get(self.page_kwarg) or 1
limit = self.get_paginate_by(None)
try:
offset = (int(page) - 1) * limit
except ValueError:
offset = 0
resultids = list(qs.order_by().values_list('id', flat=True)[:201])
if len(resultids) <= 200 and len(resultids) <= offset + limit:
qs = OrderPayment.objects.using(settings.DATABASE_REPLICA).filter(
id__in=resultids
)
"""
We use prefetch_related here instead of select_related for a reason, even though select_related
would be the common choice for a foreign key and doesn't require an additional database query.
The problem is, that if our results contain the same event 25 times, select_related will create
25 Django objects which will all try to pull their ownsettings cache to show the event properly,
leading to lots of unnecessary queries. Due to the way prefetch_related works differently, it
will only create one shared Django object.
"""
return qs.prefetch_related('order', 'order__event', 'order__event__organizer')

View File

@@ -27,7 +27,8 @@ from django_scopes import scopes_disabled
from tests.base import SoupTest
from pretix.base.models import (
Event, InvoiceAddress, Item, Order, OrderPosition, Organizer, Team, User,
Event, InvoiceAddress, Item, Order, OrderPayment, OrderPosition, Organizer,
Team, User,
)
@@ -168,3 +169,243 @@ class OrderSearchTest(SoupTest):
assert '30C3-FO1' not in resp
resp = self.client.get('/control/search/orders/?query=FO2').content.decode()
assert '30C3-FO1' not in resp
class PaymentSearchTest(SoupTest):
@scopes_disabled()
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.orga2 = Organizer.objects.create(name='NoOrga', slug='no')
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, locale='en'
)
InvoiceAddress.objects.create(order=o1, company="Test Ltd.", name_parts={'full_name': "Peter Miller", "_scheme": "full"})
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_parts={'full_name': "Peter", "_scheme": "full"},
attendee_email="att@att.com"
)
OrderPayment.objects.create(
local_id=1,
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
amount=Decimal("14"),
order=o1,
provider="giftcard",
info="{test payment order 1}"
)
OrderPayment.objects.create(
local_id=1,
state=OrderPayment.PAYMENT_STATE_REFUNDED,
amount=Decimal("14"),
order=o1,
provider="manual",
info="{refunded payment}"
)
OrderPayment.objects.create(
local_id=1,
state=OrderPayment.PAYMENT_STATE_CANCELED,
amount=Decimal("14"),
order=o1,
provider="manual",
info="{canceled payment}"
)
OrderPayment.objects.create(
local_id=1,
state=OrderPayment.PAYMENT_STATE_FAILED,
amount=Decimal("14"),
order=o1,
provider="manual",
info="{failed payment}"
)
OrderPayment.objects.create(
local_id=1,
state=OrderPayment.PAYMENT_STATE_PENDING,
amount=Decimal("14"),
order=o1,
provider="manual",
info="{pending payment}"
)
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=15, 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("15"),
attendee_name_parts={'full_name': "Mark", "_scheme": "full"}
)
OrderPayment.objects.create(
local_id=1,
state=OrderPayment.PAYMENT_STATE_CREATED,
amount=Decimal("15"),
order=o2,
provider="manual",
info="{test payment order 2}"
)
self.team = Team.objects.create(organizer=self.orga1, can_view_orders=True)
self.team2 = Team.objects.create(organizer=self.orga2, 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/payments/').content.decode()
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/payments/').content.decode()
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/payments/').content.decode()
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/payments/').content.decode()
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/payments/').content.decode()
assert 'FO1' not in resp
assert 'FO2' not in resp
def test_superuser(self):
self.user.is_staff = True
self.user.staffsession_set.create(date_start=now(), session_key=self.client.session.session_key)
self.user.save()
self.team.members.clear()
resp = self.client.get('/control/search/payments/').content.decode()
assert 'FO1' in resp
assert 'FO2' in resp
def test_filter_email(self):
resp = self.client.get('/control/search/payments/?query=dummy1@dummy').content.decode()
assert 'FO1' in resp
resp = self.client.get('/control/search/payments/?query=dummynope').content.decode()
assert 'FO1' not in resp
def test_filter_invoice_name(self):
resp = self.client.get('/control/search/payments/?query=Pete').content.decode()
assert 'FO1' in resp
resp = self.client.get('/control/search/payments/?query=Mark').content.decode()
assert 'FO1' not in resp
def test_filter_invoice_address(self):
resp = self.client.get('/control/search/payments/?query=Ltd').content.decode()
assert 'FO1' in resp
resp = self.client.get('/control/search/payments/?query=Miller').content.decode()
assert 'FO1' in resp
resp = self.client.get('/control/search/payments/?query=Mark').content.decode()
assert 'FO1' not in resp
def test_filter_code(self):
resp = self.client.get('/control/search/payments/?query=FO1').content.decode()
assert '30C3-FO1' in resp
resp = self.client.get('/control/search/payments/?query=30c3-FO1').content.decode()
assert '30C3-FO1' in resp
resp = self.client.get('/control/search/payments/?query=30C3-fO1A').content.decode()
assert '30C3-FO1' in resp
resp = self.client.get('/control/search/payments/?query=30C3-fo14').content.decode()
assert '30C3-FO1' in resp
resp = self.client.get('/control/search/payments/?query=31c3-FO1').content.decode()
assert '30C3-FO1' not in resp
resp = self.client.get('/control/search/payments/?query=FO2').content.decode()
assert '30C3-FO1' not in resp
def test_filter_amount(self):
self.team.all_events = True
self.team.save()
resp = self.client.get('/control/search/payments/?amount=14').content.decode()
assert 'FO1' in resp
assert 'FO2' not in resp
resp = self.client.get('/control/search/payments/?amount=15.00').content.decode()
assert 'FO1' not in resp
assert 'FO2' in resp
def test_filter_event(self):
self.team.all_events = True
self.team.save()
event_id = str(self.event1.pk)
resp = self.client.get('/control/search/payments/?event=' + event_id).content.decode()
assert "FO1" in resp
assert "FO2" not in resp
def test_filter_organizer(self):
self.team2.members.add(self.user)
self.user.save()
b = str(self.orga1.pk)
resp = self.client.get('/control/search/payments/?organizer=' + b).content.decode()
print(resp)
assert "FO1" in resp
b = str(self.orga2.pk)
resp = self.client.get('/control/search/payments/?organizer=' + b).content.decode()
print(resp)
assert "FO1" not in resp
def test_filter_state(self):
self.user.is_staff = True
self.user.staffsession_set.create(date_start=now(), session_key=self.client.session.session_key)
self.user.save()
confirmed = OrderPayment.PAYMENT_STATE_CONFIRMED
resp = self.client.get('/control/search/payments/?state=' + confirmed).content.decode()
assert "P-1" in resp
assert "P-2" not in resp
assert "P-3" not in resp
assert "P-4" not in resp
assert "P-5" not in resp
assert "P-6" not in resp
def test_filter_provider(self):
resp = self.client.get('/control/search/payments/?state=giftcard').content.decode()
assert "P-1" in resp
assert "P-2" not in resp
assert "P-3" not in resp
assert "P-4" not in resp
assert "P-5" not in resp
assert "P-6" not in resp