Home > Enterprise >  Download a file after saving a model in Django
Download a file after saving a model in Django

Time:07-16

I'm trying to generate a PDF based on a ModelForm using Weasyprint. The idea is when the user clicks save the PDF is generated after the model is saved (I'm using the signal post_save to accomplish this). But when I hit the save button, the model is saved but the download prompt is not shown to the user, even though I have the correct Content-Disposition configuration in my HttpResponse.

I also need to pass some parameters to the post_save signal because some of the fields that need to be added to the PDF are not in the Model, but are in the ModelForm, and I have not been able to find out how to do that.

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/[email protected]/dist/css/bootstrap.min.css"
        ]
    )
    response = HttpResponse(pdf, headers={
        'Content-Type': 'application/pdf',
        'Content-Disposition': f'attachment; filename="{file_name}"',
    })
    return response

If you need more information, do not hesitate to ask! Thank you in advance!

CodePudding user response:

You need to surround your file name in double quotes:

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

CodePudding user response:

I have solved the issue! All I had to do is use response_add and response_change in my 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)

Documentation:

  • Related