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
+6
View File
@@ -726,6 +726,12 @@ class Quota(LoggedModel):
if self.event and clear_cache: if self.event and clear_cache:
self.event.get_cache().clear() 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): def cache_is_hot(self, now_dt=None):
now_dt = now_dt or now() now_dt = now_dt or now()
return self.cached_availability_time and (now_dt - self.cached_availability_time).total_seconds() < 120 return self.cached_availability_time and (now_dt - self.cached_availability_time).total_seconds() < 120
+4 -2
View File
@@ -197,7 +197,8 @@ class OrderSearchFilterForm(OrderFilterForm):
class SubEventFilterForm(FilterForm): class SubEventFilterForm(FilterForm):
orders = { orders = {
'date_from': 'date_from', 'date_from': 'date_from',
'active': 'active' 'active': 'active',
'sum_quota_available': 'sum_quota_available'
} }
status = forms.ChoiceField( status = forms.ChoiceField(
label=_('Status'), label=_('Status'),
@@ -258,7 +259,8 @@ class EventFilterForm(FilterForm):
'organizer': 'organizer__name', 'organizer': 'organizer__name',
'date_from': 'order_from', 'date_from': 'order_from',
'date_to': 'order_to', 'date_to': 'order_to',
'live': 'live' 'live': 'live',
'sum_quota_available': 'sum_quota_available'
} }
status = forms.ChoiceField( status = forms.ChoiceField(
label=_('Status'), label=_('Status'),
@@ -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-down"></i></a>
<a href="?{% url_replace request 'ordering' 'date_to' %}"><i class="fa fa-caret-up"></i></a> <a href="?{% url_replace request 'ordering' 'date_to' %}"><i class="fa fa-caret-up"></i></a>
</th> </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"> <th class="text-right">
{% trans "Status" %} {% trans "Status" %}
<a href="?{% url_replace request 'ordering' '-live' %}"><i class="fa fa-caret-down"></i></a> <a href="?{% url_replace request 'ordering' '-live' %}"><i class="fa fa-caret-down"></i></a>
@@ -79,9 +84,6 @@
<tr> <tr>
<td class="event-name-col"> <td class="event-name-col">
<strong><a href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong> <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> <br><small>{{ e.slug }}</small>
</td> </td>
{% if not hide_orga %}<td>{{ e.organizer }}</td>{% endif %} {% if not hide_orga %}<td>{{ e.organizer }}</td>{% endif %}
@@ -91,6 +93,9 @@
{% else %} {% else %}
{{ e.get_short_date_from_display }} {{ e.get_short_date_from_display }}
{% endif %} {% endif %}
{% if e.has_subevents %}
<span class="label label-default">{% trans "Series" %}</span>
{% endif %}
{% if e.settings.show_date_to and e.date_to %} {% if e.settings.show_date_to and e.date_to %}
<br> <br>
{% if e.has_subevents %} {% if e.has_subevents %}
@@ -100,6 +105,18 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </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"> <td class="text-right">
{% if not e.live %} {% if not e.live %}
<span class="label label-danger">{% trans "Shop disabled" %}</span> <span class="label label-danger">{% trans "Shop disabled" %}</span>
@@ -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>
@@ -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-down"></i></a>
<a href="?{% url_replace request 'ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a> <a href="?{% url_replace request 'ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
</th> </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> <th>
{% trans "Status" %} {% trans "Status" %}
<a href="?{% url_replace request 'ordering' '-active' %}"><i class="fa fa-caret-down"></i></a> <a href="?{% url_replace request 'ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
@@ -67,6 +72,18 @@
{{ s.name }}</a></strong> {{ s.name }}</a></strong>
</td> </td>
<td>{{ s.get_date_from_display }}</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> <td>
{% if not s.active %} {% if not s.active %}
<span class="label label-danger">{% trans "Disabled" %}</span> <span class="label label-danger">{% trans "Disabled" %}</span>
+1
View File
@@ -698,6 +698,7 @@ class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
'id': form.instance.pk 'id': form.instance.pk
} }
) )
form.instance.rebuild_cache()
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self) -> str: def get_success_url(self) -> str:
+32 -2
View File
@@ -2,7 +2,9 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction 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.db.models.functions import Coalesce, Greatest
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import redirect from django.shortcuts import redirect
@@ -14,7 +16,7 @@ from django.views.generic import ListView
from formtools.wizard.views import SessionWizardView from formtools.wizard.views import SessionWizardView
from i18nfield.strings import LazyI18nString 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 ( from pretix.control.forms.event import (
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm, EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
) )
@@ -43,6 +45,22 @@ class EventList(ListView):
order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to'), 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(): if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs) qs = self.filter_form.filter_qs(qs)
return qs return qs
@@ -54,6 +72,18 @@ class EventList(ListView):
pk__in=self.request.user.teams.values_list('organizer', flat=True) pk__in=self.request.user.teams.values_list('organizer', flat=True)
).count() ).count()
ctx['hide_orga'] = orga_c <= 1 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 return ctx
@cached_property @cached_property
+28 -1
View File
@@ -3,6 +3,8 @@ import copy
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction 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.forms import inlineformset_factory
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.utils.functional import cached_property from django.utils.functional import cached_property
@@ -29,7 +31,21 @@ class SubEventList(EventPermissionRequiredMixin, ListView):
permission = 'can_change_settings' permission = 'can_change_settings'
def get_queryset(self): 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(): if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs) qs = self.filter_form.filter_qs(qs)
return qs return qs
@@ -37,6 +53,17 @@ class SubEventList(EventPermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form 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 return ctx
@cached_property @cached_property
@@ -88,6 +88,9 @@ $(function () {
}); });
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="tooltip_html"]').tooltip({
'html': true
});
var url = document.location.toString(); var url = document.location.toString();
if (url.match('#')) { if (url.match('#')) {
@@ -403,3 +403,32 @@ body.loading #wrapper {
.event-name-col { .event-name-col {
width: 30%; 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;
}
}