Generate organizer-level iCal files

This commit is contained in:
Raphael Michel
2017-07-14 14:25:05 +02:00
parent 4584d23434
commit f94314afec
7 changed files with 145 additions and 40 deletions

View File

@@ -5,7 +5,7 @@ from django.conf import settings
from django.contrib import messages
from django.core.urlresolvers import resolve, reverse
from django.db import transaction
from django.db.models import Count, Q, Sum
from django.db.models import Q, Sum
from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
JsonResponse,

View File

@@ -0,0 +1,61 @@
import datetime
from urllib.parse import urlparse
import pytz
import vobject
from django.conf import settings
from django.utils.formats import date_format
from django.utils.translation import ugettext as _
from pretix.base.models import Event
from pretix.multidomain.urlreverse import build_absolute_uri
def get_ical(events):
cal = vobject.iCalendar()
cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(" ", "_"))
creation_time = datetime.datetime.now(pytz.utc)
for ev in events:
event = ev if isinstance(ev, Event) else ev.event
tz = pytz.timezone(event.settings.timezone)
vevent = cal.add('vevent')
vevent.add('summary').value = str(ev.name)
vevent.add('dtstamp').value = creation_time
if ev.location:
vevent.add('location').value = str(ev.location)
vevent.add('organizer').value = event.organizer.name
vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(
event.organizer.slug, event.slug,
ev.pk if not isinstance(ev, Event) else '0',
urlparse(settings.SITE_URL).netloc
)
if event.settings.show_times:
vevent.add('dtstart').value = ev.date_from.astimezone(tz)
else:
vevent.add('dtstart').value = ev.date_from.astimezone(tz).date()
if event.settings.show_date_to and ev.date_to:
if event.settings.show_times:
vevent.add('dtend').value = ev.date_to.astimezone(tz)
else:
vevent.add('dtend').value = ev.date_to.astimezone(tz).date()
descr = []
if isinstance(ev, Event):
descr.append(_('Tickets: {url}').format(url=build_absolute_uri(event, 'presale:event.index')))
else:
descr.append(_('Tickets: {url}').format(url=build_absolute_uri(event, 'presale:event.index', {
'subevent': ev.pk
})))
if ev.date_admission:
descr.append(str(_('Admission: {datetime}')).format(
datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')
))
vevent.add('description').value = '\n'.join(descr)
return cal

View File

@@ -52,6 +52,13 @@
{% endblocktrans %}
</div>
{% endif %}
<p class="text-center">
<a href="{% eventurl request.organizer "presale:organizer.ical" %}?locale={{ request.LANGUAGE_CODE }}"
class="btn btn-default">
<span class="fa fa-calendar"></span>
{% trans "Download calendar as iCal file" %}
</a>
</p>
{% include "pretixpresale/pagination.html" %}
{% endblock %}

View File

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

View File

@@ -5,26 +5,23 @@ from datetime import date, datetime, timedelta
from importlib import import_module
import pytz
import vobject
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.db.models import Count, Prefetch, Q
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView
from pytz import timezone
from pretix.base.decimal import round_decimal
from pretix.base.models import ItemVariation
from pretix.base.models.event import SubEvent
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.ical import get_ical
from pretix.presale.views.organizer import (
add_subevents_for_days, weeks_for_template,
)
@@ -230,10 +227,6 @@ class EventIndex(EventViewMixin, CartMixin, TemplateView):
class EventIcalDownload(EventViewMixin, View):
@cached_property
def event_timezone(self):
return timezone(self.request.event.settings.timezone)
def get(self, request, *args, **kwargs):
if not self.request.event:
raise Http404(_('Unknown event code or not authorized to access this event.'))
@@ -249,37 +242,7 @@ class EventIcalDownload(EventViewMixin, View):
raise Http404(pgettext_lazy('subevent', 'Unknown date selected.'))
event = self.request.event
ev = subevent or event
creation_time = datetime.now(pytz.utc)
cal = vobject.iCalendar()
cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME)
vevent = cal.add('vevent')
vevent.add('summary').value = str(ev.name)
vevent.add('dtstamp').value = creation_time
vevent.add('location').value = str(ev.location)
vevent.add('organizer').value = event.organizer.name
vevent.add('uid').value = '{}-{}-{}-{}'.format(
event.organizer.slug, event.slug,
subevent.pk if subevent else '0',
creation_time.strftime('%Y%m%d%H%M%S%f')
)
if event.settings.show_times:
vevent.add('dtstart').value = ev.date_from.astimezone(self.event_timezone)
else:
vevent.add('dtstart').value = ev.date_from.astimezone(self.event_timezone).date()
if event.settings.show_date_to and ev.date_to:
if event.settings.show_times:
vevent.add('dtend').value = ev.date_to.astimezone(self.event_timezone)
else:
vevent.add('dtend').value = ev.date_to.astimezone(self.event_timezone).date()
if event.date_admission:
vevent.add('description').value = str(_('Admission: {datetime}')).format(
datetime=date_format(ev.date_admission.astimezone(self.event_timezone), 'SHORT_DATETIME_FORMAT')
)
cal = get_ical([subevent or event])
resp = HttpResponse(cal.serialize(), content_type='text/calendar')
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}.ics"'.format(

View File

@@ -3,13 +3,20 @@ from collections import defaultdict
from datetime import date, datetime, timedelta
import pytz
from django.conf import settings
from django.db.models import Q
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.views import View
from django.views.decorators.cache import cache_page
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.multidomain.urlreverse import eventreverse
from pretix.presale.ical import get_ical
from pretix.presale.views import OrganizerViewMixin
@@ -215,3 +222,39 @@ class CalendarView(OrganizerViewMixin, TemplateView):
), before, after, ebd, timezones)
self._multiple_timezones = len(timezones) > 1
return ebd
@method_decorator(cache_page(300), name='dispatch')
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(
'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
).prefetch_related(
'event___settings_objects', 'event__organizer___settings_objects'
).order_by(
'date_from'
)
)
if 'locale' in request.GET and request.GET.get('locale') in dict(settings.LANGUAGES):
with language(request.GET.get('locale')):
cal = get_ical(events)
else:
cal = get_ical(events)
resp = HttpResponse(cal.serialize(), content_type='text/calendar')
resp['Content-Disposition'] = 'attachment; filename="{}.ics"'.format(
request.organizer.slug
)
return resp

View File

@@ -119,3 +119,31 @@ def test_calendar(env, client):
r = client.get('/mrmcd/events/?month=10&year=2017')
assert 'MRMCD2017' not in r.rendered_content
assert 'October 2017' in r.rendered_content
@pytest.mark.django_db
def test_ics(env, client):
e = Event.objects.create(
organizer=env[0], name='MRMCD2017', slug='2017',
date_from=datetime(now().year + 1, 9, 1, tzinfo=UTC),
live=True
)
r = client.get('/mrmcd/events.ics')
assert b'MRMCD2017' not in r.content
e.is_public = True
e.save()
r = client.get('/mrmcd/events.ics')
assert b'MRMCD2017' in r.content
@pytest.mark.django_db
def test_ics_subevents(env, client):
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
)
e.subevents.create(date_from=now(), name='SE1', active=True)
r = client.get('/mrmcd/events.ics')
assert b'MRMCD2017' not in r.content
assert b'SE1' in r.content