Skip to content

Conversation

@ArnavBallinCode
Copy link
Contributor

@ArnavBallinCode ArnavBallinCode commented Nov 13, 2025

Fix PDF Ticket Download Issues

Summary

This PR fixes two related bugs in the PDF ticket download functionality:

  1. Issue Ticket PDF Filename Contains Duplicate “.pdf” Extension #1247
  2. Issue Ticket Component: Ticket PDF Download on Confirmation Page Returns HTTP 500 Error #1245

Fix for #1247

Removed the provider identifier from the filename format string across all download endpoints:

# New code (4 format placeholders instead of 5)
filename="{}-{}-{}{}".format(
    event_slug, order_code, position_id,
    cachedfile.extension  # Only '.pdf', no 'pdf' prefix
)
# Result: "EVENTSLUG-CODE-1.pdf"

Fix for #1245

Added defensive null checks before accessing order_position attributes:

# New code with null safety
if self.order_position and self.order_position.subevent:  # Safe
    filename = "..."
elif self.order_position:  # Handles position without subevent
    filename = "..."
else:  # Fallback for order-level downloads
    filename = "..."

Summary by Sourcery

Fix PDF ticket download issues by simplifying filename format and adding null checks

Bug Fixes:

  • Remove provider identifier from PDF filenames across all ticket download endpoints
  • Add defensive null checks for order_position and its subevent to prevent attribute errors when generating filenames

Summary by Sourcery

Fix PDF ticket download issues by removing the provider identifier from filenames and adding null checks to prevent attribute errors

Bug Fixes:

  • Remove provider identifier from PDF filenames across order, API, and control endpoints
  • Add defensive null checks for order_position and its subevent when generating PDF filenames to prevent errors

fixes #1247
fixes #1245

- Fix duplicate .pdf extension in ticket filenames by removing redundant provider identifier
- Add defensive null checks for order_position to prevent HTTP 500 errors
- Apply consistent filename format across presale, control, and API views
- Filenames now follow clean pattern: EVENT-CODE-POSITION.pdf

Fixes fossasia#1245: HTTP 500 error on confirmation page PDF download
Fixes fossasia#1247: Duplicate .pdf extension in ticket filename
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 13, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Simplify PDF filename generation by removing provider identifiers and strengthening null-safe logic around order positions and subevents across presale, API, and control ticket download endpoints.

Sequence diagram for PDF ticket download filename generation (with null-safe logic)

sequenceDiagram
participant User
participant "OrderView"
participant "FileResponse"
User->>OrderView: Request PDF ticket download
OrderView->>OrderView: Check if order_position and subevent exist
alt order_position and subevent
    OrderView->>FileResponse: Generate filename with event, code, position, subevent date, extension
else order_position only
    OrderView->>FileResponse: Generate filename with event, code, position, extension
else no order_position
    OrderView->>FileResponse: Generate filename with event, code, extension
end
OrderView->>User: Return PDF file with generated filename
Loading

Class diagram for FileResponse and filename generation logic

classDiagram
class OrderView {
    +order_position
    +order
    +request
    +success(value)
}
class FileResponse {
    +file
    +type
}
OrderView --> FileResponse : generates
OrderView : +success(value)
OrderView : filename generation logic
OrderView : null checks for order_position and subevent
Loading

File-Level Changes

Change Details Files
Removed provider identifier and simplified filename format strings
  • Removed provider.identifier from all Content-Disposition format strings
  • Reduced format placeholders from five to four or three in attachment filenames
  • Updated CombinedTicket and single-ticket endpoints to use only event slug, order code, position ID (and date where applicable), plus extension
app/eventyay/presale/views/order.py
app/eventyay/api/views/order.py
app/eventyay/control/views/orders.py
Added defensive null checks for order_position and its subevent
  • Guarded access to self.order_position before checking subevent attribute
  • Introduced an elif branch for tickets with a position but no subevent
  • Added an else fallback branch for order-level downloads when order_position is missing
app/eventyay/presale/views/order.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ArnavBallinCode ArnavBallinCode changed the title fix: resolve PDF download issues #1245 #1247 (fix) resolve PDF download issues Nov 13, 2025
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `app/eventyay/presale/views/order.py:1105` </location>
<code_context>
+                        value.extension,
+                    )
+                else:
+                    # Fallback if order_position is not available
+                    resp['Content-Disposition'] = 'attachment; filename="{}-{}{}}"'.format(
+                        self.request.event.slug.upper(),
</code_context>

<issue_to_address>
**issue (bug_risk):** Extra closing brace in fallback filename format string.

The format string should be corrected to '"{}-{}{}"' to avoid malformed filenames.
</issue_to_address>

### Comment 2
<location> `app/eventyay/api/views/order.py:296-304` </location>
<code_context>
    @action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
    def download(self, request, output, **kwargs):
        provider = self._get_output_provider(output)
        order = self.get_object()

        if order.status != Order.STATUS_PAID:
            raise PermissionDenied('Downloads are not available for unpaid orders.')

        ct = CachedCombinedTicket.objects.filter(order=order, provider=provider.identifier, file__isnull=False).last()
        if not ct or not ct.file:
            generate.apply_async(args=('order', order.pk, provider.identifier))
            raise RetryException()
        else:
            if ct.type == 'text/uri-list':
                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,
                    ct.extension,
                )
                return resp

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

### Comment 3
<location> `app/eventyay/api/views/order.py:1093-1102` </location>
<code_context>
    @action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
    def download(self, request, output, **kwargs):
        provider = self._get_output_provider(output)
        pos = self.get_object()

        if pos.order.status != Order.STATUS_PAID:
            raise PermissionDenied('Downloads are not available for unpaid orders.')
        if not pos.generate_ticket:
            raise PermissionDenied('Downloads are not enabled for this product.')

        ct = CachedTicket.objects.filter(order_position=pos, provider=provider.identifier, file__isnull=False).last()
        if not ct or not ct.file:
            generate.apply_async(args=('orderposition', pos.pk, provider.identifier))
            raise RetryException()
        else:
            if ct.type == 'text/uri-list':
                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,
                    ct.extension,
                )
                return resp

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

### Comment 4
<location> `app/eventyay/control/views/orders.py:510` </location>
<code_context>
    def success(self, value):
        if 'ajax' in self.request.POST or 'ajax' in self.request.GET:
            return JsonResponse(
                {
                    'ready': True,
                    'success': True,
                    'redirect': self.get_success_url(value),
                    'message': str(self.get_success_message(value)),
                }
            )
        if isinstance(value, CachedTicket):
            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.order_position.positionid,
                    value.extension,
                )
                return resp
        elif isinstance(value, CachedCombinedTicket):
            resp = FileResponse(value.file.file, content_type=value.type)
            resp['Content-Disposition'] = 'attachment; filename="{}-{}{}"'.format(
                self.request.event.slug.upper(),
                self.order.code,
                value.extension,
            )
            return resp
        else:
            return redirect(self.get_self_url())

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string [×2] ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Fixes syntax error where fallback filename
had an extra '}' character causing malformed filenames.
@ArnavBallinCode
Copy link
Contributor Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `app/eventyay/api/views/order.py:296-304` </location>
<code_context>
    @action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
    def download(self, request, output, **kwargs):
        provider = self._get_output_provider(output)
        order = self.get_object()

        if order.status != Order.STATUS_PAID:
            raise PermissionDenied('Downloads are not available for unpaid orders.')

        ct = CachedCombinedTicket.objects.filter(order=order, provider=provider.identifier, file__isnull=False).last()
        if not ct or not ct.file:
            generate.apply_async(args=('order', order.pk, provider.identifier))
            raise RetryException()
        else:
            if ct.type == 'text/uri-list':
                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,
                    ct.extension,
                )
                return resp

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

### Comment 2
<location> `app/eventyay/api/views/order.py:1093-1102` </location>
<code_context>
    @action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
    def download(self, request, output, **kwargs):
        provider = self._get_output_provider(output)
        pos = self.get_object()

        if pos.order.status != Order.STATUS_PAID:
            raise PermissionDenied('Downloads are not available for unpaid orders.')
        if not pos.generate_ticket:
            raise PermissionDenied('Downloads are not enabled for this product.')

        ct = CachedTicket.objects.filter(order_position=pos, provider=provider.identifier, file__isnull=False).last()
        if not ct or not ct.file:
            generate.apply_async(args=('orderposition', pos.pk, provider.identifier))
            raise RetryException()
        else:
            if ct.type == 'text/uri-list':
                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,
                    ct.extension,
                )
                return resp

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

### Comment 3
<location> `app/eventyay/control/views/orders.py:510` </location>
<code_context>
    def success(self, value):
        if 'ajax' in self.request.POST or 'ajax' in self.request.GET:
            return JsonResponse(
                {
                    'ready': True,
                    'success': True,
                    'redirect': self.get_success_url(value),
                    'message': str(self.get_success_message(value)),
                }
            )
        if isinstance(value, CachedTicket):
            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.order_position.positionid,
                    value.extension,
                )
                return resp
        elif isinstance(value, CachedCombinedTicket):
            resp = FileResponse(value.file.file, content_type=value.type)
            resp['Content-Disposition'] = 'attachment; filename="{}-{}{}"'.format(
                self.request.event.slug.upper(),
                self.order.code,
                value.extension,
            )
            return resp
        else:
            return redirect(self.get_self_url())

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string [×2] ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

### Comment 4
<location> `app/eventyay/presale/views/order.py:1085` </location>
<code_context>
    def success(self, value):
        if 'ajax' in self.request.POST or 'ajax' in self.request.GET:
            return JsonResponse(
                {
                    'ready': True,
                    'success': True,
                    'redirect': self.get_success_url(value),
                    'message': str(self.get_success_message(value)),
                }
            )
        if isinstance(value, CachedTicket):
            if value.type == 'text/uri-list':
                resp = HttpResponseRedirect(value.file.file.read())
                return resp
            else:
                resp = FileResponse(value.file.file, content_type=value.type)
                if self.order_position and 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'),
                        value.extension,
                    )
                elif self.order_position:
                    resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
                        self.request.event.slug.upper(),
                        self.order.code,
                        self.order_position.positionid,
                        value.extension,
                    )
                else:
                    # Fallback if order_position is not available
                    resp['Content-Disposition'] = (
                        'attachment; filename="{}-{}{}"'.format(
                            self.request.event.slug.upper(),
                            self.order.code,
                            value.extension,
                        )
                    )
                    return resp
                return resp
        elif isinstance(value, CachedCombinedTicket):
            resp = FileResponse(value.file.file, content_type=value.type)
            resp['Content-Disposition'] = 'attachment; filename="{}-{}{}"'.format(
                self.request.event.slug.upper(),
                self.order.code,
                value.extension,
            )
            return resp
        else:
            return redirect(self.get_self_url())

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string [×4] ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@ArnavBallinCode
Copy link
Contributor Author

The entire codebase uses .format() - changing just these 3 files would be inconsistent

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `app/eventyay/api/views/order.py:296-304` </location>
<code_context>
    @action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
    def download(self, request, output, **kwargs):
        provider = self._get_output_provider(output)
        order = self.get_object()

        if order.status != Order.STATUS_PAID:
            raise PermissionDenied('Downloads are not available for unpaid orders.')

        ct = CachedCombinedTicket.objects.filter(order=order, provider=provider.identifier, file__isnull=False).last()
        if not ct or not ct.file:
            generate.apply_async(args=('order', order.pk, provider.identifier))
            raise RetryException()
        else:
            if ct.type == 'text/uri-list':
                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,
                    ct.extension,
                )
                return resp

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

### Comment 2
<location> `app/eventyay/api/views/order.py:1093-1102` </location>
<code_context>
    @action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
    def download(self, request, output, **kwargs):
        provider = self._get_output_provider(output)
        pos = self.get_object()

        if pos.order.status != Order.STATUS_PAID:
            raise PermissionDenied('Downloads are not available for unpaid orders.')
        if not pos.generate_ticket:
            raise PermissionDenied('Downloads are not enabled for this product.')

        ct = CachedTicket.objects.filter(order_position=pos, provider=provider.identifier, file__isnull=False).last()
        if not ct or not ct.file:
            generate.apply_async(args=('orderposition', pos.pk, provider.identifier))
            raise RetryException()
        else:
            if ct.type == 'text/uri-list':
                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,
                    ct.extension,
                )
                return resp

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

### Comment 3
<location> `app/eventyay/control/views/orders.py:510` </location>
<code_context>
    def success(self, value):
        if 'ajax' in self.request.POST or 'ajax' in self.request.GET:
            return JsonResponse(
                {
                    'ready': True,
                    'success': True,
                    'redirect': self.get_success_url(value),
                    'message': str(self.get_success_message(value)),
                }
            )
        if isinstance(value, CachedTicket):
            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.order_position.positionid,
                    value.extension,
                )
                return resp
        elif isinstance(value, CachedCombinedTicket):
            resp = FileResponse(value.file.file, content_type=value.type)
            resp['Content-Disposition'] = 'attachment; filename="{}-{}{}"'.format(
                self.request.event.slug.upper(),
                self.order.code,
                value.extension,
            )
            return resp
        else:
            return redirect(self.get_self_url())

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string [×2] ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

### Comment 4
<location> `app/eventyay/presale/views/order.py:1085` </location>
<code_context>
    def success(self, value):
        if 'ajax' in self.request.POST or 'ajax' in self.request.GET:
            return JsonResponse(
                {
                    'ready': True,
                    'success': True,
                    'redirect': self.get_success_url(value),
                    'message': str(self.get_success_message(value)),
                }
            )
        if isinstance(value, CachedTicket):
            if value.type == 'text/uri-list':
                resp = HttpResponseRedirect(value.file.file.read())
                return resp
            else:
                resp = FileResponse(value.file.file, content_type=value.type)
                if self.order_position and 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'),
                        value.extension,
                    )
                elif self.order_position:
                    resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
                        self.request.event.slug.upper(),
                        self.order.code,
                        self.order_position.positionid,
                        value.extension,
                    )
                else:
                    # Fallback if order_position is not available
                    resp['Content-Disposition'] = (
                        'attachment; filename="{}-{}{}"'.format(
                            self.request.event.slug.upper(),
                            self.order.code,
                            value.extension,
                        )
                    )
                    return resp
                return resp
        elif isinstance(value, CachedCombinedTicket):
            resp = FileResponse(value.file.file, content_type=value.type)
            resp['Content-Disposition'] = 'attachment; filename="{}-{}{}"'.format(
                self.request.event.slug.upper(),
                self.order.code,
                value.extension,
            )
            return resp
        else:
            return redirect(self.get_self_url())

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Hoist repeated code outside conditional statement ([`hoist-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-statement-from-if/))
- Replace call to format with f-string [×4] ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@mariobehling mariobehling requested a review from Copilot November 13, 2025 15:14
Copy link
Member

@mariobehling mariobehling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Please resolve AI comments.

Copilot finished reviewing on behalf of mariobehling November 13, 2025 15:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR addresses two bugs in the PDF ticket download functionality by removing the provider identifier from filenames and adding defensive null checks for order_position attributes.

  • Simplified filename format by removing provider identifier parameter across all download endpoints
  • Added null safety checks in presale view to handle cases where order_position might not be available
  • Maintained consistent filename patterns across presale, control, and API modules

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
app/eventyay/presale/views/order.py Updated filename format strings to remove provider identifier; added defensive null checks for order_position and order_position.subevent with fallback handling for order-level downloads
app/eventyay/control/views/orders.py Simplified filename format strings by removing provider identifier from both position-based and combined ticket downloads
app/eventyay/api/views/order.py Removed provider identifier from filename format strings in both order-level and position-level download endpoints

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ArnavBallinCode
Copy link
Contributor Author

Pull Request Overview

This PR addresses two bugs in the PDF ticket download functionality by removing the provider identifier from filenames and adding defensive null checks for order_position attributes.

  • Simplified filename format by removing provider identifier parameter across all download endpoints
  • Added null safety checks in presale view to handle cases where order_position might not be available
  • Maintained consistent filename patterns across presale, control, and API modules

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
app/eventyay/presale/views/order.py Updated filename format strings to remove provider identifier; added defensive null checks for order_position and order_position.subevent with fallback handling for order-level downloads
app/eventyay/control/views/orders.py Simplified filename format strings by removing provider identifier from both position-based and combined ticket downloads
app/eventyay/api/views/order.py Removed provider identifier from filename format strings in both order-level and position-level download endpoints
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

This return statement is necessary. The code has multiple conditions:

  1. if isinstance(value, CachedTicket) with nested type checking
  2. elif isinstance(value, CachedCombinedTicket) - handles COMBINED tickets
  3. else - fallback redirect

If we remove the return at line 1107, the elif at line 1108 would never execute,
breaking combined ticket downloads. The early return is intentional to separate
handling of individual tickets vs. combined tickets.

The code structure is:

  • CachedTicket (single position) → return at line 1107
  • CachedCombinedTicket (order-level) → return at line 1115
  • Neither → redirect at line 1117

Each branch needs its own return statement.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mariobehling mariobehling changed the title (fix) resolve PDF download issues fix: resolve PDF download issues Nov 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ticket PDF Filename Contains Duplicate “.pdf” Extension Ticket Component: Ticket PDF Download on Confirmation Page Returns HTTP 500 Error

2 participants