Search code examples
djangodjango-templatesdjango-viewszip

Django: How to download all files as zip when button is clicked?


I've seen so many answers on how to serve zip files for download in Django but no one is explicitly telling how to call it in the client side. Maybe the answer is too obvious but I really don't get it.

I have a datatable and I've included a download button in each row. When the user clicks it, all associated files will be downloaded as zip. Here's my code:

In my views.py:

def download_zipfile(self, request):
  filelist = [MyModel.path_to_file1, MyModel.path_to_file2, MyModel.path_to_file3]
  byte_data = BytesIO()
  zip_name = "%s.zip" % MyModel.id_no
  zip_file = zipfile.ZipFile(byte_data, 'w')

  for file in filelist:
    filename = os.path.basename(os.path.normpath(file))
    zip_file.write(file, filename)
  zip_file.close()

  response = HttpResponse(byte_data.getvalue(), content_type='application/zip')
  response['Content-Disposition'] = 'attachment; filename=%s' %zip_name

  zip_file.printdir()

  return response

In my urls.py I have:

urlpatterns = [
  path('download/<int:pk>', download_zipfile, name='download_zipfile'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

In my html template I have:

<tr id="datarows">
  <td>{{ mymodel.data1 }}</td>
  <td>{{ mymodel.data2 }}</td>
  <td>{{ mymodel.data3 }}</td>
  <td>
    <a href="{% url 'download_zipfile' mymodel.pk %}" target="_blank">
      <i class="fas fa-download"></i>
    </a>
  </td>
</tr>

And I keep getting this error:

django.urls.exceptions.NoReverseMatch: Reverse for 'download_zipfile' with arguments '('',)' not found. 1 pattern(s) tried: ['download/(?P<pk>[0-9]+)$']

I would really appreciate it if anyone would be able to help. Thanks in advance!

EDIT Here's the view that renders my HTML template:

class MyModelListView(LoginRequiredMixin, ListView):
  model = MyModel
  template_name = 'mymodel_list.html'
  context_object_name = 'mymodels'
  login_url = 'login'

Solution

  • Decode that error!

    Reverse for 'download_zipfile' with arguments '('',)' not found. 
    

    It's trying to reverse 'download_zipfile' with one positional argument, a null string. (The arguments are reported as a tuple, '('',)' is a one-tuple containing a null string)

    href="{% url 'download_zipfile' mymodel.pk %}"
    

    mymodel.pk yields a null string. I'm guessing that either mymodel is not passed to the template for rendering, or that it's not a saved object and therefore that it's pk is None. Anyway, a null string is not an integer as required by

    path('download/<int:pk>', download_zipfile, name='download_zipfile'),