Загрузка файла после сохранения модели в Django

Я пытаюсь сгенерировать PDF на основе ModelForm с помощью Weasyprint. Идея заключается в том, что когда пользователь нажимает кнопку сохранения, PDF генерируется после сохранения модели (для этого я использую сигнал post_save). Но когда я нажимаю кнопку сохранения, модель сохраняется, но запрос на загрузку не показывается пользователю, хотя у меня правильная конфигурация Content-Disposition в моем HttpResponse.

Мне также нужно передать некоторые параметры сигналу post_save, потому что некоторые поля, которые нужно добавить в PDF, находятся не в Model, а в ModelForm, и я не смог найти, как это сделать.

admin.py

def save_model(self, request: HttpRequest, obj: Contract, form: ContractForm, change: Any) -> None:
        if not change and not request.user.is_superuser:
            obj = MythLabsMultiTenancy.assign_organization_to_obj(
                obj, request.user
            )
        return super().save_model(request, obj, form, change)

forms.py

from dal.autocomplete import ModelSelect2
from django import forms
from django.core.validators import RegexValidator


class ContractForm(forms.ModelForm):
    class Meta:
        widgets = {
            'handover_vehicle': ModelSelect2(
                url='vehicle_autocomplete', forward=('organization',),
            ),
            'lease_holder': ModelSelect2(
                url='client_autocomplete', forward=('organization',),
            ),
            'replacement_vehicle': ModelSelect2(
                url='replacement_vehicle_autocomplete', forward=('organization',),
            ),
        }

    warranty_card_company = forms.CharField(required=False, label='Tarjeta')
    warranty_card_number = forms.CharField(
        max_length=19, required=False, label='N° de tarjeta'
    )
    warranty_card_expiration_date = forms.CharField(
        max_length=5, required=False, label='Fecha de expiración',
        help_text='Formato: MM/YY',
        validators=[RegexValidator("(0[1-9]|1[0-2])\/[0-9]{2}")]
    )
    warranty_card_auth_code = forms.IntegerField(
        max_value=9999, min_value=0, required=False,
        label='Código de autorización'
    )

signals.py

from datetime import datetime

from django.db.models import QuerySet
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.http import HttpResponse
from django.template.loader import render_to_string
from weasyprint import HTML

from .models import Charge, Contract, Control, Extension


@receiver(post_save, sender=Contract, dispatch_uid='download_contract_pdf')
def download_contract_pdf(sender, instance, **kwargs) -> HttpResponse:
    file_name = f"contrato-{datetime.now()}.pdf"
    key_list = [
        'warranty_card_company', 'warranty_card_number',
        'warranty_card_expiration_date', 'warranty_card_auth_code'
    ]
    credit_card_data = []
    for key, value in instance.items:
        if key in key_list:
            credit_card_data.append(value)
    charge_qs: QuerySet = Charge.objects.filter(contract=instance.pk)
    control_qs: QuerySet = Control.objects.filter(contract=instance.pk)
    extension_qs: QuerySet = Extension.objects.filter(contract=instance.pk)
    html_string = render_to_string(
        'pdf_template.html', {
            'charge_obj': charge_qs,
            'contract_obj': instance,
            'control_obj': control_qs,
            'extension_obj': extension_qs,
            'credit_card_data': credit_card_data
        }
    )
    html = HTML(string=html_string)
    pdf = html.write_pdf(
        stylesheets=[
            "https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
        ]
    )
    response = HttpResponse(pdf, content_type="application/pdf",)
    response['Content-Disposition'] = f'attachment; filename={file_name}'
    return response

Если вам нужна дополнительная информация, не стесняйтесь спрашивать! Спасибо заранее!

Вам необходимо заключить имя файла в двойные кавычки:

response['Content-Disposition'] = f'attachment; filename="{file_name}"'

https://docs.djangoproject.com/en/4.0/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment

Я решил эту проблему! Все, что мне нужно было сделать, это использовать response_add и response_change в моей ModelAdmin.

def response_add(self, request: HttpRequest, obj: Contract, post_url_continue: None) -> HttpResponse:
    super().response_add(request, obj, post_url_continue)
    return generate_contract_pdf(request, obj, None)

def response_change(self, request: HttpRequest, obj: Contract) -> HttpResponse:
    super().response_change(request, obj)
    return generate_contract_pdf(request, obj, None)

Документация:

Вернуться на верх