Fix #630 -- manual check-in of attendees (#642)

* [WIP] manual check-in of attendees

This enables manual check-in of attendees.  The post-code was heavily
copied from the APIRedeemView of the pretixdroid, thus so far this seems
to be working, but I have a few questions:

The checkin-Objects generated by the pretixdroid-app have a nonce.
Should the checkin object generated here have a nonce, too?

Should the result of the check-in be noted in any other way than by the
change of the status?

* addressed review comments

* implement unit test for manual checkin

* fix style-issues

* Slight layout change

* Log who did the manual check-in

* Improve unit test to check the result of the action
This commit is contained in:
Jakob Schnell
2017-10-28 23:16:22 +02:00
committed by Raphael Michel
parent 9213a40219
commit 1a894d71b8
4 changed files with 74 additions and 3 deletions
+18
View File
@@ -1,8 +1,11 @@
import json import json
from decimal import Decimal from decimal import Decimal
import dateutil.parser
import pytz
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import formats from django.utils import formats
from django.utils.formats import date_format
from django.utils.translation import pgettext_lazy, ugettext_lazy as _ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
@@ -200,6 +203,21 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
if logentry.action_type.startswith('pretix.event.tickets.provider.'): if logentry.action_type.startswith('pretix.event.tickets.provider.'):
return _('The settings of a ticket output provider have been changed.') return _('The settings of a ticket output provider have been changed.')
if logentry.action_type == 'pretix.control.views.checkin':
dt = dateutil.parser.parse(data.get('datetime'))
tz = pytz.timezone(sender.settings.timezone)
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
if data.get('first'):
return _('Position #{posid} has been checked in manually at {datetime}.').format(
posid=data.get('positionid'),
datetime=dt_formatted
)
return _('Position #{posid} has been checked in again at {datetime}.').format(
posid=data.get('positionid'),
datetime=dt_formatted
)
if logentry.action_type == 'pretix.team.member.added': if logentry.action_type == 'pretix.team.member.added':
return _('{user} has been added to the team.').format(user=data.get('email')) return _('{user} has been added to the team.').format(user=data.get('email'))
@@ -52,6 +52,7 @@
<table class="table table-condensed table-hover"> <table class="table table-condensed table-hover">
<thead> <thead>
<tr> <tr>
<th />
<th>{% trans "Order code" %} <a href="?{% url_replace request 'ordering' '-code'%}"><i class="fa fa-caret-down"></i></a> <th>{% trans "Order code" %} <a href="?{% url_replace request 'ordering' '-code'%}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'code'%}"><i class="fa fa-caret-up"></i></a></th> <a href="?{% url_replace request 'ordering' 'code'%}"><i class="fa fa-caret-up"></i></a></th>
<th>{% trans "Item" %} <a href="?{% url_replace request 'ordering' '-item'%}"><i class="fa fa-caret-down"></i></a> <th>{% trans "Item" %} <a href="?{% url_replace request 'ordering' '-item'%}"><i class="fa fa-caret-down"></i></a>
@@ -74,6 +75,11 @@
{% for e in entries %} {% for e in entries %}
{% with e.checkins.first as checkin %} {% with e.checkins.first as checkin %}
<tr> <tr>
<td>
<input type="checkbox" name="checkin"
id="id_checkin" class=""
value="{{e.pk}}"/>
</td>
<td> <td>
<strong><a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=e.order.code %}">{{ e.order.code }}</a></strong> <strong><a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=e.order.code %}">{{ e.order.code }}</a></strong>
</td> </td>
@@ -107,6 +113,10 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<button type="submit" class="btn btn-primary btn-save">
{% trans "Check-In selected attendees" %}
</button>
</form> </form>
{% include "pretixcontrol/pagination.html" %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
+31 -1
View File
@@ -1,8 +1,13 @@
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db.models import F, Prefetch, Q from django.db.models import F, Prefetch, Q
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.shortcuts import redirect
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.views.generic import ListView from django.views.generic import ListView
from pretix.base.models import Checkin, Item, OrderPosition from pretix.base.models import Checkin, Item, Order, OrderPosition
from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
@@ -69,6 +74,31 @@ class CheckInView(EventPermissionRequiredMixin, ListView):
or "subevent" in self.request.GET) or "subevent" in self.request.GET)
return ctx return ctx
def post(self, request, *args, **kwargs):
positions = OrderPosition.objects.select_related('item', 'variation', 'order', 'addon_to').filter(
order__event=self.request.event,
pk__in=request.POST.getlist('checkin')
)
for op in positions:
created = False
if op.order.status == Order.STATUS_PAID:
ci, created = Checkin.objects.get_or_create(position=op, defaults={
'datetime': now(),
})
op.order.log_action('pretix.control.views.checkin', data={
'position': op.id,
'positionid': op.positionid,
'first': created,
'datetime': now()
}, user=request.user)
messages.success(request, _('The selected tickets have been marked as checked in.'))
return redirect(reverse('control:event.orders.checkins', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug
}) + '?' + request.GET.urlencode())
@staticmethod @staticmethod
def get_ordering_keys_mappings(): def get_ordering_keys_mappings():
return { return {
+15 -2
View File
@@ -5,8 +5,8 @@ import pytest
from django.utils.timezone import now from django.utils.timezone import now
from pretix.base.models import ( from pretix.base.models import (
Checkin, Event, Item, ItemAddOn, ItemCategory, Order, OrderPosition, Checkin, Event, Item, ItemAddOn, ItemCategory, LogEntry, Order,
Organizer, Team, User, OrderPosition, Organizer, Team, User,
) )
from pretix.control.views.dashboards import checkin_widget from pretix.control.views.dashboards import checkin_widget
@@ -269,6 +269,19 @@ def test_checkins_list_mixed(client, checkin_list_env, query, expected):
assert item_keys == expected assert item_keys == expected
@pytest.mark.django_db
def test_manual_checkins(client, checkin_list_env):
client.login(email='dummy@dummy.dummy', password='dummy')
assert not checkin_list_env[5][3].checkins.exists()
client.post('/control/event/dummy/dummy/checkins/', {
'checkin': [checkin_list_env[5][3].pk]
})
assert checkin_list_env[5][3].checkins.exists()
assert LogEntry.objects.filter(
action_type='pretix.control.views.checkin', object_id=checkin_list_env[5][3].order.pk
).exists()
@pytest.fixture @pytest.fixture
def checkin_list_with_addon_env(): def checkin_list_with_addon_env():
# permission # permission