Search code examples
javascriptpythondjangohttpresponsepython-docx

How to download a document generated with python-docx in Django project?


I'm trying to figure how to download a word document generated with python-docx in my django app (I'm still learning and this is the first time I working with documents); with the help of ajax I send all the information needed to the view and call a function that uses that information and returns the document, then I'm trying to send this document as response in order to download it with the help of a "Download" button (or show web browser download dialog) in the same template from where I'm submitting the data, but here is where I'm stuck.

to send this document as response in order to download it with the help of a "Download" button (or show web browser download dialog) in the same template from where I'm submitting the data, but here is where I'm stuck.

What I have until now is:

1) In javascript I'm sending the information as follows:

data = {
        categoria: cat,
        familia: fam,
        Gcas: gcas,
        FI: FI,
        FF: FF,
        Test: test,
        Grafica: grafica
    },
        $.ajax({
            type: 'post',
            headers: {
                "X-CSRFToken": csrftoken
            },
            url: url,
            data: { json_data: JSON.stringify(data) },

            success: function (response) {
                $('#instrucciones').hide(); //Hide a div with a message 
                $('#btndesc').show(); //Show the button to download the file generated                
            }
        });
    return false;
}

2) In my Django view:

def Documento(request):
    if request.method == "GET":
        context={}
        context['form'] = catForm
        return render(request, 'report/report_base.html', context)

    if request.method == 'POST':
    #Data from ajax
    datos = request.POST.get('json_data')
    jsondata = json.loads(datos)
    Gcas = jsondata['Gcas']
    FI = jsondata['FI']
    FF = jsondata['FF']
    grafica = jsondata['Grafica']
    #Using function to create the report
    Reporte = ReporteWord(Gcas, FI, FF, grafica)
    #Response
    response = HttpResponse(content_type='application/vnd.openxmlformats-
    officedocument.wordprocessingml.document')
    response['Content-Disposition'] = 'attachment; filename = "Reporte.docx"'
    response['Content-Encoding'] = 'UTF-8'
    Reporte.save(response)
    return response

3) My function to create the document looks like:

def ReporteWord( gcas, FI, FF, Chart):
    #Cargamos el template
    template = finders.find('otros/Template_reporte.docx')
    document = Document(template) 

    #Header
    logo = finders.find('otros/logo.png')
    header = document.sections[0].header
    paragraph = header.paragraphs[0]
    r = paragraph.add_run()
    r.add_picture(logo)

    #Adding title
    titulo = document.add_heading('', 0)
    titulo.add_run('Mi reporte').bold = True
    titulo.style.font.size=Pt(13)
    .
    Many other steps to add more content    
    .
    .
    #IF I SAVE THE FILE NORMALLY ALL WORKS FINE
    #document.save(r'C:\tests\new_demo.docx')

    return document

I'll be very grateful for any idea or suggestion, many thanks in advance.

NOTE: I've reviewed these answers (and others) without luck.

Q1, Q2, Q3, Q4

UPDATE: Thanks to the feedback received I finally found how to generate the document and show the download dialog:

As was suggested the best way to achieve its using the view and not ajax, so the final updates in the code are:

a) Update view to work as show in feedback

b) JavaScript - Ajax control for POST method was removed and now all is handled directly with python (no extra code needed)

1) View:

def Reporte(request):
    if request.method == "GET":
        context={}
        context['form'] = catForm
        return render(request, 'reportes/reporte_base.html', context)

    if request.method == 'POST':
        #Getting data needed after submit the form in the page
        GcasID = request.POST.get('GCASS')
        FI = request.POST.get('dp1')
        FF = request.POST.get('dp2')
        Grafica = request.POST.get('options')

        #Function to obtain complete code from GcasID 
        Gcas =  GcasNumber(GcasID)

        #Report creation
        Reporte = ReporteWord(Gcas, FI, FF, Grafica)

        #PART UPDATED TO SHOW DOWNLOAD REPORT DIALOG
        bio = io.BytesIO()
        Reporte.save(bio)  # save to memory stream
        bio.seek(0)  # rewind the stream
        response = HttpResponse(
        bio.getvalue(),  # use the stream's contents
        content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    )

        response["Content-Disposition"] = 'attachment; filename = "Reporte.docx"'
        response["Content-Encoding"] = "UTF-8"
        return response

With those changes now when I press "Create report" (submit button of form) all works as expected (as a plus no more libraries are necessary). At the end as you suggested its easier do it in this way than using ajax.

Many thanks to all for your kind help.


Solution

  • Python-docx's Document.save() method accepts a stream instead of a filename. Thus, you can initialize an io.BytesIO() object to save the document into, then dump that to the user.

    Reporte = ReporteWord(Gcas, FI, FF, grafica)
    bio = io.BytesIO()
    Reporte.save(bio)  # save to memory stream
    bio.seek(0)  # rewind the stream
    response = HttpResponse(
        bio.getvalue(),  # use the stream's contents
        content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    )
    response["Content-Disposition"] = 'attachment; filename = "Reporte.docx"'
    response["Content-Encoding"] = "UTF-8"
    return response
    

    This will work if you use a regular link or a form to submit the request, but since you're using $.ajax, you may need to do additional work on the browser end to have the client download the file. It would be easier not to use $.ajax.