Ajax to generate a FileResponse and file/save dialog?

I have a function view that creates a file based on two parameters, and returns it with FileResponse. When called from a template url, this works fine. BUT…calls creating/returning large files can take a while – several seconds or more. So I need to start and stop a spinner in the GUI. My understanding is there are no accessible events involving the File/Save window - at least none on save or close of the window.

So I’ve tried executing the view with an ajax call, and that does start the spinner, generate the file, and stops the spinner, BUT…no file/save window opens! Below are the relevant code snippets. Seems like a routine thing to do, but haven’t worked out a solution after trying many things.

The two fallback options I can think of are 1) no spinner; 2) generate the file and a link to it somwhere. But is this actually doable? and is there a better alternative?

template

<a href="{% url 'download' id=ds.id format='json' %}" ref="lpf">download json</a>
<a href="{% url 'download' id=ds.id format='tsv' %}" ref="lpf">download tsv</a>

ajax GET

$("#downloads a").click(function(e){
  urly='/datasets/{{ds.id}}/augmented/'+$(this).attr('ref')
  startDownloadSpinner()
  $.ajax({
      type: 'GET',
      url: urly
    }).done(function() {
      spinner_dl.stop();
      console.timeEnd('big dl')  
    })
})

ajax POST alternative

const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;

$.ajax({
    type: 'POST',
    url: urly,
    headers: {'X-CSRFToken': csrftoken},
    mode: 'same-origin'
  }).done(function() {
    spinner_dl.stop();
  }) 

view

def download(request, *args, **kwargs):
  from django.db import connection
  user = request.user.username
  ds=get_object_or_404(Dataset,pk=kwargs['id'])
  date=maketime()
  req_format = kwargs['format']
  if req_format is not None:
    print('download format',req_format)

  features=ds.places.all().order_by('id')

  if req_format == 'tsv':
    # make file name
    fn = 'media/user_'+user+'/'+ds.label+'_aug_'+date+'.tsv'

    with open(fn, 'w', newline='', encoding='utf-8') as csvfile:
      writer = csv.writer(csvfile, delimiter='\t', quotechar='', quoting=csv.QUOTE_NONE)
      writer.writerow(['id','whg_pid','title','ccodes','lon','lat','added','matches'])
      for f in features:
        geoms = f.geoms.all()
        gobj = augGeom(geoms)
        row = [str(f.src_id),
               str(f.id),
               f.title,
               ';'.join(f.ccodes),
               gobj['lonlat'][0] if 'lonlat' in gobj else None,
               gobj['lonlat'][1] if 'lonlat' in gobj else None,
               gobj['new'] if 'new' in gobj else None,
               str(augLinks(f.links.all()))
               ]
        writer.writerow(row)
    response = FileResponse(open(fn, 'rb'), content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="'+os.path.basename(fn)+'"'

    return response
  elif req_format == 'json':

  	... similar to above, but json

  	return response

Also keep in mind that based on the browser’s configuration, you may not even get the dialog box.

1 Like

WHen you’re calling ajax you’re not refreshing the whole page as a regular POST would do, you’re just sending/receiving a bit of info. Any changes to the page need to be then done by you, with JS, in the front end.

If you want a popup for this, I would suggest including a modal in your django template which is initially hidden, but which you display when the user clicks one of these buttons. This would all be controlled using JS of course.

I did a quick google for a tutorial on modals, and this seems to be the simplest that still gets the point across : How To Make a Modal Box With CSS and JavaScript

Hope this helps!