diff --git a/src/pretix/api/serializers/waitinglist.py b/src/pretix/api/serializers/waitinglist.py
index ed0ca9dd04..8be870d985 100644
--- a/src/pretix/api/serializers/waitinglist.py
+++ b/src/pretix/api/serializers/waitinglist.py
@@ -8,7 +8,7 @@ class WaitingListSerializer(I18nAwareModelSerializer):
class Meta:
model = WaitingListEntry
- fields = ('id', 'created', 'email', 'voucher', 'item', 'variation', 'locale', 'subevent')
+ fields = ('id', 'created', 'email', 'voucher', 'item', 'variation', 'locale', 'subevent', 'priority')
read_only_fields = ('id', 'created', 'voucher')
def validate(self, data):
diff --git a/src/pretix/base/migrations/0099_auto_20180807_0841.py b/src/pretix/base/migrations/0099_auto_20180807_0841.py
new file mode 100644
index 0000000000..8b1c4d4295
--- /dev/null
+++ b/src/pretix/base/migrations/0099_auto_20180807_0841.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.1 on 2018-08-07 08:41
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pretixbase', '0098_auto_20180731_1243'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='waitinglistentry',
+ options={'ordering': ('-priority', 'created'), 'verbose_name': 'Waiting list entry', 'verbose_name_plural': 'Waiting list entries'},
+ ),
+ migrations.AddField(
+ model_name='waitinglistentry',
+ name='priority',
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AlterField(
+ model_name='waitinglistentry',
+ name='voucher',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Voucher', verbose_name='Assigned voucher'),
+ ),
+ ]
diff --git a/src/pretix/base/models/waitinglist.py b/src/pretix/base/models/waitinglist.py
index 886b97be79..c2b49883a3 100644
--- a/src/pretix/base/models/waitinglist.py
+++ b/src/pretix/base/models/waitinglist.py
@@ -65,11 +65,12 @@ class WaitingListEntry(LoggedModel):
max_length=190,
default='en'
)
+ priority = models.IntegerField(default=0)
class Meta:
verbose_name = _("Waiting list entry")
verbose_name_plural = _("Waiting list entries")
- ordering = ['created']
+ ordering = ('-priority', 'created')
def __str__(self):
return '%s waits for %s' % (str(self.email), str(self.item))
diff --git a/src/pretix/base/services/waitinglist.py b/src/pretix/base/services/waitinglist.py
index b71c711609..e1e81f7dcb 100644
--- a/src/pretix/base/services/waitinglist.py
+++ b/src/pretix/base/services/waitinglist.py
@@ -22,7 +22,9 @@ def assign_automatically(event_id: int, user_id: int=None, subevent_id: int=None
qs = WaitingListEntry.objects.filter(
event=event, voucher__isnull=True
- ).select_related('item', 'variation').prefetch_related('item__quotas', 'variation__quotas').order_by('created')
+ ).select_related('item', 'variation').prefetch_related(
+ 'item__quotas', 'variation__quotas'
+ ).order_by('-priority', 'created')
if subevent_id and event.has_subevents:
subevent = event.subevents.get(id=subevent_id)
diff --git a/src/pretix/control/templates/pretixcontrol/waitinglist/index.html b/src/pretix/control/templates/pretixcontrol/waitinglist/index.html
index 01800aa796..561ed19003 100644
--- a/src/pretix/control/templates/pretixcontrol/waitinglist/index.html
+++ b/src/pretix/control/templates/pretixcontrol/waitinglist/index.html
@@ -151,7 +151,15 @@
{% if request.event.has_subevents %}
{{ e.subevent.name }} – {{ e.subevent.get_date_range_display }} |
{% endif %}
- {{ e.created|date:"SHORT_DATETIME_FORMAT" }} |
+
+ {{ e.created|date:"SHORT_DATETIME_FORMAT" }}
+ {% if e.priority != 0 %}
+
+ {% if e.priority > 0 %}+{% endif %}{{ e.priority }}
+
+ {% endif %}
+ |
{% if e.voucher %}
{% if e.voucher.redeemed >= e.voucher.max_usages %}
@@ -184,8 +192,22 @@
|
{% if not e.voucher %}
+
+
{% else %}
+
+
{% endif %}
|
diff --git a/src/pretix/control/views/waitinglist.py b/src/pretix/control/views/waitinglist.py
index 07a2bedef2..9f047972ac 100644
--- a/src/pretix/control/views/waitinglist.py
+++ b/src/pretix/control/views/waitinglist.py
@@ -3,7 +3,7 @@ import io
from django.contrib import messages
from django.db import transaction
-from django.db.models import F, Q, Sum
+from django.db.models import F, Max, Min, Q, Sum
from django.db.models.functions import Coalesce
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect
@@ -52,14 +52,12 @@ class WaitingListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
permission = 'can_view_orders'
def post(self, request, *args, **kwargs):
+ if not request.user.has_event_permission(request.organizer, request.event, 'can_change_orders',
+ request=request):
+ messages.error(request, _('You do not have permission to do this'))
+ return self._redirect_back()
+
if 'assign' in request.POST:
- if not request.user.has_event_permission(request.organizer, request.event, 'can_change_orders',
- request=request):
- messages.error(request, _('You do not have permission to do this'))
- return redirect(reverse('control:event.orders.waitinglist', kwargs={
- 'event': request.event.slug,
- 'organizer': request.event.organizer.slug
- }))
try:
wle = WaitingListEntry.objects.get(
pk=request.POST.get('assign'), event=self.request.event,
@@ -71,18 +69,44 @@ class WaitingListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
else:
messages.success(request, _('An email containing a voucher code has been sent to the '
'specified address.'))
- if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
- return redirect(self.request.GET.get("next"))
- return redirect(reverse('control:event.orders.waitinglist', kwargs={
- 'event': request.event.slug,
- 'organizer': request.event.organizer.slug
- }))
+ return self._redirect_back()
except WaitingListEntry.DoesNotExist:
messages.error(request, _('Waiting list entry not found.'))
- return redirect(reverse('control:event.orders.waitinglist', kwargs={
- 'event': request.event.slug,
- 'organizer': request.event.organizer.slug
- }))
+ return self._redirect_back()
+
+ if 'move_top' in request.POST:
+ try:
+ wle = WaitingListEntry.objects.get(
+ pk=request.POST.get('move_top'), event=self.request.event,
+ )
+ wle.priority = self.request.event.waitinglistentries.aggregate(m=Max('priority'))['m'] + 1
+ wle.save(update_fields=['priority'])
+ messages.success(request, _('The waiting list entry has been moved to the top.'))
+ return self._redirect_back()
+ except WaitingListEntry.DoesNotExist:
+ messages.error(request, _('Waiting list entry not found.'))
+ return self._redirect_back()
+
+ if 'move_end' in request.POST:
+ try:
+ wle = WaitingListEntry.objects.get(
+ pk=request.POST.get('move_end'), event=self.request.event,
+ )
+ wle.priority = self.request.event.waitinglistentries.aggregate(m=Min('priority'))['m'] - 1
+ wle.save(update_fields=['priority'])
+ messages.success(request, _('The waiting list entry has been moved to the end of the list.'))
+ return self._redirect_back()
+ except WaitingListEntry.DoesNotExist:
+ messages.error(request, _('Waiting list entry not found.'))
+ return self._redirect_back()
+
+ def _redirect_back(self):
+ if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
+ return redirect(self.request.GET.get("next"))
+ return redirect(reverse('control:event.orders.waitinglist', kwargs={
+ 'event': self.request.event.slug,
+ 'organizer': self.request.event.organizer.slug
+ }))
def get_queryset(self):
qs = WaitingListEntry.objects.filter(
@@ -172,7 +196,7 @@ class WaitingListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
headers = [
_('E-mail address'), _('Product'), _('On list since'), _('Status'), _('Voucher code'),
- _('Language')
+ _('Language'), _('Priority')
]
if self.request.event.has_subevents:
headers.append(pgettext('subevent', 'Date'))
@@ -201,6 +225,7 @@ class WaitingListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
status,
w.voucher.code if w.voucher else '',
w.locale,
+ str(w.priority)
]
if self.request.event.has_subevents:
row.append(str(w.subevent))
diff --git a/src/tests/control/test_waitinglist.py b/src/tests/control/test_waitinglist.py
index f376c0fef3..2995f46892 100644
--- a/src/tests/control/test_waitinglist.py
+++ b/src/tests/control/test_waitinglist.py
@@ -104,6 +104,29 @@ def test_assign_single(client, env):
assert wle.voucher
+@pytest.mark.django_db
+def test_priority_single(client, env):
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ wle = WaitingListEntry.objects.filter(voucher__isnull=True).last()
+ assert wle.priority == 0
+
+ client.post('/control/event/dummy/dummy/waitinglist/', {
+ 'move_top': wle.pk
+ })
+ wle.refresh_from_db()
+ assert wle.priority == 1
+ client.post('/control/event/dummy/dummy/waitinglist/', {
+ 'move_top': wle.pk
+ })
+ wle.refresh_from_db()
+ assert wle.priority == 2
+ client.post('/control/event/dummy/dummy/waitinglist/', {
+ 'move_end': wle.pk
+ })
+ wle.refresh_from_db()
+ assert wle.priority == -1
+
+
@pytest.mark.django_db
def test_delete_single(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')