mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
Widget: add versioning support and add v2 with improved a11y-support (#5136)
* Add support for versioning widget.js * add versionable css * add version deprecation + redirect * use dynamic template_path instead of dynamic css_path * remove dummy code from widget.v1.scss * fix typo * [A11y] fix input border & focus style (#5149) * [A11y] fix input border & focus style * Fix double semi-colon * [A11y] make collapse-indicator a button (#5150) * Fix source order for cart-exists-message (#5152) * [A11y] underline links (#5151) * [A11y] Move modal-dialogs to HTMLDialogElement (#5147) * [A11y] move widget/iframe to html-dialog * make lightbox a dialog * move error-alert to dialog * re-add crossorigin * fix esc-handling and move animation to icon to enable focusing the button * fix code-style issues * block canceling loading iframe * Escape/cancel blocking fix for Chrome * add round focus-outline when dialog is loading * Widget v2: change voucher-link to hash-based link (#5161) * Fix variants toggle-button being submit-button * Widget v2: make single-item-select button and always show custom-spinners (#5165) * Widget v2: make single-item-select=button default * remove native-spinners and single_item_select * Stop suggesting old parameter --------- Co-authored-by: Raphael Michel <michel@rami.io> * Widget v2: add filter button to events metadata-filter (#5162) * Widget v2: do not underline events in list and calendar (#5163) * Fix checkbox button missing border radius (#5158) * Widget v2: turn add-to-cart-button into resume-button if cart-exists and no items selected (#5160) * Widget v2: make cart-alert live=polite * Add resume-button if cart-exists and no items selected * fix error handling with new-tab and later returning to old window * Fix cart-message button being full height * fix amount_selected recalc * Fix broken v-model * fix merge * Widget v2: Remove link from variation-product title (#5159) * Remove link from variation-product, focus associated input * open variations onclick on product-title * clickable elements should be focussable and interactive, so better remove click-handler on product-title * Widget v2: Fix calendar events color contrast (#5164) * Widget v2: Fix calendar events color contrast * fix status-bubbles in list-view * fix color in mobile * add striped-background to calendar and week * improve display of calendar for super small screens * Fix meta-filter legend not being screen-reader accessible * update version_default to 2 Co-authored-by: Raphael Michel <michel@rami.io> --------- Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
committed by
GitHub
parent
e46e689f01
commit
92f7456eca
@@ -38,8 +38,10 @@ from django.core.files.base import ContentFile, File
|
||||
from django.core.files.storage import default_storage
|
||||
from django.db.models import Q
|
||||
from django.http import FileResponse, Http404, HttpResponse, JsonResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.template import Context, Engine
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import get_language, gettext, pgettext
|
||||
@@ -81,15 +83,22 @@ logger = logging.getLogger(__name__)
|
||||
# we never change static source without restart, so we can cache this thread-wise
|
||||
_source_cache_key = None
|
||||
|
||||
version_min = 1
|
||||
version_max = 2
|
||||
version_default = 2 # used for output in widget-embed-code
|
||||
|
||||
def _get_source_cache_key():
|
||||
|
||||
def _get_source_cache_key(version):
|
||||
global _source_cache_key
|
||||
checksum = hashlib.sha256()
|
||||
if not _source_cache_key:
|
||||
with open(finders.find("pretixbase/scss/_theme_variables.scss"), "r") as f:
|
||||
checksum.update(f.read().encode())
|
||||
tpl = get_template('pretixpresale/widget_dummy.html')
|
||||
et = html.fromstring(tpl.render({})).xpath('/html/head/link')[0].attrib['href'].replace(settings.STATIC_URL, '')
|
||||
|
||||
template_path = 'pretixpresale/widget_dummy.html' if version == version_max else 'pretixpresale/widget_dummy.v{}.html'.format(version)
|
||||
|
||||
tpl = get_template(template_path)
|
||||
et = html.fromstring(tpl.render()).xpath('/html/head/link')[0].attrib['href'].replace(settings.STATIC_URL, '')
|
||||
checksum.update(et.encode())
|
||||
_source_cache_key = checksum.hexdigest()[:12]
|
||||
return _source_cache_key
|
||||
@@ -99,29 +108,39 @@ def indent(s):
|
||||
return s.replace('\n', '\n ')
|
||||
|
||||
|
||||
def widget_css_etag(request, **kwargs):
|
||||
def widget_css_etag(request, version, **kwargs):
|
||||
# This makes sure a new version of the theme is loaded whenever settings or the source files have changed
|
||||
if hasattr(request, 'event'):
|
||||
return (f'{_get_source_cache_key()}-'
|
||||
return (f'{_get_source_cache_key(version)}-'
|
||||
f'{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}-'
|
||||
f'{request.event.cache.get_or_set("css_version", default=lambda: int(time.time()))}')
|
||||
else:
|
||||
return f'{_get_source_cache_key()}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}'
|
||||
return f'{_get_source_cache_key(version)}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}'
|
||||
|
||||
|
||||
def widget_js_etag(request, lang, **kwargs):
|
||||
def widget_js_etag(request, version, lang, **kwargs):
|
||||
gs = GlobalSettingsObject()
|
||||
return gs.settings.get('widget_checksum_{}'.format(lang))
|
||||
return gs.settings.get('widget_checksum_{}_{}'.format(version, lang))
|
||||
|
||||
|
||||
@gzip_page
|
||||
@condition(etag_func=widget_css_etag)
|
||||
@cache_page(60)
|
||||
def widget_css(request, **kwargs):
|
||||
def widget_css(request, version, **kwargs):
|
||||
if version > version_max:
|
||||
raise Http404()
|
||||
if version < version_min:
|
||||
return redirect(reverse('presale:event.widget.css' if hasattr(request, 'event') else 'organizer.widget.css', kwargs={
|
||||
'version': version_min,
|
||||
'organizer': request.organizer.slug,
|
||||
'event': request.event.slug if hasattr(request, 'event') else None,
|
||||
}))
|
||||
o = getattr(request, 'event', request.organizer)
|
||||
|
||||
tpl = get_template('pretixpresale/widget_dummy.html')
|
||||
et = html.fromstring(tpl.render({})).xpath('/html/head/link')[0].attrib['href'].replace(settings.STATIC_URL, '')
|
||||
template_path = 'pretixpresale/widget_dummy.html' if version == version_max else 'pretixpresale/widget_dummy.v{}.html'.format(version)
|
||||
|
||||
tpl = get_template(template_path)
|
||||
et = html.fromstring(tpl.render()).xpath('/html/head/link')[0].attrib['href'].replace(settings.STATIC_URL, '')
|
||||
with open(finders.find(et), 'r') as f:
|
||||
widget_css = f.read()
|
||||
|
||||
@@ -134,7 +153,7 @@ def widget_css(request, **kwargs):
|
||||
return resp
|
||||
|
||||
|
||||
def generate_widget_js(lang):
|
||||
def generate_widget_js(version, lang):
|
||||
code = []
|
||||
with language(lang):
|
||||
# Provide isolation
|
||||
@@ -169,7 +188,7 @@ def generate_widget_js(lang):
|
||||
'vuejs/vue.js' if settings.DEBUG else 'vuejs/vue.min.js',
|
||||
'pretixpresale/js/widget/docready.js',
|
||||
'pretixpresale/js/widget/floatformat.js',
|
||||
'pretixpresale/js/widget/widget.js',
|
||||
'pretixpresale/js/widget/widget.js' if version == version_max else 'pretixpresale/js/widget/widget.v{}.js'.format(version),
|
||||
]
|
||||
for fname in files:
|
||||
f = finders.find(fname)
|
||||
@@ -188,11 +207,17 @@ def generate_widget_js(lang):
|
||||
|
||||
@gzip_page
|
||||
@condition(etag_func=widget_js_etag)
|
||||
def widget_js(request, lang, **kwargs):
|
||||
if lang not in [lc for lc, ll in settings.LANGUAGES]:
|
||||
def widget_js(request, version, lang, **kwargs):
|
||||
if version > version_max or lang not in [lc for lc, ll in settings.LANGUAGES]:
|
||||
raise Http404()
|
||||
|
||||
cached_js = cache.get('widget_js_data_{}'.format(lang))
|
||||
if version < version_min:
|
||||
return redirect(reverse('presale:widget.js', kwargs={
|
||||
'version': version_min,
|
||||
'lang': lang,
|
||||
}))
|
||||
|
||||
cached_js = cache.get('widget_js_data_{}_{}'.format(version, lang))
|
||||
if cached_js and not settings.DEBUG:
|
||||
resp = HttpResponse(cached_js, content_type='text/javascript')
|
||||
resp._csp_ignore = True
|
||||
@@ -200,7 +225,7 @@ def widget_js(request, lang, **kwargs):
|
||||
return resp
|
||||
|
||||
gs = GlobalSettingsObject()
|
||||
fname = gs.settings.get('widget_file_{}'.format(lang))
|
||||
fname = gs.settings.get('widget_file_{}_{}'.format(version, lang))
|
||||
resp = None
|
||||
if fname and not settings.DEBUG:
|
||||
if isinstance(fname, File):
|
||||
@@ -208,21 +233,21 @@ def widget_js(request, lang, **kwargs):
|
||||
try:
|
||||
data = default_storage.open(fname).read()
|
||||
resp = HttpResponse(data, content_type='text/javascript')
|
||||
cache.set('widget_js_data_{}'.format(lang), data, 3600 * 4)
|
||||
cache.set('widget_js_data_{}_{}'.format(version, lang), data, 3600 * 4)
|
||||
except:
|
||||
logger.exception('Failed to open widget.js')
|
||||
|
||||
if not resp:
|
||||
data = generate_widget_js(lang).encode()
|
||||
data = generate_widget_js(version, lang).encode()
|
||||
checksum = hashlib.sha1(data).hexdigest()
|
||||
if not settings.DEBUG:
|
||||
newname = default_storage.save(
|
||||
'widget/widget.{}.{}.js'.format(lang, checksum),
|
||||
'widget/widget.{}.{}.{}.js'.format(version, lang, checksum),
|
||||
ContentFile(data)
|
||||
)
|
||||
gs.settings.set('widget_file_{}'.format(lang), 'file://' + newname)
|
||||
gs.settings.set('widget_checksum_{}'.format(lang), checksum)
|
||||
cache.set('widget_js_data_{}'.format(lang), data, 3600 * 4)
|
||||
gs.settings.set('widget_file_{}_{}'.format(version, lang), 'file://' + newname)
|
||||
gs.settings.set('widget_checksum_{}_{}'.format(version, lang), checksum)
|
||||
cache.set('widget_js_data_{}_{}'.format(version, lang), data, 3600 * 4)
|
||||
resp = HttpResponse(data, content_type='text/javascript')
|
||||
resp._csp_ignore = True
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
|
||||
Reference in New Issue
Block a user