0

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.

  • Could you post error messages, or description of what works and what doesn't? – Chris Jan 13 '20 at 23:28
  • Thanks for the reply; I don't receive any error messages in my console, basically all the parts I post work: the data from AJAX post pass to the view, the function I use to generate the document (point 3) returning a 'docx.document.Document' object and the response of Django view is a 'django.http.response.HttpResponse', if I make 'console.log(reponse)' on javascript I receive: 'string', and here is where I can't figure out how to download the docx file (from button in my template or directly from Download dialog). – Leonel Garcia Jan 13 '20 at 23:48
  • Have you verified the view opens the file correctly? I don't have as much experience as you in this part of JS, but – Chris Jan 14 '20 at 14:33
  • Hello. The view seems to work fine as when I save directly the report using the line "document.save(r'C:\tests\new_demo.docx')" the document is generated in that route. I have started to review the alternative using TKinter (while I continue looking for javascript information) to show the "save as" dialog and pass the filename and route selected to the view, this option works but sometimes I'm receiving randomly the error: "RuntimeError: main thread is not in main loop" – Leonel Garcia Jan 14 '20 at 15:11

1 Answers1

1

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.

AKX
  • 93,995
  • 11
  • 81
  • 98