mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Fix #878 -- Add multi-event widget
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import string
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime, time
|
||||
from datetime import datetime, time, timedelta
|
||||
from operator import attrgetter
|
||||
|
||||
import pytz
|
||||
@@ -668,8 +668,8 @@ class Event(EventMixin, LoggedModel):
|
||||
}[ordering]
|
||||
subevs = queryset.filter(
|
||||
Q(active=True) & (
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
|
||||
| Q(date_to__gte=now())
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__gte=now() - timedelta(hours=24)))
|
||||
| Q(date_to__gte=now() - timedelta(hours=24))
|
||||
)
|
||||
) # order_by doesn't make sense with I18nField
|
||||
for f in reversed(orderfields):
|
||||
|
||||
@@ -1192,7 +1192,7 @@ class TaxRuleForm(I18nModelForm):
|
||||
class WidgetCodeForm(forms.Form):
|
||||
subevent = forms.ModelChoiceField(
|
||||
label=pgettext_lazy('subevent', "Date"),
|
||||
required=True,
|
||||
required=False,
|
||||
queryset=SubEvent.objects.none()
|
||||
)
|
||||
language = forms.ChoiceField(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -36,6 +36,33 @@ var strings = {
|
||||
'close': django.pgettext('widget', 'Close'),
|
||||
'continue': django.pgettext('widget', 'Continue'),
|
||||
'variations': django.pgettext('widget', 'See variations'),
|
||||
'back_to_list': django.pgettext('widget', 'Choose a different event'),
|
||||
'back': django.pgettext('widget', 'Back'),
|
||||
'next_month': django.pgettext('widget', 'Next month'),
|
||||
'previous_month': django.pgettext('widget', 'Previous month'),
|
||||
'days': {
|
||||
'MO': django.gettext('Mo'),
|
||||
'TU': django.gettext('Tu'),
|
||||
'WE': django.gettext('We'),
|
||||
'TH': django.gettext('Th'),
|
||||
'FR': django.gettext('Fr'),
|
||||
'SA': django.gettext('Sa'),
|
||||
'SU': django.gettext('Su'),
|
||||
},
|
||||
'months': {
|
||||
'01': django.gettext('January'),
|
||||
'02': django.gettext('February'),
|
||||
'03': django.gettext('March'),
|
||||
'04': django.gettext('April'),
|
||||
'05': django.gettext('May'),
|
||||
'06': django.gettext('June'),
|
||||
'07': django.gettext('July'),
|
||||
'08': django.gettext('August'),
|
||||
'09': django.gettext('September'),
|
||||
'10': django.gettext('October'),
|
||||
'11': django.gettext('November'),
|
||||
'12': django.gettext('December'),
|
||||
}
|
||||
};
|
||||
|
||||
var setCookie = function (cname, cvalue, exdays) {
|
||||
@@ -51,6 +78,12 @@ var getCookie = function (name) {
|
||||
else return null;
|
||||
};
|
||||
|
||||
var padNumber = function(number, size) {
|
||||
var s = String(number);
|
||||
while (s.length < (size || 2)) {s = "0" + s;}
|
||||
return s;
|
||||
};
|
||||
|
||||
/* HTTP API Call helpers */
|
||||
var api = {
|
||||
'_getXHR': function () {
|
||||
@@ -181,9 +214,9 @@ Vue.component('availbox', {
|
||||
},
|
||||
waiting_list_url: function () {
|
||||
if (this.item.has_variations) {
|
||||
return this.$root.event_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id + '&var=' + this.variation.id;
|
||||
return this.$root.target_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id + '&var=' + this.variation.id;
|
||||
} else {
|
||||
return this.$root.event_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id;
|
||||
return this.$root.target_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,13 +438,17 @@ var shared_methods = {
|
||||
this.$root.overlay.frame_loading = true;
|
||||
|
||||
this.async_task_interval = 100;
|
||||
api._postFormJSON(url, this.$refs.form, this.buy_callback, this.buy_error_callback);
|
||||
var form = this.$refs.form;
|
||||
if (form === undefined) {
|
||||
form = this.$refs.formcomp.$refs.form;
|
||||
}
|
||||
api._postFormJSON(url, form, this.buy_callback, this.buy_error_callback);
|
||||
}
|
||||
},
|
||||
buy_error_callback: function (xhr, data) {
|
||||
if (xhr.status === 405 && typeof xhr.responseURL !== "undefined") {
|
||||
// Likely a redirect!
|
||||
this.$root.event_url = xhr.responseURL.substr(0, xhr.responseURL.indexOf("/cart/add") - 18);
|
||||
this.$root.target_url = xhr.responseURL.substr(0, xhr.responseURL.indexOf("/cart/add") - 18);
|
||||
this.$root.overlay.frame_loading = false;
|
||||
this.buy();
|
||||
return;
|
||||
@@ -435,7 +472,7 @@ var shared_methods = {
|
||||
setCookie(this.$root.cookieName, data.cart_id, 30);
|
||||
}
|
||||
if (data.redirect.substr(0, 1) === '/') {
|
||||
data.redirect = this.$root.event_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.redirect;
|
||||
data.redirect = this.$root.target_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.redirect;
|
||||
}
|
||||
var url = data.redirect;
|
||||
if (url.indexOf('?')) {
|
||||
@@ -456,7 +493,7 @@ var shared_methods = {
|
||||
} else {
|
||||
this.async_task_id = data.async_id;
|
||||
if (data.check_url) {
|
||||
this.async_task_check_url = this.$root.event_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.check_url;
|
||||
this.async_task_check_url = this.$root.target_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.check_url;
|
||||
}
|
||||
this.async_task_timeout = window.setTimeout(this.buy_check, this.async_task_interval);
|
||||
this.async_task_interval = 250;
|
||||
@@ -480,7 +517,7 @@ var shared_methods = {
|
||||
iframe.src = redirect_url;
|
||||
},
|
||||
resume: function () {
|
||||
var redirect_url = this.$root.event_url + 'w/' + widget_id + '/?iframe=1&locale=' + lang;
|
||||
var redirect_url = this.$root.target_url + 'w/' + widget_id + '/?iframe=1&locale=' + lang;
|
||||
if (this.$root.cart_id) {
|
||||
redirect_url += '&take_cart_id=' + this.$root.cart_id;
|
||||
}
|
||||
@@ -504,6 +541,7 @@ var shared_widget_data = function () {
|
||||
async_task_timeout: null,
|
||||
async_task_interval: 100,
|
||||
voucher: null,
|
||||
mobile: false,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -586,10 +624,16 @@ Vue.component('pretix-overlay', {
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('pretix-widget', {
|
||||
template: ('<div class="pretix-widget-wrapper">'
|
||||
+ '<div class="pretix-widget">'
|
||||
+ shared_loading_fragment
|
||||
Vue.component('pretix-widget-event-form', {
|
||||
template: ('<div class="pretix-widget-event-form">'
|
||||
+ '<div class="pretix-widget-event-list-back" v-if="$root.events || $root.weeks">'
|
||||
+ '<a href="#" @click.prevent="back_to_list">‹ '
|
||||
+ strings['back_to_list']
|
||||
+ '</a>'
|
||||
+ '</div>'
|
||||
+ '<div class="pretix-widget-event-header" v-if="$root.events || $root.weeks">'
|
||||
+ '<strong>{{ $root.name }}</strong>'
|
||||
+ '</div>'
|
||||
+ '<form method="post" :action="$root.formTarget" ref="form" target="_blank">'
|
||||
+ '<input type="hidden" name="_voucher_code" :value="$root.voucher_code" v-if="$root.voucher_code">'
|
||||
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
||||
@@ -597,7 +641,7 @@ Vue.component('pretix-widget', {
|
||||
+ '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>'
|
||||
+ '<div class="pretix-widget-info-message pretix-widget-clickable"'
|
||||
+ ' v-if="$root.cart_exists">'
|
||||
+ '<button @click.prevent="resume" class="pretix-widget-resume-button" type="button">'
|
||||
+ '<button @click.prevent="$parent.resume" class="pretix-widget-resume-button" type="button">'
|
||||
+ strings['resume_checkout']
|
||||
+ '</button>'
|
||||
+ strings['cart_exists']
|
||||
@@ -605,7 +649,7 @@ Vue.component('pretix-widget', {
|
||||
+ '</div>'
|
||||
+ '<category v-for="category in this.$root.categories" :category="category" :key="category.id"></category>'
|
||||
+ '<div class="pretix-widget-action" v-if="$root.display_add_to_cart">'
|
||||
+ '<button @click="buy" type="submit">' + strings.buy + '</button>'
|
||||
+ '<button @click="$parent.buy" type="submit">' + strings.buy + '</button>'
|
||||
+ '</div>'
|
||||
+ '</form>'
|
||||
+ '<form method="get" :action="$root.voucherFormTarget" target="_blank" '
|
||||
@@ -613,15 +657,262 @@ Vue.component('pretix-widget', {
|
||||
+ '<div class="pretix-widget-voucher">'
|
||||
+ '<h3 class="pretix-widget-voucher-headline">'+ strings['redeem_voucher'] +'</h3>'
|
||||
+ '<div class="pretix-widget-voucher-input-wrap">'
|
||||
+ '<input class="pretix-widget-voucher-input" type="text" v-model="voucher" name="voucher" placeholder="'+strings.voucher_code+'">'
|
||||
+ '<input class="pretix-widget-voucher-input" type="text" v-model="$parent.voucher" name="voucher" placeholder="'+strings.voucher_code+'">'
|
||||
+ '</div>'
|
||||
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
||||
+ '<input type="hidden" name="locale" value="' + lang + '" />'
|
||||
+ '<div class="pretix-widget-voucher-button-wrap">'
|
||||
+ '<button @click="redeem">' + strings.redeem + '</button>'
|
||||
+ '<button @click="$parent.redeem">' + strings.redeem + '</button>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '</form>'
|
||||
+ '</div>'
|
||||
),
|
||||
methods: {
|
||||
back_to_list: function() {
|
||||
this.$root.target_url = this.$root.parent_stack.pop();
|
||||
this.$root.error = null;
|
||||
this.$root.subevent = null;
|
||||
if (this.$root.events !== undefined) {
|
||||
this.$root.view = "events";
|
||||
} else {
|
||||
this.$root.view = "weeks";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('pretix-widget-event-list-entry', {
|
||||
template: ('<a :class="classObject" @click.prevent="select">'
|
||||
+ '<div class="pretix-widget-event-list-entry-name">{{ event.name }}</div>'
|
||||
+ '<div class="pretix-widget-event-list-entry-date">{{ event.date_range }}</div>'
|
||||
+ '<div class="pretix-widget-event-list-entry-availability"><span>{{ event.availability.text }}</span></div>'
|
||||
+ '</a>'),
|
||||
props: {
|
||||
event: Object
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
var o = {
|
||||
'pretix-widget-event-list-entry': true
|
||||
};
|
||||
o['pretix-widget-event-availability-' + this.event.availability.color] = true;
|
||||
return o
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
select: function () {
|
||||
this.$root.parent_stack.push(this.$root.target_url);
|
||||
this.$root.target_url = this.event.event_url;
|
||||
this.$root.error = null;
|
||||
this.$root.subevent = this.event.subevent;
|
||||
this.$root.loading++;
|
||||
this.$root.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('pretix-widget-event-list', {
|
||||
template: ('<div class="pretix-widget-event-list">'
|
||||
+ '<div class="pretix-widget-back" v-if="$root.weeks || $root.parent_stack.length > 0">'
|
||||
+ '<a href="#" @click.prevent="back_to_calendar">‹ '
|
||||
+ strings['back']
|
||||
+ '</a>'
|
||||
+ '</div>'
|
||||
+ '<pretix-widget-event-list-entry v-for="event in $root.events" :event="event" :key="event.url"></pretix-widget-event-list-entry>'
|
||||
+ '</div>'),
|
||||
methods: {
|
||||
back_to_calendar: function () {
|
||||
if (this.$root.weeks) {
|
||||
this.$root.events = undefined;
|
||||
this.$root.view = "weeks";
|
||||
} else {
|
||||
this.$root.loading++;
|
||||
this.$root.target_url = this.$root.parent_stack.pop();
|
||||
this.$root.error = null;
|
||||
this.$root.reload();
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('pretix-widget-event-calendar-event', {
|
||||
template: ('<a :class="classObject" @click.prevent="select">'
|
||||
+ '<strong class="pretix-widget-event-calendar-event-name">'
|
||||
+ '{{ event.name }}'
|
||||
+ '</strong>'
|
||||
+ '<div class="pretix-widget-event-calendar-event-date" v-if="!event.continued && event.time">{{ event.time }}</div>'
|
||||
+ '<div class="pretix-widget-event-calendar-event-availability" v-if="!event.continued">{{ event.availability.text }}</div>'
|
||||
+ '</a>'),
|
||||
props: {
|
||||
event: Object
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
var o = {
|
||||
'pretix-widget-event-calendar-event': true
|
||||
};
|
||||
o['pretix-widget-event-availability-' + this.event.availability.color] = true;
|
||||
return o
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
select: function () {
|
||||
this.$root.parent_stack.push(this.$root.target_url);
|
||||
this.$root.target_url = this.event.event_url;
|
||||
this.$root.error = null;
|
||||
this.$root.subevent = this.event.subevent;
|
||||
this.$root.loading++;
|
||||
this.$root.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('pretix-widget-event-calendar-cell', {
|
||||
template: ('<td :class="classObject" @click.prevent="selectDay">'
|
||||
+ '<div class="pretix-widget-event-calendar-day" v-if="day">'
|
||||
+ '{{ daynum }}'
|
||||
+ '</div>'
|
||||
+ '<div class="pretix-widget-event-calendar-events" v-if="day">'
|
||||
+ '<pretix-widget-event-calendar-event v-for="e in day.events" :event="e"></pretix-widget-event-calendar-event>'
|
||||
+ '</div>'
|
||||
+ '</td>'),
|
||||
props: {
|
||||
day: Object
|
||||
},
|
||||
methods: {
|
||||
selectDay: function () {
|
||||
if (!this.day || !this.day.events.length || !this.$parent.$parent.$parent.mobile) {
|
||||
return;
|
||||
}
|
||||
if (this.day.events.length === 1) {
|
||||
var ev = this.day.events[0];
|
||||
this.$root.parent_stack.push(this.$root.target_url);
|
||||
this.$root.target_url = ev.event_url;
|
||||
this.$root.error = null;
|
||||
this.$root.subevent = ev.subevent;
|
||||
this.$root.loading++;
|
||||
this.$root.reload();
|
||||
} else {
|
||||
this.$root.events = this.day.events;
|
||||
this.$root.view = "events";
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
daynum: function () {
|
||||
if (!this.day) {
|
||||
return;
|
||||
}
|
||||
return this.day.date.substr(8);
|
||||
},
|
||||
classObject: function () {
|
||||
var o = {};
|
||||
if (this.day && this.day.events.length > 0) {
|
||||
o['pretix-widget-has-events'] = true;
|
||||
var best = 'red';
|
||||
for (var i = 0; i < this.day.events.length; i++) {
|
||||
var ev = this.day.events[i];
|
||||
if (ev.availability.color === 'green') {
|
||||
best = 'green';
|
||||
} else if (ev.availability.color === 'orange' || best !== 'green') {
|
||||
best = 'orange'
|
||||
}
|
||||
}
|
||||
o['pretix-widget-day-availability-' + best] = true;
|
||||
}
|
||||
return o
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('pretix-widget-event-calendar-row', {
|
||||
template: ('<tr>'
|
||||
+ '<pretix-widget-event-calendar-cell v-for="d in week" :day="d"></pretix-widget-event-calendar-cell>'
|
||||
+ '</tr>'),
|
||||
props: {
|
||||
week: Array
|
||||
},
|
||||
});
|
||||
|
||||
Vue.component('pretix-widget-event-calendar', {
|
||||
template: ('<div class="pretix-widget-event-calendar" ref="calendar">'
|
||||
+ '<div class="pretix-widget-back" v-if="$root.events !== undefined">'
|
||||
+ '<a href="#" @click.prevent="back_to_list">‹ '
|
||||
+ strings['back']
|
||||
+ '</a>'
|
||||
+ '</div>'
|
||||
+ '<div class="pretix-widget-event-calendar-head">'
|
||||
+ '<a class="pretix-widget-event-calendar-previous-month" href="#" @click.prevent="prevmonth">« '
|
||||
+ strings['previous_month']
|
||||
+ '</a> '
|
||||
+ '<strong>{{ monthname }}</strong> '
|
||||
+ '<a class="pretix-widget-event-calendar-next-month" href="#" @click.prevent="nextmonth">'
|
||||
+ strings['next_month']
|
||||
+ ' »</a>'
|
||||
+ '</div>'
|
||||
+ '<table class="pretix-widget-event-calendar-table">'
|
||||
+ '<thead>'
|
||||
+ '<tr>'
|
||||
+ '<th>' + strings['days']['MO'] + '</th>'
|
||||
+ '<th>' + strings['days']['TU'] + '</th>'
|
||||
+ '<th>' + strings['days']['WE'] + '</th>'
|
||||
+ '<th>' + strings['days']['TH'] + '</th>'
|
||||
+ '<th>' + strings['days']['FR'] + '</th>'
|
||||
+ '<th>' + strings['days']['SA'] + '</th>'
|
||||
+ '<th>' + strings['days']['SU'] + '</th>'
|
||||
+ '</tr>'
|
||||
+ '</thead>'
|
||||
+ '<tbody>'
|
||||
+ '<pretix-widget-event-calendar-row v-for="week in $root.weeks" :week="week"></pretix-widget-event-calendar-row>'
|
||||
+ '</tbody>'
|
||||
+ '</table>'
|
||||
+ '</div>'),
|
||||
computed: {
|
||||
monthname: function () {
|
||||
return strings['months'][this.$root.date.substr(5, 2)] + ' ' + this.$root.date.substr(0, 4);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
back_to_list: function () {
|
||||
this.$root.weeks = undefined;
|
||||
this.$root.view = "events";
|
||||
},
|
||||
prevmonth: function () {
|
||||
var curMonth = parseInt(this.$root.date.substr(5, 2));
|
||||
var curYear = parseInt(this.$root.date.substr(0, 4));
|
||||
curMonth--;
|
||||
if (curMonth < 1) {
|
||||
curMonth = 12;
|
||||
curYear--;
|
||||
}
|
||||
this.$root.date = String(curYear) + "-" + padNumber(curMonth, 2) + "-01";
|
||||
this.$root.loading++;
|
||||
this.$root.reload();
|
||||
},
|
||||
nextmonth: function () {
|
||||
var curMonth = parseInt(this.$root.date.substr(5, 2));
|
||||
var curYear = parseInt(this.$root.date.substr(0, 4));
|
||||
curMonth++;
|
||||
if (curMonth > 12) {
|
||||
curMonth = 1;
|
||||
curYear++;
|
||||
}
|
||||
this.$root.date = String(curYear) + "-" + padNumber(curMonth, 2) + "-01";
|
||||
this.$root.loading++;
|
||||
this.$root.reload();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Vue.component('pretix-widget', {
|
||||
template: ('<div class="pretix-widget-wrapper" ref="wrapper">'
|
||||
+ '<div :class="classObject">'
|
||||
+ shared_loading_fragment
|
||||
+ '<div class="pretix-widget-error-message" v-if="$root.error && $root.view !== \'event\'">{{ $root.error }}</div>'
|
||||
+ '<pretix-widget-event-form ref="formcomp" v-if="$root.view === \'event\'"></pretix-widget-event-form>'
|
||||
+ '<pretix-widget-event-list v-if="$root.view === \'events\'"></pretix-widget-event-list>'
|
||||
+ '<pretix-widget-event-calendar v-if="$root.view === \'weeks\'"></pretix-widget-event-calendar>'
|
||||
+ '<div class="pretix-widget-clear"></div>'
|
||||
+ '<div class="pretix-widget-attribution">'
|
||||
+ strings.poweredby
|
||||
@@ -632,6 +923,18 @@ Vue.component('pretix-widget', {
|
||||
),
|
||||
data: shared_widget_data,
|
||||
methods: shared_methods,
|
||||
mounted: function () {
|
||||
this.mobile = this.$refs.wrapper.clientWidth <= 800;
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
o = {'pretix-widget': true};
|
||||
if (this.mobile) {
|
||||
o['pretix-widget-mobile'] = true;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('pretix-button', {
|
||||
@@ -674,9 +977,9 @@ var shared_root_methods = {
|
||||
reload: function () {
|
||||
var url;
|
||||
if (this.$root.subevent) {
|
||||
url = this.$root.event_url + this.$root.subevent + '/widget/product_list?lang=' + lang;
|
||||
url = this.$root.target_url + this.$root.subevent + '/widget/product_list?lang=' + lang;
|
||||
} else {
|
||||
url = this.$root.event_url + 'widget/product_list?lang=' + lang;
|
||||
url = this.$root.target_url + 'widget/product_list?lang=' + lang;
|
||||
}
|
||||
var cart_id = getCookie(this.cookieName);
|
||||
if (this.$root.voucher_code) {
|
||||
@@ -685,6 +988,12 @@ var shared_root_methods = {
|
||||
if (cart_id) {
|
||||
url += "&cart_id=" + cart_id;
|
||||
}
|
||||
if (this.$root.date !== null) {
|
||||
url += "&year=" + this.$root.date.substr(0, 4) + "&month=" + this.$root.date.substr(5, 2);
|
||||
}
|
||||
if (this.$root.style !== null) {
|
||||
url = url + '&style=' + this.$root.style;
|
||||
}
|
||||
var root = this.$root;
|
||||
api._getJSON(url, function (data, xhr) {
|
||||
if (typeof xhr.responseURL !== "undefined" && xhr.responseURL !== url) {
|
||||
@@ -692,21 +1001,34 @@ var shared_root_methods = {
|
||||
if (root.subevent) {
|
||||
new_url = new_url.substr(0, new_url.lastIndexOf("/", new_url.length - 1) + 1);
|
||||
}
|
||||
root.event_url = new_url;
|
||||
root.target_url = new_url;
|
||||
root.reload();
|
||||
return;
|
||||
}
|
||||
root.categories = data.items_by_category;
|
||||
root.currency = data.currency;
|
||||
root.display_net_prices = data.display_net_prices;
|
||||
root.error = data.error;
|
||||
root.display_add_to_cart = data.display_add_to_cart;
|
||||
root.waiting_list_enabled = data.waiting_list_enabled;
|
||||
root.show_variations_expanded = data.show_variations_expanded;
|
||||
root.cart_id = cart_id;
|
||||
root.cart_exists = data.cart_exists;
|
||||
root.vouchers_exist = data.vouchers_exist;
|
||||
root.itemnum = data.itemnum;
|
||||
if (data.weeks !== undefined) {
|
||||
root.weeks = data.weeks;
|
||||
root.date = data.date;
|
||||
root.events = undefined;
|
||||
root.view = "weeks";
|
||||
} else if (data.events !== undefined) {
|
||||
root.events = data.events;
|
||||
root.weeks = undefined;
|
||||
root.view = "events";
|
||||
} else {
|
||||
root.view = "event";
|
||||
root.name = data.name;
|
||||
root.categories = data.items_by_category;
|
||||
root.currency = data.currency;
|
||||
root.display_net_prices = data.display_net_prices;
|
||||
root.error = data.error;
|
||||
root.display_add_to_cart = data.display_add_to_cart;
|
||||
root.waiting_list_enabled = data.waiting_list_enabled;
|
||||
root.show_variations_expanded = data.show_variations_expanded;
|
||||
root.cart_id = cart_id;
|
||||
root.cart_exists = data.cart_exists;
|
||||
root.vouchers_exist = data.vouchers_exist;
|
||||
root.itemnum = data.itemnum;
|
||||
}
|
||||
if (root.loading > 0) {
|
||||
root.loading--;
|
||||
}
|
||||
@@ -718,15 +1040,22 @@ var shared_root_methods = {
|
||||
root.loading--;
|
||||
}
|
||||
});
|
||||
},
|
||||
choose_event: function (event) {
|
||||
root.target_url = event.event_url;
|
||||
this.$root.error = null;
|
||||
root.subevent = event.subevent;
|
||||
root.loading++;
|
||||
root.reload();
|
||||
}
|
||||
};
|
||||
|
||||
var shared_root_computed = {
|
||||
cookieName: function () {
|
||||
return "pretix_widget_" + this.event_url.replace(/[^a-zA-Z0-9]+/g, "_");
|
||||
return "pretix_widget_" + this.target_url.replace(/[^a-zA-Z0-9]+/g, "_");
|
||||
},
|
||||
voucherFormTarget: function () {
|
||||
var form_target = this.event_url + 'w/' + widget_id + '/redeem?iframe=1&locale=' + lang;
|
||||
var form_target = this.target_url + 'w/' + widget_id + '/redeem?iframe=1&locale=' + lang;
|
||||
var cookie = getCookie(this.cookieName);
|
||||
if (cookie) {
|
||||
form_target += "&take_cart_id=" + cookie;
|
||||
@@ -737,11 +1066,11 @@ var shared_root_computed = {
|
||||
return form_target;
|
||||
},
|
||||
formTarget: function () {
|
||||
var checkout_url = "/" + this.event_url.replace(/^[^\/]+:\/\/([^\/]+)\//, "") + "w/" + widget_id + "/";
|
||||
var checkout_url = "/" + this.target_url.replace(/^[^\/]+:\/\/([^\/]+)\//, "") + "w/" + widget_id + "/";
|
||||
if (!this.$root.cart_exists) {
|
||||
checkout_url += "checkout/start";
|
||||
}
|
||||
var form_target = this.event_url + 'w/' + widget_id + '/cart/add?iframe=1&next=' + encodeURIComponent(checkout_url);
|
||||
var form_target = this.target_url + 'w/' + widget_id + '/cart/add?iframe=1&next=' + encodeURIComponent(checkout_url);
|
||||
var cookie = getCookie(this.cookieName);
|
||||
if (cookie) {
|
||||
form_target += "&take_cart_id=" + cookie;
|
||||
@@ -812,12 +1141,13 @@ function get_ga_client_id(tracking_id) {
|
||||
}
|
||||
|
||||
var create_widget = function (element) {
|
||||
var event_url = element.attributes.event.value;
|
||||
if (!event_url.match(/\/$/)) {
|
||||
event_url += "/";
|
||||
var target_url = element.attributes.event.value;
|
||||
if (!target_url.match(/\/$/)) {
|
||||
target_url += "/";
|
||||
}
|
||||
var voucher = element.attributes.voucher ? element.attributes.voucher.value : null;
|
||||
var subevent = element.attributes.subevent ? element.attributes.subevent.value : null;
|
||||
var style = element.attributes.style ? element.attributes.style.value : null;
|
||||
var skip_ssl = element.attributes["skip-ssl-check"] ? true : false;
|
||||
var disable_vouchers = element.attributes["disable-vouchers"] ? true : false;
|
||||
var widget_data = JSON.parse(JSON.stringify(window.PretixWidget.widget_data));
|
||||
@@ -836,16 +1166,23 @@ var create_widget = function (element) {
|
||||
el: element,
|
||||
data: function () {
|
||||
return {
|
||||
event_url: event_url,
|
||||
target_url: target_url,
|
||||
parent_stack: [],
|
||||
subevent: subevent,
|
||||
is_button: false,
|
||||
categories: null,
|
||||
currency: null,
|
||||
name: null,
|
||||
voucher_code: voucher,
|
||||
display_net_prices: false,
|
||||
show_variations_expanded: false,
|
||||
skip_ssl: skip_ssl,
|
||||
style: style,
|
||||
error: null,
|
||||
weeks: null,
|
||||
date: null,
|
||||
events: null,
|
||||
view: null,
|
||||
display_add_to_cart: false,
|
||||
widget_data: widget_data,
|
||||
loading: 1,
|
||||
@@ -868,9 +1205,9 @@ var create_widget = function (element) {
|
||||
};
|
||||
|
||||
var create_button = function (element) {
|
||||
var event_url = element.attributes.event.value;
|
||||
if (!event_url.match(/\/$/)) {
|
||||
event_url += "/";
|
||||
var target_url = element.attributes.event.value;
|
||||
if (!target_url.match(/\/$/)) {
|
||||
target_url += "/";
|
||||
}
|
||||
var voucher = element.attributes.voucher ? element.attributes.voucher.value : null;
|
||||
var subevent = element.attributes.subevent ? element.attributes.subevent.value : null;
|
||||
@@ -902,7 +1239,7 @@ var create_button = function (element) {
|
||||
el: element,
|
||||
data: function () {
|
||||
return {
|
||||
event_url: event_url,
|
||||
target_url: target_url,
|
||||
subevent: subevent,
|
||||
is_button: true,
|
||||
skip_ssl: skip_ssl,
|
||||
|
||||
@@ -116,9 +116,9 @@
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
background-color: $alert-danger-bg;
|
||||
border-color: $alert-danger-border;
|
||||
color: $alert-danger-text;
|
||||
background-color: white;
|
||||
border: 2px solid $brand-danger;
|
||||
color: $brand-danger;
|
||||
border-radius: $alert-border-radius;
|
||||
}
|
||||
|
||||
@@ -345,6 +345,136 @@
|
||||
max-height: 1000px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.pretix-widget-event-header {
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.pretix-widget-event-list-back {
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.pretix-widget-back {
|
||||
padding-bottom: 10px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.pretix-widget-event-list {
|
||||
padding: 10px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.pretix-widget-event-list-entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px 0;
|
||||
flex-wrap: wrap;
|
||||
color: $text-color;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
background: $gray-lighter;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pretix-widget-event-list-entry-name {
|
||||
width: 50%;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.pretix-widget-event-list-entry-date {
|
||||
width: 25%;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.pretix-widget-event-list-entry-availability {
|
||||
width: 25%;
|
||||
text-align: right;
|
||||
padding: 7px 5px 3px;
|
||||
box-sizing: border-box;
|
||||
span {
|
||||
display: inline;
|
||||
padding: 2px 6px 3px;
|
||||
font-size: 75%;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pretix-widget-event-availability-orange .pretix-widget-event-list-entry-availability span,
|
||||
.pretix-widget-event-availability-orange.pretix-widget-event-calendar-event {
|
||||
background-color: $brand-warning;
|
||||
}
|
||||
.pretix-widget-event-availability-none .pretix-widget-event-list-entry-availability span {
|
||||
background-color: $brand-primary;
|
||||
}
|
||||
.pretix-widget-event-availability-green .pretix-widget-event-list-entry-availability span,
|
||||
.pretix-widget-event-availability-green.pretix-widget-event-calendar-event {
|
||||
background-color: $brand-success;
|
||||
}
|
||||
.pretix-widget-event-availability-red .pretix-widget-event-list-entry-availability span,
|
||||
.pretix-widget-event-availability-red.pretix-widget-event-calendar-event {
|
||||
background-color: $brand-danger;
|
||||
}
|
||||
|
||||
.pretix-widget-event-calendar {
|
||||
padding-top: 10px;
|
||||
|
||||
.pretix-widget-event-calendar-head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
strong {
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
.pretix-widget-event-calendar-next-month, .pretix-widget-event-calendar-previous-month {
|
||||
display: block;
|
||||
width: 25%;
|
||||
}
|
||||
.pretix-widget-event-calendar-next-month {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
.pretix-widget-event-calendar-event {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pretix-widget-event-calendar-table {
|
||||
width: 100%;
|
||||
th, td {
|
||||
width: 14.285714285714286%;
|
||||
vertical-align: top;
|
||||
padding: 10px 5px;
|
||||
}
|
||||
}
|
||||
.pretix-widget-event-calendar-day {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pretix-widget-bounce-in {
|
||||
@@ -504,28 +634,71 @@
|
||||
fill: $brand-primary;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-sm-max) {
|
||||
.pretix-widget {
|
||||
.pretix-widget-item-info-col {
|
||||
.pretix-widget.pretix-widget-mobile {
|
||||
.pretix-widget-item-info-col {
|
||||
width: 100%;
|
||||
float: none;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.pretix-widget-item-price-col, .pretix-widget-item-availability-col {
|
||||
width: 50%;
|
||||
}
|
||||
.pretix-widget-action {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
.pretix-widget-voucher-input-wrap {
|
||||
width: 100%;
|
||||
float: none;
|
||||
}
|
||||
.pretix-widget-voucher-button-wrap {
|
||||
width: 100%;
|
||||
float: none;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.pretix-widget-event-list-entry {
|
||||
.pretix-widget-event-list-entry-name {
|
||||
width: 100%;
|
||||
float: none;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.pretix-widget-item-price-col, .pretix-widget-item-availability-col {
|
||||
.pretix-widget-event-list-entry-date {
|
||||
width: 50%;
|
||||
}
|
||||
.pretix-widget-action {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
.pretix-widget-event-list-entry-availability {
|
||||
width: 50%;
|
||||
}
|
||||
.pretix-widget-voucher-input-wrap {
|
||||
width: 100%;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.pretix-widget-event-calendar {
|
||||
.pretix-widget-event-calendar-events {
|
||||
display: none;
|
||||
}
|
||||
.pretix-widget-voucher-button-wrap {
|
||||
width: 100%;
|
||||
float: none;
|
||||
margin-top: 10px;
|
||||
td.pretix-widget-has-events {
|
||||
background: $brand-primary;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
&.pretix-widget-day-availability-red {
|
||||
background: $brand-danger;
|
||||
}
|
||||
&.pretix-widget-day-availability-green {
|
||||
background: $brand-success;
|
||||
}
|
||||
&.pretix-widget-day-availability-orange {
|
||||
background: $brand-warning;
|
||||
}
|
||||
}
|
||||
|
||||
.pretix-widget-event-calendar-head {
|
||||
display: block;
|
||||
strong {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
.pretix-widget-event-calendar-next-month, .pretix-widget-event-calendar-previous-month {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,3 +17,5 @@ pytest-cache
|
||||
pytest-sugar
|
||||
responses
|
||||
potypo
|
||||
freezegun
|
||||
|
||||
|
||||
@@ -158,7 +158,8 @@ setup(
|
||||
'isort',
|
||||
'pytest-mock==1.6.*',
|
||||
'pytest-rerunfailures',
|
||||
'responses'
|
||||
'responses',
|
||||
'freezegun',
|
||||
],
|
||||
'memcached': ['pylibmc'],
|
||||
'mysql': ['mysqlclient'],
|
||||
|
||||
@@ -6,6 +6,7 @@ from bs4 import BeautifulSoup
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from freezegun import freeze_time
|
||||
|
||||
from pretix.base.models import Order, OrderPosition
|
||||
from pretix.presale.style import regenerate_css, regenerate_organizer_css
|
||||
@@ -123,6 +124,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
assert response['Access-Control-Allow-Origin'] == '*'
|
||||
data = json.loads(response.content.decode())
|
||||
assert data == {
|
||||
"name": "30C3",
|
||||
"currency": "EUR",
|
||||
"show_variations_expanded": False,
|
||||
"display_net_prices": False,
|
||||
@@ -202,6 +204,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
assert response['Access-Control-Allow-Origin'] == '*'
|
||||
data = json.loads(response.content.decode())
|
||||
assert data == {
|
||||
"name": "30C3",
|
||||
"currency": "EUR",
|
||||
"show_variations_expanded": False,
|
||||
"display_net_prices": False,
|
||||
@@ -245,6 +248,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
assert response['Access-Control-Allow-Origin'] == '*'
|
||||
data = json.loads(response.content.decode())
|
||||
assert data == {
|
||||
"name": "30C3",
|
||||
"currency": "EUR",
|
||||
"show_variations_expanded": False,
|
||||
"display_net_prices": False,
|
||||
@@ -289,3 +293,221 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
c = response.content.decode()
|
||||
assert '%m/%d/%Y' not in c
|
||||
assert '%d.%m.%Y' in c
|
||||
|
||||
def test_subevent_list(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
with freeze_time("2019-01-01 10:00:00"):
|
||||
self.event.subevents.create(name="Past", active=True, date_from=now() - datetime.timedelta(days=3))
|
||||
se1 = self.event.subevents.create(name="Present", active=True, date_from=now())
|
||||
se2 = self.event.subevents.create(name="Future", active=True, date_from=now() + datetime.timedelta(days=3))
|
||||
self.event.subevents.create(name="Disabled", active=False, date_from=now() + datetime.timedelta(days=3))
|
||||
|
||||
response = self.client.get('/%s/%s/widget/product_list' % (self.orga.slug, self.event.slug))
|
||||
data = json.loads(response.content.decode())
|
||||
settings.SITE_URL = 'http://example.com'
|
||||
assert data == {
|
||||
'list_type': 'list',
|
||||
'events': [
|
||||
{'name': 'Present', 'date_range': 'Jan. 1, 2019 10:00', 'availability': {'color': 'green', 'text': 'Tickets on sale'},
|
||||
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se1.pk},
|
||||
{'name': 'Future', 'date_range': 'Jan. 4, 2019 10:00', 'availability': {'color': 'green', 'text': 'Tickets on sale'},
|
||||
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se2.pk}
|
||||
]
|
||||
}
|
||||
|
||||
def test_subevent_calendar(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
with freeze_time("2019-01-01 10:00:00"):
|
||||
self.event.subevents.create(name="Past", active=True, date_from=now() - datetime.timedelta(days=3))
|
||||
se1 = self.event.subevents.create(name="Present", active=True, date_from=now())
|
||||
se2 = self.event.subevents.create(name="Future", active=True, date_from=now() + datetime.timedelta(days=3))
|
||||
self.event.subevents.create(name="Disabled", active=False, date_from=now() + datetime.timedelta(days=3))
|
||||
|
||||
response = self.client.get('/%s/%s/widget/product_list?style=calendar' % (self.orga.slug, self.event.slug))
|
||||
settings.SITE_URL = 'http://example.com'
|
||||
data = json.loads(response.content.decode())
|
||||
assert data == {
|
||||
'list_type': 'calendar',
|
||||
'date': '2019-01-01',
|
||||
'weeks': [
|
||||
[
|
||||
None,
|
||||
{'day': 1, 'date': '2019-01-01', 'events': [
|
||||
{'name': 'Present', 'time': '10:00', 'continued': False, 'date_range': 'Jan. 1, 2019 10:00',
|
||||
'availability': {'color': 'green', 'text': 'Tickets on sale'},
|
||||
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se1.pk}]},
|
||||
{'day': 2, 'date': '2019-01-02', 'events': []},
|
||||
{'day': 3, 'date': '2019-01-03', 'events': []},
|
||||
{'day': 4, 'date': '2019-01-04', 'events': [
|
||||
{'name': 'Future', 'time': '10:00', 'continued': False, 'date_range': 'Jan. 4, 2019 10:00',
|
||||
'availability': {'color': 'green', 'text': 'Tickets on sale'},
|
||||
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se2.pk}]},
|
||||
{'day': 5, 'date': '2019-01-05', 'events': []},
|
||||
{'day': 6, 'date': '2019-01-06', 'events': []}
|
||||
],
|
||||
[
|
||||
{'day': 7, 'date': '2019-01-07', 'events': []},
|
||||
{'day': 8, 'date': '2019-01-08', 'events': []},
|
||||
{'day': 9, 'date': '2019-01-09', 'events': []},
|
||||
{'day': 10, 'date': '2019-01-10', 'events': []},
|
||||
{'day': 11, 'date': '2019-01-11', 'events': []},
|
||||
{'day': 12, 'date': '2019-01-12', 'events': []},
|
||||
{'day': 13, 'date': '2019-01-13', 'events': []}
|
||||
],
|
||||
[
|
||||
{'day': 14, 'date': '2019-01-14', 'events': []},
|
||||
{'day': 15, 'date': '2019-01-15', 'events': []},
|
||||
{'day': 16, 'date': '2019-01-16', 'events': []},
|
||||
{'day': 17, 'date': '2019-01-17', 'events': []},
|
||||
{'day': 18, 'date': '2019-01-18', 'events': []},
|
||||
{'day': 19, 'date': '2019-01-19', 'events': []},
|
||||
{'day': 20, 'date': '2019-01-20', 'events': []}
|
||||
],
|
||||
[
|
||||
{'day': 21, 'date': '2019-01-21', 'events': []},
|
||||
{'day': 22, 'date': '2019-01-22', 'events': []},
|
||||
{'day': 23, 'date': '2019-01-23', 'events': []},
|
||||
{'day': 24, 'date': '2019-01-24', 'events': []},
|
||||
{'day': 25, 'date': '2019-01-25', 'events': []},
|
||||
{'day': 26, 'date': '2019-01-26', 'events': []},
|
||||
{'day': 27, 'date': '2019-01-27', 'events': []}
|
||||
],
|
||||
[
|
||||
{'day': 28, 'date': '2019-01-28', 'events': []},
|
||||
{'day': 29, 'date': '2019-01-29', 'events': []},
|
||||
{'day': 30, 'date': '2019-01-30', 'events': []},
|
||||
{'day': 31, 'date': '2019-01-31', 'events': []},
|
||||
None, None, None
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
def test_event_list(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
with freeze_time("2019-01-01 10:00:00"):
|
||||
self.orga.events.create(name="Past", live=True, is_public=True, slug='past', date_from=now() - datetime.timedelta(days=3))
|
||||
self.orga.events.create(name="Present", live=True, is_public=True, slug='present', date_from=now())
|
||||
self.orga.events.create(name="Future", live=True, is_public=True, slug='future', date_from=now() + datetime.timedelta(days=3))
|
||||
self.orga.events.create(name="Disabled", live=False, is_public=True, slug='disabled', date_from=now() + datetime.timedelta(days=3))
|
||||
self.orga.events.create(name="Secret", live=True, is_public=False, slug='secret', date_from=now() + datetime.timedelta(days=3))
|
||||
self.event.subevents.create(name="Past", active=True, date_from=now() - datetime.timedelta(days=3))
|
||||
self.event.subevents.create(name="Present", active=True, date_from=now())
|
||||
self.event.subevents.create(name="Future", active=True, date_from=now() + datetime.timedelta(days=3))
|
||||
self.event.subevents.create(name="Disabled", active=False, date_from=now() + datetime.timedelta(days=3))
|
||||
|
||||
settings.SITE_URL = 'http://example.com'
|
||||
response = self.client.get('/%s/widget/product_list' % (self.orga.slug,))
|
||||
data = json.loads(response.content.decode())
|
||||
assert data == {
|
||||
'events': [
|
||||
{'availability': {'color': 'none', 'text': 'Event series'},
|
||||
'date_range': 'Dec. 29, 2018 – Jan. 4, 2019',
|
||||
'event_url': 'http://example.com/ccc/30c3/',
|
||||
'name': '30C3'},
|
||||
{'availability': {'color': 'green', 'text': 'Tickets on sale'},
|
||||
'date_range': 'Jan. 1, 2019 10:00',
|
||||
'event_url': 'http://example.com/ccc/present/',
|
||||
'name': 'Present'},
|
||||
{'availability': {'color': 'green', 'text': 'Tickets on sale'},
|
||||
'date_range': 'Jan. 4, 2019 10:00',
|
||||
'event_url': 'http://example.com/ccc/future/',
|
||||
'name': 'Future'}
|
||||
],
|
||||
'list_type': 'list'
|
||||
}
|
||||
|
||||
def test_event_calendar(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
with freeze_time("2019-01-01 10:00:00"):
|
||||
self.orga.events.create(name="Past", live=True, is_public=True, slug='past', date_from=now() - datetime.timedelta(days=3))
|
||||
self.orga.events.create(name="Present", live=True, is_public=True, slug='present', date_from=now())
|
||||
self.orga.events.create(name="Future", live=True, is_public=True, slug='future', date_from=now() + datetime.timedelta(days=3))
|
||||
self.orga.events.create(name="Disabled", live=False, is_public=True, slug='disabled', date_from=now() + datetime.timedelta(days=3))
|
||||
self.orga.events.create(name="Secret", live=True, is_public=False, slug='secret', date_from=now() + datetime.timedelta(days=3))
|
||||
self.event.subevents.create(name="Past", active=True, date_from=now() - datetime.timedelta(days=3))
|
||||
se1 = self.event.subevents.create(name="Present", active=True, date_from=now())
|
||||
se2 = self.event.subevents.create(name="Future", active=True, date_from=now() + datetime.timedelta(days=3))
|
||||
self.event.subevents.create(name="Disabled", active=False, date_from=now() + datetime.timedelta(days=3))
|
||||
|
||||
response = self.client.get('/%s/widget/product_list?style=calendar' % (self.orga.slug,))
|
||||
settings.SITE_URL = 'http://example.com'
|
||||
data = json.loads(response.content.decode())
|
||||
assert data == {
|
||||
'date': '2019-01-01',
|
||||
'list_type': 'calendar',
|
||||
'weeks': [
|
||||
[None,
|
||||
{'date': '2019-01-01',
|
||||
'day': 1,
|
||||
'events': [{'availability': {'color': 'green',
|
||||
'text': 'Tickets on sale'},
|
||||
'continued': False,
|
||||
'date_range': 'Jan. 1, 2019 10:00',
|
||||
'event_url': 'http://example.com/ccc/present/',
|
||||
'name': 'Present',
|
||||
'subevent': None,
|
||||
'time': '10:00'},
|
||||
{'availability': {'color': 'green',
|
||||
'text': 'Tickets on sale'},
|
||||
'continued': False,
|
||||
'date_range': 'Jan. 1, 2019 10:00',
|
||||
'event_url': 'http://example.com/ccc/30c3/',
|
||||
'name': 'Present',
|
||||
'subevent': se1.pk,
|
||||
'time': '10:00'}]},
|
||||
{'date': '2019-01-02', 'day': 2, 'events': []},
|
||||
{'date': '2019-01-03', 'day': 3, 'events': []},
|
||||
{'date': '2019-01-04',
|
||||
'day': 4,
|
||||
'events': [{'availability': {'color': 'green',
|
||||
'text': 'Tickets on sale'},
|
||||
'continued': False,
|
||||
'date_range': 'Jan. 4, 2019 10:00',
|
||||
'event_url': 'http://example.com/ccc/future/',
|
||||
'name': 'Future',
|
||||
'subevent': None,
|
||||
'time': '10:00'},
|
||||
{'availability': {'color': 'green',
|
||||
'text': 'Tickets on sale'},
|
||||
'continued': False,
|
||||
'date_range': 'Jan. 4, 2019 10:00',
|
||||
'event_url': 'http://example.com/ccc/30c3/',
|
||||
'name': 'Future',
|
||||
'subevent': se2.pk,
|
||||
'time': '10:00'}]},
|
||||
{'date': '2019-01-05', 'day': 5, 'events': []},
|
||||
{'date': '2019-01-06', 'day': 6, 'events': []}],
|
||||
[{'date': '2019-01-07', 'day': 7, 'events': []},
|
||||
{'date': '2019-01-08', 'day': 8, 'events': []},
|
||||
{'date': '2019-01-09', 'day': 9, 'events': []},
|
||||
{'date': '2019-01-10', 'day': 10, 'events': []},
|
||||
{'date': '2019-01-11', 'day': 11, 'events': []},
|
||||
{'date': '2019-01-12', 'day': 12, 'events': []},
|
||||
{'date': '2019-01-13', 'day': 13, 'events': []}],
|
||||
[{'date': '2019-01-14', 'day': 14, 'events': []},
|
||||
{'date': '2019-01-15', 'day': 15, 'events': []},
|
||||
{'date': '2019-01-16', 'day': 16, 'events': []},
|
||||
{'date': '2019-01-17', 'day': 17, 'events': []},
|
||||
{'date': '2019-01-18', 'day': 18, 'events': []},
|
||||
{'date': '2019-01-19', 'day': 19, 'events': []},
|
||||
{'date': '2019-01-20', 'day': 20, 'events': []}],
|
||||
[{'date': '2019-01-21', 'day': 21, 'events': []},
|
||||
{'date': '2019-01-22', 'day': 22, 'events': []},
|
||||
{'date': '2019-01-23', 'day': 23, 'events': []},
|
||||
{'date': '2019-01-24', 'day': 24, 'events': []},
|
||||
{'date': '2019-01-25', 'day': 25, 'events': []},
|
||||
{'date': '2019-01-26', 'day': 26, 'events': []},
|
||||
{'date': '2019-01-27', 'day': 27, 'events': []}],
|
||||
[{'date': '2019-01-28', 'day': 28, 'events': []},
|
||||
{'date': '2019-01-29', 'day': 29, 'events': []},
|
||||
{'date': '2019-01-30', 'day': 30, 'events': []},
|
||||
{'date': '2019-01-31', 'day': 31, 'events': []},
|
||||
None,
|
||||
None,
|
||||
None]
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user