diff --git a/src/pretix/base/migrations/0021_auto_20150320_1622.py b/src/pretix/base/migrations/0021_auto_20150320_1622.py new file mode 100644 index 0000000000..64d4acf42c --- /dev/null +++ b/src/pretix/base/migrations/0021_auto_20150320_1622.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.core.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0020_auto_20150319_1044'), + ] + + operations = [ + migrations.AddField( + model_name='eventpermission', + name='can_change_orders', + field=models.BooleanField(default=True, verbose_name='Can change orders'), + ), + migrations.AddField( + model_name='eventpermission', + name='can_view_orders', + field=models.BooleanField(default=True, verbose_name='Can view orders'), + ), + migrations.AlterField( + model_name='event', + name='slug', + field=models.SlugField(verbose_name='Slug', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$')], help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.'), + ), + ] diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py index fc9c81e7f0..6e985e64c0 100644 --- a/src/pretix/base/models.py +++ b/src/pretix/base/models.py @@ -535,6 +535,10 @@ class EventPermission(Versionable): :type can_change_settings: bool :param can_change_items: If ``True``, the user can change and add items and related objects for this event. :type can_change_items: bool + :param can_view_orders: If ``True``, the user can inspect details of all orders. + :type can_view_orders: bool + :param can_change_orders: If ``True``, the user can change details of orders + :type can_change_orders: bool """ event = VersionedForeignKey(Event) @@ -547,6 +551,14 @@ class EventPermission(Versionable): default=True, verbose_name=_("Can change product settings") ) + can_view_orders = models.BooleanField( + default=True, + verbose_name=_("Can view orders") + ) + can_change_orders = models.BooleanField( + default=True, + verbose_name=_("Can change orders") + ) class Meta: verbose_name = _("Event permission") diff --git a/src/pretix/control/static/pretixcontrol/less/main.less b/src/pretix/control/static/pretixcontrol/less/main.less index 60b2e66545..52417816f9 100644 --- a/src/pretix/control/static/pretixcontrol/less/main.less +++ b/src/pretix/control/static/pretixcontrol/less/main.less @@ -18,4 +18,32 @@ nav.navbar { .nav-pills { margin-bottom: 10px; -} \ No newline at end of file +} +.product-row { + padding: 10px 0; + + .count form { + display: inline; + } + .price, .count { + text-align: right; + } + .price small, + .availability-box small { + display: block; + line-height: 1; + } + + &.total { + border-top: 1px solid @table-border-color; + } + + dl { + padding-left: 20px; + margin-bottom: 0; + + dd { + padding-left: 20px; + } + } +} diff --git a/src/pretix/control/templates/pretixcontrol/event/base.html b/src/pretix/control/templates/pretixcontrol/event/base.html index c4efdee71d..31c84cb170 100644 --- a/src/pretix/control/templates/pretixcontrol/event/base.html +++ b/src/pretix/control/templates/pretixcontrol/event/base.html @@ -76,7 +76,7 @@
  • + {% if "event.order" in url_name %}class="active"{% endif %}> {% trans "Orders" %} diff --git a/src/pretix/control/templates/pretixcontrol/order/index.html b/src/pretix/control/templates/pretixcontrol/order/index.html new file mode 100644 index 0000000000..3f2b0394b1 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/order/index.html @@ -0,0 +1,81 @@ +{% extends "pretixcontrol/event/base.html" %} +{% load i18n %} +{% block title %} + {% blocktrans trimmed with code=order.code %} + Order details: {{ code }} + {% endblocktrans %} +{% endblock %} +{% block content %} +

    + {% blocktrans trimmed with code=order.code %} + Order details: {{ code }} + {% endblocktrans %} +

    +
    +
    +

    + {% trans "Ordered items" %} +

    +
    +
    + {% for line in items.positions %} +
    +
    + {{ line.item }} + {% if line.variation %} + – {{ line.variation }} + {% endif %} + {% if line.has_questions %} +
    + {% if line.item.admission and event.settings.attendee_names_asked == 'True' %} +
    {% trans "Attendee name" %}
    +
    {% if line.attendee_name %}{{ line.attendee_name }}{% else %}{% trans "not answered" %}{% endif %}
    + {% endif %} + {% for q in line.questions %} +
    {{ q.question }}
    +
    {% if q.answer %}{{ q.answer }}{% else %}{% trans "not answered" %}{% endif %}
    + {% endfor %} +
    + {% endif %} +
    +
    + {{ event.currency }} {{ line.price|floatformat:2 }} +
    +
    + {{ line.count }} +
    +
    + {{ event.currency }} {{ line.total|floatformat:2 }} + {% if line.item.tax_rate %} +
    {% blocktrans trimmed with rate=line.item.tax_rate %} + incl. {{ rate }}% taxes + {% endblocktrans %} + {% endif %} +
    +
    +
    + {% endfor %} + {% if items.payment_fee %} + {# TODO: Tax rate? #} +
    +
    + {% trans "Payment method fee" %} +
    +
    + {{ event.currency }} {{ items.payment_fee|floatformat:2 }} +
    +
    +
    + {% endif %} +
    +
    + {% trans "Total" %} +
    +
    + {{ event.currency }} {{ items.total|floatformat:2 }} +
    +
    +
    +
    +
    +{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/orders/index.html b/src/pretix/control/templates/pretixcontrol/orders/index.html index 121faaf5d4..e8e6e7f1e5 100644 --- a/src/pretix/control/templates/pretixcontrol/orders/index.html +++ b/src/pretix/control/templates/pretixcontrol/orders/index.html @@ -16,7 +16,9 @@ {% for o in orders %} - {{ o.code }} + {{ o.code }} {{ o.user.get_short_name }} {{ o.total|floatformat:2 }} {{ request.event.currency }} {{ o.datetime|date }} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 20ac8c5cde..c12abb346f 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -46,6 +46,7 @@ urlpatterns = [ url(r'^quotas/(?P[0-9a-f-]+)/delete$', item.QuotaDelete.as_view(), name='event.items.quotas.delete'), url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'), + url(r'^orders/(?P[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'), url(r'^orders/$', orders.OrderList.as_view(), name='event.orders'), ])), ] diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py index dca64a2555..13223a7db4 100644 --- a/src/pretix/control/views/orders.py +++ b/src/pretix/control/views/orders.py @@ -1,15 +1,76 @@ -from django.views.generic import ListView +from itertools import groupby +from django.db.models import Q +from django.views.generic import ListView, DetailView from pretix.base.models import Order +from pretix.control.permissions import EventPermissionRequiredMixin -class OrderList(ListView): +class OrderList(EventPermissionRequiredMixin, ListView): model = Order context_object_name = 'orders' template_name = 'pretixcontrol/orders/index.html' paginate_by = 30 + permission = 'can_view_orders' def get_queryset(self): return Order.objects.current.filter( event=self.request.event ).select_related("user") + + +class OrderDetail(EventPermissionRequiredMixin, DetailView): + model = Order + context_object_name = 'order' + template_name = 'pretixcontrol/order/index.html' + permission = 'can_view_orders' + + def get_object(self, queryset=None): + return Order.objects.current.get( + event=self.request.event, + code=self.kwargs['code'].upper() + ) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['items'] = self.get_items() + ctx['event'] = self.request.event + return ctx + + def get_items(self): + queryset = self.object.positions.all() + + cartpos = queryset.order_by( + 'item', 'variation' + ).select_related( + 'item', 'variation' + ).prefetch_related( + 'variation__values', 'variation__values__prop', 'item__questions', + 'answers' + ) + + # Group items of the same variation + # We do this by list manipulations instead of a GROUP BY query, as + # Django is unable to join related models in a .values() query + def keyfunc(pos): + if ((pos.item.admission and self.request.event.settings.attendee_names_asked == 'True') + or pos.item.questions.all()): + return pos.id, "", "", "" + return "", pos.item_id, pos.variation_id, pos.price + + positions = [] + for k, g in groupby(sorted(list(cartpos), key=keyfunc), key=keyfunc): + g = list(g) + group = g[0] + group.count = len(g) + group.total = group.count * group.price + group.has_questions = k[0] != "" + group.cache_answers() + positions.append(group) + + return { + 'positions': positions, + 'raw': cartpos, + 'total': self.object.total, + 'payment_fee': self.object.payment_fee, + }