List of events: Optional calendar view

This commit is contained in:
Raphael Michel
2017-07-08 22:50:48 +02:00
parent 675956a7c4
commit 275d162b81
11 changed files with 267 additions and 4 deletions

View File

@@ -175,6 +175,10 @@ DEFAULTS = {
'default': 'True', 'default': 'True',
'type': bool 'type': bool
}, },
'event_list_type': {
'default': 'list',
'type': str
},
'last_order_modification_date': { 'last_order_modification_date': {
'default': None, 'default': None,
'type': datetime 'type': datetime

View File

@@ -31,6 +31,7 @@ class EventSlugBlacklistValidator(BlacklistValidator):
'_global', '_global',
'__debug__', '__debug__',
'api', 'api',
'events',
] ]

View File

@@ -126,3 +126,11 @@ class OrganizerSettingsForm(SettingsForm):
help_text=_('If you provide a logo image, we will by default not show your organization name ' help_text=_('If you provide a logo image, we will by default not show your organization name '
'in the page header. We will show your logo with a maximal height of 120 pixels.') 'in the page header. We will show your logo with a maximal height of 120 pixels.')
) )
event_list_type = forms.ChoiceField(
label=_('Event overview stile'),
choices=(
('list', _('List')),
('calendar', _('Calendar'))
)
)

View File

@@ -21,6 +21,7 @@
{% bootstrap_field sform.locales layout="horizontal" %} {% bootstrap_field sform.locales layout="horizontal" %}
{% bootstrap_field sform.organizer_logo_image layout="horizontal" %} {% bootstrap_field sform.organizer_logo_image layout="horizontal" %}
{% bootstrap_field sform.organizer_homepage_text layout="horizontal" %} {% bootstrap_field sform.organizer_homepage_text layout="horizontal" %}
{% bootstrap_field sform.event_list_type layout="horizontal" %}
</fieldset> </fieldset>
<div class="form-group submit-group"> <div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">

View File

@@ -12,8 +12,8 @@ from pretix.urls import common_patterns
presale_patterns_main = [ presale_patterns_main = [
url(r'', include((locale_patterns + [ url(r'', include((locale_patterns + [
url(r'^(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include(event_patterns)),
url(r'^(?P<organizer>[^/]+)/', include(organizer_patterns)), url(r'^(?P<organizer>[^/]+)/', include(organizer_patterns)),
url(r'^(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include(event_patterns)),
url(r'^$', TemplateView.as_view(template_name='pretixpresale/index.html')) url(r'^$', TemplateView.as_view(template_name='pretixpresale/index.html'))
], 'presale'))) ], 'presale')))
] ]

View File

@@ -11,8 +11,8 @@ from pretix.urls import common_patterns
presale_patterns = [ presale_patterns = [
url(r'', include((locale_patterns + [ url(r'', include((locale_patterns + [
url(r'', include(organizer_patterns)),
url(r'^(?P<event>[^/]+)/', include(event_patterns)), url(r'^(?P<event>[^/]+)/', include(event_patterns)),
url(r'', include(organizer_patterns))
], 'presale'))) ], 'presale')))
] ]

View File

@@ -0,0 +1,99 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load rich_text %}
{% load eventurl %}
{% block title %}{% trans "Event overview" %}{% endblock %}
{% block content %}
<div>
{% if organizer_homepage_text %}
{{ organizer_homepage_text | rich_text }}
{% endif %}
</div>
<h3 class="text-center">{{ date|date:"F Y" }}</h3>
<div class="row">
<div class="col-sm-6">
<a href="{% eventurl request.organizer "presale:organizer.calendar" year=before.year month=before.month %}"
class="btn btn-default">
<span class="fa fa-arrow-left"></span>
{{ before|date:"F Y" }}
</a>
</div>
<div class="col-sm-6 text-right">
<a href="{% eventurl request.organizer "presale:organizer.calendar" year=after.year month=after.month %}"
class="btn btn-default">
<span class="fa fa-arrow-right"></span>
{{ after|date:"F Y" }}
</a>
</div>
</div>
<div class="table-responsive">
<table class="table table-calendar">
<thead>
<tr>
<th>{{ weeks.1.0.date|date:"D" }}</th>
<th>{{ weeks.1.1.date|date:"D" }}</th>
<th>{{ weeks.1.2.date|date:"D" }}</th>
<th>{{ weeks.1.3.date|date:"D" }}</th>
<th>{{ weeks.1.4.date|date:"D" }}</th>
<th>{{ weeks.1.5.date|date:"D" }}</th>
<th>{{ weeks.1.6.date|date:"D" }}</th>
</tr>
</thead>
<tbody>
{% for week in weeks %}
<tr>
{% for day in week %}
{% if day %}
<td class="day">
<h3>{{ day.day }}</h3>
{% for event in day.events %}
<a class="event {% if event.continued %}continued{% endif %}" href="{{ event.url }}">
<span class="event-name">
{{ event.event.name }}
</span>
{% if not event.continued %}
{% if event.time %}
<span class="event-time">
<span class="fa fa-clock-o"></span>
{{ event.time|date:"TIME_FORMAT" }}
{% if multiple_timezones %}
{{ event.timezone }}
{% endif %}
</span>
{% endif %}
<span class="event-status">
{% if event.event.presale_is_running %}
<span class="fa fa-ticket"></span> {% trans "Tickets on sale" %}
{% elif event.event.presale_has_ended %}
<span class="fa fa-ticket"></span> {% trans "Sale over" %}
{% elif event.event.settings.presale_start_show_date and event.event.presale_start %}
<span class="fa fa-ticket"></span>
{% blocktrans with start_date=event.event.presale_start|date:"SHORT_DATE_FORMAT" %}
from {{ start_date }}
{% endblocktrans %}
{% else %}
<span class="fa fa-ticket"></span> {% trans "Soon" %}
{% endif %}
</span>
{% endif %}
</a>
{% endfor %}
</td>
{% else %}
<td class="no-day"></td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% if multiple_timezones %}
<div class="alert alert-info">
{% blocktrans trimmed %}
Note that the events in this view are in different timezones.
{% endblocktrans %}
</div>
{% endif %}
{% include "pretixpresale/pagination.html" %}
{% endblock %}

View File

@@ -72,6 +72,12 @@ event_patterns = [
organizer_patterns = [ organizer_patterns = [
url(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'), url(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'),
url(r'^events/$',
pretix.presale.views.organizer.CalendarView.as_view(),
name='organizer.calendar'),
url(r'^events/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$',
pretix.presale.views.organizer.CalendarView.as_view(),
name='organizer.calendar'),
] ]
locale_patterns = [ locale_patterns = [

View File

@@ -1,8 +1,15 @@
import calendar
from collections import defaultdict
from datetime import date, datetime, timedelta
import pytz
from django.db.models import Q from django.db.models import Q
from django.utils.timezone import now from django.utils.timezone import now
from django.views.generic import ListView from django.views.generic import ListView, TemplateView
from pytz import UTC
from pretix.base.models import Event from pretix.base.models import Event
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.views import OrganizerViewMixin from pretix.presale.views import OrganizerViewMixin
@@ -12,8 +19,16 @@ class OrganizerIndex(OrganizerViewMixin, ListView):
template_name = 'pretixpresale/organizers/index.html' template_name = 'pretixpresale/organizers/index.html'
paginate_by = 30 paginate_by = 30
def get(self, request, *args, **kwargs):
if request.organizer.settings.event_list_type == 'calendar':
cv = CalendarView()
cv.request = request
return cv.get(request, *args, **kwargs)
else:
return super().get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
query = Q(is_public=True) query = Q(is_public=True) & Q(live=True)
if "old" in self.request.GET: if "old" in self.request.GET:
query &= Q(Q(date_from__lte=now()) & Q(date_to__lte=now())) query &= Q(Q(date_from__lte=now()) & Q(date_to__lte=now()))
order = '-date_from' order = '-date_from'
@@ -23,3 +38,95 @@ class OrganizerIndex(OrganizerViewMixin, ListView):
return Event.objects.filter( return Event.objects.filter(
Q(organizer=self.request.organizer) & query Q(organizer=self.request.organizer) & query
).order_by(order) ).order_by(order)
class CalendarView(OrganizerViewMixin, TemplateView):
template_name = 'pretixpresale/organizers/calendar.html'
def get(self, request, *args, **kwargs):
if 'year' in kwargs and 'month' in kwargs:
self.year = int(kwargs.get('year'))
self.month = int(kwargs.get('month'))
else:
next_ev = Event.objects.filter(live=True, is_public=True, date_from__gte=now()).order_by('date_from').first()
tz = pytz.timezone(next_ev.settings.timezone)
datetime_from = next_ev.date_from.astimezone(tz)
self.year = datetime_from.year
self.month = datetime_from.month
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
_, ndays = calendar.monthrange(self.year, self.month)
before = datetime(self.year, self.month, 1, 0, 0, 0, tzinfo=UTC) - timedelta(days=1)
after = datetime(self.year, self.month, ndays, 0, 0, 0, tzinfo=UTC) + timedelta(days=1)
ctx['date'] = date(self.year, self.month, 1)
ctx['before'] = before
ctx['after'] = after
ebd = self._events_by_day()
calendar.setfirstweekday(0) # TODO: Configurable
ctx['multiple_timezones'] = self._multiple_timezones
ctx['weeks'] = [
[
{
'day': day,
'date': date(self.year, self.month, day),
'events': ebd[date(self.year, self.month, day)]
}
if day > 0
else None
for day in week
]
for week in calendar.monthcalendar(self.year, self.month)
]
return ctx
def _events_by_day(self):
_, ndays = calendar.monthrange(self.year, self.month)
before = datetime(self.year, self.month, 1, 0, 0, 0, tzinfo=UTC) - timedelta(days=1)
after = datetime(self.year, self.month, ndays, 0, 0, 0, tzinfo=UTC) + timedelta(days=1)
ebd = defaultdict(list)
qs = self.request.organizer.events.filter(is_public=True, live=True).filter(
Q(Q(date_to__gte=before) & Q(date_from__lte=after)) |
Q(Q(date_from__lte=after) & Q(date_to__gte=before)) |
Q(Q(date_to__isnull=True) & Q(date_from__gte=before) & Q(date_from__lte=after))
).order_by(
'date_from'
).prefetch_related(
'_settings_objects', 'organizer___settings_objects'
)
timezones = set()
for event in qs:
timezones.add(event.settings.timezones)
tz = pytz.timezone(event.settings.timezone)
datetime_from = event.date_from.astimezone(tz)
date_from = datetime_from.date()
if event.settings.show_date_to and event.date_to:
date_to = event.date_to.astimezone(tz).date()
d = date_from
while d <= date_to:
first = d == date_from
ebd[d].append({
'event': event,
'continued': not first,
'time': datetime_from.time().replace(tzinfo=None) if first and event.settings.show_times else None,
'url': eventreverse(event, 'presale:event.index'),
'timezone': event.settings.timezone,
})
d += timedelta(days=1)
else:
ebd[date_from].append({
'event': event,
'continued': False,
'time': datetime_from.time().replace(tzinfo=None) if event.settings.show_times else None,
'url': eventreverse(event, 'presale:event.index'),
'timezone': event.settings.timezone,
})
self._multiple_timezones = len(timezones) > 1
return ebd

View File

@@ -0,0 +1,36 @@
.table-calendar {
td, th {
width: 14.29%;
}
h3 {
margin: 0;
font-size: 16px;
}
a.event {
display: block;
background: $brand-primary;
color: white;
border-radius: 5px;
padding: 3px 5px;
margin-top: 3px;
font-size: 12px;
&.continued {
background: #888888;
opacity: 0.8;
}
.event-name {
font-weight: bold;
display: block;
}
.event-time, .event-status {
display: block;
}
&:hover {
text-decoration: none;
background: darken($brand-primary, 15%);
}
}
}

View File

@@ -6,6 +6,7 @@
@import "_event.scss"; @import "_event.scss";
@import "_cart.scss"; @import "_cart.scss";
@import "_forms.scss"; @import "_forms.scss";
@import "_calendar.scss";
@import "../../pretixbase/scss/webfont.scss"; @import "../../pretixbase/scss/webfont.scss";
footer { footer {