69

I'm quite new to using Django and I am trying to develop a website where the user is able to upload a number of excel files, these files are then stored in a media folder Webproject/project/media.

def upload(request):
    if request.POST:
        form = FileForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return render_to_response('project/upload_successful.html')
    else:
        form = FileForm()
    args = {}
    args.update(csrf(request))
    args['form'] = form

    return render_to_response('project/create.html', args)

The document is then displayed in a list along with any other document they have uploaded, which you can click into and it will displays basic info about them and the name of the excelfile they have uploaded. From here I want to be able to download the same excel file again using the link:

 <a  href="/project/download"> Download Document </a>

My urls are

 urlpatterns = [

              url(r'^$', ListView.as_view(queryset=Post.objects.all().order_by("-date")[:25],
                                          template_name="project/project.html")),
              url(r'^(?P<pk>\d+)$', DetailView.as_view(model=Post, template_name="project/post.html")),
              url(r'^upload/$', upload),
              url(r'^download/(?P<path>.*)$', serve, {'document root': settings.MEDIA_ROOT}),

          ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

but I get the error, serve() got an unexpected keyword argument 'document root'. can anyone explain how to fix this?

OR

Explain how I can get the uploaded files to to be selected and served using

def download(request):
    file_name = #get the filename of desired excel file
    path_to_file = #get the path of desired excel file
    response = HttpResponse(mimetype='application/force-download')
    response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name)
    response['X-Sendfile'] = smart_str(path_to_file)
    return response
Ozgur Vatansever
  • 49,246
  • 17
  • 84
  • 119
Caroline
  • 701
  • 1
  • 6
  • 4

14 Answers14

136

You missed underscore in argument document_root. But it's bad idea to use serve in production. Use something like this instead:

import os
from django.conf import settings
from django.http import HttpResponse, Http404

def download(request, path):
    file_path = os.path.join(settings.MEDIA_ROOT, path)
    if os.path.exists(file_path):
        with open(file_path, 'rb') as fh:
            response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel")
            response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
            return response
    raise Http404
lapinkoira
  • 8,320
  • 9
  • 51
  • 94
Sergey Gornostaev
  • 7,596
  • 3
  • 27
  • 39
  • 2
    Doesn't this let users download any file on the server by using a `path` like `../../some/other/path/secrets.txt`? – Don Kirkby Aug 01 '18 at 22:08
  • @DonKirkby likely, I never used this code. It would be reasonable to limit allowed symbols in URL or add something like `if ROOT_DIR not in os.path.abspath(os.path.join(ROOT_DIR, path)): raise PermissionDenied` to view. – Sergey Gornostaev Aug 02 '18 at 04:21
  • 2
    Is it possible to adjust this method so that the user will download .exe files for example? – Keselme Oct 02 '18 at 20:49
  • @Keselme yes. You only need to change `content_type` to 'application/octet-stream'. – Sergey Gornostaev Oct 03 '18 at 05:01
  • @SergeyGornostaev, thank you. If the file is of .apk type, should I use 'application/vnd.android.package-archive' as content-type, or is 'application/octet-stream' still good? – Keselme Oct 03 '18 at 06:45
  • @Keselme it's always better to use a more specific type. Use 'application/vnd.android.package-archive'. – Sergey Gornostaev Oct 03 '18 at 06:56
  • Worked like a charm! – Muhammad Haseeb Aug 29 '19 at 16:35
  • 2
    I'm late to the party but: what is the path argument for? And could I use this to download a powerpoint file? – Micromegas Oct 22 '19 at 13:59
  • why sould we use `open`? why should python read the file(s), by doing this, there is two possible errors maybe. first maybe the file is not such kind that python could `open/read`, and second it's an unnecessary loading in memory.(sorry for my english) – mh-firouzjah Nov 22 '20 at 06:23
  • If you want to download with default app, just change ```content_type="application/vnd.ms-excel"``` to ```content_type="application/default"``` – Natan Almeida de Lima Feb 09 '21 at 10:15
  • How can this be achieved but still using ```return render(request, template, context)```. I want to change the template to tell the user that ABC has been downloaded and for this I need to send ```context``` back to the template. – gmarais Mar 03 '21 at 15:42
  • Why not just provide the file in the html template and let the user download on demand? Django supports this. Is there an advantage to the proposed solution rather just use `href="{{file.url}}" download`? – Gr3at Mar 10 '21 at 16:01
65

You can add "download" attribute inside your tag to download files.

<a  href="/project/download" download> Download Document </a>

https://www.w3schools.com/tags/att_a_download.asp

Hasan Basri
  • 848
  • 8
  • 16
  • 3
    Damn this is so easy to implement, I wonder why so less upvotes! – thatman303 Jan 24 '20 at 17:52
  • 8
    Because it doesn't work with Django. Django processes "/project/download" as a URL... so if `/project/myfile.pdf` doesn't resolve to a URL... the GET request will give a 404 in Django - even though the html is marked as a download. – Hanny Feb 27 '20 at 16:20
  • you can use it with Django doing: return format_html('{0}', value) – Jorge López Mar 03 '20 at 22:21
  • 3
    Its working for me in djago Download – Harun-Ur-Rashid May 09 '20 at 07:11
  • @Hanny are you sure for download tag didn't solve your problem. Maybe you can check your MEDIA_ROOT folder settings. – Hasan Basri May 09 '20 at 14:00
  • Worked for me as well, I just needed to change the path in `upload_to` attribute in the `FileField` on my `models.py`. My template look like this: `` – Danny Jul 02 '21 at 17:27
  • This one worked for me also using – Breno Veríssimo Aug 24 '22 at 19:30
  • Works fine in development environment, curious to see how this will work during deployment and serving from server for example. Either way, in my case I just have a model in which i store the directories with ```models.FileField(storage=FileSystemStorage(location=settings.MEDIA_ROOT), upload_to='experiments', blank=True)```. Later on these can be called upon in html for download as: ```{% if 'experiments' in every_column %} {{ every_column }}``` – Rivered Nov 22 '22 at 15:13
41

Reference:

In view.py Implement function like,

def download(request, id):
    obj = your_model_name.objects.get(id=id)
    filename = obj.model_attribute_name.path
    response = FileResponse(open(filename, 'rb'))
    return response
bhristov
  • 3,137
  • 2
  • 10
  • 26
mananbh9
  • 541
  • 4
  • 3
  • 2
    This seems better than the accepted answer, thanks! Does this work with nginx too somehow? – Johannes Pertl Jan 16 '21 at 20:20
  • Inside the reference, but worth mentioning, is that no context manager is needed: "The file will be closed automatically, so don’t open it with a context manager." – Djones4822 Sep 22 '22 at 20:12
7

When you upload a file using FileField, the file will have a URL that you can use to point to the file and use HTML download attribute to download that file you can simply do this.

models.py

The model.py looks like this

class CsvFile(models.Model):
    csv_file = models.FileField(upload_to='documents')

views.py

#csv upload

class CsvUploadView(generic.CreateView):

   model = CsvFile
   fields = ['csv_file']
   template_name = 'upload.html'

#csv download

class CsvDownloadView(generic.ListView):

    model = CsvFile
    fields = ['csv_file']
    template_name = 'download.html'

Then in your templates.

#Upload template

upload.html

<div class="container">
<form action="#" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.media }}
    {{ form.as_p }}
    <button class="btn btn-primary btn-sm" type="submit">Upload</button>
</form>

#download template

download.html

  {% for document in object_list %}

     <a href="{{ document.csv_file.url }}" download  class="btn btn-dark float-right">Download</a>

  {% endfor %}

I did not use forms, just rendered model but either way, FileField is there and it will work the same.

sikaili99
  • 532
  • 6
  • 14
4

I've found Django's FileField to be really helpful for letting users upload and download files. The Django documentation has a section on managing files. You can store some information about the file in a table, along with a FileField that points to the file itself. Then you can list the available files by searching the table.

Don Kirkby
  • 53,582
  • 27
  • 205
  • 286
2

@Biswadp's solution worked greatly for me

In your static folder, make sure to have the desired files you would like the user to download

In your HTML template, your code should look like this :

<a href="{% static 'Highlight.docx' %}"> Download </a>
El Bachir
  • 111
  • 1
  • 4
  • Make sure you load static at the top of the page {% load static %}, right after: {% block content %}. This is thew simplest approach and it worked super well and fast for me. – Rene Chan Aug 31 '21 at 06:33
2

Using the below approach makes everything less secure since any user can access any user's file.

<a  href="/project/download" download> Download Document </a>

Using the below approach makes no sense since Django only handles one requests at the time (unless you are using gunicorn or something else), and believe me, the below approach takes a lot of time to complete.

def download(request, path):
    file_path = os.path.join(settings.MEDIA_ROOT, path)
    if os.path.exists(file_path):
        with open(file_path, 'rb') as fh:
            response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel")
            response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
            return response
    raise Http404

So what is the optimum solution? Use Nginx authenticated routes. When requesting a file from Nginx you can make a request to a route and depending on the HTTP response Nginx allows to denies that request. This makes it very secure and also scalable and performant. You can ready about more here

Freddy Mcloughlan
  • 4,129
  • 1
  • 13
  • 29
2
  1. <a href='/your-download-view/' download>Download</a>

  2. In your view:

from django.http import FileResponse

def download(request):
    # pre-processing, authorizations, etc.
    # ...
    return FileResponse(open(path_to_file, 'rb'), as_attachment=True)
Greg Sadetsky
  • 4,863
  • 1
  • 38
  • 48
Mourad Qqch
  • 373
  • 1
  • 5
  • 16
  • what if the return is with a different extension, for example I have a pdf file and then I process the file into JSON then I download the result and save it to my computer? – perymerdeka Jun 29 '22 at 14:47
2
import mimetypes
from django.http import HttpResponse, Http404

mime_type, _ = mimetypes.guess_type(json_file_path)
    
if os.path.exists(json_file_path):
    with open(json_file_path, 'r') as fh:
        response = HttpResponse(fh, content_type=mime_type)
        response['Content-Disposition'] = "attachment; filename=%s" % 'config.json'
        return response
raise Http404
Gaurav Nagar
  • 191
  • 2
  • 4
1

Simple using html like this downloads the file mentioned using static keyword

<a href="{% static 'bt.docx' %}" class="btn btn-secondary px-4 py-2 btn-sm">Download CV</a>
Biswadp
  • 21
  • 1
1

1.settings.py:

MEDIA_DIR = os.path.join(BASE_DIR,'media')
#Media
MEDIA_ROOT = MEDIA_DIR
MEDIA_URL = '/media/'

2.urls.py:

from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)

3.in template:

<a href="{{ file.url }}" download>Download File.</a>

Work and test in django >=3

for more detail use this link: https://youtu.be/MpDZ34mEJ5Y

1

If the file is a FileField in the model, this is the way I do it:

    try:
        download_file = PrintingFile.objects.get(pk=kwargs.get('pk_file', 0))
        return FileResponse(download_file.file.open(), as_attachment=True)
    except PrintingFile.DoesNotExist:
        raise Http404

More here

cwhisperer
  • 1,478
  • 1
  • 32
  • 63
0

I use this method:

{% if quote.myfile %}
    <div class="">
        <a role="button" 
            href="{{ quote.myfile.url }}"
            download="{{ quote.myfile.url }}"
            class="btn btn-light text-dark ml-0">
            Download attachment
        </a>
    </div>
{% endif %}
Shahriar.M
  • 818
  • 1
  • 11
  • 24
0

If you hafe upload your file in media than:

media
example-input-file.txt

views.py

def download_csv(request):    
    file_path = os.path.join(settings.MEDIA_ROOT, 'example-input-file.txt')    
    if os.path.exists(file_path):    
        with open(file_path, 'rb') as fh:    
            response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel")    
            response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)    
            return response

urls.py

path('download_csv/', views.download_csv, name='download_csv'),

download.html

a href="{% url 'download_csv' %}" download=""
Cananau Cristian
  • 436
  • 2
  • 6
  • 30
Timeless
  • 104
  • 5