mirror of
https://github.com/pretix/pretix.git
synced 2026-05-03 14:54:04 +00:00
Add event meta filter to organizer page
This commit is contained in:
@@ -667,7 +667,7 @@ class EventMetaProperty(LoggedModel):
|
||||
name = models.CharField(
|
||||
max_length=50, db_index=True,
|
||||
help_text=_(
|
||||
"Can not contain spaces or special characters execpt underscores"
|
||||
"Can not contain spaces or special characters except underscores"
|
||||
),
|
||||
validators=[
|
||||
RegexValidator(
|
||||
|
||||
@@ -4,7 +4,17 @@ register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def url_replace(request, field, value):
|
||||
def url_replace(request, *pairs):
|
||||
dict_ = request.GET.copy()
|
||||
dict_[field] = value
|
||||
return dict_.urlencode()
|
||||
key = None
|
||||
for p in pairs:
|
||||
if key is None:
|
||||
key = p
|
||||
else:
|
||||
if p == "":
|
||||
if key in dict_:
|
||||
del dict_[key]
|
||||
else:
|
||||
dict_[key] = p
|
||||
key = None
|
||||
return dict_.urlencode(safe='[]')
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% load rich_text %}
|
||||
{% load eventurl %}
|
||||
{% load urlreplace %}
|
||||
{% block title %}{% trans "Event overview" %}{% endblock %}
|
||||
{% block content %}
|
||||
{% if organizer_homepage_text %}
|
||||
@@ -11,21 +12,26 @@
|
||||
{% endif %}
|
||||
<h3>{{ date|date:"F Y" }}</h3>
|
||||
<form class="form-inline" method="get" id="monthselform" action="{% eventurl request.organizer "presale:organizer.index" %}">
|
||||
<input type="hidden" name="style" value="calendar">
|
||||
{% for f, v in request.GET.items %}
|
||||
{% if f != "month" and f != "year" %}
|
||||
<input type="hidden" name="{{ f }}" value="{{ v }}">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-xs-12">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% eventurl request.organizer "presale:organizer.index" %}?style=list" type="button" class="btn btn-default">
|
||||
<a href="?{% url_replace request "style" "list" %}" type="button" class="btn btn-default">
|
||||
<span class="fa fa-list"></span>
|
||||
{% trans "List" %}
|
||||
</a>
|
||||
<a href="{% eventurl request.organizer "presale:organizer.index" %}?style=calendar" type="button"
|
||||
<a href="?{% url_replace request "style" "calendar" %}"
|
||||
type="button"
|
||||
class="btn btn-default active">
|
||||
<span class="fa fa-calendar"></span>
|
||||
{% trans "Calendar" %}
|
||||
</a>
|
||||
</div>
|
||||
<a href="{% eventurl request.organizer "presale:organizer.ical" %}?locale={{ request.LANGUAGE_CODE }}"
|
||||
<a href="{% eventurl request.organizer "presale:organizer.ical" %}?{% url_replace request "locale" request.LANGUAGE_CODE "style" "" "month" "" "year" "" %}"
|
||||
class="btn btn-default">
|
||||
<span class="fa fa-calendar-plus-o"></span>
|
||||
{% trans "iCal" %}
|
||||
@@ -47,13 +53,11 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-4 hidden-xs text-right">
|
||||
<a href="{% eventurl request.organizer "presale:organizer.index" %}?style=calendar&year={{ before.year }}&month={{ before.month }}"
|
||||
class="btn btn-default">
|
||||
<a href="?{% url_replace request "year" before.year "month" before.month %}" class="btn btn-default">
|
||||
<span class="fa fa-arrow-left"></span>
|
||||
{{ before|date:"F Y" }}
|
||||
</a>
|
||||
<a href="{% eventurl request.organizer "presale:organizer.index" %}?style=calendar&year={{ after.year }}&month={{ after.month }}"
|
||||
class="btn btn-default">
|
||||
<a href="?{% url_replace request "year" after.year "month" after.month %}" class="btn btn-default">
|
||||
<span class="fa fa-arrow-right"></span>
|
||||
{{ after|date:"F Y" }}
|
||||
</a>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% load rich_text %}
|
||||
{% load eventurl %}
|
||||
{% load urlreplace %}
|
||||
{% block title %}{% trans "Event list" %}{% endblock %}
|
||||
{% block content %}
|
||||
<div>
|
||||
@@ -16,26 +17,26 @@
|
||||
{% endif %}
|
||||
<div>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% eventurl request.organizer "presale:organizer.index" %}?style=list" type="button"
|
||||
<a href="?{% url_replace request "style" "list" %}" type="button"
|
||||
class="btn btn-default active">
|
||||
<span class="fa fa-list"></span>
|
||||
{% trans "List" %}
|
||||
</a>
|
||||
<a href="{% eventurl request.organizer "presale:organizer.index" %}?style=calendar" type="button"
|
||||
<a href="?{% url_replace request "style" "calendar" %}" type="button"
|
||||
class="btn btn-default">
|
||||
<span class="fa fa-calendar"></span>
|
||||
{% trans "Calendar" %}
|
||||
</a>
|
||||
</div>
|
||||
<a href="{% eventurl request.organizer "presale:organizer.ical" %}?locale={{ request.LANGUAGE_CODE }}"
|
||||
<a href="{% eventurl request.organizer "presale:organizer.ical" %}?{% eventurl request.organizer "presale:organizer.ical" %}?{% url_replace request "locale" request.LANGUAGE_CODE "style" "" "month" "" "year" "" %}"
|
||||
class="btn btn-default">
|
||||
<span class="fa fa-calendar-plus-o"></span>
|
||||
{% trans "iCal" %}
|
||||
</a>
|
||||
{% if "old" in request.GET %}
|
||||
<a href="?style=list" class="btn btn-link">{% trans "Show upcoming" %}</a>
|
||||
<a href="?{% url_replace request "old" "" %}" class="btn btn-link">{% trans "Show upcoming" %}</a>
|
||||
{% else %}
|
||||
<a href="?style=list&old=1" class="btn btn-link">{% trans "Show past events" %}</a>
|
||||
<a href="?{% url_replace request "old" "1" %}" class="btn btn-link">{% trans "Show past events" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
|
||||
@@ -4,7 +4,7 @@ from datetime import date, datetime, timedelta
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.http import HttpResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import now
|
||||
@@ -14,12 +14,73 @@ from django.views.generic import ListView, TemplateView
|
||||
from pytz import UTC
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Event, SubEvent
|
||||
from pretix.base.models import (
|
||||
Event, EventMetaValue, SubEvent, SubEventMetaValue,
|
||||
)
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.ical import get_ical
|
||||
from pretix.presale.views import OrganizerViewMixin
|
||||
|
||||
|
||||
def filter_qs_by_attr(qs, request):
|
||||
"""
|
||||
We'll allow to filter the event list using attributes defined in the event meta data
|
||||
models in the format ?attr[meta_name]=meta_value
|
||||
"""
|
||||
attrs = {}
|
||||
for i, item in enumerate(request.GET.items()):
|
||||
k, v = item
|
||||
if k.startswith("attr[") and k.endswith("]"):
|
||||
attrs[k[5:-1]] = v
|
||||
|
||||
props = {
|
||||
p.name: p for p in request.organizer.meta_properties.filter(
|
||||
name__in=attrs.keys()
|
||||
)
|
||||
}
|
||||
|
||||
for i, item in enumerate(attrs.items()):
|
||||
attr, v = item
|
||||
emv_with_value = EventMetaValue.objects.filter(
|
||||
event=OuterRef('event' if qs.model == SubEvent else 'pk'),
|
||||
property__name=attr,
|
||||
value=v
|
||||
)
|
||||
emv_with_any_value = EventMetaValue.objects.filter(
|
||||
event=OuterRef('event' if qs.model == SubEvent else 'pk'),
|
||||
property__name=attr,
|
||||
)
|
||||
if qs.model == SubEvent:
|
||||
semv_with_value = SubEventMetaValue.objects.filter(
|
||||
subevent=OuterRef('pk'),
|
||||
property__name=attr,
|
||||
value=v
|
||||
)
|
||||
semv_with_any_value = SubEventMetaValue.objects.filter(
|
||||
subevent=OuterRef('pk'),
|
||||
property__name=attr,
|
||||
)
|
||||
|
||||
prop = props[attr]
|
||||
annotations = {'attr_{}'.format(i): Exists(emv_with_value)}
|
||||
if qs.model == SubEvent:
|
||||
annotations['attr_{}_sub'.format(i)] = Exists(semv_with_value)
|
||||
annotations['attr_{}_sub_any'.format(i)] = Exists(semv_with_any_value)
|
||||
filters = Q(**{'attr_{}_sub'.format(i): True})
|
||||
filters |= Q(Q(**{'attr_{}_sub_any'.format(i): False}) & Q(**{'attr_{}'.format(i): True}))
|
||||
if prop.default == v:
|
||||
annotations['attr_{}_any'.format(i)] = Exists(emv_with_any_value)
|
||||
filters |= Q(Q(**{'attr_{}_sub_any'.format(i): False}) & Q(**{'attr_{}_any'.format(i): False}))
|
||||
else:
|
||||
filters = Q(**{'attr_{}'.format(i): True})
|
||||
if prop.default == v:
|
||||
annotations['attr_{}_any'.format(i)] = Exists(emv_with_any_value)
|
||||
filters |= Q(**{'attr_{}_any'.format(i): False})
|
||||
|
||||
qs = qs.annotate(**annotations).filter(filters)
|
||||
return qs
|
||||
|
||||
|
||||
class OrganizerIndex(OrganizerViewMixin, ListView):
|
||||
model = Event
|
||||
context_object_name = 'events'
|
||||
@@ -43,12 +104,14 @@ class OrganizerIndex(OrganizerViewMixin, ListView):
|
||||
else:
|
||||
query &= Q(Q(date_from__gte=now()) | Q(date_to__gte=now()))
|
||||
order = 'date_from'
|
||||
return Event.objects.filter(
|
||||
qs = Event.objects.filter(
|
||||
Q(organizer=self.request.organizer) & query
|
||||
).order_by(order)
|
||||
)
|
||||
qs = filter_qs_by_attr(qs, self.request)
|
||||
return qs.order_by(order)
|
||||
|
||||
|
||||
def add_events_for_days(organizer, before, after, ebd, timezones):
|
||||
def add_events_for_days(request, organizer, before, after, ebd, timezones):
|
||||
qs = organizer.events.filter(is_public=True, live=True, has_subevents=False).filter(
|
||||
Q(Q(date_to__gte=before) & Q(date_from__lte=after)) |
|
||||
Q(Q(date_from__lte=after) & Q(date_to__gte=before)) |
|
||||
@@ -58,6 +121,7 @@ def add_events_for_days(organizer, before, after, ebd, timezones):
|
||||
).prefetch_related(
|
||||
'_settings_objects', 'organizer___settings_objects'
|
||||
)
|
||||
qs = filter_qs_by_attr(qs, request)
|
||||
for event in qs:
|
||||
timezones.add(event.settings.timezones)
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
@@ -161,19 +225,19 @@ class CalendarView(OrganizerViewMixin, TemplateView):
|
||||
self.year = now().year
|
||||
self.month = now().month
|
||||
else:
|
||||
next_ev = Event.objects.filter(
|
||||
next_ev = filter_qs_by_attr(Event.objects.filter(
|
||||
live=True,
|
||||
is_public=True,
|
||||
date_from__gte=now(),
|
||||
has_subevents=False
|
||||
).order_by('date_from').first()
|
||||
next_sev = SubEvent.objects.filter(
|
||||
), self.request).order_by('date_from').first()
|
||||
next_sev = filter_qs_by_attr(SubEvent.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
active=True,
|
||||
date_from__gte=now()
|
||||
).select_related('event').order_by('date_from').first()
|
||||
), self.request).select_related('event').order_by('date_from').first()
|
||||
|
||||
datetime_from = None
|
||||
if (next_ev and next_sev and next_sev.date_from < next_ev.date_from) or (next_sev and not next_ev):
|
||||
@@ -213,14 +277,14 @@ class CalendarView(OrganizerViewMixin, TemplateView):
|
||||
def _events_by_day(self, before, after):
|
||||
ebd = defaultdict(list)
|
||||
timezones = set()
|
||||
add_events_for_days(self.request.organizer, before, after, ebd, timezones)
|
||||
add_subevents_for_days(SubEvent.objects.filter(
|
||||
add_events_for_days(self.request, self.request.organizer, before, after, ebd, timezones)
|
||||
add_subevents_for_days(filter_qs_by_attr(SubEvent.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
).prefetch_related(
|
||||
'event___settings_objects', 'event__organizer___settings_objects'
|
||||
), before, after, ebd, timezones)
|
||||
), self.request), before, after, ebd, timezones)
|
||||
self._multiple_timezones = len(timezones) > 1
|
||||
return ebd
|
||||
|
||||
@@ -229,18 +293,24 @@ class CalendarView(OrganizerViewMixin, TemplateView):
|
||||
class OrganizerIcalDownload(OrganizerViewMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
events = list(
|
||||
self.request.organizer.events.filter(is_public=True, live=True, has_subevents=False).order_by(
|
||||
filter_qs_by_attr(
|
||||
self.request.organizer.events.filter(is_public=True, live=True, has_subevents=False),
|
||||
request
|
||||
).order_by(
|
||||
'date_from'
|
||||
).prefetch_related(
|
||||
'_settings_objects', 'organizer___settings_objects'
|
||||
)
|
||||
)
|
||||
events += list(
|
||||
SubEvent.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
active=True
|
||||
filter_qs_by_attr(
|
||||
SubEvent.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
active=True
|
||||
),
|
||||
request
|
||||
).prefetch_related(
|
||||
'event___settings_objects', 'event__organizer___settings_objects'
|
||||
).order_by(
|
||||
|
||||
@@ -33,6 +33,25 @@ def test_public_event_on_page(env, client):
|
||||
assert 'MRMCD2015' in r.rendered_content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_attributes_on_page(env, client):
|
||||
env[1].is_public = True
|
||||
env[1].save()
|
||||
|
||||
prop = env[0].meta_properties.create(name='loc', default='HH')
|
||||
propval = env[1].meta_values.create(value='HD', property=prop)
|
||||
|
||||
r = client.get('/mrmcd/?attr[loc]=HD')
|
||||
assert 'MRMCD2015' in r.rendered_content
|
||||
r = client.get('/mrmcd/?attr[loc]=MA')
|
||||
assert 'MRMCD2015' not in r.rendered_content
|
||||
r = client.get('/mrmcd/?attr[loc]=HH')
|
||||
assert 'MRMCD2015' not in r.rendered_content
|
||||
propval.delete()
|
||||
r = client.get('/mrmcd/?attr[loc]=HH')
|
||||
assert 'MRMCD2015' in r.rendered_content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_non_public_event_not_on_page(env, client):
|
||||
env[1].is_public = False
|
||||
@@ -118,6 +137,24 @@ def test_calendar(env, client):
|
||||
assert 'October 2017' in r.rendered_content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_attributes_in_calendar(env, client):
|
||||
env[0].settings.event_list_type = 'calendar'
|
||||
e = Event.objects.create(
|
||||
organizer=env[0], name='MRMCD2017', slug='2017',
|
||||
date_from=datetime(now().year + 1, 9, 1, tzinfo=UTC),
|
||||
live=True, is_public=True
|
||||
)
|
||||
prop = env[0].meta_properties.create(name='loc')
|
||||
e.meta_values.create(value='HD', property=prop)
|
||||
|
||||
r = client.get('/mrmcd/?attr[loc]=HD&style=calendar')
|
||||
print(r.rendered_content)
|
||||
assert 'MRMCD2017' in r.rendered_content
|
||||
r = client.get('/mrmcd/?attr[loc]=MA&style=calendar')
|
||||
assert 'MRMCD2017' not in r.rendered_content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_ics(env, client):
|
||||
e = Event.objects.create(
|
||||
@@ -144,3 +181,39 @@ def test_ics_subevents(env, client):
|
||||
r = client.get('/mrmcd/events/ical/')
|
||||
assert b'MRMCD2017' not in r.content
|
||||
assert b'SE1' in r.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_ics_subevents_attributes(env, client):
|
||||
e0 = Event.objects.create(
|
||||
organizer=env[0], name='DS2017', slug='DS2017',
|
||||
date_from=datetime(now().year + 1, 9, 1, tzinfo=UTC),
|
||||
live=True, is_public=True
|
||||
)
|
||||
e = Event.objects.create(
|
||||
organizer=env[0], name='MRMCD2017', slug='2017',
|
||||
date_from=datetime(now().year + 1, 9, 1, tzinfo=UTC),
|
||||
live=True, is_public=True, has_subevents=True
|
||||
)
|
||||
se1 = e.subevents.create(date_from=now(), name='SE1', active=True)
|
||||
|
||||
prop = env[0].meta_properties.create(name='loc', default='HH')
|
||||
e0.meta_values.create(value='MA', property=prop)
|
||||
propval = se1.meta_values.create(value='HD', property=prop)
|
||||
r = client.get('/mrmcd/events/ical/?attr[loc]=HD')
|
||||
assert b'SE1' in r.content
|
||||
assert b'DS2017' not in r.content
|
||||
r = client.get('/mrmcd/events/ical/?attr[loc]=MA')
|
||||
assert b'SE1' not in r.content
|
||||
assert b'DS2017' in r.content
|
||||
|
||||
r = client.get('/mrmcd/events/ical/?attr[loc]=HH')
|
||||
assert b'SE1' not in r.content
|
||||
propval.delete()
|
||||
r = client.get('/mrmcd/events/ical/?attr[loc]=HH')
|
||||
assert b'SE1' in r.content
|
||||
e.meta_values.create(value='B', property=prop)
|
||||
r = client.get('/mrmcd/events/ical/?attr[loc]=HH')
|
||||
assert b'SE1' not in r.content
|
||||
r = client.get('/mrmcd/events/ical/?attr[loc]=B')
|
||||
assert b'SE1' in r.content
|
||||
|
||||
Reference in New Issue
Block a user