htmldjangodjango-media

Django How to download files from file system?


I'm trying to make a link to a file path on a Windows server downloadable to the user after they click the file link.

My app displays entries from a result of searching a ticket model. The results on the page include items like: Ticket number, description, status and a link to a flat file (that is stored on a Windows server network share drive; starting with \server.name\folder A\folder B\folder C\file to download.txt

Note

On the display page/template.html I can list all my values in a table (even a link the the file I'd like to download). However, when I hover over the link at the bottom of the browser I see file://server.name/folder A/folder B\\folder C\\file to download.txt. Yes, the last "slash" is a forward slash while all the others are back slashes (not sure why they are changing?)

When I pass the file address to the template it is formatted like : \\server.name\\folder A\\folder B\\folder C\\file to download.txt (I can see that in testing when I just displayed the filepath on the page.

How do you make the link to a file path on a Windows server downloadable to users that wish to download the file?

.html

<body>

<h1><p>Ticket Number: {{ thisincident.IncidentID }}</p></h1>
<h3>Description: {{ thisincident.Description }}</h3>
<h4><p>Full Description</h4> {{ thisincident.FullDescription | linebreaks }}</p>

    
    
       Journal Entry #
       Date
       Person
       Journal Entry
       File Name
    
    
    
    {% for f in je %}
    
       {{ f.JE }}
       {{ f.Date }} 
       {{ f.resource }}
       {{ f.entry | linebreaks }}
       <a href="{{ f.Location }}">Download</a>
    
    {% endfor %}

view.py


def ticket_view(request, IncidentID):
    file_contents = []
    thisincident = ticket.objects.get(id=IncidentID)
    for root, dirs, files in os.walk(thisincident.LocationPath):
        for file in files:
            if not file == 'manifest.json':
                path_file = os.path.join(root, file)
                path_file = path_file.replace(thisincident.LocationPath + "\\", "")
                file_contents.append(path_file)
    log_list = parseJElog(thisincident.logfiletoparse)

    data = {
        "thisincident": thisincident,
        "result": file_contents,
        "je":log_list
    }
    return render(request, "ticket.html", data)

urls.py

urlpatterns = [
    url(r'^homepage/$', views.home, name='home'),
    url(r'^tickets/$', views.tickets, name='tickets'),
    url(r'^ticket/(?P<IncidentID>\d+)$', views.ticket_view, name='ticket'),
]
if settings.DEBUG:
    urlpatterns+=static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

settings.py

MEDIA_BASE_ROOT = "\\server.name\\folder A\\folder B"
MEDIA_ROOT = os.path.join(MEDIA_BASE_ROOT, "\\folder B\\")
MEDIA_URL = ''

enter image description here

I'm fairly new to Django so any help will be much appreciated.


Solution

  • I also had a similar issue before. You can follow these steps to solve this. I will split the logic into two functions to make this straightforward and easy.

    1, display the tickets

    def ticket_view(request, IncidentID):
        thisincident = ticket.objects.get(id=IncidentID)
        log_list = parseJElog(thisincident.logfiletoparse)
    
        data = {
            "thisincident": thisincident,
            "je": log_list
        }
        return render(request, "ticket.html", data)
    

    2, download the file

    import os
    from django.http import FileResponse
    
    def download_file(request, file_path):
        file_name = os.path.basename(file_path)
        file = open(file_path, 'rb')
        response = FileResponse(file)
        response['Content-Disposition'] = f'attachment; filename="{file_name}"'
        return response
    

    I think this is the code you need, the issue your face can be simply solved by using the os.path or you can use pathlib. here we are passing the file path as an argument into the function and it opens the file in binary mode, creates a FileResponse with the file content, and sets the Content-Disposition header to specify the filename for the user to download it.

    Content-Disposition header is an optional header by the way.

    more about Content-Disposition

    3, update your URLs according to the functions we define above.

    4, update your template.

    {% for f in je %}
            {{ f.JE }}
            {{ f.Date }} 
            {{ f.resource }}
            {{ f.entry | linebreaks }}
            <a href="{% url 'download_file' file_path=f.Location %}">Download</a>
        {% endfor %}
    

    5, update your settings

    MEDIA_URL = '/media/' # edit this to your requirements 
    MEDIA_ROOT = '\\server.name\folder A\folder B\folder C'  # Windows server path
    
    # make sure that you also serving media files in the URL
    if settings.DEBUG:
        urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    

    I hope this solution helps you.