Added runtime SASS compliation and color settings

This commit is contained in:
Raphael Michel
2016-07-29 20:15:13 +02:00
parent 1b3cacb196
commit d32c1bd9c8
17 changed files with 170 additions and 6 deletions

View File

@@ -46,4 +46,4 @@ RUN make production
EXPOSE 80 EXPOSE 80
ENTRYPOINT ["pretix"] ENTRYPOINT ["pretix"]
CMD ["web"] CMD ["all"]

View File

@@ -40,5 +40,9 @@ if [ "$1" == "shell" ]; then
exec python3 -m pretix shell exec python3 -m pretix shell
fi fi
echo "Specify argument: all|cron|webworker|taskworker|shell" if [ "$1" == "upgrade" ]; then
exec python3 -m pretix updatestyles
fi
echo "Specify argument: all|cron|webworker|taskworker|shell|upgrade"
exit 1 exit 1

View File

@@ -224,6 +224,7 @@ Updates are fairly simple, but require at least a short downtime::
# docker pull pretix/standalone # docker pull pretix/standalone
# systemctl restart pretix.service # systemctl restart pretix.service
# docker exec -it pretix.service pretix upgrade
Restarting the service can take a few seconds, especially if the update requires changes to the database. Restarting the service can take a few seconds, especially if the update requires changes to the database.

View File

@@ -205,6 +205,18 @@ Your {event} team"""))
'default': 'False', 'default': 'False',
'type': bool 'type': bool
}, },
'primary_color': {
'default': '#8E44B3',
'type': str
},
'presale_css_file': {
'default': None,
'type': str
},
'presale_css_checksum': {
'default': None,
'type': str
},
} }

View File

@@ -1,6 +1,7 @@
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pytz import common_timezones from pytz import common_timezones
@@ -349,6 +350,17 @@ class MailSettingsForm(SettingsForm):
raise ValidationError(_('You can activate either SSL or STARTTLS security, but not both at the same time.')) raise ValidationError(_('You can activate either SSL or STARTTLS security, but not both at the same time.'))
class DisplaySettingsForm(SettingsForm):
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.'))
]
)
class TicketSettingsForm(SettingsForm): class TicketSettingsForm(SettingsForm):
ticket_download = forms.BooleanField( ticket_download = forms.BooleanField(
label=_("Use feature"), label=_("Use feature"),

View File

@@ -0,0 +1,18 @@
{% extends "pretixcontrol/event/settings_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inside %}
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form_errors form %}
<fieldset>
<legend>{% trans "Display settings" %}</legend>
{% bootstrap_field form.primary_color layout="horizontal" %}
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -21,6 +21,11 @@
{% trans "Plugins" %} {% trans "Plugins" %}
</a> </a>
</li> </li>
<li {% if "event.settings.display" == url_name %}class="active"{% endif %}>
<a href="{% url 'control:event.settings.display' organizer=request.event.organizer.slug event=request.event.slug %}">
{% trans "Display" %}
</a>
</li>
<li {% if "event.settings.tickets" == url_name %}class="active"{% endif %}> <li {% if "event.settings.tickets" == url_name %}class="active"{% endif %}>
<a href="{% url 'control:event.settings.tickets' organizer=request.event.organizer.slug event=request.event.slug %}"> <a href="{% url 'control:event.settings.tickets' organizer=request.event.organizer.slug event=request.event.slug %}">
{% trans "Tickets" %} {% trans "Tickets" %}

View File

@@ -29,6 +29,7 @@ urlpatterns = [
url(r'^settings/tickets$', event.TicketSettings.as_view(), name='event.settings.tickets'), url(r'^settings/tickets$', event.TicketSettings.as_view(), name='event.settings.tickets'),
url(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'), url(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'),
url(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'), url(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'),
url(r'^settings/display', event.DisplaySettings.as_view(), name='event.settings.display'),
url(r'^items/$', item.ItemList.as_view(), name='event.items'), url(r'^items/$', item.ItemList.as_view(), name='event.items'),
url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'), url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'), url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),

View File

@@ -21,10 +21,12 @@ from pretix.base.signals import (
register_payment_providers, register_ticket_outputs, register_payment_providers, register_ticket_outputs,
) )
from pretix.control.forms.event import ( from pretix.control.forms.event import (
EventSettingsForm, EventUpdateForm, InvoiceSettingsForm, MailSettingsForm, DisplaySettingsForm, EventSettingsForm, EventUpdateForm,
PaymentSettingsForm, ProviderForm, TicketSettingsForm, InvoiceSettingsForm, MailSettingsForm, PaymentSettingsForm, ProviderForm,
TicketSettingsForm,
) )
from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.presale.style import regenerate_css
from . import UpdateView from . import UpdateView
@@ -241,7 +243,7 @@ class EventSettingsFormView(EventPermissionRequiredMixin, FormView):
self.request.event.log_action( self.request.event.log_action(
'pretix.event.settings', user=self.request.user, data={ 'pretix.event.settings', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data k: form.cleaned_data.get(k) for k in form.changed_data
} }
) )
messages.success(self.request, _('Your changes have been saved.')) messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url()) return redirect(self.get_success_url())
@@ -262,6 +264,38 @@ class InvoiceSettings(EventSettingsFormView):
}) })
class DisplaySettings(EventSettingsFormView):
model = Event
form_class = DisplaySettingsForm
template_name = 'pretixcontrol/event/display.html'
permission = 'can_change_settings'
def get_success_url(self) -> str:
return reverse('control:event.settings.display', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug
})
@transaction.atomic()
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
form.save()
if form.has_changed():
self.request.event.log_action(
'pretix.event.settings', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
regenerate_css(self.request.event.pk)
messages.success(self.request, _('Your changes have been saved. Please note that it can '
'take a short period of time until your changes become '
'active.'))
return redirect(self.get_success_url())
else:
return self.get(request)
class MailSettings(EventSettingsFormView): class MailSettings(EventSettingsFormView):
model = Event model = Event
form_class = MailSettingsForm form_class = MailSettingsForm

View File

@@ -5,4 +5,8 @@ class PretixPresaleConfig(AppConfig):
name = 'pretix.presale' name = 'pretix.presale'
label = 'pretixpresale' label = 'pretixpresale'
def ready(self):
from . import style # noqa
default_app_config = 'pretix.presale.PretixPresaleConfig' default_app_config = 'pretix.presale.PretixPresaleConfig'

View File

@@ -1,4 +1,5 @@
from django.conf import settings from django.conf import settings
from django.core.files.storage import default_storage
from django.core.urlresolvers import Resolver404, resolve from django.core.urlresolvers import Resolver404, resolve
from .signals import footer_link, html_head from .signals import footer_link, html_head
@@ -16,6 +17,7 @@ def contextprocessor(request):
return {} return {}
ctx = { ctx = {
'css_file': None
} }
_html_head = [] _html_head = []
_footer = [] _footer = []
@@ -24,6 +26,10 @@ def contextprocessor(request):
_html_head.append(response) _html_head.append(response)
for receiver, response in footer_link.send(request.event, request=request): for receiver, response in footer_link.send(request.event, request=request):
_footer.append(response) _footer.append(response)
if request.event.settings.presale_css_file:
ctx['css_file'] = default_storage.url(request.event.settings.presale_css_file)
ctx['html_head'] = "".join(_html_head) ctx['html_head'] = "".join(_html_head)
ctx['footer'] = _footer ctx['footer'] = _footer
ctx['site_url'] = settings.SITE_URL ctx['site_url'] = settings.SITE_URL

View File

@@ -0,0 +1,13 @@
from django.core.management.base import BaseCommand
from pretix.base.models import EventSetting
from ...style import regenerate_css
class Command(BaseCommand):
help = "Re-generate all custom stylesheets"
def handle(self, *args, **options):
for es in EventSetting.objects.filter(key="presale_css_file"):
regenerate_css(es.object_id)

View File

@@ -0,0 +1,47 @@
import hashlib
import logging
import os
import django_libsass
import sass
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from pretix.base.models import Event
logger = logging.getLogger('pretix.presale.style')
def regenerate_css(event_id: int):
event = Event.objects.select_related('organizer').get(pk=event_id)
sassdir = os.path.join(settings.STATIC_ROOT, 'pretixpresale/scss')
sassrules = [
'$brand-primary: {};'.format(event.settings.get('primary_color')),
'@import "main.scss";',
]
css = sass.compile(
string="\n".join(sassrules),
include_paths=[sassdir], output_style='compressed',
custom_functions=django_libsass.CUSTOM_FUNCTIONS
)
checksum = hashlib.sha1(css.encode('utf-8')).hexdigest()
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))
event.settings.set('presale_css_file', newname)
event.settings.set('presale_css_checksum', checksum)
if settings.HAS_CELERY:
from pretix.celery import app
regenerate_css_task = app.task(regenerate_css)
def regenerate_css(*args, **kwargs):
regenerate_css_task.apply_async(args=args, kwargs=kwargs)

View File

@@ -7,8 +7,14 @@
<title>{% block thetitle %}{% endblock %}</title> <title>{% block thetitle %}{% endblock %}</title>
{% compress css %} {% compress css %}
<link rel="stylesheet" type="text/css" href="{% static "lightbox/css/lightbox.css" %}" /> <link rel="stylesheet" type="text/css" href="{% static "lightbox/css/lightbox.css" %}" />
<link rel="stylesheet" type="text/x-scss" href="{% static "pretixpresale/scss/main.scss" %}" />
{% endcompress %} {% endcompress %}
{% if css_file %}
<link rel="stylesheet" type="text/css" href="{{ css_file }}"/>
{% else %}
{% compress css %}
<link rel="stylesheet" type="text/x-scss" href="{% static "pretixpresale/scss/main.scss" %}"/>
{% endcompress %}
{% endif %}
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script> <script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
{% compress js %} {% compress js %}
<script type="text/javascript" src="{% static "jquery/js/jquery-2.1.1.min.js" %}"></script> <script type="text/javascript" src="{% static "jquery/js/jquery-2.1.1.min.js" %}"></script>

View File

@@ -9,6 +9,7 @@ reportlab>=3.2,<3.3
git+https://github.com/pretix/PyPDF2.git@pretix#egg=PyPDF2 git+https://github.com/pretix/PyPDF2.git@pretix#egg=PyPDF2
easy-thumbnails>=2.2,<3 easy-thumbnails>=2.2,<3
django-libsass django-libsass
libsass
# Deployment / static file compilation requirements # Deployment / static file compilation requirements
BeautifulSoup4 BeautifulSoup4