Refactor check-in logic into core

This commit is contained in:
Raphael Michel
2018-04-25 09:32:10 +02:00
parent fd47e2de29
commit a94f416b3c
5 changed files with 245 additions and 177 deletions

View File

@@ -1,14 +1,10 @@
import json
import dateutil.parser
import pytz
from django.core.urlresolvers import resolve, reverse
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import CheckinList
from pretix.base.signals import logentry_display
from pretix.control.logdisplay import _display_checkin
from pretix.control.signals import nav_event
@@ -35,49 +31,4 @@ def pretixcontrol_logentry_display(sender, logentry, **kwargs):
if logentry.action_type != 'pretix.plugins.pretixdroid.scan':
return
data = json.loads(logentry.data)
show_dt = False
if 'datetime' in data:
dt = dateutil.parser.parse(data.get('datetime'))
show_dt = abs((logentry.datetime - dt).total_seconds()) > 60 or 'forced' in data
tz = pytz.timezone(sender.settings.timezone)
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
if 'list' in data:
try:
checkin_list = sender.checkin_lists.get(pk=data.get('list')).name
except CheckinList.DoesNotExist:
checkin_list = _("(unknown)")
else:
checkin_list = _("(unknown)")
if data.get('first'):
if show_dt:
return _('Position #{posid} has been scanned at {datetime} for list "{list}".').format(
posid=data.get('positionid'),
datetime=dt_formatted,
list=checkin_list
)
else:
return _('Position #{posid} has been scanned for list "{list}".').format(
posid=data.get('positionid'),
list=checkin_list
)
else:
if data.get('forced'):
return _(
'A scan for position #{posid} at {datetime} for list "{list}" has been uploaded even though it has '
'been scanned already.'.format(
posid=data.get('positionid'),
datetime=dt_formatted,
list=checkin_list
)
)
return _(
'Position #{posid} has been scanned and rejected because it has already been scanned before '
'on list "{list}".'.format(
posid=data.get('positionid'),
list=checkin_list
)
)
return _display_checkin(sender, logentry)

View File

@@ -5,8 +5,7 @@ import urllib.parse
import dateutil.parser
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Count, Max, OuterRef, Prefetch, Q, Subquery
from django.db.models import Count, Max, OuterRef, Q, Subquery
from django.http import (
HttpResponseForbidden, HttpResponseNotFound, JsonResponse,
)
@@ -19,10 +18,11 @@ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView, View
from pretix.base.models import (
Checkin, Event, Order, OrderPosition, Question, QuestionOption,
)
from pretix.base.models import Checkin, Event, Order, OrderPosition
from pretix.base.models.event import SubEvent
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, perform_checkin,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.helpers.urls import build_absolute_uri
from pretix.multidomain.urlreverse import (
@@ -155,39 +155,6 @@ class ApiView(View):
class ApiRedeemView(ApiView):
def _save_answers(self, op, answers, given_answers):
for q, a in given_answers.items():
if not a:
if q in answers:
answers[q].delete()
else:
continue
if isinstance(a, QuestionOption):
if q in answers:
qa = answers[q]
qa.answer = str(a.answer)
qa.save()
qa.options.clear()
else:
qa = op.answers.create(question=q, answer=str(a.answer))
qa.options.add(a)
elif isinstance(a, list):
if q in answers:
qa = answers[q]
qa.answer = ", ".join([str(o) for o in a])
qa.save()
qa.options.clear()
else:
qa = op.answers.create(question=q, answer=", ".join([str(o) for o in a]))
qa.options.add(*a)
else:
if q in answers:
qa = answers[q]
qa.answer = str(a)
qa.save()
else:
op.answers.create(question=q, answer=str(a))
def post(self, request, **kwargs):
secret = request.POST.get('secret', '!INVALID!')
force = request.POST.get('force', 'false') in ('true', 'True')
@@ -203,93 +170,43 @@ class ApiRedeemView(ApiView):
dt = now()
try:
with transaction.atomic():
created = False
op = OrderPosition.objects.select_related(
'item', 'variation', 'order', 'addon_to'
).prefetch_related(
'item__questions',
Prefetch(
'item__questions',
queryset=Question.objects.filter(ask_during_checkin=True),
to_attr='checkin_questions'
),
'answers'
).get(
order__event=self.event, secret=secret, subevent=self.subevent
)
answers = {a.question: a for a in op.answers.all()}
require_answers = []
given_answers = {}
for q in op.item.checkin_questions:
if 'answer_{}'.format(q.pk) in request.POST:
try:
given_answers[q] = q.clean_answer(request.POST.get('answer_{}'.format(q.pk)))
continue
except ValidationError:
pass
if q in answers:
continue
require_answers.append(serialize_question(q))
self._save_answers(op, answers, given_answers)
if not self.config.list.all_products and op.item_id not in [i.pk for i in
self.config.list.limit_products.all()]:
response['status'] = 'error'
response['reason'] = 'product'
elif not self.config.all_items and op.item_id not in [i.pk for i in self.config.items.all()]:
response['status'] = 'error'
response['reason'] = 'product'
elif op.order.status != Order.STATUS_PAID and not force and not (
ignore_unpaid and self.config.list.include_pending and op.order.status == Order.STATUS_PENDING
):
response['status'] = 'error'
response['reason'] = 'unpaid'
elif require_answers and not force and request.POST.get('questions_supported'):
response['status'] = 'incomplete'
response['questions'] = require_answers
else:
ci, created = Checkin.objects.get_or_create(position=op, list=self.config.list, defaults={
'datetime': dt,
'nonce': nonce,
})
if 'status' not in response:
if created or (nonce and nonce == ci.nonce):
response['status'] = 'ok'
if created:
op.order.log_action('pretix.plugins.pretixdroid.scan', data={
'position': op.id,
'positionid': op.positionid,
'first': True,
'forced': op.order.status != Order.STATUS_PAID,
'datetime': dt,
'list': self.config.list.pk
})
else:
if force:
response['status'] = 'ok'
else:
response['status'] = 'error'
response['reason'] = 'already_redeemed'
op.order.log_action('pretix.plugins.pretixdroid.scan', data={
'position': op.id,
'positionid': op.positionid,
'first': False,
'forced': force,
'datetime': dt,
'list': self.config.list.pk
})
response['data'] = serialize_op(op, redeemed=op.order.status == Order.STATUS_PAID or force,
clist=self.config.list)
op = OrderPosition.objects.get(order__event=self.event, secret=secret, subevent=self.subevent)
except OrderPosition.DoesNotExist:
response['status'] = 'error'
response['reason'] = 'unknown_ticket'
else:
given_answers = {}
for q in op.item.questions.filter(ask_during_checkin=True):
if 'answer_{}'.format(q.pk) in request.POST:
try:
given_answers[q] = q.clean_answer(request.POST.get('answer_{}'.format(q.pk)))
except ValidationError:
pass
try:
if not self.config.all_items and op.item_id not in [i.pk for i in self.config.items.all()]:
raise CheckInError('', 'product')
perform_checkin(
op=op,
clist=self.config.list,
given_answers=given_answers,
force=force,
ignore_unpaid=ignore_unpaid,
nonce=nonce,
datetime=dt,
questions_supported=bool(request.POST.get('questions_supported'))
)
except RequiredQuestionsError as e:
response['status'] = 'incomplete'
response['questions'] = [serialize_question(q) for q in e.questions]
except CheckInError as e:
response['status'] = 'error'
response['reason'] = e.code
else:
response['status'] = 'ok'
response['data'] = serialize_op(op, redeemed=op.order.status == Order.STATUS_PAID or force,
clist=self.config.list)
return JsonResponse(response)