We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Voucher-counting returning True/False
The tag-counting of vouchers seems two have two issues at the moment:
- No voucher has been used yet:
  `False/<Number of Vouchers>` instead of `0/<Number of Vouchers>`
- Some other bug, if some vouchers have already been redeemed (cannot put my finger on, when this happens/why this happens):
  `True/<Number of Vouchers>` instead of `<Number of used Vouchers>/<Number of Vouchers>`

Second item can be observed at the moment for example for budapest.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pretix/control/views/vouchers.py`
Content:
```
1 import csv
2 import io
3 
4 from django.conf import settings
5 from django.contrib import messages
6 from django.core.urlresolvers import resolve, reverse
7 from django.db import transaction
8 from django.db.models import Count, Q, Sum
9 from django.http import (
10     Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
11     JsonResponse,
12 )
13 from django.utils.timezone import now
14 from django.utils.translation import ugettext_lazy as _
15 from django.views.generic import (
16     CreateView, DeleteView, ListView, TemplateView, UpdateView, View,
17 )
18 
19 from pretix.base.models import Voucher
20 from pretix.base.models.vouchers import _generate_random_code
21 from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm
22 from pretix.control.permissions import EventPermissionRequiredMixin
23 from pretix.control.signals import voucher_form_class
24 
25 
26 class VoucherList(EventPermissionRequiredMixin, ListView):
27     model = Voucher
28     context_object_name = 'vouchers'
29     paginate_by = 30
30     template_name = 'pretixcontrol/vouchers/index.html'
31     permission = 'can_view_vouchers'
32 
33     def get_queryset(self):
34         qs = self.request.event.vouchers.all().select_related('item', 'variation')
35         if self.request.GET.get("search", "") != "":
36             s = self.request.GET.get("search", "")
37             qs = qs.filter(Q(code__icontains=s) | Q(tag__icontains=s) | Q(comment__icontains=s))
38         if self.request.GET.get("tag", "") != "":
39             s = self.request.GET.get("tag", "")
40             qs = qs.filter(tag__icontains=s)
41         if self.request.GET.get("status", "") != "":
42             s = self.request.GET.get("status", "")
43             if s == 'v':
44                 qs = qs.filter(Q(valid_until__isnull=True) | Q(valid_until__gt=now())).filter(redeemed=False)
45             elif s == 'r':
46                 qs = qs.filter(redeemed=True)
47             elif s == 'e':
48                 qs = qs.filter(Q(valid_until__isnull=False) & Q(valid_until__lt=now())).filter(redeemed=False)
49         return qs
50 
51     def get(self, request, *args, **kwargs):
52         if request.GET.get("download", "") == "yes":
53             return self._download_csv()
54         return super().get(request, *args, **kwargs)
55 
56     def _download_csv(self):
57         output = io.StringIO()
58         writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=",")
59 
60         headers = [
61             _('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
62             _('Price'), _('Tag'), _('Redeemed')
63         ]
64         writer.writerow(headers)
65 
66         for v in self.get_queryset():
67             if v.item:
68                 if v.variation:
69                     prod = '%s – %s' % (str(v.item.name), str(v.variation.name))
70                 else:
71                     prod = '%s' % str(v.item.name)
72             elif v.quota:
73                 prod = _('Any product in quota "{quota}"').format(quota=str(v.quota.name))
74             row = [
75                 v.code,
76                 v.valid_until.isoformat() if v.valid_until else "",
77                 prod,
78                 _("Yes") if v.block_quota else _("No"),
79                 _("Yes") if v.allow_ignore_quota else _("No"),
80                 str(v.price) if v.price else "",
81                 v.tag,
82                 _("Yes") if v.redeemed else _("No"),
83             ]
84             writer.writerow(row)
85 
86         r = HttpResponse(output.getvalue().encode("utf-8"), content_type='text/csv')
87         r['Content-Disposition'] = 'attachment; filename="vouchers.csv"'
88         return r
89 
90 
91 class VoucherTags(EventPermissionRequiredMixin, TemplateView):
92     template_name = 'pretixcontrol/vouchers/tags.html'
93     permission = 'can_view_vouchers'
94 
95     def get_context_data(self, **kwargs):
96         ctx = super().get_context_data(**kwargs)
97 
98         tags = self.request.event.vouchers.order_by('tag').filter(tag__isnull=False).values('tag').annotate(
99             total=Count('id'),
100             redeemed=Sum('redeemed')
101         )
102         for t in tags:
103             t['percentage'] = int((t['redeemed'] / t['total']) * 100)
104 
105         ctx['tags'] = tags
106         return ctx
107 
108 
109 class VoucherDelete(EventPermissionRequiredMixin, DeleteView):
110     model = Voucher
111     template_name = 'pretixcontrol/vouchers/delete.html'
112     permission = 'can_change_vouchers'
113     context_object_name = 'voucher'
114 
115     def get_object(self, queryset=None) -> Voucher:
116         try:
117             return self.request.event.vouchers.get(
118                 id=self.kwargs['voucher']
119             )
120         except Voucher.DoesNotExist:
121             raise Http404(_("The requested voucher does not exist."))
122 
123     def get(self, request, *args, **kwargs):
124         if self.get_object().redeemed:
125             messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))
126             return HttpResponseRedirect(self.get_success_url())
127         return super().get(request, *args, **kwargs)
128 
129     @transaction.atomic
130     def delete(self, request, *args, **kwargs):
131         self.object = self.get_object()
132         success_url = self.get_success_url()
133 
134         if self.object.redeemed:
135             messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))
136         else:
137             self.object.log_action('pretix.voucher.deleted', user=self.request.user)
138             self.object.delete()
139             messages.success(request, _('The selected voucher has been deleted.'))
140         return HttpResponseRedirect(success_url)
141 
142     def get_success_url(self) -> str:
143         return reverse('control:event.vouchers', kwargs={
144             'organizer': self.request.event.organizer.slug,
145             'event': self.request.event.slug,
146         })
147 
148 
149 class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
150     model = Voucher
151     template_name = 'pretixcontrol/vouchers/detail.html'
152     permission = 'can_change_vouchers'
153     context_object_name = 'voucher'
154 
155     def get_form_class(self):
156         form_class = VoucherForm
157         for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):
158             if response:
159                 form_class = response
160         return form_class
161 
162     def get_object(self, queryset=None) -> VoucherForm:
163         url = resolve(self.request.path_info)
164         try:
165             return self.request.event.vouchers.get(
166                 id=url.kwargs['voucher']
167             )
168         except Voucher.DoesNotExist:
169             raise Http404(_("The requested voucher does not exist."))
170 
171     @transaction.atomic
172     def form_valid(self, form):
173         messages.success(self.request, _('Your changes have been saved.'))
174         if form.has_changed():
175             self.object.log_action(
176                 'pretix.voucher.changed', user=self.request.user, data={
177                     k: form.cleaned_data.get(k) for k in form.changed_data
178                 }
179             )
180         return super().form_valid(form)
181 
182     def get_success_url(self) -> str:
183         return reverse('control:event.vouchers', kwargs={
184             'organizer': self.request.event.organizer.slug,
185             'event': self.request.event.slug,
186         })
187 
188 
189 class VoucherCreate(EventPermissionRequiredMixin, CreateView):
190     model = Voucher
191     template_name = 'pretixcontrol/vouchers/detail.html'
192     permission = 'can_change_vouchers'
193     context_object_name = 'voucher'
194 
195     def get_form_class(self):
196         form_class = VoucherForm
197         for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):
198             if response:
199                 form_class = response
200         return form_class
201 
202     def get_success_url(self) -> str:
203         return reverse('control:event.vouchers', kwargs={
204             'organizer': self.request.event.organizer.slug,
205             'event': self.request.event.slug,
206         })
207 
208     def get_form_kwargs(self):
209         kwargs = super().get_form_kwargs()
210         kwargs['instance'] = Voucher(event=self.request.event)
211         return kwargs
212 
213     @transaction.atomic
214     def form_valid(self, form):
215         form.instance.event = self.request.event
216         messages.success(self.request, _('The new voucher has been created.'))
217         ret = super().form_valid(form)
218         form.instance.log_action('pretix.voucher.added', data=dict(form.cleaned_data), user=self.request.user)
219         return ret
220 
221     def post(self, request, *args, **kwargs):
222         # TODO: Transform this into an asynchronous call?
223         with request.event.lock():
224             return super().post(request, *args, **kwargs)
225 
226 
227 class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
228     model = Voucher
229     template_name = 'pretixcontrol/vouchers/bulk.html'
230     permission = 'can_change_vouchers'
231     context_object_name = 'voucher'
232 
233     def get_success_url(self) -> str:
234         return reverse('control:event.vouchers', kwargs={
235             'organizer': self.request.event.organizer.slug,
236             'event': self.request.event.slug,
237         })
238 
239     def get_form_kwargs(self):
240         kwargs = super().get_form_kwargs()
241         kwargs['instance'] = Voucher(event=self.request.event)
242         return kwargs
243 
244     @transaction.atomic
245     def form_valid(self, form):
246         for o in form.save(self.request.event):
247             o.log_action('pretix.voucher.added', data=form.cleaned_data, user=self.request.user)
248         messages.success(self.request, _('The new vouchers have been created.'))
249         return HttpResponseRedirect(self.get_success_url())
250 
251     def get_form_class(self):
252         form_class = VoucherBulkForm
253         for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):
254             if response:
255                 form_class = response
256         return form_class
257 
258     def get_context_data(self, **kwargs):
259         ctx = super().get_context_data(**kwargs)
260         ctx['code_length'] = settings.ENTROPY['voucher_code']
261         return ctx
262 
263     def post(self, request, *args, **kwargs):
264         # TODO: Transform this into an asynchronous call?
265         with request.event.lock():
266             return super().post(request, *args, **kwargs)
267 
268 
269 class VoucherRNG(EventPermissionRequiredMixin, View):
270     permission = 'can_change_vouchers'
271 
272     def get(self, request, *args, **kwargs):
273         codes = set()
274         try:
275             num = int(request.GET.get('num', '5'))
276         except ValueError:  # NOQA
277             return HttpResponseBadRequest()
278 
279         while len(codes) < num:
280             new_codes = set()
281             for i in range(min(num - len(codes), 500)):  # Work around SQLite's SQLITE_MAX_VARIABLE_NUMBER
282                 new_codes.add(_generate_random_code())
283             new_codes -= set([v['code'] for v in Voucher.objects.filter(code__in=new_codes).values('code')])
284             codes |= new_codes
285 
286         return JsonResponse({
287             'codes': list(codes)
288         })
289 
290     def get_success_url(self) -> str:
291         return reverse('control:event.vouchers', kwargs={
292             'organizer': self.request.event.organizer.slug,
293             'event': self.request.event.slug,
294         })
295
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/pretix/control/views/vouchers.py b/src/pretix/control/views/vouchers.py
--- a/src/pretix/control/views/vouchers.py
+++ b/src/pretix/control/views/vouchers.py
@@ -5,7 +5,7 @@
 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.db.models import Case, Count, IntegerField, Q, Sum, When
 from django.http import (
     Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
     JsonResponse,
@@ -97,7 +97,14 @@
 
         tags = self.request.event.vouchers.order_by('tag').filter(tag__isnull=False).values('tag').annotate(
             total=Count('id'),
-            redeemed=Sum('redeemed')
+            # This is a fix for this MySQL issue: https://code.djangoproject.com/ticket/24662
+            redeemed=Sum(
+                Case(
+                    When(redeemed=True, then=1),
+                    When(redeemed=False, then=0),
+                    output_field=IntegerField()
+                )
+            )
         )
         for t in tags:
             t['percentage'] = int((t['redeemed'] / t['total']) * 100)

{"golden_diff": "diff --git a/src/pretix/control/views/vouchers.py b/src/pretix/control/views/vouchers.py\n--- a/src/pretix/control/views/vouchers.py\n+++ b/src/pretix/control/views/vouchers.py\n@@ -5,7 +5,7 @@\n from django.contrib import messages\n from django.core.urlresolvers import resolve, reverse\n from django.db import transaction\n-from django.db.models import Count, Q, Sum\n+from django.db.models import Case, Count, IntegerField, Q, Sum, When\n from django.http import (\n     Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,\n     JsonResponse,\n@@ -97,7 +97,14 @@\n \n         tags = self.request.event.vouchers.order_by('tag').filter(tag__isnull=False).values('tag').annotate(\n             total=Count('id'),\n-            redeemed=Sum('redeemed')\n+            # This is a fix for this MySQL issue: https://code.djangoproject.com/ticket/24662\n+            redeemed=Sum(\n+                Case(\n+                    When(redeemed=True, then=1),\n+                    When(redeemed=False, then=0),\n+                    output_field=IntegerField()\n+                )\n+            )\n         )\n         for t in tags:\n             t['percentage'] = int((t['redeemed'] / t['total']) * 100)\n", "issue": "Voucher-counting returning True/False\nThe tag-counting of vouchers seems two have two issues at the moment:\n- No voucher has been used yet:\n  `False/<Number of Vouchers>` instead of `0/<Number of Vouchers>`\n- Some other bug, if some vouchers have already been redeemed (cannot put my finger on, when this happens/why this happens):\n  `True/<Number of Vouchers>` instead of `<Number of used Vouchers>/<Number of Vouchers>`\n\nSecond item can be observed at the moment for example for budapest.\n\n", "before_files": [{"content": "import csv\nimport io\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.core.urlresolvers import resolve, reverse\nfrom django.db import transaction\nfrom django.db.models import Count, Q, Sum\nfrom django.http import (\n    Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,\n    JsonResponse,\n)\nfrom django.utils.timezone import now\nfrom django.utils.translation import ugettext_lazy as _\nfrom django.views.generic import (\n    CreateView, DeleteView, ListView, TemplateView, UpdateView, View,\n)\n\nfrom pretix.base.models import Voucher\nfrom pretix.base.models.vouchers import _generate_random_code\nfrom pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm\nfrom pretix.control.permissions import EventPermissionRequiredMixin\nfrom pretix.control.signals import voucher_form_class\n\n\nclass VoucherList(EventPermissionRequiredMixin, ListView):\n    model = Voucher\n    context_object_name = 'vouchers'\n    paginate_by = 30\n    template_name = 'pretixcontrol/vouchers/index.html'\n    permission = 'can_view_vouchers'\n\n    def get_queryset(self):\n        qs = self.request.event.vouchers.all().select_related('item', 'variation')\n        if self.request.GET.get(\"search\", \"\") != \"\":\n            s = self.request.GET.get(\"search\", \"\")\n            qs = qs.filter(Q(code__icontains=s) | Q(tag__icontains=s) | Q(comment__icontains=s))\n        if self.request.GET.get(\"tag\", \"\") != \"\":\n            s = self.request.GET.get(\"tag\", \"\")\n            qs = qs.filter(tag__icontains=s)\n        if self.request.GET.get(\"status\", \"\") != \"\":\n            s = self.request.GET.get(\"status\", \"\")\n            if s == 'v':\n                qs = qs.filter(Q(valid_until__isnull=True) | Q(valid_until__gt=now())).filter(redeemed=False)\n            elif s == 'r':\n                qs = qs.filter(redeemed=True)\n            elif s == 'e':\n                qs = qs.filter(Q(valid_until__isnull=False) & Q(valid_until__lt=now())).filter(redeemed=False)\n        return qs\n\n    def get(self, request, *args, **kwargs):\n        if request.GET.get(\"download\", \"\") == \"yes\":\n            return self._download_csv()\n        return super().get(request, *args, **kwargs)\n\n    def _download_csv(self):\n        output = io.StringIO()\n        writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=\",\")\n\n        headers = [\n            _('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),\n            _('Price'), _('Tag'), _('Redeemed')\n        ]\n        writer.writerow(headers)\n\n        for v in self.get_queryset():\n            if v.item:\n                if v.variation:\n                    prod = '%s \u2013 %s' % (str(v.item.name), str(v.variation.name))\n                else:\n                    prod = '%s' % str(v.item.name)\n            elif v.quota:\n                prod = _('Any product in quota \"{quota}\"').format(quota=str(v.quota.name))\n            row = [\n                v.code,\n                v.valid_until.isoformat() if v.valid_until else \"\",\n                prod,\n                _(\"Yes\") if v.block_quota else _(\"No\"),\n                _(\"Yes\") if v.allow_ignore_quota else _(\"No\"),\n                str(v.price) if v.price else \"\",\n                v.tag,\n                _(\"Yes\") if v.redeemed else _(\"No\"),\n            ]\n            writer.writerow(row)\n\n        r = HttpResponse(output.getvalue().encode(\"utf-8\"), content_type='text/csv')\n        r['Content-Disposition'] = 'attachment; filename=\"vouchers.csv\"'\n        return r\n\n\nclass VoucherTags(EventPermissionRequiredMixin, TemplateView):\n    template_name = 'pretixcontrol/vouchers/tags.html'\n    permission = 'can_view_vouchers'\n\n    def get_context_data(self, **kwargs):\n        ctx = super().get_context_data(**kwargs)\n\n        tags = self.request.event.vouchers.order_by('tag').filter(tag__isnull=False).values('tag').annotate(\n            total=Count('id'),\n            redeemed=Sum('redeemed')\n        )\n        for t in tags:\n            t['percentage'] = int((t['redeemed'] / t['total']) * 100)\n\n        ctx['tags'] = tags\n        return ctx\n\n\nclass VoucherDelete(EventPermissionRequiredMixin, DeleteView):\n    model = Voucher\n    template_name = 'pretixcontrol/vouchers/delete.html'\n    permission = 'can_change_vouchers'\n    context_object_name = 'voucher'\n\n    def get_object(self, queryset=None) -> Voucher:\n        try:\n            return self.request.event.vouchers.get(\n                id=self.kwargs['voucher']\n            )\n        except Voucher.DoesNotExist:\n            raise Http404(_(\"The requested voucher does not exist.\"))\n\n    def get(self, request, *args, **kwargs):\n        if self.get_object().redeemed:\n            messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))\n            return HttpResponseRedirect(self.get_success_url())\n        return super().get(request, *args, **kwargs)\n\n    @transaction.atomic\n    def delete(self, request, *args, **kwargs):\n        self.object = self.get_object()\n        success_url = self.get_success_url()\n\n        if self.object.redeemed:\n            messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))\n        else:\n            self.object.log_action('pretix.voucher.deleted', user=self.request.user)\n            self.object.delete()\n            messages.success(request, _('The selected voucher has been deleted.'))\n        return HttpResponseRedirect(success_url)\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n\n\nclass VoucherUpdate(EventPermissionRequiredMixin, UpdateView):\n    model = Voucher\n    template_name = 'pretixcontrol/vouchers/detail.html'\n    permission = 'can_change_vouchers'\n    context_object_name = 'voucher'\n\n    def get_form_class(self):\n        form_class = VoucherForm\n        for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):\n            if response:\n                form_class = response\n        return form_class\n\n    def get_object(self, queryset=None) -> VoucherForm:\n        url = resolve(self.request.path_info)\n        try:\n            return self.request.event.vouchers.get(\n                id=url.kwargs['voucher']\n            )\n        except Voucher.DoesNotExist:\n            raise Http404(_(\"The requested voucher does not exist.\"))\n\n    @transaction.atomic\n    def form_valid(self, form):\n        messages.success(self.request, _('Your changes have been saved.'))\n        if form.has_changed():\n            self.object.log_action(\n                'pretix.voucher.changed', user=self.request.user, data={\n                    k: form.cleaned_data.get(k) for k in form.changed_data\n                }\n            )\n        return super().form_valid(form)\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n\n\nclass VoucherCreate(EventPermissionRequiredMixin, CreateView):\n    model = Voucher\n    template_name = 'pretixcontrol/vouchers/detail.html'\n    permission = 'can_change_vouchers'\n    context_object_name = 'voucher'\n\n    def get_form_class(self):\n        form_class = VoucherForm\n        for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):\n            if response:\n                form_class = response\n        return form_class\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs['instance'] = Voucher(event=self.request.event)\n        return kwargs\n\n    @transaction.atomic\n    def form_valid(self, form):\n        form.instance.event = self.request.event\n        messages.success(self.request, _('The new voucher has been created.'))\n        ret = super().form_valid(form)\n        form.instance.log_action('pretix.voucher.added', data=dict(form.cleaned_data), user=self.request.user)\n        return ret\n\n    def post(self, request, *args, **kwargs):\n        # TODO: Transform this into an asynchronous call?\n        with request.event.lock():\n            return super().post(request, *args, **kwargs)\n\n\nclass VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):\n    model = Voucher\n    template_name = 'pretixcontrol/vouchers/bulk.html'\n    permission = 'can_change_vouchers'\n    context_object_name = 'voucher'\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs['instance'] = Voucher(event=self.request.event)\n        return kwargs\n\n    @transaction.atomic\n    def form_valid(self, form):\n        for o in form.save(self.request.event):\n            o.log_action('pretix.voucher.added', data=form.cleaned_data, user=self.request.user)\n        messages.success(self.request, _('The new vouchers have been created.'))\n        return HttpResponseRedirect(self.get_success_url())\n\n    def get_form_class(self):\n        form_class = VoucherBulkForm\n        for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):\n            if response:\n                form_class = response\n        return form_class\n\n    def get_context_data(self, **kwargs):\n        ctx = super().get_context_data(**kwargs)\n        ctx['code_length'] = settings.ENTROPY['voucher_code']\n        return ctx\n\n    def post(self, request, *args, **kwargs):\n        # TODO: Transform this into an asynchronous call?\n        with request.event.lock():\n            return super().post(request, *args, **kwargs)\n\n\nclass VoucherRNG(EventPermissionRequiredMixin, View):\n    permission = 'can_change_vouchers'\n\n    def get(self, request, *args, **kwargs):\n        codes = set()\n        try:\n            num = int(request.GET.get('num', '5'))\n        except ValueError:  # NOQA\n            return HttpResponseBadRequest()\n\n        while len(codes) < num:\n            new_codes = set()\n            for i in range(min(num - len(codes), 500)):  # Work around SQLite's SQLITE_MAX_VARIABLE_NUMBER\n                new_codes.add(_generate_random_code())\n            new_codes -= set([v['code'] for v in Voucher.objects.filter(code__in=new_codes).values('code')])\n            codes |= new_codes\n\n        return JsonResponse({\n            'codes': list(codes)\n        })\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n", "path": "src/pretix/control/views/vouchers.py"}], "after_files": [{"content": "import csv\nimport io\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.core.urlresolvers import resolve, reverse\nfrom django.db import transaction\nfrom django.db.models import Case, Count, IntegerField, Q, Sum, When\nfrom django.http import (\n    Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,\n    JsonResponse,\n)\nfrom django.utils.timezone import now\nfrom django.utils.translation import ugettext_lazy as _\nfrom django.views.generic import (\n    CreateView, DeleteView, ListView, TemplateView, UpdateView, View,\n)\n\nfrom pretix.base.models import Voucher\nfrom pretix.base.models.vouchers import _generate_random_code\nfrom pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm\nfrom pretix.control.permissions import EventPermissionRequiredMixin\nfrom pretix.control.signals import voucher_form_class\n\n\nclass VoucherList(EventPermissionRequiredMixin, ListView):\n    model = Voucher\n    context_object_name = 'vouchers'\n    paginate_by = 30\n    template_name = 'pretixcontrol/vouchers/index.html'\n    permission = 'can_view_vouchers'\n\n    def get_queryset(self):\n        qs = self.request.event.vouchers.all().select_related('item', 'variation')\n        if self.request.GET.get(\"search\", \"\") != \"\":\n            s = self.request.GET.get(\"search\", \"\")\n            qs = qs.filter(Q(code__icontains=s) | Q(tag__icontains=s) | Q(comment__icontains=s))\n        if self.request.GET.get(\"tag\", \"\") != \"\":\n            s = self.request.GET.get(\"tag\", \"\")\n            qs = qs.filter(tag__icontains=s)\n        if self.request.GET.get(\"status\", \"\") != \"\":\n            s = self.request.GET.get(\"status\", \"\")\n            if s == 'v':\n                qs = qs.filter(Q(valid_until__isnull=True) | Q(valid_until__gt=now())).filter(redeemed=False)\n            elif s == 'r':\n                qs = qs.filter(redeemed=True)\n            elif s == 'e':\n                qs = qs.filter(Q(valid_until__isnull=False) & Q(valid_until__lt=now())).filter(redeemed=False)\n        return qs\n\n    def get(self, request, *args, **kwargs):\n        if request.GET.get(\"download\", \"\") == \"yes\":\n            return self._download_csv()\n        return super().get(request, *args, **kwargs)\n\n    def _download_csv(self):\n        output = io.StringIO()\n        writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=\",\")\n\n        headers = [\n            _('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),\n            _('Price'), _('Tag'), _('Redeemed')\n        ]\n        writer.writerow(headers)\n\n        for v in self.get_queryset():\n            if v.item:\n                if v.variation:\n                    prod = '%s \u2013 %s' % (str(v.item.name), str(v.variation.name))\n                else:\n                    prod = '%s' % str(v.item.name)\n            elif v.quota:\n                prod = _('Any product in quota \"{quota}\"').format(quota=str(v.quota.name))\n            row = [\n                v.code,\n                v.valid_until.isoformat() if v.valid_until else \"\",\n                prod,\n                _(\"Yes\") if v.block_quota else _(\"No\"),\n                _(\"Yes\") if v.allow_ignore_quota else _(\"No\"),\n                str(v.price) if v.price else \"\",\n                v.tag,\n                _(\"Yes\") if v.redeemed else _(\"No\"),\n            ]\n            writer.writerow(row)\n\n        r = HttpResponse(output.getvalue().encode(\"utf-8\"), content_type='text/csv')\n        r['Content-Disposition'] = 'attachment; filename=\"vouchers.csv\"'\n        return r\n\n\nclass VoucherTags(EventPermissionRequiredMixin, TemplateView):\n    template_name = 'pretixcontrol/vouchers/tags.html'\n    permission = 'can_view_vouchers'\n\n    def get_context_data(self, **kwargs):\n        ctx = super().get_context_data(**kwargs)\n\n        tags = self.request.event.vouchers.order_by('tag').filter(tag__isnull=False).values('tag').annotate(\n            total=Count('id'),\n            # This is a fix for this MySQL issue: https://code.djangoproject.com/ticket/24662\n            redeemed=Sum(\n                Case(\n                    When(redeemed=True, then=1),\n                    When(redeemed=False, then=0),\n                    output_field=IntegerField()\n                )\n            )\n        )\n        for t in tags:\n            t['percentage'] = int((t['redeemed'] / t['total']) * 100)\n\n        ctx['tags'] = tags\n        return ctx\n\n\nclass VoucherDelete(EventPermissionRequiredMixin, DeleteView):\n    model = Voucher\n    template_name = 'pretixcontrol/vouchers/delete.html'\n    permission = 'can_change_vouchers'\n    context_object_name = 'voucher'\n\n    def get_object(self, queryset=None) -> Voucher:\n        try:\n            return self.request.event.vouchers.get(\n                id=self.kwargs['voucher']\n            )\n        except Voucher.DoesNotExist:\n            raise Http404(_(\"The requested voucher does not exist.\"))\n\n    def get(self, request, *args, **kwargs):\n        if self.get_object().redeemed:\n            messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))\n            return HttpResponseRedirect(self.get_success_url())\n        return super().get(request, *args, **kwargs)\n\n    @transaction.atomic\n    def delete(self, request, *args, **kwargs):\n        self.object = self.get_object()\n        success_url = self.get_success_url()\n\n        if self.object.redeemed:\n            messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))\n        else:\n            self.object.log_action('pretix.voucher.deleted', user=self.request.user)\n            self.object.delete()\n            messages.success(request, _('The selected voucher has been deleted.'))\n        return HttpResponseRedirect(success_url)\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n\n\nclass VoucherUpdate(EventPermissionRequiredMixin, UpdateView):\n    model = Voucher\n    template_name = 'pretixcontrol/vouchers/detail.html'\n    permission = 'can_change_vouchers'\n    context_object_name = 'voucher'\n\n    def get_form_class(self):\n        form_class = VoucherForm\n        for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):\n            if response:\n                form_class = response\n        return form_class\n\n    def get_object(self, queryset=None) -> VoucherForm:\n        url = resolve(self.request.path_info)\n        try:\n            return self.request.event.vouchers.get(\n                id=url.kwargs['voucher']\n            )\n        except Voucher.DoesNotExist:\n            raise Http404(_(\"The requested voucher does not exist.\"))\n\n    @transaction.atomic\n    def form_valid(self, form):\n        messages.success(self.request, _('Your changes have been saved.'))\n        if form.has_changed():\n            self.object.log_action(\n                'pretix.voucher.changed', user=self.request.user, data={\n                    k: form.cleaned_data.get(k) for k in form.changed_data\n                }\n            )\n        return super().form_valid(form)\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n\n\nclass VoucherCreate(EventPermissionRequiredMixin, CreateView):\n    model = Voucher\n    template_name = 'pretixcontrol/vouchers/detail.html'\n    permission = 'can_change_vouchers'\n    context_object_name = 'voucher'\n\n    def get_form_class(self):\n        form_class = VoucherForm\n        for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):\n            if response:\n                form_class = response\n        return form_class\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs['instance'] = Voucher(event=self.request.event)\n        return kwargs\n\n    @transaction.atomic\n    def form_valid(self, form):\n        form.instance.event = self.request.event\n        messages.success(self.request, _('The new voucher has been created.'))\n        ret = super().form_valid(form)\n        form.instance.log_action('pretix.voucher.added', data=dict(form.cleaned_data), user=self.request.user)\n        return ret\n\n    def post(self, request, *args, **kwargs):\n        # TODO: Transform this into an asynchronous call?\n        with request.event.lock():\n            return super().post(request, *args, **kwargs)\n\n\nclass VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):\n    model = Voucher\n    template_name = 'pretixcontrol/vouchers/bulk.html'\n    permission = 'can_change_vouchers'\n    context_object_name = 'voucher'\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs['instance'] = Voucher(event=self.request.event)\n        return kwargs\n\n    @transaction.atomic\n    def form_valid(self, form):\n        for o in form.save(self.request.event):\n            o.log_action('pretix.voucher.added', data=form.cleaned_data, user=self.request.user)\n        messages.success(self.request, _('The new vouchers have been created.'))\n        return HttpResponseRedirect(self.get_success_url())\n\n    def get_form_class(self):\n        form_class = VoucherBulkForm\n        for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):\n            if response:\n                form_class = response\n        return form_class\n\n    def get_context_data(self, **kwargs):\n        ctx = super().get_context_data(**kwargs)\n        ctx['code_length'] = settings.ENTROPY['voucher_code']\n        return ctx\n\n    def post(self, request, *args, **kwargs):\n        # TODO: Transform this into an asynchronous call?\n        with request.event.lock():\n            return super().post(request, *args, **kwargs)\n\n\nclass VoucherRNG(EventPermissionRequiredMixin, View):\n    permission = 'can_change_vouchers'\n\n    def get(self, request, *args, **kwargs):\n        codes = set()\n        try:\n            num = int(request.GET.get('num', '5'))\n        except ValueError:  # NOQA\n            return HttpResponseBadRequest()\n\n        while len(codes) < num:\n            new_codes = set()\n            for i in range(min(num - len(codes), 500)):  # Work around SQLite's SQLITE_MAX_VARIABLE_NUMBER\n                new_codes.add(_generate_random_code())\n            new_codes -= set([v['code'] for v in Voucher.objects.filter(code__in=new_codes).values('code')])\n            codes |= new_codes\n\n        return JsonResponse({\n            'codes': list(codes)\n        })\n\n    def get_success_url(self) -> str:\n        return reverse('control:event.vouchers', kwargs={\n            'organizer': self.request.event.organizer.slug,\n            'event': self.request.event.slug,\n        })\n", "path": "src/pretix/control/views/vouchers.py"}]}