forked from CGM_Public/pretix_original
Fix #878 -- Add multi-event widget
This commit is contained in:
@@ -105,7 +105,7 @@ def regenerate_organizer_css(organizer_id: int):
|
||||
organizer.settings.set('presale_css_checksum', checksum)
|
||||
|
||||
# widget.scss
|
||||
css, checksum = compile_scss(organizer)
|
||||
css, checksum = compile_scss(organizer, file='widget.scss', fonts=False)
|
||||
fname = 'pub/{}/widget.{}.css'.format(organizer.slug, checksum[:16])
|
||||
if organizer.settings.get('presale_widget_css_checksum', '') != checksum:
|
||||
newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
|
||||
|
||||
@@ -107,6 +107,9 @@ organizer_patterns = [
|
||||
url(r'^events/ical/$',
|
||||
pretix.presale.views.organizer.OrganizerIcalDownload.as_view(),
|
||||
name='organizer.ical'),
|
||||
url(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
|
||||
name='organizer.widget.productlist'),
|
||||
url(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='organizer.widget.css'),
|
||||
]
|
||||
|
||||
locale_patterns = [
|
||||
|
||||
@@ -22,7 +22,7 @@ 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,
|
||||
EventListMixin, add_subevents_for_days, weeks_for_template,
|
||||
)
|
||||
|
||||
from . import (
|
||||
@@ -157,7 +157,7 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web'):
|
||||
|
||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||
@method_decorator(iframe_entry_view_wrapper, 'dispatch')
|
||||
class EventIndex(EventViewMixin, CartMixin, TemplateView):
|
||||
class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
template_name = "pretixpresale/event/index.html"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@@ -204,32 +204,6 @@ class EventIndex(EventViewMixin, CartMixin, TemplateView):
|
||||
else:
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def _set_month_year(self):
|
||||
tz = pytz.timezone(self.request.event.settings.timezone)
|
||||
if self.subevent:
|
||||
self.year = self.subevent.date_from.astimezone(tz).year
|
||||
self.month = self.subevent.date_from.astimezone(tz).month
|
||||
elif 'year' in self.request.GET and 'month' in self.request.GET:
|
||||
try:
|
||||
self.year = int(self.request.GET.get('year'))
|
||||
self.month = int(self.request.GET.get('month'))
|
||||
except ValueError:
|
||||
self.year = now().year
|
||||
self.month = now().month
|
||||
else:
|
||||
next_sev = self.request.event.subevents.filter(
|
||||
active=True,
|
||||
date_from__gte=now()
|
||||
).select_related('event').order_by('date_from').first()
|
||||
|
||||
if next_sev:
|
||||
datetime_from = next_sev.date_from
|
||||
self.year = datetime_from.astimezone(tz).year
|
||||
self.month = datetime_from.astimezone(tz).month
|
||||
else:
|
||||
self.year = now().year
|
||||
self.month = now().month
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if not self.request.event.has_subevents or self.subevent:
|
||||
|
||||
@@ -83,22 +83,9 @@ def filter_qs_by_attr(qs, request):
|
||||
return qs
|
||||
|
||||
|
||||
class OrganizerIndex(OrganizerViewMixin, ListView):
|
||||
model = Event
|
||||
context_object_name = 'events'
|
||||
template_name = 'pretixpresale/organizers/index.html'
|
||||
paginate_by = 30
|
||||
class EventListMixin:
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
style = request.GET.get("style", request.organizer.settings.event_list_type)
|
||||
if style == "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_event_queryset(self):
|
||||
query = Q(is_public=True) & Q(live=True)
|
||||
qs = self.request.organizer.events.filter(query)
|
||||
qs = qs.annotate(
|
||||
@@ -131,6 +118,89 @@ class OrganizerIndex(OrganizerViewMixin, ListView):
|
||||
qs = Event.annotated(filter_qs_by_attr(qs, self.request))
|
||||
return qs
|
||||
|
||||
def _set_month_to_next_subevent(self):
|
||||
tz = pytz.timezone(self.request.event.settings.timezone)
|
||||
next_sev = self.request.event.subevents.filter(
|
||||
active=True,
|
||||
date_from__gte=now()
|
||||
).select_related('event').order_by('date_from').first()
|
||||
|
||||
if next_sev:
|
||||
datetime_from = next_sev.date_from
|
||||
self.year = datetime_from.astimezone(tz).year
|
||||
self.month = datetime_from.astimezone(tz).month
|
||||
else:
|
||||
self.year = now().year
|
||||
self.month = now().month
|
||||
|
||||
def _set_month_to_next_event(self):
|
||||
next_ev = filter_qs_by_attr(Event.objects.filter(
|
||||
organizer=self.request.organizer,
|
||||
live=True,
|
||||
is_public=True,
|
||||
date_from__gte=now(),
|
||||
has_subevents=False
|
||||
), 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()
|
||||
), 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):
|
||||
datetime_from = next_sev.date_from
|
||||
next_ev = next_sev.event
|
||||
elif next_ev:
|
||||
datetime_from = next_ev.date_from
|
||||
|
||||
if datetime_from:
|
||||
tz = pytz.timezone(next_ev.settings.timezone)
|
||||
self.year = datetime_from.astimezone(tz).year
|
||||
self.month = datetime_from.astimezone(tz).month
|
||||
else:
|
||||
self.year = now().year
|
||||
self.month = now().month
|
||||
|
||||
def _set_month_year(self):
|
||||
if hasattr(self.request, 'event') and self.subevent:
|
||||
tz = pytz.timezone(self.request.event.settings.timezone)
|
||||
self.year = self.subevent.date_from.astimezone(tz).year
|
||||
self.month = self.subevent.date_from.astimezone(tz).month
|
||||
if 'year' in self.request.GET and 'month' in self.request.GET:
|
||||
try:
|
||||
self.year = int(self.request.GET.get('year'))
|
||||
self.month = int(self.request.GET.get('month'))
|
||||
except ValueError:
|
||||
self.year = now().year
|
||||
self.month = now().month
|
||||
else:
|
||||
if hasattr(self.request, 'event'):
|
||||
self._set_month_to_next_subevent()
|
||||
else:
|
||||
self._set_month_to_next_event()
|
||||
|
||||
|
||||
class OrganizerIndex(OrganizerViewMixin, EventListMixin, ListView):
|
||||
model = Event
|
||||
context_object_name = 'events'
|
||||
template_name = 'pretixpresale/organizers/index.html'
|
||||
paginate_by = 30
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
style = request.GET.get("style", request.organizer.settings.event_list_type)
|
||||
if style == "calendar":
|
||||
cv = CalendarView()
|
||||
cv.request = request
|
||||
return cv.get(request, *args, **kwargs)
|
||||
else:
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self._get_event_queryset()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
for event in ctx['events']:
|
||||
@@ -243,50 +313,11 @@ def weeks_for_template(ebd, year, month):
|
||||
]
|
||||
|
||||
|
||||
class CalendarView(OrganizerViewMixin, TemplateView):
|
||||
class CalendarView(OrganizerViewMixin, EventListMixin, 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'))
|
||||
elif 'year' in request.GET and 'month' in request.GET:
|
||||
try:
|
||||
self.year = int(request.GET.get('year'))
|
||||
self.month = int(request.GET.get('month'))
|
||||
except ValueError:
|
||||
self.year = now().year
|
||||
self.month = now().month
|
||||
else:
|
||||
next_ev = filter_qs_by_attr(Event.objects.filter(
|
||||
organizer=self.request.organizer,
|
||||
live=True,
|
||||
is_public=True,
|
||||
date_from__gte=now(),
|
||||
has_subevents=False
|
||||
), 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()
|
||||
), 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):
|
||||
datetime_from = next_sev.date_from
|
||||
next_ev = next_sev.event
|
||||
elif next_ev:
|
||||
datetime_from = next_ev.date_from
|
||||
|
||||
if datetime_from:
|
||||
tz = pytz.timezone(next_ev.settings.timezone)
|
||||
self.year = datetime_from.astimezone(tz).year
|
||||
self.month = datetime_from.astimezone(tz).month
|
||||
else:
|
||||
self.year = now().year
|
||||
self.month = now().month
|
||||
self._set_month_year()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import calendar
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime, timedelta
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles import finders
|
||||
from django.core.files.base import ContentFile
|
||||
@@ -24,16 +28,21 @@ from django.views.i18n import (
|
||||
from lxml import etree
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import CartPosition, Voucher
|
||||
from pretix.base.models import CartPosition, Event, Quota, SubEvent, Voucher
|
||||
from pretix.base.services.cart import error_messages
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.helpers.daterange import daterange
|
||||
from pretix.helpers.thumb import get_thumbnail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
from pretix.presale.views.event import (
|
||||
get_grouped_items, item_group_by_category,
|
||||
)
|
||||
from pretix.presale.views.organizer import (
|
||||
EventListMixin, add_events_for_days, add_subevents_for_days,
|
||||
filter_qs_by_attr, weeks_for_template,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -43,7 +52,8 @@ def indent(s):
|
||||
|
||||
|
||||
def widget_css_etag(request, **kwargs):
|
||||
return request.event.settings.presale_widget_css_checksum or request.organizer.settings.presale_widget_css_checksum
|
||||
o = getattr(request, 'event', request.organizer)
|
||||
return o.settings.presale_widget_css_checksum or o.settings.presale_widget_css_checksum
|
||||
|
||||
|
||||
def widget_js_etag(request, lang, **kwargs):
|
||||
@@ -54,8 +64,9 @@ def widget_js_etag(request, lang, **kwargs):
|
||||
@condition(etag_func=widget_css_etag)
|
||||
@cache_page(60)
|
||||
def widget_css(request, **kwargs):
|
||||
if request.event.settings.presale_widget_css_file:
|
||||
resp = FileResponse(default_storage.open(request.event.settings.presale_widget_css_file),
|
||||
o = getattr(request, 'event', request.organizer)
|
||||
if o.settings.presale_widget_css_file:
|
||||
resp = FileResponse(default_storage.open(o.settings.presale_widget_css_file),
|
||||
content_type='text/css')
|
||||
return resp
|
||||
else:
|
||||
@@ -151,7 +162,7 @@ def get_picture(event, picture):
|
||||
return urljoin(build_absolute_uri(event, 'presale:event.index'), get_thumbnail(picture.name, '60x60^').thumb.url)
|
||||
|
||||
|
||||
class WidgetAPIProductList(View):
|
||||
class WidgetAPIProductList(EventListMixin, View):
|
||||
|
||||
def _get_items(self):
|
||||
items, display_add_to_cart = get_grouped_items(
|
||||
@@ -201,33 +212,179 @@ class WidgetAPIProductList(View):
|
||||
})
|
||||
return grps, display_add_to_cart, len(items)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
def response(self, data):
|
||||
resp = JsonResponse(data)
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not hasattr(request, 'event'):
|
||||
return self._get_event_list(request, **kwargs)
|
||||
|
||||
if not request.event.live:
|
||||
resp = JsonResponse({
|
||||
return self.response({
|
||||
'error': ugettext('This ticket shop is currently disabled.')
|
||||
})
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
self.subevent = None
|
||||
if request.event.has_subevents:
|
||||
if 'subevent' in kwargs:
|
||||
self.subevent = request.event.subevents.filter(pk=kwargs['subevent'], active=True).first()
|
||||
if not self.subevent:
|
||||
raise Http404()
|
||||
return self.response({
|
||||
'error': ugettext('The selected date does not exist in this event series.')
|
||||
})
|
||||
else:
|
||||
raise Http404()
|
||||
return self._get_event_list(request, **kwargs)
|
||||
else:
|
||||
if 'subevent' in kwargs:
|
||||
raise Http404()
|
||||
return self.response({
|
||||
'error': ugettext('This is not an event series.')
|
||||
})
|
||||
return self._get_event_view(request, **kwargs)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if 'lang' in request.GET and request.GET.get('lang') in [lc for lc, ll in settings.LANGUAGES]:
|
||||
with language(request.GET.get('lang')):
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
return self.get(request, **kwargs)
|
||||
else:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
return self.get(request, **kwargs)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
def _get_availability(self, ev, event):
|
||||
availability = {}
|
||||
if ev.presale_is_running and event.settings.event_list_availability and ev.best_availability_state is not None:
|
||||
if ev.best_availability_state == Quota.AVAILABILITY_OK:
|
||||
availability['color'] = 'green'
|
||||
availability['text'] = ugettext('Tickets on sale')
|
||||
elif event.settings.waiting_list_enabled and ev.best_availability_state >= 0:
|
||||
availability['color'] = 'orange'
|
||||
availability['text'] = ugettext('Waiting list')
|
||||
elif ev.best_availability_state == Quota.AVAILABILITY_RESERVED:
|
||||
availability['color'] = 'orange'
|
||||
availability['text'] = ugettext('Reserved')
|
||||
elif ev.best_availability_state < Quota.AVAILABILITY_RESERVED:
|
||||
availability['color'] = 'red'
|
||||
availability['text'] = ugettext('Sold out')
|
||||
elif ev.presale_is_running:
|
||||
availability['color'] = 'green'
|
||||
availability['text'] = ugettext('Tickets on sale')
|
||||
elif ev.presale_has_ended:
|
||||
availability['color'] = 'red'
|
||||
availability['text'] = ugettext('Sale over')
|
||||
elif event.settings.presale_start_show_date and ev.presale_start:
|
||||
availability['color'] = 'orange'
|
||||
availability['text'] = ugettext('from %(start_date)s') % date_format(ev.presale_start, "SHORT_DATE_FORMAT")
|
||||
else:
|
||||
availability['color'] = 'orange'
|
||||
availability['text'] = ugettext('Sale Soon')
|
||||
return availability
|
||||
|
||||
def _serialize_events(self, ebd):
|
||||
events = []
|
||||
for e in ebd:
|
||||
ev = e['event']
|
||||
if isinstance(ev, SubEvent):
|
||||
event = ev.event
|
||||
else:
|
||||
event = ev
|
||||
events.append({
|
||||
'name': str(ev.name),
|
||||
'time': date_format(e['time'], 'TIME_FORMAT') if e.get('time') and event.settings.show_times else None,
|
||||
'continued': e['continued'],
|
||||
'date_range': ev.get_date_range_display() + (
|
||||
" " + date_format(ev.date_from, "TIME_FORMAT") if event.settings.show_times else ""
|
||||
),
|
||||
'availability': self._get_availability(ev, event),
|
||||
'event_url': build_absolute_uri(event, 'presale:event.index'),
|
||||
'subevent': ev.pk if isinstance(ev, SubEvent) else None,
|
||||
})
|
||||
return events
|
||||
|
||||
def _get_event_list(self, request, **kwargs):
|
||||
data = {}
|
||||
o = getattr(request, 'event', request.organizer)
|
||||
list_type = self.request.GET.get("style", o.settings.event_list_type)
|
||||
data['list_type'] = list_type
|
||||
|
||||
if list_type == "calendar":
|
||||
self._set_month_year()
|
||||
_, ndays = calendar.monthrange(self.year, self.month)
|
||||
|
||||
data['date'] = date(self.year, self.month, 1)
|
||||
if hasattr(self.request, 'event'):
|
||||
tz = pytz.timezone(self.request.event.settings.timezone)
|
||||
else:
|
||||
tz = pytz.UTC
|
||||
before = datetime(self.year, self.month, 1, 0, 0, 0, tzinfo=tz) - timedelta(days=1)
|
||||
after = datetime(self.year, self.month, ndays, 0, 0, 0, tzinfo=tz) + timedelta(days=1)
|
||||
|
||||
ebd = defaultdict(list)
|
||||
|
||||
if hasattr(self.request, 'event'):
|
||||
add_subevents_for_days(
|
||||
self.request.event.subevents_annotated('web'),
|
||||
before, after, ebd, set(), self.request.event,
|
||||
kwargs.get('cart_namespace')
|
||||
)
|
||||
else:
|
||||
timezones = set()
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web'), before, after, ebd, timezones)
|
||||
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
).prefetch_related(
|
||||
'event___settings_objects', 'event__organizer___settings_objects'
|
||||
)), self.request), before, after, ebd, timezones)
|
||||
|
||||
data['weeks'] = weeks_for_template(ebd, self.year, self.month)
|
||||
for w in data['weeks']:
|
||||
for d in w:
|
||||
if not d:
|
||||
continue
|
||||
d['events'] = self._serialize_events(d['events'] or [])
|
||||
else:
|
||||
if hasattr(self.request, 'event'):
|
||||
evs = self.request.event.subevents_sorted(
|
||||
self.request.event.subevents_annotated(self.request.sales_channel)
|
||||
)
|
||||
data['events'] = [
|
||||
{
|
||||
'name': str(ev.name),
|
||||
'date_range': ev.get_date_range_display() + (
|
||||
" " + date_format(ev.date_from, "TIME_FORMAT") if ev.event.settings.show_times else ""
|
||||
),
|
||||
'availability': self._get_availability(ev, ev.event),
|
||||
'event_url': build_absolute_uri(ev.event, 'presale:event.index'),
|
||||
'subevent': ev.pk,
|
||||
} for ev in evs
|
||||
]
|
||||
else:
|
||||
data['events'] = []
|
||||
qs = self._get_event_queryset()
|
||||
for event in qs:
|
||||
tz = pytz.timezone(event.cache.get_or_set('timezone', lambda: event.settings.timezone))
|
||||
if event.has_subevents:
|
||||
dr = daterange(
|
||||
event.min_from.astimezone(tz),
|
||||
(event.max_fromto or event.max_to or event.max_from).astimezone(tz)
|
||||
)
|
||||
avail = {'color': 'none', 'text': ugettext('Event series')}
|
||||
else:
|
||||
dr = event.get_date_range_display() + (
|
||||
" " + date_format(event.date_from, "TIME_FORMAT") if event.settings.show_times else ""
|
||||
)
|
||||
avail = self._get_availability(event, event)
|
||||
data['events'].append({
|
||||
'name': str(event.name),
|
||||
'date_range': dr,
|
||||
'availability': avail,
|
||||
'event_url': build_absolute_uri(event, 'presale:event.index'),
|
||||
})
|
||||
|
||||
return self.response(data)
|
||||
|
||||
def _get_event_view(self, request, **kwargs):
|
||||
data = {
|
||||
'currency': request.event.currency,
|
||||
'display_net_prices': request.event.settings.display_net_prices,
|
||||
@@ -241,6 +398,7 @@ class WidgetAPIProductList(View):
|
||||
data['cart_exists'] = True
|
||||
|
||||
ev = self.subevent or request.event
|
||||
data['name'] = str(ev.name)
|
||||
fail = False
|
||||
|
||||
if not ev.presale_is_running:
|
||||
@@ -298,6 +456,4 @@ class WidgetAPIProductList(View):
|
||||
self.request.event.get_cache().set('vouchers_exist', vouchers_exist)
|
||||
data['vouchers_exist'] = vouchers_exist
|
||||
|
||||
resp = JsonResponse(data)
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
return self.response(data)
|
||||
|
||||
Reference in New Issue
Block a user