From 34e66f166fdab5ce0d56b28638a5c1e78ae6f132 Mon Sep 17 00:00:00 2001 From: "Duong (Danny) Luu" <51145179+lcduong@users.noreply.github.com> Date: Fri, 18 Oct 2024 23:04:12 +0700 Subject: [PATCH] Remove Event QR feature (#403) * remove event qr feature --------- Co-authored-by: odkhang --- src/pretix/base/forms/__init__.py | 4 +- .../event/fragment_plugin_description.html | 36 ------ .../event/fragment_qr_dropdown.html | 27 ---- .../pretixcontrol/event/plugins.html | 120 +++++++++--------- src/pretix/control/urls.py | 1 - src/pretix/control/views/event.py | 111 +--------------- src/pretix/helpers/compat.py | 31 ++++- src/pretix/settings.py | 1 - .../static/pretixcontrol/scss/main.scss | 50 -------- 9 files changed, 92 insertions(+), 289 deletions(-) delete mode 100644 src/pretix/control/templates/pretixcontrol/event/fragment_plugin_description.html delete mode 100644 src/pretix/control/templates/pretixcontrol/event/fragment_qr_dropdown.html diff --git a/src/pretix/base/forms/__init__.py b/src/pretix/base/forms/__init__.py index cabc429b5..42870b5cb 100644 --- a/src/pretix/base/forms/__init__.py +++ b/src/pretix/base/forms/__init__.py @@ -159,8 +159,8 @@ def run_validators(self, value): class I18nMarkdownTextarea(i18nfield.forms.I18nTextarea): def format_output(self, rendered_widgets) -> str: markdown_note = _( - "You can use {markup_name} in this field." - ).format(markup_name='Markdown') + "You can use {name} in this field." + ).format(name='Markdown') rendered_widgets.append(f'
{markdown_note}
') return super().format_output(rendered_widgets) diff --git a/src/pretix/control/templates/pretixcontrol/event/fragment_plugin_description.html b/src/pretix/control/templates/pretixcontrol/event/fragment_plugin_description.html deleted file mode 100644 index 7baece041..000000000 --- a/src/pretix/control/templates/pretixcontrol/event/fragment_plugin_description.html +++ /dev/null @@ -1,36 +0,0 @@ -{% load i18n %} -{% if show_meta %} - {% if plugin.author %} -

- {% blocktrans trimmed with a=plugin.author %} - by {{ a }} - {% endblocktrans %}

- {% endif %} -{% endif %} -

{{ plugin.description|safe }}

-{% if plugin.restricted and plugin.module not in request.event.settings.allowed_restricted_plugins %} -

- - {% trans "This plugin must be activated by a system administrator for your account." %} -

-{% endif %} -{% if plugin.app.compatibility_errors %} -
- {% trans "This plugin cannot be activated for the following reasons:" %} - -
-{% endif %} -{% if plugin.app.compatibility_warnings %} -
- {% trans "This plugin identifies the following issues:" %} - -
-{% endif %} diff --git a/src/pretix/control/templates/pretixcontrol/event/fragment_qr_dropdown.html b/src/pretix/control/templates/pretixcontrol/event/fragment_qr_dropdown.html deleted file mode 100644 index e0d6c5bf5..000000000 --- a/src/pretix/control/templates/pretixcontrol/event/fragment_qr_dropdown.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n %} - diff --git a/src/pretix/control/templates/pretixcontrol/event/plugins.html b/src/pretix/control/templates/pretixcontrol/event/plugins.html index ea2c3a86b..8907937fe 100644 --- a/src/pretix/control/templates/pretixcontrol/event/plugins.html +++ b/src/pretix/control/templates/pretixcontrol/event/plugins.html @@ -3,13 +3,7 @@ {% load static %} {% load bootstrap3 %} {% block inside %} -

{% trans "Available plugins" %}

-

- {% blocktrans trimmed %} - On this page, you can select the plugins you would like to enable for your event. Plugins can add extra functionality, - integrate third-party services, or provide various customization options to enhance your event experience. - {% endblocktrans %} -

+

{% trans "Installed plugins" %}

{% csrf_token %} {% if "success" in request.GET %} @@ -18,71 +12,71 @@

{% trans "Available plugins" %}

{% endif %}
- {% for cat, catlabel, plist, has_pictures in plugins %} + {% for cat, catlabel, plist in plugins %}
{{ catlabel }} -
- {% for plugin in plist %} -
- {% if plugin.featured %} -
-
- {% endif %} -
- {% if plugin.featured or plugin.experimental %} -

- {% if plugin.featured %} - - {% trans "Top recommendation" %} - {% endif %} - {% if plugin.experimental %} - - {% trans "Experimental feature" %} - {% endif %} -

+
+ + {% for plugin in plist %} + + + + + {% endfor %} +
+ {{ plugin.name }} + {% if plugin.author %} +

+ {% blocktrans trimmed with v=plugin.version a=plugin.author %} + Version {{ v }} by {{ a }} + {% endblocktrans %}

+ {% else %} +

+ {% blocktrans trimmed with v=plugin.version a=plugin.author %} + Version {{ v }} + {% endblocktrans %}

{% endif %} - {% if plugin.picture %} -

+

{{ plugin.description }}

+ {% if plugin.restricted and not request.user.is_staff %} + + {% trans "This plugin needs to be enabled by a system administrator for your event." %} + {% endif %} -

- {{ plugin.name }} - {% if show_meta %} - {{ plugin.version }} - {% endif %} - {% if plugin.module in plugins_active %} - - - {% trans "Active" %} - - {% endif %} -

- {% include "pretixcontrol/event/fragment_plugin_description.html" with plugin=plugin %} - {% if plugin.app.compatibility_errors %} -
- {% trans "Incompatible" %} -
- {% elif plugin.restricted and plugin.module not in request.event.settings.allowed_restricted_plugins %} -
- {% trans "Not available" %} +
+ {% trans "This plugin cannot be enabled for the following reasons:" %} +
    + {% for e in plugin.app.compatibility_errors %} +
  • {{ e }}
  • + {% endfor %} +
- {% elif plugin.module in plugins_active %} -
- + {% endif %} + {% if plugin.app.compatibility_warnings %} +
+ {% trans "This plugin reports the following problems:" %} +
    + {% for e in plugin.app.compatibility_warnings %} +
  • {{ e }}
  • + {% endfor %} +
+ {% endif %} +
+ {% if plugin.app.compatibility_errors %} + + {% elif plugin.restricted and not staff_session %} + + {% elif plugin.module in plugins_active %} + {% else %} -
- -
+ {% endif %} - {% if plugin.featured %} - - - {% endif %} - - {% endfor %} +
{% endfor %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 8f6f14485..f58da2a2b 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -144,7 +144,6 @@ url(r'^event/(?P[^/]+)/(?P[^/]+)/', include([ url(r'^$', dashboards.event_index, name='event.index'), url(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'), - url(r'^qrcode.(?P(png|jpeg|gif|svg))$', event.EventQRCode.as_view(), name='event.qrcode'), url(r'^live/$', event.EventLive.as_view(), name='event.live'), url(r'^logs/$', event.EventLog.as_view(), name='event.log'), url(r'^delete/$', event.EventDelete.as_view(), name='event.delete'), diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index 73054b3c1..2b8303e2d 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -2,16 +2,12 @@ import re from collections import OrderedDict from decimal import Decimal -from io import BytesIO from itertools import groupby -from urllib.parse import urlparse, urlsplit +from urllib.parse import urlsplit -import qrcode -import qrcode.image.svg from django.conf import settings from django.contrib import messages from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import PermissionDenied from django.core.files import File from django.db import transaction from django.db.models import ProtectedError @@ -23,7 +19,6 @@ from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.functional import cached_property -from django.utils.http import url_has_allowed_host_and_scheme from django.utils.timezone import now from django.utils.translation import gettext, gettext_lazy as _ from django.views.generic import DeleteView, FormView, ListView @@ -55,7 +50,7 @@ from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.views.user import RecentAuthenticationRequiredMixin from pretix.helpers.database import rolledback_transaction -from pretix.multidomain.urlreverse import build_absolute_uri, get_event_domain +from pretix.multidomain.urlreverse import get_event_domain from pretix.presale.style import regenerate_css from ...base.i18n import language @@ -311,25 +306,15 @@ def get_context_data(self, *args, **kwargs) -> dict: 'FORMAT': _('Output and export formats'), 'API': _('API features'), } - plugins_grouped = groupby( - sorted( - plugins, - key=lambda p: ( - str(getattr(p, 'category', _('Other'))), - (0 if getattr(p, 'featured', False) else 1), - str(p.name).lower().replace('pretix ', '') - ), - ), - lambda p: str(getattr(p, 'category', _('Other'))) - ) - plugins_grouped = [(c, list(plist)) for c, plist in plugins_grouped] context['plugins'] = sorted([ - (c, labels.get(c, c), plist, any(getattr(p, 'picture', None) for p in plist)) + (c, labels.get(c, c), list(plist)) for c, plist - in plugins_grouped + in groupby( + sorted(plugins, key=lambda p: str(getattr(p, 'category', _('Other')))), + lambda p: str(getattr(p, 'category', _('Other'))) + ) ], key=lambda c: (order.index(c[0]), c[1]) if c[0] in order else (999, str(c[1]))) context['plugins_active'] = self.object.get_plugins() - context['show_meta'] = settings.PRETIX_PLUGINS_SHOW_META return context def get(self, request, *args, **kwargs): @@ -1461,85 +1446,3 @@ def formset(self): }, ] if self.request.method != "POST" else [] ) - - -class EventQRCode(EventPermissionRequiredMixin, View): - """ - Access the view to generate QR codes for event URLs. This class requires specific permissions for each event and - can produce QR codes in multiple formats (e.g. SVG, JPEG, PNG, GIF). - - Attributes: - permission (str): Required permissions for accessing this view. - """ - permission = None - - def get(self, request, *args, filetype, **kwargs): - """ - Handle GET requests to generate a QR code. - """ - # Build the base URL for the event - url = build_absolute_uri(request.event, 'presale:event.index') - - # Check if a custom URL is provided and validate it - custom_url = request.GET.get("url") - if custom_url: - if url_has_allowed_host_and_scheme(custom_url, allowed_hosts=[urlparse(url).netloc]): - url = custom_url - else: - raise PermissionDenied("Untrusted URL") - - # Create a QRCode object - qr = qrcode.QRCode( - version=1, - error_correction=qrcode.constants.ERROR_CORRECT_M, - box_size=10, - border=4, - ) - qr.add_data(url) - qr.make(fit=True) - - # Generate the QR code in the specified format - if filetype == 'svg': - return self._generate_svg_response(qr, request.event.slug, filetype) - elif filetype in ('jpeg', 'png', 'gif'): - return self._generate_image_response(qr, request.event.slug, filetype) - else: - raise ValueError(f"Unsupported file type: {filetype}") - - def _generate_svg_response(self, qr, event_slug, filetype): - """ - Generate an SVG response for the QR code. - - Parameters: - qr (QRCode): The QRCode object. - event_slug (str): The slug for the event to be included in the filename. - filetype (str): The desired file type (must be 'svg'). - - Returns: - The HTTP response that contains the SVG QR code. - """ - factory = qrcode.image.svg.SvgPathImage - img = qr.make_image(image_factory=factory) - response = HttpResponse(img.to_string(), content_type='image/svg+xml') - response['Content-Disposition'] = f'inline; filename="qrcode-{event_slug}.{filetype}"' - return response - - def _generate_image_response(self, qr, event_slug, filetype): - """ - Generate an image response (JPEG, PNG, GIF) for the QR code. - - Parameters: - qr (QRCode): The QRCode object. - event_slug (str): The event slug to be used in the filename. - filetype (str): The file type (jpeg, png, gif). - - Returns: - HttpResponse: An HTTP response containing the image QR code. - """ - img = qr.make_image(fill_color="black", back_color="white") - byte_io = BytesIO() - img.save(byte_io, filetype.upper()) - byte_io.seek(0) - response = HttpResponse(byte_io.read(), content_type=f'image/{filetype}') - response['Content-Disposition'] = f'inline; filename="qrcode-{event_slug}.{filetype}"' - return response diff --git a/src/pretix/helpers/compat.py b/src/pretix/helpers/compat.py index 9aed1e56e..88621c372 100644 --- a/src/pretix/helpers/compat.py +++ b/src/pretix/helpers/compat.py @@ -34,15 +34,36 @@ def post(self, request, *args, **kwargs): Returns: HttpResponse: The response following form validation. """ - self.object = self.get_object() - form = self.get_form() - if form.is_valid(): - return self.form_valid(form) + self.object = self.retrieve_object() + form = self.retrieve_form() + return self.process_form(form) + + def retrieve_object(self): + return self.get_object() + + def retrieve_form(self): + return self.get_form() + + def process_form(self, form): + if self.is_form_valid(form): + return self.handle_valid_form(form) else: - return self.form_invalid(form) + return self.handle_invalid_form(form) + + def is_form_valid(self, form): + return form.is_valid() + + def handle_valid_form(self, form): + return self.form_valid(form) + + def handle_invalid_form(self, form): + return self.form_invalid(form) def form_valid(self, form): """ Remove the object and redirect to the success URL. """ + return self.perform_deletion() + + def perform_deletion(self): return self.delete(self.request, self.args, self.kwargs) diff --git a/src/pretix/settings.py b/src/pretix/settings.py index 910b49b05..a6ed8fa90 100644 --- a/src/pretix/settings.py +++ b/src/pretix/settings.py @@ -146,7 +146,6 @@ PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default', fallback='pretix.plugins.sendmail,pretix.plugins.statistics,pretix.plugins.checkinlists,pretix.plugins.autocheckin') PRETIX_PLUGINS_EXCLUDE = config.get('pretix', 'plugins_exclude', fallback='').split(',') -PRETIX_PLUGINS_SHOW_META = config.getboolean('pretix', 'plugins_show_meta', fallback=True) FETCH_ECB_RATES = config.getboolean('pretix', 'ecb_rates', fallback=True) diff --git a/src/pretix/static/pretixcontrol/scss/main.scss b/src/pretix/static/pretixcontrol/scss/main.scss index 51a9c380c..c36b8a550 100644 --- a/src/pretix/static/pretixcontrol/scss/main.scss +++ b/src/pretix/static/pretixcontrol/scss/main.scss @@ -674,56 +674,6 @@ h1 .label { } } -.plugin-category { - clear: both; - padding: 15px 15px; -} -.plugin-list { - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 15px; - .plugin-container { - flex-basis: 100%; - flex-shrink: 0; - border-top: 1px solid #ccc; - padding-top: 15px; - } - h4 { - margin-top: 0; - } - .plugin-picture { - width: auto; - max-width: 100%; - height: 32px; - object-fit: contain; - } - .featured-plugin { - flex: 1; - min-width: 28em; - border-top: none; - padding-top: 0; - - .panel { - border-left: 5px solid $brand-primary; - height: 100%; - } - - .panel-body { - display: flex; - flex-direction: column; - height: 100%; - - .plugin-text { - flex-grow: 1; - } - .plugin-picture { - height: 64px; - } - } - } -} - .answer-thumb img { width: auto; height: auto;