mirror of
https://github.com/pretix/pretix.git
synced 2026-08-21 12:36:26 +00:00
Display quotas in event list
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
···
|
||||||
|
</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">
|
||||||
|
···
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user