mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +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:
|
||||
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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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">
|
||||
···
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% if not e.live %}
|
||||
<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-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">
|
||||
···
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not s.active %}
|
||||
<span class="label label-danger">{% trans "Disabled" %}</span>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -88,6 +88,9 @@ $(function () {
|
||||
});
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('[data-toggle="tooltip_html"]').tooltip({
|
||||
'html': true
|
||||
});
|
||||
|
||||
var url = document.location.toString();
|
||||
if (url.match('#')) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user