Search code examples
pythondjangoresponsehttpresponse

Django 4, how can I download a generated CSV file to browser while passing additional context to template?


In views.py, I have a view that displays invoice information to the user, and a button for creating a batch payment file. When the button is clicked, a CSV template file is read, and a new CSV is created from that template with updated with data from the database, and then downloaded directly to the browser.

At the same time, I want to return some context to generate toast notifications about the processing status, return error messages, etc. It seems I need to do this via multiple HTTP requests/responses, and I'm not sure how to go about it.

Here's the view:

class InvoiceView(TemplateView):
    model = Cost
    template_name = "pipeline/invoices_list.html"
    costs = Cost.objects.all()

    def get_context_data(self, **kwargs):
        # some context data
        return context

    def post(self, request, *args, **kwargs):

        if "batch-pay-csv" in request.POST:
        
            response, batch_payment_status = create_batch_payment_template(self.costs)
            context = self.get_context_data(**kwargs)
            context['batch_payment_status'] = batch_payment_status

            return response

and an abbreviated version of the create_batch_payment_file() function (in utils.py):

def create_batch_payment_file(costs):

    '''
    Create a batch payment file from the template in /static. 
    
    '''

    invoices = costs.filter(invoice_status__in=["REC", "REC2"])
    processing_status = {} # format: invoice PO number {status (success/error), message}
    
    response = HttpResponse(
        content_type='text/csv',
        headers = {'Content-Disposition': 'attachment; filename = "WISE_BATCH_PAYMENT.csv"'},
    )

    # for invoice in invoices, write stuff to the CSV

    context = processing_status
    return response, context

I'm starting to think it would be easier to generate a download button on the template rather than try to automatically download it to the browser, but would love to hear any ideas.


Solution

  • Figured it out! Instead of using it as a utility function, I turned it into a view in views.py so I could call it with AJAX, and passed the data dict into the headers of the HttpResponse object.

    def create_batch_payment_file(request):
    
        '''
        Create a batch payment file from the template in /static. 
        
        '''
    
        invoices = Cost.objects.filter(invoice_status__in=["REC", "REC2"])
        processing_status = {} # format: invoice PO number {status (success/error), message}
        
        response = HttpResponse(
            content_type='text/csv',
            headers = {'Content-Disposition': 'attachment; filename = "WISE_BATCH_PAYMENT.csv"'},
        )
    
        # for invoice in invoices, write stuff to the CSV
    
        data = processing_status
        response['X-Processing-Status'] = json.dumps(data)
        return response
    

    Then on the client side, made an AJAX call:

    $("#batch-payment-create").on("submit", function(e) {
            e.preventDefault()
            const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
            $.ajax({
                headers: { 'X-CSRFToken': csrftoken },
                type: "POST",
                url: "/myapp/myview/",
                data: "",
                success: function(data, testStatus, xhr) {
                    var blob = new Blob([data]);
                    var link = document.createElement('a');
                    var processing_status = xhr.getResponseHeader('X-Processing-Status');
                    console.log('Processing status:', processing_status);
                    link.href = window.URL.createObjectURL(blob);
                    link.download = "WISE_batch_payment.csv";
                    link.click();
                }
            })
        })
    

    Hopefully this helps someone else!