API: Allow to answer file upload questions during ticket redemption

This commit is contained in:
Raphael Michel
2021-01-07 10:16:47 +01:00
parent 5b81507600
commit 01c3b08583
4 changed files with 82 additions and 3 deletions

View File

@@ -22,7 +22,7 @@ from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter
from pretix.base.i18n import language
from pretix.base.models import (
Checkin, CheckinList, Event, Order, OrderPosition,
CachedFile, Checkin, CheckinList, Event, Order, OrderPosition, Question,
)
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, perform_checkin,
@@ -302,7 +302,10 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
for q in op.item.questions.filter(ask_during_checkin=True):
if str(q.pk) in aws:
try:
given_answers[q] = q.clean_answer(aws[str(q.pk)])
if q.type == Question.TYPE_FILE:
given_answers[q] = self._handle_file_upload(aws[str(q.pk)])
else:
given_answers[q] = q.clean_answer(aws[str(q.pk)])
except ValidationError:
pass
@@ -352,3 +355,25 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data
}, status=201)
def _handle_file_upload(self, data):
try:
cf = CachedFile.objects.get(
session_key=f'api-upload-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}',
file__isnull=False,
pk=data[len("file:"):],
)
except (ValidationError, IndexError): # invalid uuid
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
except CachedFile.DoesNotExist:
raise ValidationError('The submitted file ID "{fid}" was not found.'.format(fid=data))
allowed_types = (
'image/png', 'image/jpeg', 'image/gif', 'application/pdf'
)
if cf.type not in allowed_types:
raise ValidationError('The submitted file "{fid}" has a file type that is not allowed in this field.'.format(fid=data))
if cf.file.size > 10 * 1024 * 1024:
raise ValidationError('The submitted file "{fid}" is too large to be used in this field.'.format(fid=data))
return cf.file

View File

@@ -1026,7 +1026,7 @@ class Question(LoggedModel):
(TYPE_PHONENUMBER, _("Phone number")),
)
UNLOCALIZED_TYPES = [TYPE_DATE, TYPE_TIME, TYPE_DATETIME]
ASK_DURING_CHECKIN_UNSUPPORTED = [TYPE_FILE, TYPE_PHONENUMBER]
ASK_DURING_CHECKIN_UNSUPPORTED = [TYPE_PHONENUMBER]
event = models.ForeignKey(
Event,
@@ -1069,6 +1069,7 @@ class Question(LoggedModel):
)
ask_during_checkin = models.BooleanField(
verbose_name=_('Ask during check-in instead of in the ticket buying process'),
help_text=_('Not supported by all check-in apps for all question types.'),
default=False
)
hidden = models.BooleanField(

View File

@@ -1,6 +1,7 @@
from datetime import timedelta
import dateutil
from django.core.files import File
from django.db import transaction
from django.db.models.functions import TruncDate
from django.dispatch import receiver
@@ -125,6 +126,14 @@ def _save_answers(op, answers, given_answers):
else:
qa = op.answers.create(question=q, answer=", ".join([str(o) for o in a]))
qa.options.add(*a)
elif isinstance(a, File):
if q in answers:
qa = answers[q]
else:
qa = op.answers.create(question=q, answer=str(a))
qa.file.save(a.name, a, save=False)
qa.answer = 'file://' + qa.file.name
qa.save()
else:
if q in answers:
qa = answers[q]

View File

@@ -7,6 +7,7 @@ from unittest import mock
from urllib.parse import quote as urlquote
import pytest
from django.core.files.base import ContentFile
from django.utils.timezone import now
from django_countries.fields import Country
from django_scopes import scopes_disabled
@@ -995,3 +996,46 @@ def test_question_multiple_choice(token_client, organizer, clist, event, order,
with scopes_disabled():
assert order.positions.first().answers.get(question=question[0]).answer == 'M, L'
assert set(order.positions.first().answers.get(question=question[0]).options.all()) == {question[1], question[2]}
@pytest.mark.django_db
def test_question_upload(token_client, organizer, clist, event, order, question):
r = token_client.post(
'/api/v1/upload',
data={
'media_type': 'image/png',
'file': ContentFile('file.png', 'invalid png content')
},
format='upload',
HTTP_CONTENT_DISPOSITION='attachment; filename="file.png"',
)
assert r.status_code == 201
file_id_png = r.data['id']
with scopes_disabled():
p = order.positions.first()
question[0].type = 'F'
question[0].save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, p.pk
), {}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
with scopes_disabled():
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, p.pk
), {'answers': {question[0].pk: "invalid"}}, format='json')
assert resp.status_code == 400
assert resp.data['status'] == 'incomplete'
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
organizer.slug, event.slug, clist.pk, p.pk
), {'answers': {question[0].pk: file_id_png}}, format='json')
assert resp.status_code == 201
assert resp.data['status'] == 'ok'
with scopes_disabled():
assert order.positions.first().answers.get(question=question[0]).answer.startswith('file://')
assert order.positions.first().answers.get(question=question[0]).file