diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py
index b1b05338c1..fc3b06b356 100644
--- a/src/pretix/base/models/vouchers.py
+++ b/src/pretix/base/models/vouchers.py
@@ -10,10 +10,14 @@ from .items import Item, ItemVariation, Quota
from .orders import CartPosition, Order, OrderPosition
-def generate_code():
+def _generate_random_code():
charset = list('ABCDEFGHKLMNPQRSTUVWXYZ23456789')
+ return get_random_string(length=settings.ENTROPY['voucher_code'], allowed_chars=charset)
+
+
+def generate_code():
while True:
- code = get_random_string(length=settings.ENTROPY['voucher_code'], allowed_chars=charset)
+ code = _generate_random_code()
if not Voucher.objects.filter(code=code).exists():
return code
diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html b/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html
index e7d5cc4892..a69f70b6dd 100644
--- a/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html
+++ b/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html
@@ -18,7 +18,7 @@
placeholder="{% trans "Number" %}">
diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py
index c3ba01fb12..0de92b9668 100644
--- a/src/pretix/control/urls.py
+++ b/src/pretix/control/urls.py
@@ -63,6 +63,7 @@ urlpatterns = [
url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'),
url(r'^vouchers/$', vouchers.VoucherList.as_view(), name='event.vouchers'),
url(r'^vouchers/tags/$', vouchers.VoucherTags.as_view(), name='event.vouchers.tags'),
+ url(r'^vouchers/rng$', vouchers.VoucherRNG.as_view(), name='event.vouchers.rng'),
url(r'^vouchers/(?P\d+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'),
url(r'^vouchers/(?P\d+)/delete$', vouchers.VoucherDelete.as_view(),
name='event.voucher.delete'),
diff --git a/src/pretix/control/views/vouchers.py b/src/pretix/control/views/vouchers.py
index 77ba4c5af3..66a3e3789d 100644
--- a/src/pretix/control/views/vouchers.py
+++ b/src/pretix/control/views/vouchers.py
@@ -6,15 +6,18 @@ from django.contrib import messages
from django.core.urlresolvers import resolve, reverse
from django.db import transaction
from django.db.models import Count, Q, Sum
-from django.http import Http404, HttpResponse, HttpResponseRedirect
-from django.utils.formats import date_format
+from django.http import (
+ Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
+ JsonResponse,
+)
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (
- CreateView, DeleteView, ListView, TemplateView, UpdateView,
+ CreateView, DeleteView, ListView, TemplateView, UpdateView, View,
)
from pretix.base.models import Voucher
+from pretix.base.models.vouchers import _generate_random_code
from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.signals import voucher_form_class
@@ -261,3 +264,32 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
# TODO: Transform this into an asynchronous call?
with request.event.lock():
return super().post(request, *args, **kwargs)
+
+
+class VoucherRNG(EventPermissionRequiredMixin, View):
+ template_name = 'pretixcontrol/vouchers/bulk.html'
+ permission = 'can_change_vouchers'
+
+ def get(self, request, *args, **kwargs):
+ codes = set()
+ try:
+ num = int(request.GET.get('num', '5'))
+ except ValueError:
+ return HttpResponseBadRequest()
+
+ while len(codes) < num:
+ new_codes = set()
+ for i in range(min(num - len(codes), 500)): # Work around SQLite's SQLITE_MAX_VARIABLE_NUMBER
+ new_codes.add(_generate_random_code())
+ new_codes -= set([v['code'] for v in Voucher.objects.filter(code__in=new_codes).values('code')])
+ codes |= new_codes
+
+ return JsonResponse({
+ 'codes': list(codes)
+ })
+
+ def get_success_url(self) -> str:
+ return reverse('control:event.vouchers', kwargs={
+ 'organizer': self.request.event.organizer.slug,
+ 'event': self.request.event.slug,
+ })
diff --git a/src/static/pretixcontrol/js/ui/main.js b/src/static/pretixcontrol/js/ui/main.js
index c2475ddab5..01f45f5201 100644
--- a/src/static/pretixcontrol/js/ui/main.js
+++ b/src/static/pretixcontrol/js/ui/main.js
@@ -56,26 +56,11 @@ $(function () {
// Vouchers
$("#voucher-bulk-codes-generate").click(function () {
- var charset = "ABCDEFGHKLMNPQRSTUVWXYZ23456789",
- i = 0, j = 0, len = parseInt($(this).attr("data-length")),
- num = parseInt($("#voucher-bulk-codes-num").val()), text = "";
- for (j = 0; j < num; j++) {
- var key = [];
- if (window.crypto && window.crypto.getRandomValues && Uint8Array) {
- key = new Uint8Array(len);
- window.crypto.getRandomValues(key);
- } else {
- for (i = 0; i < len; i++) {
- key.push(Math.floor(Math.random() * charset.length));
- }
- }
- if (i > 0) {
- text += "\n";
- }
- for (i = 0; i < len; i++) {
- text += charset.charAt(key[i] % charset.length);
- }
- }
- $("#id_codes").html(text);
+ var url = $(this).attr("data-rng-url"),
+ num = $("#voucher-bulk-codes-num").val();
+ $("#id_codes").html("Generating...");
+ $.getJSON(url + '?num=' + num, function (data) {
+ $("#id_codes").text(data.codes.join("\n"));
+ });
});
});
diff --git a/src/tests/control/test_permissions.py b/src/tests/control/test_permissions.py
index e07d0c2766..c99aae2cba 100644
--- a/src/tests/control/test_permissions.py
+++ b/src/tests/control/test_permissions.py
@@ -55,6 +55,7 @@ event_urls = [
"vouchers/2/",
"vouchers/add",
"vouchers/bulk_add",
+ "vouchers/rng",
"quotas/",
"quotas/2/delete",
"quotas/2/",