Compare commits

...

14 Commits

Author SHA1 Message Date
Richard Schreiber
d28fcf2319 fix missing int str conversion 2026-03-16 15:29:46 +01:00
Richard Schreiber
d1df2e5213 improve code style 2026-03-16 15:16:02 +01:00
Richard Schreiber
5b93687611 imrpove code style 2026-03-16 15:15:29 +01:00
Richard Schreiber
26ccfee67b do not force download on AnswerDownload 2026-03-16 15:14:03 +01:00
Richard Schreiber
bc1eff4e56 do not force download on tickets in backend 2026-03-16 15:13:42 +01:00
Richard Schreiber
c53b10e04b Invoice preview inline 2026-03-16 15:10:55 +01:00
Richard Schreiber
12295fde84 improve code style 2026-03-16 15:08:33 +01:00
Richard Schreiber
094193efc8 remove unused code 2026-03-16 15:04:12 +01:00
Richard Schreiber
3b2b00bf67 use as_attachment for file responses in control 2026-03-16 15:03:35 +01:00
Richard Schreiber
35822e7f25 do not ignore csp even for disposition=inline 2026-03-16 15:03:06 +01:00
Richard Schreiber
e68189a2ea use as_attachment for API FileResponses 2026-03-16 15:02:29 +01:00
Richard Schreiber
e4dae627d6 Use inline for PDF-view in pretix-control editor 2026-03-16 14:48:12 +01:00
Richard Schreiber
3550a23e33 Use FileResponse filename for AnswerDownload 2026-03-16 14:44:33 +01:00
Richard Schreiber
2d988406e3 Display invoice and tickets inline in browser (Z#23225892) 2026-03-16 14:38:36 +01:00
7 changed files with 88 additions and 81 deletions

View File

@@ -381,12 +381,15 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
return resp
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
self.request.event.slug.upper(), order.code,
provider.identifier, ct.extension
return FileResponse(
ct.file.file,
filename='{}-{}-{}{}'.format(
self.request.event.slug.upper(), order.code,
provider.identifier, ct.extension
),
as_attachment=True,
content_type=ct.type
)
return resp
@action(detail=True, methods=['POST'])
def mark_paid(self, request, **kwargs):
@@ -1301,14 +1304,17 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
raise NotFound()
ftype, ignored = mimetypes.guess_type(answer.file.name)
resp = FileResponse(answer.file, content_type=ftype or 'application/binary')
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}"'.format(
self.request.event.slug.upper(),
pos.order.code,
pos.positionid,
os.path.basename(answer.file.name).split('.', 1)[1]
return FileResponse(
answer.file,
filename='{}-{}-{}-{}"'.format(
self.request.event.slug.upper(),
pos.order.code,
pos.positionid,
os.path.basename(answer.file.name).split('.', 1)[1]
),
as_attachment=True,
content_type=ftype or 'application/binary'
)
return resp
@action(detail=True, url_name="printlog", url_path="printlog", methods=["POST"])
def printlog(self, request, **kwargs):
@@ -1363,15 +1369,18 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
if hasattr(image_file, 'seek'):
image_file.seek(0)
resp = FileResponse(image_file, content_type=ftype or 'application/binary')
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}.{}"'.format(
self.request.event.slug.upper(),
pos.order.code,
pos.positionid,
key,
extension,
return FileResponse(
image_file,
filename='{}-{}-{}-{}.{}'.format(
self.request.event.slug.upper(),
pos.order.code,
pos.positionid,
key,
extension,
),
as_attachment=True,
content_type=ftype or 'application/binary'
)
return resp
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
def download(self, request, output, **kwargs):
@@ -1397,12 +1406,15 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
return resp
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
self.request.event.slug.upper(), pos.order.code, pos.positionid,
provider.identifier, ct.extension
return FileResponse(
ct.file.file,
filename='{}-{}-{}-{}{}'.format(
self.request.event.slug.upper(), pos.order.code, pos.positionid,
provider.identifier, ct.extension
),
as_attachment=True,
content_type=ct.type
)
return resp
@action(detail=True, methods=['POST'])
def regenerate_secrets(self, request, **kwargs):
@@ -1979,9 +1991,12 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
if not invoice.file:
raise RetryException()
resp = FileResponse(invoice.file.file, content_type='application/pdf')
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
return resp
return FileResponse(
invoice.file.file,
filename='{}.pdf"'.format(invoice.number),
as_attachment=True,
content_type='application/pdf'
)
@action(detail=True, methods=['POST'])
def transmit(self, request, **kwargs):

View File

@@ -743,12 +743,7 @@ class InvoicePreview(EventPermissionRequiredMixin, View):
def get(self, request, *args, **kwargs):
fname, ftype, fcontent = build_preview_invoice_pdf(request.event)
resp = HttpResponse(fcontent, content_type=ftype)
if settings.DEBUG:
# attachment is more secure as we're dealing with user-generated stuff here, but inline is much more convenient during debugging
resp['Content-Disposition'] = 'inline; filename="{}"'.format(fname)
resp._csp_ignore = True
else:
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(fname)
resp['Content-Disposition'] = 'inline; filename="{}"'.format(fname)
return resp

View File

@@ -300,5 +300,4 @@ class SysReportView(AdministratorPermissionRequiredMixin, TemplateView):
resp = HttpResponse(data)
resp['Content-Type'] = mime
resp['Content-Disposition'] = 'inline; filename="{}"'.format(name)
resp._csp_ignore = True
return resp

View File

@@ -710,22 +710,26 @@ class OrderDownload(AsyncAction, OrderView):
resp = HttpResponseRedirect(value.file.file.read())
return resp
else:
resp = FileResponse(value.file.file, content_type=value.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
self.output.identifier, value.extension
return FileResponse(
value.file.file,
filename='{}-{}-{}-{}{}'.format(
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
self.output.identifier, value.extension
),
content_type=value.type
)
return resp
elif isinstance(value, CachedCombinedTicket):
if value.type == 'text/uri-list':
resp = HttpResponseRedirect(value.file.file.read())
return resp
else:
resp = FileResponse(value.file.file, content_type=value.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
return FileResponse(
value.file.file,
filename='{}-{}-{}{}'.format(
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
),
content_type=value.type
)
return resp
else:
return redirect(self.get_self_url())
@@ -1831,15 +1835,15 @@ class InvoiceDownload(EventPermissionRequiredMixin, View):
return redirect(self.get_order_url())
try:
resp = FileResponse(self.invoice.file.file, content_type='application/pdf')
return FileResponse(
self.invoice.file.file,
filename='{}.pdf'.format(re.sub("[^a-zA-Z0-9-_.]+", "_", self.invoice.number)),
content_type='application/pdf'
)
except FileNotFoundError:
invoice_pdf_task.apply(args=(self.invoice.pk,))
return self.get(request, *args, **kwargs)
resp['Content-Disposition'] = 'inline; filename="{}.pdf"'.format(re.sub("[^a-zA-Z0-9-_.]+", "_", self.invoice.number))
resp._csp_ignore = True # Some browser's PDF readers do not work with CSP
return resp
class OrderExtend(OrderView):
permission = 'can_change_orders'

View File

@@ -263,12 +263,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
resp = HttpResponse(data, content_type=mimet)
ftype = fname.split(".")[-1]
if settings.DEBUG:
# attachment is more secure as we're dealing with user-generated stuff here, but inline is much more convenient during debugging
resp['Content-Disposition'] = 'inline; filename="ticket-preview.{}"'.format(ftype)
resp._csp_ignore = True
else:
resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
resp['Content-Disposition'] = 'inline; filename="ticket-preview.{}"'.format(ftype)
return resp
elif "data" in request.POST:
if cf:
@@ -309,6 +304,5 @@ class FontsCSSView(TemplateView):
class PdfView(TemplateView):
def get(self, request, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
resp = FileResponse(cf.file, content_type='application/pdf')
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename)
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
return resp

View File

@@ -841,9 +841,13 @@ class AnswerDownload(EventViewMixin, View):
return Http404()
ftype, _ = mimetypes.guess_type(answer.file.name)
resp = FileResponse(answer.file, content_type=ftype or 'application/binary')
resp['Content-Disposition'] = 'attachment; filename="{}-cart-{}"'.format(
filename = '{}-cart-{}'.format(
self.request.event.slug.upper(),
os.path.basename(answer.file.name).split('.', 1)[1]
).encode("ascii", "ignore")
resp = FileResponse(
answer.file,
filename=filename,
content_type=ftype or 'application/binary'
)
return resp

View File

@@ -1220,30 +1220,25 @@ class OrderDownloadMixin:
resp = HttpResponseRedirect(value.file.file.read())
return resp
else:
resp = FileResponse(value.file.file, content_type=value.type)
if self.order_position.subevent:
# Subevent date in filename improves accessibility e.g. for screen reader users
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}-{}{}"'.format(
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
self.order_position.subevent.date_from.strftime('%Y_%m_%d'),
self.output.identifier, value.extension
)
else:
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
self.output.identifier, value.extension
)
return resp
name_parts = (
self.request.event.slug.upper(),
self.order.code,
str(self.order_position.positionid),
self.order_position.subevent.date_from.strftime('%Y_%m_%d') if self.order_position.subevent else None,
self.output.identifier
)
filename = "-".join(filter(None, name_parts)) + value.extension
return FileResponse(value.file.file, filename=filename, content_type=value.type)
elif isinstance(value, CachedCombinedTicket):
if value.type == 'text/uri-list':
resp = HttpResponseRedirect(value.file.file.read())
return resp
else:
resp = FileResponse(value.file.file, content_type=value.type)
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
return FileResponse(
value.file.file,
filename="-".join(self.request.event.slug.upper(), self.order.code, self.output.identifier) + value.extension,
content_type=value.type
)
return resp
else:
return redirect(self.get_self_url())
@@ -1383,13 +1378,14 @@ class InvoiceDownload(EventViewMixin, OrderDetailMixin, View):
return redirect(self.get_order_url())
try:
resp = FileResponse(invoice.file.file, content_type='application/pdf')
return FileResponse(
invoice.file.file,
filename='{}.pdf'.format(re.sub("[^a-zA-Z0-9-_.]+", "_", invoice.number)),
content_type='application/pdf'
)
except FileNotFoundError:
invoice_pdf_task.apply(args=(invoice.pk,))
return self.get(request, *args, **kwargs)
resp['Content-Disposition'] = 'inline; filename="{}.pdf"'.format(re.sub("[^a-zA-Z0-9-_.]+", "_", invoice.number))
resp._csp_ignore = True # Some browser's PDF readers do not work with CSP
return resp
class OrderChangeMixin: