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 %} +

    +
    + {% csrf_token %} + {% bootstrap_form form layout="control" %} +
    +
    + +
    +
    +
    + {% 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 %} + + +
    +

    {% trans "The payment process has started in a new window." %}

    + +

    + {% trans "The window to enter your payment data was not opened or was closed?" %} +

    +

    + + {% trans "Click here in order to open the window." %} + +

    + +
    + + 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 %}
    @@ -24,12 +25,13 @@