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}"'
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: