diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index e7b35948a..3982fd332 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -1,6 +1,7 @@ from django import forms from django.conf import settings from django.core.exceptions import ValidationError +from django.core.validators import RegexValidator from django.utils.translation import ugettext_lazy as _ from i18nfield.forms import I18nFormField, I18nTextarea @@ -120,6 +121,16 @@ class OrganizerSettingsForm(SettingsForm): help_text=_('Choose all languages that your organizer homepage should be available in.') ) + organizer_primary_color = forms.CharField( + label=_("Primary color"), + required=False, + validators=[ + RegexValidator(regex='^#[0-9a-fA-F]{6}$', + message=_('Please enter the hexadecimal code of a color, e.g. #990000.')) + ], + widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) + ) + organizer_homepage_text = I18nFormField( label=_('Homepage text'), required=False, diff --git a/src/pretix/control/templates/pretixcontrol/organizers/edit.html b/src/pretix/control/templates/pretixcontrol/organizers/edit.html index ee0cfda1b..5b575b0d6 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/edit.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/edit.html @@ -20,6 +20,7 @@ {% trans "Display settings" %} {% bootstrap_form_errors sform %} {% bootstrap_field sform.locales layout="horizontal" %} + {% bootstrap_field sform.organizer_primary_color layout="horizontal" %} {% bootstrap_field sform.organizer_logo_image layout="horizontal" %} {% bootstrap_field sform.organizer_homepage_text layout="horizontal" %} {% bootstrap_field sform.event_list_type layout="horizontal" %} diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py index 0094bd48c..92e802486 100644 --- a/src/pretix/control/views/organizer.py +++ b/src/pretix/control/views/organizer.py @@ -24,6 +24,7 @@ from pretix.control.forms.organizer import ( from pretix.control.permissions import OrganizerPermissionRequiredMixin from pretix.control.signals import nav_organizer from pretix.helpers.urls import build_absolute_uri +from pretix.presale.style import regenerate_organizer_css class OrganizerList(ListView): @@ -135,6 +136,7 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView): user=self.request.user, data={k: form.cleaned_data.get(k) for k in form.changed_data} ) + regenerate_organizer_css.apply_async(args=(self.request.organizer.pk,)) messages.success(self.request, _('Your changes have been saved.')) return super().form_valid(form) diff --git a/src/pretix/presale/context.py b/src/pretix/presale/context.py index 17383f52b..9b13413e3 100644 --- a/src/pretix/presale/context.py +++ b/src/pretix/presale/context.py @@ -57,6 +57,8 @@ def contextprocessor(request): ctx['languages'] = [get_language_info(code) for code in request.event.settings.locales] 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) ctx['organizer_logo'] = request.organizer.settings.get('organizer_logo_image', as_type=str, default='')[7:] ctx['organizer_homepage_text'] = request.organizer.settings.get('organizer_homepage_text', as_type=LazyI18nString) ctx['organizer'] = request.organizer diff --git a/src/pretix/presale/style.py b/src/pretix/presale/style.py index 878c4d8bb..fee23c2c3 100644 --- a/src/pretix/presale/style.py +++ b/src/pretix/presale/style.py @@ -11,23 +11,22 @@ from django.core.files.storage import default_storage from django.dispatch import Signal from django.templatetags.static import static as _static -from pretix.base.models import Event +from pretix.base.models import Event, Event_SettingsStore, Organizer from pretix.base.services.async import ProfiledTask from pretix.celery_app import app from pretix.multidomain.urlreverse import get_domain logger = logging.getLogger('pretix.presale.style') +affected_keys = ['primary_font', 'primary_color'] -@app.task(base=ProfiledTask) -def regenerate_css(event_id: int): - event = Event.objects.select_related('organizer').get(pk=event_id) +def compile_scss(object): sassdir = os.path.join(settings.STATIC_ROOT, 'pretixpresale/scss') def static(path): sp = _static(path) if not settings.MEDIA_URL.startswith("/") and sp.startswith("/"): - domain = get_domain(event.organizer) + domain = get_domain(object.organizer if isinstance(object, Event) else object) if domain: siteurlsplit = urlsplit(settings.SITE_URL) if siteurlsplit.port and siteurlsplit.port not in (80, 443): @@ -38,15 +37,16 @@ def regenerate_css(event_id: int): return '"{}"'.format(sp) sassrules = [] - if event.settings.get('primary_color'): - sassrules.append('$brand-primary: {};'.format(event.settings.get('primary_color'))) + if object.settings.get('primary_color'): + sassrules.append('$brand-primary: {};'.format(object.settings.get('primary_color'))) - font = event.settings.get('primary_font') + font = object.settings.get('primary_font') if font != 'Open Sans': 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( + '$font-family-sans-serif: "{}", "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif !default'.format( + font + )) sassrules.append('@import "main.scss";') @@ -58,6 +58,14 @@ def regenerate_css(event_id: int): custom_functions=cf ) checksum = hashlib.sha1(css.encode('utf-8')).hexdigest() + return css, checksum + + +@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] ) @@ -68,6 +76,28 @@ def regenerate_css(event_id: int): event.settings.set('presale_css_checksum', checksum) +@app.task(base=ProfiledTask) +def regenerate_organizer_css(organizer_id: int): + organizer = Organizer.objects.get(pk=organizer_id) + css, checksum = compile_scss(organizer) + + 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) + + non_inherited_events = set(Event_SettingsStore.objects.filter( + event__organizer=organizer, key__in=affected_keys + ).values_list('event_id', flat=True)) + for event in organizer.events.all(): + if event.pk not in non_inherited_events: + regenerate_css.apply_async(args=(event.pk,)) + + register_fonts = Signal() """ Return a dictionaries of the following structure. Paths should be relative to static root.