Repository: pretix/pretix

Issue:
Search orders/go to voucher
- When I enter an order code in the input box on the order list, and the order exists, I'll see the detail view of the order.
- When I enter a partial order code, I'll get no matches.
- When I enter a voucher code in the input box on the voucher list, and the voucher exists, I'll see a list view containing that one voucher.
- When I enter a partial voucher code, I'll get all vouchers matching that code.

I'd like to see consistent behavior here; at the very least going to a voucher if the entered code corresponds to a voucher exactly. Substring search for order codes would be nice, too, unless you have security concerns there?


Discussion:
I'm not a fan of search of forms which *sometimes* take you to the detail page and *sometimes* to a result list. If you look closely, for orders there is a "go to" and a "search" part of the form, while vouchers only have the "search" part. Probably, this is also not a perfect UI. I'm open for ideas here.

Substring search for orders is useful, sure thing. And no, no security concerns there.
I am aware of the difference between "go to" and "search", but I really enjoyed going directly to orders and found myself often surprised when copying a voucher would not take me there. But since I prefer the go-to style to remain, I think I'll retract this one - substring search for vouchers might be nice, but not as nice as going directly to a voucher.
No reason to close this, I absolutely see the point! The title of this issue is still valid:

* Implement substring search for order codes
* Implement **go to** for vouchers

Resolving patch:
diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py
--- a/src/pretix/control/urls.py
+++ b/src/pretix/control/urls.py
@@ -151,6 +151,7 @@
         url(r'^vouchers/(?P<voucher>\d+)/delete$', vouchers.VoucherDelete.as_view(),
             name='event.voucher.delete'),
         url(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'),
+        url(r'^vouchers/go$', vouchers.VoucherGo.as_view(), name='event.vouchers.go'),
         url(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'),
         url(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
             name='event.order.transition'),
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
@@ -10,6 +10,7 @@
     Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
     JsonResponse,
 )
+from django.shortcuts import redirect
 from django.utils.functional import cached_property
 from django.utils.translation import ugettext_lazy as _
 from django.views.generic import (
@@ -225,6 +226,23 @@ def post(self, request, *args, **kwargs):
             return super().post(request, *args, **kwargs)
 
 
+class VoucherGo(EventPermissionRequiredMixin, View):
+    permission = 'can_view_vouchers'
+
+    def get_voucher(self, code):
+        return Voucher.objects.get(code__iexact=code, event=self.request.event)
+
+    def get(self, request, *args, **kwargs):
+        code = request.GET.get("code", "").strip()
+        try:
+            voucher = self.get_voucher(code)
+            return redirect('control:event.voucher', event=request.event.slug, organizer=request.event.organizer.slug,
+                            voucher=voucher.id)
+        except Voucher.DoesNotExist:
+            messages.error(request, _('There is no voucher with the given voucher code.'))
+            return redirect('control:event.vouchers', event=request.event.slug, organizer=request.event.organizer.slug)
+
+
 class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
     model = Voucher
     template_name = 'pretixcontrol/vouchers/bulk.html'


Test patch: