Display quotas in event list

This commit is contained in:
Raphael Michel
2017-10-04 11:25:51 +02:00
parent 217ed905d4
commit f074e642ec
10 changed files with 158 additions and 8 deletions

View File

@@ -726,6 +726,12 @@ class Quota(LoggedModel):
if self.event and clear_cache:
self.event.get_cache().clear()
def rebuild_cache(self, now_dt=None):
self.cached_availability_time = None
self.cached_availability_number = None
self.cached_availability_state = None
self.availability(now_dt=now_dt)
def cache_is_hot(self, now_dt=None):
now_dt = now_dt or now()
return self.cached_availability_time and (now_dt - self.cached_availability_time).total_seconds() < 120

View File

@@ -197,7 +197,8 @@ class OrderSearchFilterForm(OrderFilterForm):
class SubEventFilterForm(FilterForm):
orders = {
'date_from': 'date_from',
'active': 'active'
'active': 'active',
'sum_quota_available': 'sum_quota_available'
}
status = forms.ChoiceField(
label=_('Status'),
@@ -258,7 +259,8 @@ class EventFilterForm(FilterForm):
'organizer': 'organizer__name',
'date_from': 'order_from',
'date_to': 'order_to',
'live': 'live'
'live': 'live',
'sum_quota_available': 'sum_quota_available'
}
status = forms.ChoiceField(
label=_('Status'),

View File

@@ -67,6 +67,11 @@
<a href="?{% url_replace request 'ordering' '-date_to' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'date_to' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Quota available" %}
<a href="?{% url_replace request 'ordering' '-sum_quota_available' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'sum_quota_available' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th class="text-right">
{% trans "Status" %}
<a href="?{% url_replace request 'ordering' '-live' %}"><i class="fa fa-caret-down"></i></a>
@@ -79,9 +84,6 @@
<tr>
<td class="event-name-col">
<strong><a href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong>
{% if e.has_subevents %}
<span class="label label-default">{% trans "Series" %}</span>
{% endif %}
<br><small>{{ e.slug }}</small>
</td>
{% if not hide_orga %}<td>{{ e.organizer }}</td>{% endif %}
@@ -91,6 +93,9 @@
{% else %}
{{ e.get_short_date_from_display }}
{% endif %}
{% if e.has_subevents %}
<span class="label label-default">{% trans "Series" %}</span>
{% endif %}
{% if e.settings.show_date_to and e.date_to %}
<br>
{% if e.has_subevents %}
@@ -100,6 +105,18 @@
{% endif %}
{% endif %}
</td>
<td>
{% for q in e.first_quotas|slice:":3" %}
{% include "pretixcontrol/fragment_quota_box.html" with quota=q %}
{% endfor %}
{% if e.first_quotas|length > 3 %}
<a href="{% url "control:event.items.quotas" organizer=e.organizer.slug event=e.slug %}"
class="quotabox-more" data-toggle="tooltip" title="{% trans "More quotas" %}"
data-placement="top">
&middot;&middot;&middot;
</a>
{% endif %}
</td>
<td class="text-right">
{% if not e.live %}
<span class="label label-danger">{% trans "Shop disabled" %}</span>

View File

@@ -0,0 +1,18 @@
{% load i18n %}
<div class="quotabox" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name }}<br>{% blocktrans with date=q.cached_availability_time|date:"SHORT_DATETIME_FORMAT" %}Numbers as of {{ date }}{% endblocktrans %}">
{% if q.size|default_if_none:"NONE" == "NONE" %}
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-100">
</div>
</div>
{% else %}
<div class="progress">
<div class="progress-bar progress-bar-{% if q.cached_avail.0 <= 10 or q.cached_avail.0 >= 100 %}danger{% else %}warning{% endif %} progress-bar-{{ q.inv_percent }}">
</div>
</div>
{% endif %}
<div class="numbers">
{{ q.cached_avail.1|default_if_none:"∞" }} / {{ q.size|default_if_none:"∞" }}
</div>
</div>

View File

@@ -51,6 +51,11 @@
<a href="?{% url_replace request 'ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Quota available" %}
<a href="?{% url_replace request 'ordering' '-sum_quota_available' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'sum_quota_available' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Status" %}
<a href="?{% url_replace request 'ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
@@ -67,6 +72,18 @@
{{ s.name }}</a></strong>
</td>
<td>{{ s.get_date_from_display }}</td>
<td>
{% for q in s.first_quotas|slice:":3" %}
{% include "pretixcontrol/fragment_quota_box.html" with quota=q %}
{% endfor %}
{% if s.first_quotas|length > 3 %}
<a href="{% url "control:event.items.quotas" organizer=request.event.organizer.slug event=request.event.slug %}?subevent={{ s.id }}"
class="quotabox-more" data-toggle="tooltip" title="{% trans "More quotas" %}"
data-placement="top">
&middot;&middot;&middot;
</a>
{% endif %}
</td>
<td>
{% if not s.active %}
<span class="label label-danger">{% trans "Disabled" %}</span>

View File

@@ -698,6 +698,7 @@ class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
'id': form.instance.pk
}
)
form.instance.rebuild_cache()
return super().form_valid(form)
def get_success_url(self) -> str:

View File

@@ -2,7 +2,9 @@ from django.conf import settings
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Max, Min
from django.db.models import (
F, IntegerField, Max, Min, OuterRef, Prefetch, Subquery, Sum,
)
from django.db.models.functions import Coalesce, Greatest
from django.http import JsonResponse
from django.shortcuts import redirect
@@ -14,7 +16,7 @@ from django.views.generic import ListView
from formtools.wizard.views import SessionWizardView
from i18nfield.strings import LazyI18nString
from pretix.base.models import Event, Organizer, Team
from pretix.base.models import Event, Organizer, Quota, Team
from pretix.control.forms.event import (
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
)
@@ -43,6 +45,22 @@ class EventList(ListView):
order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to'),
)
sum_quota_available = Quota.objects.filter(
event=OuterRef('pk'), subevent__isnull=True
).order_by().values('event').annotate(
s=Sum('cached_availability_number')
).values(
's'
)
qs = qs.annotate(
sum_quota_available=Subquery(sum_quota_available, output_field=IntegerField())
).prefetch_related(
Prefetch('quotas',
queryset=Quota.objects.filter(subevent__isnull=True).annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
to_attr='first_quotas')
)
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
return qs
@@ -54,6 +72,18 @@ class EventList(ListView):
pk__in=self.request.user.teams.values_list('organizer', flat=True)
).count()
ctx['hide_orga'] = orga_c <= 1
for s in ctx['events']:
s.first_quotas = s.first_quotas[:4]
for q in s.first_quotas:
q.cached_avail = (
(q.cached_availability_state, q.cached_availability_number)
if q.cached_availability_time is not None
else q.availability(allow_cache=True)
)
if q.cached_avail[1] is not None:
q.percent = round(q.cached_avail[1] / q.size * 100) if q.size > 0 else 0
q.inv_percent = 100 - q.percent
return ctx
@cached_property

View File

@@ -3,6 +3,8 @@ import copy
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum
from django.db.models.functions import Coalesce
from django.forms import inlineformset_factory
from django.http import Http404, HttpResponseRedirect
from django.utils.functional import cached_property
@@ -29,7 +31,21 @@ class SubEventList(EventPermissionRequiredMixin, ListView):
permission = 'can_change_settings'
def get_queryset(self):
qs = self.request.event.subevents.all()
sum_quota_available = Quota.objects.filter(
subevent=OuterRef('pk')
).order_by().values('subevent').annotate(
s=Sum('cached_availability_number')
).values(
's'
)
qs = self.request.event.subevents.annotate(
sum_quota_available=Subquery(sum_quota_available, output_field=IntegerField())
).prefetch_related(
Prefetch('quotas',
queryset=Quota.objects.annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
to_attr='first_quotas')
)
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
return qs
@@ -37,6 +53,17 @@ class SubEventList(EventPermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form
for s in ctx['subevents']:
s.first_quotas = s.first_quotas[:4]
for q in s.first_quotas:
q.cached_avail = (
(q.cached_availability_state, q.cached_availability_number)
if q.cached_availability_time is not None
else q.availability(allow_cache=True)
)
if q.cached_avail[1] is not None:
q.percent = round(q.cached_avail[1] / q.size * 100) if q.size > 0 else 0
q.inv_percent = 100 - q.percent
return ctx
@cached_property

View File

@@ -88,6 +88,9 @@ $(function () {
});
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="tooltip_html"]').tooltip({
'html': true
});
var url = document.location.toString();
if (url.match('#')) {

View File

@@ -403,3 +403,32 @@ body.loading #wrapper {
.event-name-col {
width: 30%;
}
.quotabox {
display: inline-block;
vertical-align: top;
width: 50px;
.progress {
height: 7px;
margin-bottom: 2px;
}
.numbers {
font-size: 10px;
color: $text-muted;
display: block;
text-align: center;
}
.progress-bar-success {
background: lighten($brand-success, 20%);
}
}
.quotabox-more {
font-weight: bold;
display: inline-block;
vertical-align: top;
line-height: 20px;
margin-top: -6px;
&:hover {
text-decoration: none;
}
}