QR code generator for voucher URLs and general URLs (#3518)

* QR code generator: Allow other URLs to be used (e.g. for plugins)

* Add QR code to voucher URL view

* Fix allowed_hosts

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
Raphael Michel
2023-08-17 10:10:27 +02:00
committed by GitHub
parent c1c47e50c3
commit 89ba2da7e7
5 changed files with 58 additions and 27 deletions

View File

@@ -0,0 +1,27 @@
{% load i18n %}
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="png" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
target="_blank" download>
{% blocktrans with filetype="PNG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="svg" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
target="_blank" download>
{% blocktrans with filetype="SVG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="jpeg" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
target="_blank" download>
{% blocktrans with filetype="JPG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="gif" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
target="_blank" download>
{% blocktrans with filetype="GIF" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
</ul>

View File

@@ -27,28 +27,7 @@
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" title="{% trans "Create QR code" %}" aria-haspopup="true" aria-expanded="false"> <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" title="{% trans "Create QR code" %}" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-qrcode" aria-hidden="true"></i> <i class="fa fa-qrcode" aria-hidden="true"></i>
</button> </button>
<ul class="dropdown-menu dropdown-menu-right"> {% include "pretixcontrol/event/fragment_qr_dropdown.html" with url=0 %}
<li>
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="png" %}" target="_blank" download>
{% blocktrans with filetype="PNG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="svg" %}" target="_blank" download>
{% blocktrans with filetype="SVG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="jpeg" %}" target="_blank" download>
{% blocktrans with filetype="JPG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="gif" %}" target="_blank" download>
{% blocktrans with filetype="GIF" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
</ul>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>

View File

@@ -42,10 +42,18 @@
<div class="form-group"> <div class="form-group">
<label class="col-md-3 control-label" for="id_url">{% trans "Voucher link" %}</label> <label class="col-md-3 control-label" for="id_url">{% trans "Voucher link" %}</label>
<div class="col-md-9"> <div class="col-md-9">
<div class="input-group">
<input type="text" name="url" <input type="text" name="url"
value="{% abseventurl request.event "presale:event.redeem" %}?voucher={{ voucher.code|urlencode }}{% if voucher.subevent_id %}&subevent={{ voucher.subevent_id }}{% endif %}" value="{{ url }}"
class="form-control" class="form-control"
id="id_url" readonly> id="id_url" readonly>
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="{% trans "Create QR code" %}" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-qrcode" aria-hidden="true"></i>
</button>
{% include "pretixcontrol/event/fragment_qr_dropdown.html" with url=url %}
</div>
</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View File

@@ -40,7 +40,7 @@ from collections import OrderedDict
from decimal import Decimal from decimal import Decimal
from io import BytesIO from io import BytesIO
from itertools import groupby from itertools import groupby
from urllib.parse import urlsplit from urllib.parse import urlparse, urlsplit
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
import bleach import bleach
@@ -50,6 +50,7 @@ from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.core.files import File from django.core.files import File
from django.db import transaction from django.db import transaction
from django.db.models import ProtectedError from django.db.models import ProtectedError
@@ -61,6 +62,7 @@ from django.http import (
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
from django.utils.functional import cached_property 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.timezone import now
from django.utils.translation import gettext, gettext_lazy as _, gettext_noop from django.utils.translation import gettext, gettext_lazy as _, gettext_noop
from django.views.generic import FormView, ListView from django.views.generic import FormView, ListView
@@ -1530,6 +1532,12 @@ class EventQRCode(EventPermissionRequiredMixin, View):
def get(self, request, *args, filetype, **kwargs): def get(self, request, *args, filetype, **kwargs):
url = build_absolute_uri(request.event, 'presale:event.index') url = build_absolute_uri(request.event, 'presale:event.index')
if "url" in request.GET:
if url_has_allowed_host_and_scheme(request.GET["url"], allowed_hosts=[urlparse(url).netloc]):
url = request.GET["url"]
else:
raise PermissionDenied("Untrusted URL")
qr = qrcode.QRCode( qr = qrcode.QRCode(
version=1, version=1,
error_correction=qrcode.constants.ERROR_CORRECT_M, error_correction=qrcode.constants.ERROR_CORRECT_M,

View File

@@ -34,6 +34,7 @@
# License for the specific language governing permissions and limitations under the License. # License for the specific language governing permissions and limitations under the License.
import io import io
from urllib.parse import urlencode
import bleach import bleach
from defusedcsv import csv from defusedcsv import csv
@@ -75,6 +76,7 @@ from pretix.control.views import PaginationMixin
from pretix.helpers.compat import CompatDeleteView from pretix.helpers.compat import CompatDeleteView
from pretix.helpers.format import format_map from pretix.helpers.format import format_map
from pretix.helpers.models import modelcopy from pretix.helpers.models import modelcopy
from pretix.multidomain.urlreverse import build_absolute_uri
class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView): class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
@@ -315,6 +317,13 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
expires__gte=now() expires__gte=now()
).count() ).count()
ctx['redeemed_in_carts'] = redeemed_in_carts ctx['redeemed_in_carts'] = redeemed_in_carts
url_params = {
'voucher': self.object.code
}
if self.object.subevent_id:
url_params['subevent'] = self.object.subevent_id
ctx['url'] = build_absolute_uri(self.request.event, "presale:event.redeem") + "?" + urlencode(url_params)
return ctx return ctx