diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index 2ec0b2db5c..1d1c125806 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -483,6 +483,14 @@ Your {event} team""")) 'default': '#8E44B3', 'type': str }, + 'theme_color_success': { + 'default': '#50A167', + 'type': str + }, + 'theme_color_danger': { + 'default': '#D36060', + 'type': str + }, 'primary_font': { 'default': 'Open Sans', 'type': str diff --git a/src/pretix/control/forms/__init__.py b/src/pretix/control/forms/__init__.py index 34e86e49e1..ce1ea43da8 100644 --- a/src/pretix/control/forms/__init__.py +++ b/src/pretix/control/forms/__init__.py @@ -5,6 +5,7 @@ from django import forms from django.conf import settings from django.core.exceptions import ValidationError from django.forms.utils import from_current_timezone +from django.utils.deconstruct import deconstructible from django.utils.html import conditional_escape from django.utils.translation import ugettext_lazy as _ @@ -185,3 +186,44 @@ class SplitDateTimeField(forms.SplitDateTimeField): result = datetime.datetime.combine(*data_list) return from_current_timezone(result) return None + + +@deconstructible +class ColorContrastValidator: + message = _('This color is too bright to allow for proper contrast when used for text on white ground.') + code = 'contrast' + + def __init__(self, message=None, code=None): + if message is not None: + self.message = message + if code is not None: + self.code = code + + def __call__(self, value): + # See https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + fg_color = [int(str(value)[1:3], 16), int(str(value)[1:3], 16), int(str(value)[1:3], 16)] + bg_color = [255, 255, 255] + fg_lum = [ + (float(cc) / 255) / 12.92 + if (float(cc) / 255) < 0.03928 + else (((float(cc) / 255) + 0.055) / 1.055) ** 2.4 + for cc in fg_color + ] + bg_lum = [ + (float(cc) / 255) / 12.92 + if (float(cc) / 255) < 0.03928 + else (((float(cc) / 255) + 0.055) / 1.055) ** 2.4 + for cc in bg_color + ] + fg_rel_lum = 0.2126 * fg_lum[0] + 0.7152 * fg_lum[1] + 0.0722 * fg_lum[2] + bg_rel_lum = 0.2126 * bg_lum[0] + 0.7152 * bg_lum[1] + 0.0722 * bg_lum[2] + contrast_ratio = (bg_rel_lum + 0.05) / (fg_rel_lum + 0.05) + if contrast_ratio < 1.4: + raise ValidationError(self.message, code=self.code) + + def __eq__(self, other): + return ( + isinstance(other, ColorContrastValidator) and + (self.message == other.message) and + (self.code == other.code) + ) diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 21242ecd5f..705d5d7c9b 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -22,8 +22,9 @@ from pretix.base.models.event import EventMetaValue, SubEvent from pretix.base.reldate import RelativeDateField, RelativeDateTimeField from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.control.forms import ( - ExtFileField, MultipleLanguagesWidget, SingleLanguageWidget, SlugWidget, - SplitDateTimeField, SplitDateTimePickerWidget, + ColorContrastValidator, ExtFileField, MultipleLanguagesWidget, + SingleLanguageWidget, SlugWidget, SplitDateTimeField, + SplitDateTimePickerWidget, ) from pretix.multidomain.urlreverse import build_absolute_uri from pretix.plugins.banktransfer.payment import BankTransfer @@ -935,10 +936,37 @@ class MailSettingsForm(SettingsForm): class DisplaySettingsForm(SettingsForm): primary_color = forms.CharField( label=_("Primary color"), + help_text=_("We strongly suggest to use a dark shade that has a good contrast for text on white ground."), required=False, validators=[ RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')) + message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + ColorContrastValidator() + ], + widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) + ) + theme_color_success = forms.CharField( + label=_("Accent color for success"), + help_text=_("We strongly suggest to use a dark shade of green that has a good contrast for text on white " + "ground."), + required=False, + validators=[ + RegexValidator(regex='^#[0-9a-fA-F]{6}$', + message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + ColorContrastValidator() + ], + widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) + ) + theme_color_danger = forms.CharField( + label=_("Accent color for errors"), + help_text=_("We strongly suggest to use a dark shade of red that has a good contrast for text on white " + "ground."), + required=False, + validators=[ + RegexValidator(regex='^#[0-9a-fA-F]{6}$', + message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + + ColorContrastValidator() ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) ) diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index 0070b2708f..51fb4b8ba5 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -10,7 +10,9 @@ from pretix.api.models import WebHook from pretix.api.webhooks import get_all_webhook_events from pretix.base.forms import I18nModelForm, SettingsForm from pretix.base.models import Device, Organizer, Team -from pretix.control.forms import ExtFileField, MultipleLanguagesWidget +from pretix.control.forms import ( + ColorContrastValidator, ExtFileField, MultipleLanguagesWidget, +) from pretix.multidomain.models import KnownDomain from pretix.presale.style import get_fonts @@ -178,7 +180,33 @@ class OrganizerDisplaySettingsForm(SettingsForm): required=False, validators=[ RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')) + message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + ColorContrastValidator() + ], + widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) + ) + theme_color_success = forms.CharField( + label=_("Accent color for success"), + help_text=_("We strongly suggest to use a dark shade of green that has a good contrast for text on white " + "ground."), + required=False, + validators=[ + RegexValidator(regex='^#[0-9a-fA-F]{6}$', + message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + ColorContrastValidator() + ], + widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) + ) + theme_color_danger = forms.CharField( + label=_("Accent color for errors"), + help_text=_("We strongly suggest to use a dark shade of red that has a good contrast for text on white " + "ground."), + required=False, + validators=[ + RegexValidator(regex='^#[0-9a-fA-F]{6}$', + message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + + ColorContrastValidator() ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) ) diff --git a/src/pretix/control/templates/pretixcontrol/event/display.html b/src/pretix/control/templates/pretixcontrol/event/display.html index 93230b4680..b92a42624d 100644 --- a/src/pretix/control/templates/pretixcontrol/event/display.html +++ b/src/pretix/control/templates/pretixcontrol/event/display.html @@ -20,8 +20,10 @@
{% trans "Shop design" %} {% url "control:organizer.display" organizer=request.organizer.slug as org_url %} - {% propagated request.event org_url "primary_color" "primary_font" %} + {% propagated request.event org_url "primary_color" "primary_font" "theme_color_success" "theme_color_danger" %} {% bootstrap_field form.primary_color layout="control" %} + {% bootstrap_field form.theme_color_success layout="control" %} + {% bootstrap_field form.theme_color_danger layout="control" %} {% bootstrap_field form.primary_font layout="control" %} {% endpropagated %}
diff --git a/src/pretix/control/templates/pretixcontrol/organizers/display.html b/src/pretix/control/templates/pretixcontrol/organizers/display.html index 8b4deb9c37..aebd0c8ee2 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/display.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/display.html @@ -23,6 +23,8 @@ {% endblocktrans %}

{% bootstrap_field form.primary_color layout="control" %} + {% bootstrap_field form.theme_color_success layout="control" %} + {% bootstrap_field form.theme_color_danger layout="control" %} {% bootstrap_field form.primary_font layout="control" %}
diff --git a/src/pretix/presale/style.py b/src/pretix/presale/style.py index 6bd9c2d4a5..8f3e8006f5 100644 --- a/src/pretix/presale/style.py +++ b/src/pretix/presale/style.py @@ -40,6 +40,10 @@ def compile_scss(object, file="main.scss", fonts=True): sassrules = [] if object.settings.get('primary_color'): sassrules.append('$brand-primary: {};'.format(object.settings.get('primary_color'))) + if object.settings.get('theme_color_success'): + sassrules.append('$brand-success: {};'.format(object.settings.get('theme_color_success'))) + if object.settings.get('theme_color_danger'): + sassrules.append('$brand-danger: {};'.format(object.settings.get('theme_color_danger'))) font = object.settings.get('primary_font') if font != 'Open Sans' and fonts: diff --git a/src/pretix/static/pretixbase/scss/_variables.scss b/src/pretix/static/pretixbase/scss/_variables.scss index 7733b80b6c..917353cfd6 100644 --- a/src/pretix/static/pretixbase/scss/_variables.scss +++ b/src/pretix/static/pretixbase/scss/_variables.scss @@ -18,10 +18,10 @@ $text-color: #222222; $text-muted: #999999; $brand-primary: #7f5a91 !default; -$brand-success: #50a167; -$brand-info: #5f9cd4; -$brand-warning: #ffb419; -$brand-danger: #d36060; +$brand-success: #50a167 !default; +$brand-info: #5f9cd4 !default; +$brand-warning: #ffb419 !default; +$brand-danger: #d36060 !default; $btn-default-border: #CCCCCC;