forked from CGM_Public/pretix_original
Do not force PDFs to be downloaded (Z#23225892) (#5994)
* Display invoice and tickets inline in browser (Z#23225892) * Use FileResponse filename for AnswerDownload * Use inline for PDF-view in pretix-control editor * use as_attachment for API FileResponses * do not ignore csp even for disposition=inline * use as_attachment for file responses in control * remove unused code * improve code style * Invoice preview inline * do not force download on tickets in backend * do not force download on AnswerDownload * imrpove code style * improve code style * fix missing int str conversion * Apply suggestions from code review Co-authored-by: luelista <mira@teamwiki.de> --------- Co-authored-by: luelista <mira@teamwiki.de>
This commit is contained in:
committed by
GitHub
parent
059ff6c99b
commit
5682d3ed56
@@ -381,12 +381,15 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
|||||||
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
|
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
resp = FileResponse(ct.file.file, content_type=ct.type)
|
return FileResponse(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
ct.file.file,
|
||||||
self.request.event.slug.upper(), order.code,
|
filename='{}-{}-{}{}'.format(
|
||||||
provider.identifier, ct.extension
|
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'])
|
@action(detail=True, methods=['POST'])
|
||||||
def mark_paid(self, request, **kwargs):
|
def mark_paid(self, request, **kwargs):
|
||||||
@@ -1303,14 +1306,17 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
|
|||||||
raise NotFound()
|
raise NotFound()
|
||||||
|
|
||||||
ftype, ignored = mimetypes.guess_type(answer.file.name)
|
ftype, ignored = mimetypes.guess_type(answer.file.name)
|
||||||
resp = FileResponse(answer.file, content_type=ftype or 'application/binary')
|
return FileResponse(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}"'.format(
|
answer.file,
|
||||||
self.request.event.slug.upper(),
|
filename='{}-{}-{}-{}"'.format(
|
||||||
pos.order.code,
|
self.request.event.slug.upper(),
|
||||||
pos.positionid,
|
pos.order.code,
|
||||||
os.path.basename(answer.file.name).split('.', 1)[1]
|
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"])
|
@action(detail=True, url_name="printlog", url_path="printlog", methods=["POST"])
|
||||||
def printlog(self, request, **kwargs):
|
def printlog(self, request, **kwargs):
|
||||||
@@ -1365,15 +1371,18 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
|
|||||||
if hasattr(image_file, 'seek'):
|
if hasattr(image_file, 'seek'):
|
||||||
image_file.seek(0)
|
image_file.seek(0)
|
||||||
|
|
||||||
resp = FileResponse(image_file, content_type=ftype or 'application/binary')
|
return FileResponse(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}.{}"'.format(
|
image_file,
|
||||||
self.request.event.slug.upper(),
|
filename='{}-{}-{}-{}.{}'.format(
|
||||||
pos.order.code,
|
self.request.event.slug.upper(),
|
||||||
pos.positionid,
|
pos.order.code,
|
||||||
key,
|
pos.positionid,
|
||||||
extension,
|
key,
|
||||||
|
extension,
|
||||||
|
),
|
||||||
|
as_attachment=True,
|
||||||
|
content_type=ftype or 'application/binary'
|
||||||
)
|
)
|
||||||
return resp
|
|
||||||
|
|
||||||
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
|
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||||
def download(self, request, output, **kwargs):
|
def download(self, request, output, **kwargs):
|
||||||
@@ -1399,12 +1408,15 @@ class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet
|
|||||||
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
|
resp = HttpResponse(ct.file.file.read(), content_type='text/uri-list')
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
resp = FileResponse(ct.file.file, content_type=ct.type)
|
return FileResponse(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
|
ct.file.file,
|
||||||
self.request.event.slug.upper(), pos.order.code, pos.positionid,
|
filename='{}-{}-{}-{}{}'.format(
|
||||||
provider.identifier, ct.extension
|
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'])
|
@action(detail=True, methods=['POST'])
|
||||||
def regenerate_secrets(self, request, **kwargs):
|
def regenerate_secrets(self, request, **kwargs):
|
||||||
@@ -1986,9 +1998,12 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
if not invoice.file:
|
if not invoice.file:
|
||||||
raise RetryException()
|
raise RetryException()
|
||||||
|
|
||||||
resp = FileResponse(invoice.file.file, content_type='application/pdf')
|
return FileResponse(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
|
invoice.file.file,
|
||||||
return resp
|
filename='{}.pdf"'.format(invoice.number),
|
||||||
|
as_attachment=True,
|
||||||
|
content_type='application/pdf'
|
||||||
|
)
|
||||||
|
|
||||||
@action(detail=True, methods=['POST'])
|
@action(detail=True, methods=['POST'])
|
||||||
def transmit(self, request, **kwargs):
|
def transmit(self, request, **kwargs):
|
||||||
|
|||||||
@@ -763,12 +763,7 @@ class InvoicePreview(EventPermissionRequiredMixin, View):
|
|||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
fname, ftype, fcontent = build_preview_invoice_pdf(request.event)
|
fname, ftype, fcontent = build_preview_invoice_pdf(request.event)
|
||||||
resp = HttpResponse(fcontent, content_type=ftype)
|
resp = HttpResponse(fcontent, content_type=ftype)
|
||||||
if settings.DEBUG:
|
resp['Content-Disposition'] = 'inline; filename="{}"'.format(fname)
|
||||||
# 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)
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -300,5 +300,4 @@ class SysReportView(AdministratorPermissionRequiredMixin, TemplateView):
|
|||||||
resp = HttpResponse(data)
|
resp = HttpResponse(data)
|
||||||
resp['Content-Type'] = mime
|
resp['Content-Type'] = mime
|
||||||
resp['Content-Disposition'] = 'inline; filename="{}"'.format(name)
|
resp['Content-Disposition'] = 'inline; filename="{}"'.format(name)
|
||||||
resp._csp_ignore = True
|
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -710,22 +710,26 @@ class OrderDownload(AsyncAction, OrderView):
|
|||||||
resp = HttpResponseRedirect(value.file.file.read())
|
resp = HttpResponseRedirect(value.file.file.read())
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
resp = FileResponse(value.file.file, content_type=value.type)
|
return FileResponse(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
|
value.file.file,
|
||||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
filename='{}-{}-{}-{}{}'.format(
|
||||||
self.output.identifier, value.extension
|
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):
|
elif isinstance(value, CachedCombinedTicket):
|
||||||
if value.type == 'text/uri-list':
|
if value.type == 'text/uri-list':
|
||||||
resp = HttpResponseRedirect(value.file.file.read())
|
resp = HttpResponseRedirect(value.file.file.read())
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
resp = FileResponse(value.file.file, content_type=value.type)
|
return FileResponse(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
value.file.file,
|
||||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
|
filename='{}-{}-{}{}'.format(
|
||||||
|
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
|
||||||
|
),
|
||||||
|
content_type=value.type
|
||||||
)
|
)
|
||||||
return resp
|
|
||||||
else:
|
else:
|
||||||
return redirect(self.get_self_url())
|
return redirect(self.get_self_url())
|
||||||
|
|
||||||
@@ -1831,15 +1835,15 @@ class InvoiceDownload(EventPermissionRequiredMixin, View):
|
|||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
try:
|
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:
|
except FileNotFoundError:
|
||||||
invoice_pdf_task.apply(args=(self.invoice.pk,))
|
invoice_pdf_task.apply(args=(self.invoice.pk,))
|
||||||
return self.get(request, *args, **kwargs)
|
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):
|
class OrderExtend(OrderView):
|
||||||
permission = 'event.orders:write'
|
permission = 'event.orders:write'
|
||||||
|
|||||||
@@ -263,12 +263,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
resp = HttpResponse(data, content_type=mimet)
|
resp = HttpResponse(data, content_type=mimet)
|
||||||
ftype = fname.split(".")[-1]
|
ftype = fname.split(".")[-1]
|
||||||
if settings.DEBUG:
|
resp['Content-Disposition'] = 'inline; filename="ticket-preview.{}"'.format(ftype)
|
||||||
# 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)
|
|
||||||
return resp
|
return resp
|
||||||
elif "data" in request.POST:
|
elif "data" in request.POST:
|
||||||
if cf:
|
if cf:
|
||||||
@@ -309,6 +304,5 @@ class FontsCSSView(TemplateView):
|
|||||||
class PdfView(TemplateView):
|
class PdfView(TemplateView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
|
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
|
||||||
resp = FileResponse(cf.file, content_type='application/pdf')
|
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename)
|
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -855,9 +855,13 @@ class AnswerDownload(EventViewMixin, View):
|
|||||||
return Http404()
|
return Http404()
|
||||||
|
|
||||||
ftype, _ = mimetypes.guess_type(answer.file.name)
|
ftype, _ = mimetypes.guess_type(answer.file.name)
|
||||||
resp = FileResponse(answer.file, content_type=ftype or 'application/binary')
|
filename = '{}-cart-{}'.format(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-cart-{}"'.format(
|
|
||||||
self.request.event.slug.upper(),
|
self.request.event.slug.upper(),
|
||||||
os.path.basename(answer.file.name).split('.', 1)[1]
|
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
|
return resp
|
||||||
|
|||||||
@@ -1220,30 +1220,26 @@ class OrderDownloadMixin:
|
|||||||
resp = HttpResponseRedirect(value.file.file.read())
|
resp = HttpResponseRedirect(value.file.file.read())
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
resp = FileResponse(value.file.file, content_type=value.type)
|
name_parts = (
|
||||||
if self.order_position.subevent:
|
self.request.event.slug.upper(),
|
||||||
# Subevent date in filename improves accessibility e.g. for screen reader users
|
self.order.code,
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}-{}{}"'.format(
|
str(self.order_position.positionid),
|
||||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
self.order_position.subevent.date_from.strftime('%Y_%m_%d') if self.order_position.subevent else None,
|
||||||
self.order_position.subevent.date_from.strftime('%Y_%m_%d'),
|
self.output.identifier
|
||||||
self.output.identifier, value.extension
|
)
|
||||||
)
|
filename = "-".join(filter(None, name_parts)) + value.extension
|
||||||
else:
|
return FileResponse(value.file.file, filename=filename, 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 resp
|
|
||||||
elif isinstance(value, CachedCombinedTicket):
|
elif isinstance(value, CachedCombinedTicket):
|
||||||
if value.type == 'text/uri-list':
|
if value.type == 'text/uri-list':
|
||||||
resp = HttpResponseRedirect(value.file.file.read())
|
resp = HttpResponseRedirect(value.file.file.read())
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
resp = FileResponse(value.file.file, content_type=value.type)
|
return FileResponse(
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
value.file.file,
|
||||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension
|
filename="{}-{}-{}{}".format(
|
||||||
|
self.request.event.slug.upper(), self.order.code, self.output.identifier, value.extension),
|
||||||
|
content_type=value.type
|
||||||
)
|
)
|
||||||
return resp
|
|
||||||
else:
|
else:
|
||||||
return redirect(self.get_self_url())
|
return redirect(self.get_self_url())
|
||||||
|
|
||||||
@@ -1383,13 +1379,14 @@ class InvoiceDownload(EventViewMixin, OrderDetailMixin, View):
|
|||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
try:
|
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:
|
except FileNotFoundError:
|
||||||
invoice_pdf_task.apply(args=(invoice.pk,))
|
invoice_pdf_task.apply(args=(invoice.pk,))
|
||||||
return self.get(request, *args, **kwargs)
|
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:
|
class OrderChangeMixin:
|
||||||
|
|||||||
Reference in New Issue
Block a user