Display answers in confirm step, fix file upload/download

This commit is contained in:
Mira Weller
2026-08-12 13:38:09 +02:00
parent f8c69120d4
commit 4fa1b9eb2c
11 changed files with 119 additions and 88 deletions
+3 -25
View File
@@ -157,16 +157,12 @@ class TimePickerWidget(forms.TimeInput):
class UploadedFileWidget(forms.ClearableFileInput):
def __init__(self, *args, **kwargs):
self.position = kwargs.pop('position')
self.event = kwargs.pop('event')
self.answer = kwargs.pop('answer')
super().__init__(*args, **kwargs)
class FakeFile:
def __init__(self, file, container, event, answer):
def __init__(self, file, answer):
self.file = file
self.container = container
self.event = event
self.answer = answer
def __str__(self):
@@ -174,25 +170,7 @@ class UploadedFileWidget(forms.ClearableFileInput):
@property
def url(self):
from pretix.base.models import Order, OrderPosition
from pretix.multidomain.urlreverse import eventreverse
order = None
if isinstance(self.container, OrderPosition):
order = self.container.order
elif isinstance(self.container, Order):
order = self.container
if order:
return eventreverse(self.event, 'presale:event.order.download.answer', kwargs={
'order': order.code,
'secret': order.secret,
'answer': self.answer.pk,
})
else:
return eventreverse(self.event, 'presale:event.cart.download.answer', kwargs={
'answer': self.answer.pk,
})
return self.answer.frontend_file_url
def get_context(self, name, value, attrs):
# Browsers can't recognize that the server already has a file uploaded
@@ -205,7 +183,7 @@ class UploadedFileWidget(forms.ClearableFileInput):
def format_value(self, value):
if self.is_initial(value):
return self.FakeFile(value, self.position, self.event, self.answer)
return self.FakeFile(value, self.answer)
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
+1 -1
View File
@@ -1608,7 +1608,7 @@ class Question(LoggedModel):
"""
class ContainerType(models.TextChoices):
ORDER = "O", _("Order")
ORDERPOSITION = "P", _("Order position")
ORDERPOSITION = "P", _("Order position")
TYPE_NUMBER = "N"
TYPE_STRING = "S"
TYPE_TEXT = "T"
+29 -12
View File
@@ -1304,10 +1304,9 @@ class Order(LockModel, LoggedModel):
def answerfile_name(instance, filename: str) -> str:
secret = get_random_string(length=32, allowed_chars=string.ascii_letters + string.digits)
event = (instance.cartposition if instance.cartposition else instance.orderposition.order).event
return 'cachedfiles/answers/{org}/{ev}/{secret}.{filename}'.format(
org=event.organizer.slug,
ev=event.slug,
org=instance.event.organizer.slug,
ev=instance.event.slug,
secret=secret,
filename=escape_uri_path(filename),
)
@@ -1369,11 +1368,11 @@ class QuestionAnswer(models.Model):
@property
def backend_file_url(self):
if self.file:
if self.orderposition:
if self.associated_order:
return reverse('control:event.order.download.answer', kwargs={
'code': self.orderposition.order.code,
'event': self.orderposition.order.event.slug,
'organizer': self.orderposition.order.event.organizer.slug,
'code': self.associated_order.code,
'event': self.associated_order.event.slug,
'organizer': self.associated_order.event.organizer.slug,
'answer': self.pk,
})
return ""
@@ -1383,14 +1382,14 @@ class QuestionAnswer(models.Model):
from pretix.multidomain.urlreverse import eventreverse
if self.file:
if self.orderposition:
url = eventreverse(self.orderposition.order.event, 'presale:event.order.download.answer', kwargs={
'order': self.orderposition.order.code,
'secret': self.orderposition.order.secret,
if self.associated_order:
url = eventreverse(self.associated_order.event, 'presale:event.order.download.answer', kwargs={
'order': self.associated_order.code,
'secret': self.associated_order.secret,
'answer': self.pk,
})
else:
url = eventreverse(self.cartposition.event, 'presale:event.cart.download.answer', kwargs={
url = eventreverse(self.event, 'presale:event.cart.download.answer', kwargs={
'answer': self.pk,
})
@@ -1405,6 +1404,24 @@ class QuestionAnswer(models.Model):
def file_name(self):
return self.file.name.split('.', 1)[-1]
@property
def associated_order(self):
if self.orderposition:
return self.orderposition.order
elif self.order:
return self.order
@property
def event(self):
if self.orderposition:
return self.orderposition.order.event
elif self.cartposition:
return self.cartposition.event
elif self.order:
return self.order.event
elif self.checkoutsession:
return self.checkoutsession.event
def __str__(self):
return self.to_string(use_cached=True)
@@ -0,0 +1,28 @@
{% load safelink %}
{% load i18n %}
{% load rich_text %}
{% if answer %}
{% if answer.file %}
<span class="fa fa-file"></span>
<a href="{{ answer.backend_file_url }}?token={% answer_token request answer %}">
{{ answer.file_name }}
</a>
<span class="label label-danger" data-toggle="tooltip"
title="{% trans "This file has been uploaded by a user and could contain viruses or other malicious content." %}">
{% trans "UNSAFE" %}
</span>
{% if answer.is_image %}
<br>
<a href="{{ answer.backend_file_url }}?token={% answer_token request answer %}" data-lightbox="order"
class="answer-thumb">
<img src="{{ answer.backend_file_url }}?token={% answer_token request answer %}">
</a>
{% endif %}
{% elif question.type == "M" %}
{{ answer.to_string_i18n|rich_text_snippet }}
{% else %}
{{ answer.to_string_i18n|linebreaksbr }}
{% endif %}
{% else %}
<em>{% trans "not answered" %}</em>
{% endif %}
@@ -433,7 +433,7 @@
{% endif %}
{% for ans in order.answers.all %}
<dt>{{ ans.question.internal_name|default:ans.question.question }}</dt>
<dd>{{ ans.answer }}</dd>
<dd>{% include "pretixcontrol/order/fragment_question_answer.html" with request=request question=ans.question answer=ans %}</dd>
{% endfor %}
</dl>
</div>
@@ -672,31 +672,7 @@
{% endif %}
</dt>
<dd>
{% if q.answer %}
{% if q.answer.file %}
<span class="fa fa-file"></span>
<a href="{{ q.answer.backend_file_url }}?token={% answer_token request q.answer %}">
{{ q.answer.file_name }}
</a>
<span class="label label-danger" data-toggle="tooltip"
title="{% trans "This file has been uploaded by a user and could contain viruses or other malicious content." %}">
{% trans "UNSAFE" %}
</span>
{% if q.answer.is_image %}
<br>
<a href="{{ q.answer.backend_file_url }}?token={% answer_token request q.answer %}" data-lightbox="order"
class="answer-thumb">
<img src="{{ q.answer.backend_file_url }}?token={% answer_token request q.answer %}">
</a>
{% endif %}
{% elif q.type == "M" %}
{{ q.answer.to_string_i18n|rich_text_snippet }}
{% else %}
{{ q.answer.to_string_i18n|linebreaksbr }}
{% endif %}
{% else %}
<em>{% trans "not answered" %}</em>
{% endif %}
{% include "pretixcontrol/order/fragment_question_answer.html" with request=request question=q answer=q.answer %}
</dd>
{% endfor %}
{% for q in line.additional_fields %}
+6 -2
View File
@@ -2591,7 +2591,11 @@ class AnswerDownload(EventPermissionRequiredMixin, OrderViewMixin, ListView):
answid = kwargs.get('answer')
token = request.GET.get('token', '')
answer = get_object_or_404(QuestionAnswer, orderposition__order=self.order, id=answid)
answer = get_object_or_404(
QuestionAnswer,
Q(orderposition__order=self.order) | Q(order=self.order),
id=answid
)
if not answer.file:
raise Http404()
if not check_token(request, answer, token):
@@ -2601,7 +2605,7 @@ class AnswerDownload(EventPermissionRequiredMixin, OrderViewMixin, ListView):
resp = FileResponse(answer.file, content_type=ftype or 'application/binary')
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}"'.format(
self.request.event.slug.upper(), self.order.code,
answer.orderposition.positionid,
answer.orderposition.positionid if answer.orderposition else '',
os.path.basename(answer.file.name).split('.', 1)[1]
)
return resp
+1
View File
@@ -1564,6 +1564,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
ctx['addr'] = self.invoice_address
ctx['confirm_messages'] = self.confirm_messages
ctx['cart_session'] = self.cart_session
ctx['checkout_session'] = self.checkout_session
ctx['invoice_address_asked'] = self.address_asked
ctx['customer'] = self.cart_customer
@@ -162,6 +162,26 @@
{% endfor %}
</div>
</div>
{% if checkout_session.answers.all %}
<div class="panel panel-default panel-contact">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Additional order information" %}
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}" aria-label="{% trans "Modify contact information" %}" class="h6">
<span class="fa fa-edit" aria-hidden="true"></span>{% trans "Modify" %}
</a>
</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
{% for ans in checkout_session.answers.all %}
<dt>{{ ans.question.question }}</dt>
<dd>{% include "pretixpresale/event/fragment_question_answer.html" with request=request question=ans.question answer=ans %}</dd>
{% endfor %}
</dl>
</div>
</div>
{% endif %}
</div>
</div>
{% if confirm_messages %}
@@ -206,27 +206,7 @@
{% for q in line.questions %}
<dt>{{ q.question }}</dt>
<dd>
{% if q.answer %}
{% if q.answer.file %}
<span class="fa fa-file" aria-hidden="true"></span>
<a href="{{ q.answer.frontend_file_url }}?token={% answer_token request q.answer %}">
{{ q.answer.file_name }}
</a>
{% if q.answer.is_image %}
<br>
<a href="{{ q.answer.frontend_file_url }}?token={% answer_token request q.answer %}" data-lightbox="order"
class="answer-thumb">
<img src="{{ q.answer.frontend_file_url }}?token={% answer_token request q.answer %}" alt="{% trans "The image you previously uploaded" %}">
</a>
{% endif %}
{% elif q.type == "M" %}
{{ q.answer|rich_text_snippet }}
{% else %}
{{ q.answer|linebreaksbr }}
{% endif %}
{% else %}
<em class="text-muted">{% trans "not answered" %}</em>
{% endif %}
{% include "pretixpresale/event/fragment_question_answer.html" with request=request question=q answer=q.answer %}
</dd>
{% endfor %}
{% for q in line.additional_answers %}
@@ -0,0 +1,24 @@
{% load safelink %}
{% load i18n %}
{% load rich_text %}
{% if answer %}
{% if answer.file %}
<span class="fa fa-file" aria-hidden="true"></span>
<a href="{{ answer.frontend_file_url }}?token={% answer_token request answer %}">
{{ answer.file_name }}
</a>
{% if answer.is_image %}
<br>
<a href="{{ answer.frontend_file_url }}?token={% answer_token request answer %}" data-lightbox="order"
class="answer-thumb">
<img src="{{ answer.frontend_file_url }}?token={% answer_token request answer %}" alt="{% trans "The image you previously uploaded" %}">
</a>
{% endif %}
{% elif question.type == "M" %}
{{ answer|rich_text_snippet }}
{% else %}
{{ answer|linebreaksbr }}
{% endif %}
{% else %}
<em class="text-muted">{% trans "not answered" %}</em>
{% endif %}
+4 -1
View File
@@ -388,6 +388,7 @@ def get_or_create_cart_id(request, create=True):
if 'carts' in request.session:
request.session['carts'][current_id] = {}
else:
# We found a valid, existing cart.
return current_id
cart_data = {}
@@ -398,6 +399,7 @@ def get_or_create_cart_id(request, create=True):
cart_data['widget_data'] = cached_widget_data
else:
if not create:
# There is no existing cart for this request and we're not supposed to create a new one.
return None
new_id = generate_cart_id(request, prefix=prefix)
@@ -846,9 +848,10 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
class AnswerDownload(EventViewMixin, View):
def get(self, request, *args, **kwargs):
answid = kwargs.get('answer')
cart_id = get_or_create_cart_id(self.request)
answer = get_object_or_404(
QuestionAnswer,
cartposition__cart_id=get_or_create_cart_id(self.request),
Q(cartposition__cart_id=cart_id) | Q(checkoutsession__cart_id=cart_id),
id=answid
)
if not answer.file: