diff --git a/.gitattributes b/.gitattributes
index 3fdddca17..722188fee 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -5,6 +5,7 @@ src/static/moment/* linguist-vendored
src/static/datetimepicker/* linguist-vendored
src/static/colorpicker/* linguist-vendored
src/static/fileupload/* linguist-vendored
+src/static/vuejs/* linguist-vendored
src/static/charts/* linguist-vendored
src/pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/fabric.* linguist-vendored
src/pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/pdf.* linguist-vendored
diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py
index 4dbacf0b5..b91d099c7 100644
--- a/src/pretix/base/settings.py
+++ b/src/pretix/base/settings.py
@@ -427,6 +427,14 @@ Your {event} team"""))
'default': None,
'type': str
},
+ 'presale_widget_css_file': {
+ 'default': None,
+ 'type': str
+ },
+ 'presale_widget_css_checksum': {
+ 'default': None,
+ 'type': str
+ },
'logo_image': {
'default': None,
'type': File
diff --git a/src/pretix/base/templatetags/rich_text.py b/src/pretix/base/templatetags/rich_text.py
index 3eaadc3fc..b8d2b956e 100644
--- a/src/pretix/base/templatetags/rich_text.py
+++ b/src/pretix/base/templatetags/rich_text.py
@@ -4,6 +4,7 @@ import bleach
import markdown
from bleach import DEFAULT_CALLBACKS
from django import template
+from django.conf import settings
from django.core import signing
from django.urls import reverse
from django.utils.http import is_safe_url
@@ -63,6 +64,12 @@ def safelink_callback(attrs, new=False):
return attrs
+def abslink_callback(attrs, new=False):
+ attrs[None, 'href'] = urllib.parse.urljoin(settings.SITE_URL, attrs.get((None, 'href'), '/'))
+ attrs[None, 'target'] = '_blank'
+ return attrs
+
+
@register.filter
def rich_text(text: str, **kwargs):
"""
@@ -73,5 +80,5 @@ def rich_text(text: str, **kwargs):
markdown.markdown(text),
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRIBUTES,
- ), callbacks=DEFAULT_CALLBACKS + [safelink_callback])
+ ), callbacks=DEFAULT_CALLBACKS + ([safelink_callback] if kwargs.get('safelinks', True) else [abslink_callback]))
return mark_safe(body_md)
diff --git a/src/pretix/base/validators.py b/src/pretix/base/validators.py
index da7d87380..0b7d8db3d 100644
--- a/src/pretix/base/validators.py
+++ b/src/pretix/base/validators.py
@@ -33,6 +33,7 @@ class EventSlugBlacklistValidator(BlacklistValidator):
'api',
'events',
'csp_report',
+ 'widget',
]
@@ -53,4 +54,5 @@ class OrganizerSlugBlacklistValidator(BlacklistValidator):
'about',
'api',
'csp_report',
+ 'widget',
]
diff --git a/src/pretix/base/views/async.py b/src/pretix/base/views/async.py
index e9e32602d..03f327f98 100644
--- a/src/pretix/base/views/async.py
+++ b/src/pretix/base/views/async.py
@@ -51,6 +51,9 @@ class AsyncAction:
return self.get_result(request)
return self.http_method_not_allowed(request)
+ def _ajax_response_data(self):
+ return {}
+
def _return_ajax_result(self, res, timeout=.5):
if not res.ready():
try:
@@ -59,10 +62,11 @@ class AsyncAction:
pass
ready = res.ready()
- data = {
+ data = self._ajax_response_data()
+ data.update({
'async_id': res.id,
'ready': ready
- }
+ })
if ready:
if res.successful() and not isinstance(res.info, Exception):
smes = self.get_success_message(res.info)
diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py
index 0dc2712e7..a44a0642f 100644
--- a/src/pretix/control/forms/event.py
+++ b/src/pretix/control/forms/event.py
@@ -4,13 +4,13 @@ from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db.models import Q
from django.utils.timezone import get_current_timezone_name
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from i18nfield.forms import I18nFormField, I18nTextarea
from pytz import common_timezones, timezone
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
from pretix.base.models import Event, Organizer, TaxRule
-from pretix.base.models.event import EventMetaValue
+from pretix.base.models.event import EventMetaValue, SubEvent
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
from pretix.control.forms import (
ExtFileField, SlugWidget, SplitDateTimePickerWidget,
@@ -897,3 +897,44 @@ class TaxRuleForm(I18nModelForm):
class Meta:
model = TaxRule
fields = ['name', 'rate', 'price_includes_tax', 'eu_reverse_charge', 'home_country']
+
+
+class WidgetCodeForm(forms.Form):
+ subevent = forms.ModelChoiceField(
+ label=pgettext_lazy('subevent', "Date"),
+ required=True,
+ queryset=SubEvent.objects.none()
+ )
+ language = forms.ChoiceField(
+ label=_("Language"),
+ required=True,
+ choices=settings.LANGUAGES
+ )
+ voucher = forms.CharField(
+ label=_("Pre-selected voucher"),
+ required=False,
+ help_text=_("If set, the widget will show products as if this voucher has been entered and when a product is "
+ "bought via the widget, this voucher will be used. This can for example be used to provide "
+ "widgets that give discounts or unlock secret products.")
+ )
+
+ def __init__(self, *args, **kwargs):
+ self.event = kwargs.pop('event')
+ super().__init__(*args, **kwargs)
+
+ if self.event.has_subevents:
+ self.fields['subevent'].queryset = self.event.subevents.all()
+ else:
+ del self.fields['subevent']
+
+ self.fields['language'].choices = [(l, n) for l, n in settings.LANGUAGES if l in self.event.settings.locales]
+
+ def clean_voucher(self):
+ v = self.cleaned_data.get('voucher')
+ if not v:
+ return
+
+ if not self.event.vouchers.filter(code=v).exists():
+ raise ValidationError(_('The given voucher code does not exist.'))
+
+ return v
diff --git a/src/pretix/control/templates/pretixcontrol/event/settings_base.html b/src/pretix/control/templates/pretixcontrol/event/settings_base.html
index 87017b1b5..fe622c35c 100644
--- a/src/pretix/control/templates/pretixcontrol/event/settings_base.html
+++ b/src/pretix/control/templates/pretixcontrol/event/settings_base.html
@@ -75,6 +75,11 @@
{% trans "Permissions" %}
+
+
+ {% trans "Widget" %}
+
+
{% endif %}
{% for nav in nav_event_settings %}
diff --git a/src/pretix/control/templates/pretixcontrol/event/widget.html b/src/pretix/control/templates/pretixcontrol/event/widget.html
new file mode 100644
index 000000000..46de81164
--- /dev/null
+++ b/src/pretix/control/templates/pretixcontrol/event/widget.html
@@ -0,0 +1,63 @@
+{% extends "pretixcontrol/event/settings_base.html" %}
+{% load i18n %}
+{% load staticfiles %}
+{% load bootstrap3 %}
+{% load eventurl %}
+{% block inside %}
+ {% trans "Widget" %}
+
+ {% blocktrans trimmed %}
+ The pretix widget is a way to embed your ticket shop into your event website. This way, your visitors can
+ buy their ticket right away without leaving your website.
+ {% endblocktrans %}
+
+ {% if valid %}
+
+ {% blocktrans trimmed %}
+ To embed the widget onto your website, simply copy the following code to the <head>
+ section of your website:
+ {% endblocktrans %}
+
+ <link rel="stylesheet" type="text/css" href="{% abseventurl request.event "presale:event.widget.css" %}">
+<script type="text/javascript" src="{{ urlprefix }}{% url "presale:widget.js" lang=form.cleaned_data.language %}" async></script>
+
+ {% blocktrans trimmed %}
+ Then, copy the following code to the place of your website where you want the widget to show up:
+ {% endblocktrans %}
+
+ {% if form.cleaned_data.subevent %}
+ {% abseventurl request.event "presale:event.index" subevent=form.cleaned_data.subevent.pk as indexurl %}
+ {% else %}
+ {% abseventurl request.event "presale:event.index" as indexurl %}
+ {% endif %}
+ <pretix-widget event="http://192.168.0.10:8000/democon/"{% if form.cleaned_data.subevent %} subevent="{{ form.cleaned_data.subevent.pk }}"{% endif %}{% if form.cleaned_data.voucher %} voucher="{{ form.cleaned_data.voucher }}"{% endif %}></pretix-widget>
+<noscript>
+ <div class="pretix-widget">
+ <div class="pretix-widget-info-message">
+ {% blocktrans trimmed with a_attr='target="_blank" href="'|add:indexurl|add:'"'|safe %}
+ JavaScript is disabled in your browser. To access our ticket shop without javascript,
+ please <a {{ a_attr }}>click here</a>.
+ {% endblocktrans %}
+ </div>
+ </div>
+</noscript>
+
+ {% else %}
+
+ {% blocktrans trimmed %}
+ Using this form, you can generate a code to copy and paste to your website source.
+ {% endblocktrans %}
+
+
+ {% endif %}
+{% endblock %}
diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py
index 7e1e3cf60..56e9bf07d 100644
--- a/src/pretix/control/urls.py
+++ b/src/pretix/control/urls.py
@@ -75,6 +75,7 @@ urlpatterns = [
url(r'^settings/tax/(?P\d+)/$', event.TaxUpdate.as_view(), name='event.settings.tax.edit'),
url(r'^settings/tax/add$', event.TaxCreate.as_view(), name='event.settings.tax.add'),
url(r'^settings/tax/(?P\d+)/delete$', event.TaxDelete.as_view(), name='event.settings.tax.delete'),
+ url(r'^settings/widget$', event.WidgetSettings.as_view(), name='event.settings.widget'),
url(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'),
url(r'^subevents/(?P\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'),
url(r'^subevents/(?P\d+)/delete$', subevents.SubEventDelete.as_view(),
diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py
index 0ef0d6d72..12486708f 100644
--- a/src/pretix/control/views/event.py
+++ b/src/pretix/control/views/event.py
@@ -1,6 +1,7 @@
import re
from collections import OrderedDict
from datetime import timedelta
+from urllib.parse import urlsplit
from django.conf import settings
from django.contrib import messages
@@ -36,10 +37,12 @@ from pretix.control.forms.event import (
CommentForm, DisplaySettingsForm, EventMetaValueForm, EventSettingsForm,
EventUpdateForm, InvoiceSettingsForm, MailSettingsForm,
PaymentSettingsForm, ProviderForm, TaxRuleForm, TicketSettingsForm,
+ WidgetCodeForm,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.signals import nav_event_settings
from pretix.helpers.urls import build_absolute_uri
+from pretix.multidomain.urlreverse import get_domain
from pretix.presale.style import regenerate_css
from . import CreateView, UpdateView
@@ -989,3 +992,31 @@ class TaxDelete(EventSettingsViewMixin, EventPermissionRequiredMixin, DeleteView
context = super().get_context_data(*args, **kwargs)
context['possible'] = self.object.allow_delete()
return context
+
+
+class WidgetSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormView):
+ template_name = 'pretixcontrol/event/widget.html'
+ permission = 'can_change_event_settings'
+ form_class = WidgetCodeForm
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ kwargs['event'] = self.request.event
+ return kwargs
+
+ def form_valid(self, form):
+ ctx = self.get_context_data()
+ ctx['form'] = form
+ ctx['valid'] = True
+ return self.render_to_response(ctx)
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx['urlprefix'] = settings.SITE_URL
+ domain = get_domain(self.request.organizer)
+ if domain:
+ siteurlsplit = urlsplit(settings.SITE_URL)
+ if siteurlsplit.port and siteurlsplit.port not in (80, 443):
+ domain = '%s:%d' % (domain, siteurlsplit.port)
+ ctx['urlprefix'] = '%s://%s' % (siteurlsplit.scheme, domain)
+ return ctx
diff --git a/src/pretix/plugins/paypal/payment.py b/src/pretix/plugins/paypal/payment.py
index a969433c0..ccfa97edd 100644
--- a/src/pretix/plugins/paypal/payment.py
+++ b/src/pretix/plugins/paypal/payment.py
@@ -1,10 +1,12 @@
import json
import logging
+import urllib.parse
from collections import OrderedDict
import paypalrestsdk
from django import forms
from django.contrib import messages
+from django.core import signing
from django.template.loader import get_template
from django.utils.translation import ugettext as __, ugettext_lazy as _
@@ -89,14 +91,18 @@ class Paypal(BasePaymentProvider):
def checkout_prepare(self, request, cart):
self.init_api()
+ kwargs = {}
+ if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs:
+ kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
+
payment = paypalrestsdk.Payment({
'intent': 'sale',
'payer': {
"payment_method": "paypal",
},
"redirect_urls": {
- "return_url": build_absolute_uri(request.event, 'plugins:paypal:return'),
- "cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort'),
+ "return_url": build_absolute_uri(request.event, 'plugins:paypal:return', kwargs=kwargs),
+ "cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort', kwargs=kwargs),
},
"transactions": [
{
@@ -131,7 +137,14 @@ class Paypal(BasePaymentProvider):
request.session['payment_paypal_id'] = payment.id
for link in payment.links:
if link.method == "REDIRECT" and link.rel == "approval_url":
- return str(link.href)
+ if request.session.get('iframe_session', False):
+ signer = signing.Signer(salt='safe-redirect')
+ return (
+ build_absolute_uri(request.event, 'plugins:paypal:redirect') + '?url=' +
+ urllib.parse.quote(signer.sign(link.href))
+ )
+ else:
+ return str(link.href)
else:
messages.error(request, _('We had trouble communicating with PayPal'))
logger.error('Error on creating payment: ' + str(payment.error))
diff --git a/src/pretix/plugins/paypal/templates/pretixplugins/paypal/redirect.html b/src/pretix/plugins/paypal/templates/pretixplugins/paypal/redirect.html
new file mode 100644
index 000000000..01c9d1ae9
--- /dev/null
+++ b/src/pretix/plugins/paypal/templates/pretixplugins/paypal/redirect.html
@@ -0,0 +1,32 @@
+{% load compress %}
+{% load i18n %}
+{% load staticfiles %}
+
+
+
+ {{ settings.PRETIX_INSTANCE_NAME }}
+ {% compress css %}
+
+ {% endcompress %}
+ {% compress js %}
+
+ {% endcompress %}
+
+
+
+
+
diff --git a/src/pretix/plugins/paypal/urls.py b/src/pretix/plugins/paypal/urls.py
index 17b7ba841..e61960dea 100644
--- a/src/pretix/plugins/paypal/urls.py
+++ b/src/pretix/plugins/paypal/urls.py
@@ -2,12 +2,17 @@ from django.conf.urls import include, url
from pretix.multidomain import event_url
-from .views import abort, refund, success, webhook
+from .views import abort, redirect_view, refund, success, webhook
event_patterns = [
url(r'^paypal/', include([
url(r'^abort/$', abort, name='abort'),
url(r'^return/$', success, name='return'),
+ url(r'^redirect/$', redirect_view, name='redirect'),
+
+ url(r'w/(?P[a-zA-Z0-9]{16})/abort/', abort, name='abort'),
+ url(r'w/(?P[a-zA-Z0-9]{16})/return/', success, name='return'),
+
event_url(r'^webhook/$', webhook, name='webhook', require_live=False),
])),
]
diff --git a/src/pretix/plugins/paypal/views.py b/src/pretix/plugins/paypal/views.py
index e883bb6a8..6c94fe33c 100644
--- a/src/pretix/plugins/paypal/views.py
+++ b/src/pretix/plugins/paypal/views.py
@@ -3,11 +3,13 @@ import logging
import paypalrestsdk
from django.contrib import messages
+from django.core import signing
from django.db import transaction
-from django.http import HttpResponse
-from django.shortcuts import get_object_or_404, redirect
+from django.http import HttpResponse, HttpResponseBadRequest
+from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
+from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
@@ -17,12 +19,25 @@ from pretix.control.permissions import event_permission_required
from pretix.multidomain.urlreverse import eventreverse
from pretix.plugins.paypal.payment import Paypal
from pretix.plugins.stripe.models import ReferencedStripeObject
-from pretix.presale.utils import event_view
logger = logging.getLogger('pretix.plugins.paypal')
-@event_view(require_live=False)
+@xframe_options_exempt
+def redirect_view(request, *args, **kwargs):
+ signer = signing.Signer(salt='safe-redirect')
+ try:
+ url = signer.unsign(request.GET.get('url', ''))
+ except signing.BadSignature:
+ return HttpResponseBadRequest('Invalid parameter')
+
+ r = render(request, 'pretixplugins/paypal/redirect.html', {
+ 'url': url,
+ })
+ r._csp_ignore = True
+ return r
+
+
def success(request, *args, **kwargs):
pid = request.GET.get('paymentId')
token = request.GET.get('token')
@@ -30,6 +45,10 @@ def success(request, *args, **kwargs):
request.session['payment_paypal_token'] = token
request.session['payment_paypal_payer'] = payer
+ urlkwargs = {}
+ if 'cart_namespace' in kwargs:
+ urlkwargs['cart_namespace'] = kwargs['cart_namespace']
+
if request.session.get('payment_paypal_order'):
order = Order.objects.get(pk=request.session.get('payment_paypal_order'))
else:
@@ -44,7 +63,8 @@ def success(request, *args, **kwargs):
else:
messages.error(request, _('Invalid response from PayPal received.'))
logger.error('Session did not contain payment_paypal_id')
- return redirect(eventreverse(request.event, 'presale:event.checkout', kwargs={'step': 'payment'}))
+ urlkwargs['step'] = 'payment'
+ return redirect(eventreverse(request.event, 'presale:event.checkout', kwargs=urlkwargs))
if order:
return redirect(eventreverse(request.event, 'presale:event.order', kwargs={
@@ -52,7 +72,8 @@ def success(request, *args, **kwargs):
'secret': order.secret
}) + ('?paid=yes' if order.status == Order.STATUS_PAID else ''))
else:
- return redirect(eventreverse(request.event, 'presale:event.checkout', kwargs={'step': 'confirm'}))
+ urlkwargs['step'] = 'confirm'
+ return redirect(eventreverse(request.event, 'presale:event.checkout', kwargs=urlkwargs))
def abort(request, *args, **kwargs):
diff --git a/src/pretix/plugins/stripe/views.py b/src/pretix/plugins/stripe/views.py
index 27ba09104..ce95a6523 100644
--- a/src/pretix/plugins/stripe/views.py
+++ b/src/pretix/plugins/stripe/views.py
@@ -8,9 +8,11 @@ from django.db import transaction
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
+from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views import View
+from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
@@ -193,6 +195,7 @@ class StripeOrderView:
return self.request.event.get_payment_providers()[self.order.payment_provider]
+@method_decorator(xframe_options_exempt, 'dispatch')
class ReturnView(StripeOrderView, View):
def get(self, request, *args, **kwargs):
prov = self.pprov
diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py
index 9822d3c24..a7451ec09 100644
--- a/src/pretix/presale/checkoutflow.py
+++ b/src/pretix/presale/checkoutflow.py
@@ -70,20 +70,26 @@ class BaseCheckoutFlowStep:
def post(self, request):
return HttpResponseNotAllowed([])
- def get_step_url(self):
- return eventreverse(self.event, 'presale:event.checkout', kwargs={'step': self.identifier})
+ def get_step_url(self, request):
+ kwargs = {'step': self.identifier}
+ if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs:
+ kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
+ return eventreverse(self.event, 'presale:event.checkout', kwargs=kwargs)
def get_prev_url(self, request):
prev = self.get_prev_applicable(request)
if not prev:
- return eventreverse(self.event, 'presale:event.index')
+ kwargs = {}
+ if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs:
+ kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
+ return eventreverse(self.request.event, 'presale:event.index', kwargs=kwargs)
else:
- return prev.get_step_url()
+ return prev.get_step_url(request)
def get_next_url(self, request):
n = self.get_next_applicable(request)
if n:
- return n.get_step_url()
+ return n.get_step_url(request)
@cached_property
def cart_session(self):
@@ -225,6 +231,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['forms'] = self.forms
+ ctx['cart'] = self.get_cart()
return ctx
def get_success_message(self, value):
@@ -234,7 +241,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
return self.get_next_url(self.request)
def get_error_url(self):
- return self.get_step_url()
+ return self.get_step_url(self.request)
def get(self, request):
self.request = request
@@ -382,6 +389,7 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
ctx['contact_form'] = self.contact_form
ctx['invoice_form'] = self.invoice_form
ctx['reverse_charge_relevant'] = self.eu_reverse_charge_relevant
+ ctx['cart'] = self.get_cart()
return ctx
@@ -436,6 +444,7 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
ctx['selected'] = self.request.POST.get('payment', self.cart_session.get('payment', ''))
if len(self.provider_forms) == 1:
ctx['selected'] = self.provider_forms[0]['provider'].identifier
+ ctx['cart'] = self.get_cart()
return ctx
@cached_property
@@ -557,7 +566,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
return super().get_error_message(exception)
def get_error_url(self):
- return self.get_step_url()
+ return self.get_step_url(self.request)
def get_order_url(self, order):
return eventreverse(self.request.event, 'presale:event.order.pay.complete', kwargs={
diff --git a/src/pretix/presale/context.py b/src/pretix/presale/context.py
index 9b13413e3..6bdd7ba3d 100644
--- a/src/pretix/presale/context.py
+++ b/src/pretix/presale/context.py
@@ -56,6 +56,9 @@ def contextprocessor(request):
ctx['event'] = request.event
ctx['languages'] = [get_language_info(code) for code in request.event.settings.locales]
+ if request.resolver_match:
+ ctx['cart_namespace'] = request.resolver_match.kwargs.get('cart_namespace', '')
+
if hasattr(request, 'organizer'):
if request.organizer.settings.presale_css_file and not hasattr(request, 'event'):
ctx['css_file'] = default_storage.url(request.organizer.settings.presale_css_file)
diff --git a/src/pretix/presale/management/commands/updatestyles.py b/src/pretix/presale/management/commands/updatestyles.py
index 9a481bfe1..4f2f2c596 100644
--- a/src/pretix/presale/management/commands/updatestyles.py
+++ b/src/pretix/presale/management/commands/updatestyles.py
@@ -1,15 +1,38 @@
+import hashlib
+
+from django.conf import settings
+from django.core.files.base import ContentFile
+from django.core.files.storage import default_storage
from django.core.management.base import BaseCommand
from pretix.base.models import Event_SettingsStore, Organizer_SettingsStore
+from pretix.base.settings import GlobalSettingsObject
+from pretix.presale.views.widget import generate_widget_js
from ...style import regenerate_css, regenerate_organizer_css
class Command(BaseCommand):
- help = "Re-generate all custom stylesheets"
+ help = "Re-generate all custom stylesheets and scripts"
def handle(self, *args, **options):
for es in Event_SettingsStore.objects.filter(key="presale_css_file"):
regenerate_css.apply_async(args=(es.object_id,))
+
for es in Organizer_SettingsStore.objects.filter(key="presale_css_file"):
regenerate_organizer_css.apply_async(args=(es.object_id,))
+
+ gs = GlobalSettingsObject()
+ for lc, ll in settings.LANGUAGES:
+ data = generate_widget_js(lc).encode()
+ checksum = hashlib.sha1(data).hexdigest()
+ fname = gs.settings.get('widget_file_{}'.format(lc))
+ if not fname or gs.settings.get('widget_checksum_{}'.format(lc), '') != checksum:
+ newname = default_storage.save(
+ 'widget/widget.{}.{}.js'.format(lc, checksum),
+ ContentFile(data)
+ )
+ gs.settings.set('widget_file_{}'.format(lc), 'file://' + newname)
+ gs.settings.set('widget_checksum_{}'.format(lc), checksum)
+ if fname:
+ default_storage.delete(fname)
diff --git a/src/pretix/presale/style.py b/src/pretix/presale/style.py
index 893f1245a..b8c655633 100644
--- a/src/pretix/presale/style.py
+++ b/src/pretix/presale/style.py
@@ -20,7 +20,7 @@ logger = logging.getLogger('pretix.presale.style')
affected_keys = ['primary_font', 'primary_color']
-def compile_scss(object):
+def compile_scss(object, file="main.scss", fonts=True):
sassdir = os.path.join(settings.STATIC_ROOT, 'pretixpresale/scss')
def static(path):
@@ -41,14 +41,14 @@ def compile_scss(object):
sassrules.append('$brand-primary: {};'.format(object.settings.get('primary_color')))
font = object.settings.get('primary_font')
- if font != 'Open Sans':
+ if font != 'Open Sans' and fonts:
sassrules.append(get_font_stylesheet(font))
sassrules.append(
'$font-family-sans-serif: "{}", "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif !default'.format(
font
))
- sassrules.append('@import "main.scss";')
+ sassrules.append('@import "{}";'.format(file))
cf = dict(django_libsass.CUSTOM_FUNCTIONS)
cf['static'] = static
@@ -64,32 +64,46 @@ def compile_scss(object):
@app.task(base=ProfiledTask)
def regenerate_css(event_id: int):
event = Event.objects.select_related('organizer').get(pk=event_id)
- css, checksum = compile_scss(event)
- fname = '{}/{}/presale.{}.css'.format(
- event.organizer.slug, event.slug, checksum[:16]
- )
+ # main.scss
+ css, checksum = compile_scss(event)
+ fname = '{}/{}/presale.{}.css'.format(event.organizer.slug, event.slug, checksum[:16])
if event.settings.get('presale_css_checksum', '') != checksum:
newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
event.settings.set('presale_css_file', newname)
event.settings.set('presale_css_checksum', checksum)
+ # widget.scss
+ css, checksum = compile_scss(event, file='widget.scss', fonts=False)
+ fname = '{}/{}/widget.{}.css'.format(event.organizer.slug, event.slug, checksum[:16])
+
+ if event.settings.get('presale_widget_css_checksum', '') != checksum:
+ newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
+ event.settings.set('presale_widget_css_file', newname)
+ event.settings.set('presale_widget_css_checksum', checksum)
+
@app.task(base=ProfiledTask)
def regenerate_organizer_css(organizer_id: int):
organizer = Organizer.objects.get(pk=organizer_id)
+
+ # main.scss
css, checksum = compile_scss(organizer)
-
- fname = '{}/presale.{}.css'.format(
- organizer.slug, checksum[:16]
- )
-
+ fname = '{}/presale.{}.css'.format(organizer.slug, checksum[:16])
if organizer.settings.get('presale_css_checksum', '') != checksum:
newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
organizer.settings.set('presale_css_file', newname)
organizer.settings.set('presale_css_checksum', checksum)
+ # widget.scss
+ css, checksum = compile_scss(organizer)
+ fname = '{}/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')))
+ organizer.settings.set('presale_widget_css_file', newname)
+ organizer.settings.set('presale_widget_css_checksum', checksum)
+
non_inherited_events = set(Event_SettingsStore.objects.filter(
object__organizer=organizer, key__in=affected_keys
).values_list('object_id', flat=True))
diff --git a/src/pretix/presale/templates/pretixpresale/base_footer.html b/src/pretix/presale/templates/pretixpresale/base_footer.html
index c10bc5fe2..92c5b5231 100644
--- a/src/pretix/presale/templates/pretixpresale/base_footer.html
+++ b/src/pretix/presale/templates/pretixpresale/base_footer.html
@@ -1,7 +1,7 @@
{% load i18n %}
{% load safelink %}
{% safelink "https://pretix.eu" as pretixurl %}
-{% with 'href="'|add:pretixurl|add:'"'|safe as a_attr %}
+{% with 'target="_blank" href="'|add:pretixurl|add:'"'|safe as a_attr %}
{% blocktrans trimmed %}
powered by pretix
{% endblocktrans %}
diff --git a/src/pretix/presale/templates/pretixpresale/event/base.html b/src/pretix/presale/templates/pretixpresale/event/base.html
index 1bd8155f4..727b19e8c 100644
--- a/src/pretix/presale/templates/pretixpresale/event/base.html
+++ b/src/pretix/presale/templates/pretixpresale/event/base.html
@@ -8,6 +8,7 @@
{% block title %}{% endblock %}{% if url_name != "event.index" %} :: {% endif %}{{ event.name }}
{% endblock %}
{% block above %}
+
{% if not event.live %}