forked from CGM_Public/pretix_original
Add program times for items (Z#23178639)
* Add program times for items * Fix frontend date validation * Add ical data for program times [wip] * Improve ical data for program times * Remove duplicate code and add comments * Adjust migration * Remove program times form for event series * Add pdf placeholder [wip] * Improve explanation text with suggestion Co-authored-by: Raphael Michel <michel@pretix.eu> * Fix import sorting * Improve ical generation * Improve ical entry description * Fix migration * Add copyability for program times fot items and events * Update migration * Add API endpoints/functions, fix isort * Improve variable name Co-authored-by: Richard Schreiber <schreiber@rami.io> * Remove todo comment * Add documentation, Change endpoint name * Change related name * Remove unnecessary code block * Add program times to item API * Fix imports * Add log text * Use daterange helper * Add and update API tests * Add another API test * Add program times to cloning tests * Update query count because of program times query * Invalidate cached tickets on program time changes * Reduce invalidation calls * Update migration after rebase * Apply improvements to invalidation from review Co-authored-by: Richard Schreiber <schreiber@rami.io> * remove unneccessary attr=item param * remove unnecessary kwargs for formset_factory * fix local var name being overwritten in for-loop * fix empty formset being saved * Use subevent if available * make code less verbose * remove double event-label in ical desc * fix unnecessary var re-assign * fix ev vs p.subevent --------- Co-authored-by: Raphael Michel <michel@pretix.eu> Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -20,10 +20,12 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import datetime
|
||||
from collections import namedtuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import vobject
|
||||
from django.conf import settings
|
||||
from django.db.models import prefetch_related_objects
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
@@ -122,61 +124,109 @@ def get_private_icals(event, positions):
|
||||
|
||||
creation_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
calobjects = []
|
||||
calentries = set() # using set for automatic deduplication of CalEntries
|
||||
CalEntry = namedtuple('CalEntry', ['summary', 'description', 'location', 'dtstart', 'dtend', 'uid'])
|
||||
|
||||
evs = set(p.subevent or event for p in positions)
|
||||
for ev in evs:
|
||||
if isinstance(ev, Event):
|
||||
# collecting the positions' calendar entries, preferring the most exact date and time available (positions > subevent > event)
|
||||
prefetch_related_objects(positions, 'item__program_times')
|
||||
for p in positions:
|
||||
ev = p.subevent or event
|
||||
program_times = p.item.program_times.all()
|
||||
if program_times:
|
||||
# if program times have been configured, they are preferred for the position's calendar entries
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
for index, pt in enumerate(program_times):
|
||||
summary = _('{event} - {item}').format(event=ev, item=p.item.name)
|
||||
if event.settings.mail_attach_ical_description:
|
||||
ctx = get_email_context(event=event, event_or_subevent=ev)
|
||||
description = format_map(str(event.settings.mail_attach_ical_description), ctx)
|
||||
else:
|
||||
# Default description
|
||||
descr = []
|
||||
descr.append(_('Tickets: {url}').format(url=url))
|
||||
descr.append(str(_('Start: {datetime}')).format(
|
||||
datetime=date_format(pt.start.astimezone(tz), 'SHORT_DATETIME_FORMAT')
|
||||
))
|
||||
descr.append(str(_('End: {datetime}')).format(
|
||||
datetime=date_format(pt.end.astimezone(tz), 'SHORT_DATETIME_FORMAT')
|
||||
))
|
||||
# Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer
|
||||
descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
|
||||
description = '\n'.join(descr)
|
||||
location = None
|
||||
dtstart = pt.start.astimezone(tz)
|
||||
dtend = pt.end.astimezone(tz)
|
||||
uid = 'pretix-{}-{}-{}-{}@{}'.format(
|
||||
event.organizer.slug,
|
||||
event.slug,
|
||||
p.item.id,
|
||||
index,
|
||||
urlparse(url).netloc
|
||||
)
|
||||
calentries.add(CalEntry(summary, description, location, dtstart, dtend, uid))
|
||||
else:
|
||||
url = build_absolute_uri(event, 'presale:event.index', {
|
||||
'subevent': ev.pk
|
||||
})
|
||||
# without program times, the subevent or event times are used for calendar entries, preferring subevents
|
||||
if p.subevent:
|
||||
url = build_absolute_uri(event, 'presale:event.index', {
|
||||
'subevent': p.subevent.pk
|
||||
})
|
||||
else:
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
|
||||
if event.settings.mail_attach_ical_description:
|
||||
ctx = get_email_context(event=event, event_or_subevent=ev)
|
||||
description = format_map(str(event.settings.mail_attach_ical_description), ctx)
|
||||
else:
|
||||
# Default description
|
||||
descr = []
|
||||
descr.append(_('Tickets: {url}').format(url=url))
|
||||
if ev.date_admission:
|
||||
descr.append(str(_('Admission: {datetime}')).format(
|
||||
datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')
|
||||
))
|
||||
if event.settings.mail_attach_ical_description:
|
||||
ctx = get_email_context(event=event, event_or_subevent=ev)
|
||||
description = format_map(str(event.settings.mail_attach_ical_description), ctx)
|
||||
else:
|
||||
# Default description
|
||||
descr = []
|
||||
descr.append(_('Tickets: {url}').format(url=url))
|
||||
if ev.date_admission:
|
||||
descr.append(str(_('Admission: {datetime}')).format(
|
||||
datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')
|
||||
))
|
||||
|
||||
# Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer
|
||||
descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
|
||||
description = '\n'.join(descr)
|
||||
# Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer
|
||||
descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
|
||||
description = '\n'.join(descr)
|
||||
summary = str(ev.name)
|
||||
if ev.location:
|
||||
location = ", ".join(l.strip() for l in str(ev.location).splitlines() if l.strip())
|
||||
else:
|
||||
location = None
|
||||
if event.settings.show_times:
|
||||
dtstart = ev.date_from.astimezone(tz)
|
||||
else:
|
||||
dtstart = ev.date_from.astimezone(tz).date()
|
||||
if event.settings.show_date_to and ev.date_to:
|
||||
if event.settings.show_times:
|
||||
dtend = ev.date_to.astimezone(tz)
|
||||
else:
|
||||
# with full-day events date_to in pretix is included (e.g. last day)
|
||||
# whereas dtend in vcalendar is non-inclusive => add one day for export
|
||||
dtend = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)
|
||||
else:
|
||||
dtend = None
|
||||
uid = 'pretix-{}-{}-{}@{}'.format(
|
||||
event.organizer.slug,
|
||||
event.slug,
|
||||
ev.pk if p.subevent else '0',
|
||||
urlparse(url).netloc
|
||||
)
|
||||
calentries.add(CalEntry(summary, description, location, dtstart, dtend, uid))
|
||||
|
||||
for calentry in calentries:
|
||||
cal = vobject.iCalendar()
|
||||
cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(" ", "_"))
|
||||
|
||||
vevent = cal.add('vevent')
|
||||
vevent.add('summary').value = str(ev.name)
|
||||
vevent.add('description').value = description
|
||||
vevent.add('summary').value = calentry.summary
|
||||
vevent.add('description').value = calentry.description
|
||||
vevent.add('dtstamp').value = creation_time
|
||||
if ev.location:
|
||||
vevent.add('location').value = ", ".join(l.strip() for l in str(ev.location).splitlines() if l.strip())
|
||||
|
||||
vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(
|
||||
event.organizer.slug,
|
||||
event.slug,
|
||||
ev.pk if not isinstance(ev, Event) else '0',
|
||||
urlparse(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:
|
||||
# with full-day events date_to in pretix is included (e.g. last day)
|
||||
# whereas dtend in vcalendar is non-inclusive => add one day for export
|
||||
vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)
|
||||
|
||||
if calentry.location:
|
||||
vevent.add('location').value = calentry.location
|
||||
vevent.add('uid').value = calentry.uid
|
||||
vevent.add('dtstart').value = calentry.dtstart
|
||||
if calentry.dtend:
|
||||
vevent.add('dtend').value = calentry.dtend
|
||||
calobjects.append(cal)
|
||||
return calobjects
|
||||
|
||||
Reference in New Issue
Block a user