mirror of
https://github.com/pretix/pretix.git
synced 2026-05-08 15:44:02 +00:00
Added runtime SASS compliation and color settings
This commit is contained in:
@@ -46,4 +46,4 @@ RUN make production
|
|||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
ENTRYPOINT ["pretix"]
|
ENTRYPOINT ["pretix"]
|
||||||
CMD ["web"]
|
CMD ["all"]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -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" %}
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
0
src/pretix/presale/management/__init__.py
Normal file
0
src/pretix/presale/management/__init__.py
Normal file
0
src/pretix/presale/management/commands/__init__.py
Normal file
0
src/pretix/presale/management/commands/__init__.py
Normal file
13
src/pretix/presale/management/commands/updatestyles.py
Normal file
13
src/pretix/presale/management/commands/updatestyles.py
Normal 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)
|
||||||
47
src/pretix/presale/style.py
Normal file
47
src/pretix/presale/style.py
Normal 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)
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user