5

I have a file on my computer that I'm trying to serve up as JSON from a django view.

def serve(request):
    file = os.path.join(BASE_DIR, 'static', 'files', 'apple-app-site-association')
    response = HttpResponse(content=file)
    response['Content-Type'] = 'application/json'

What I get back is the path to the file when navigating to the URL

/Users/myself/Developer/us/www/static/files/apple-app-site-association

What am I doing wrong here?

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
qarthandso
  • 2,100
  • 2
  • 24
  • 40
  • `os.path.join` returns a string (the path). – Emile Bergeron Aug 17 '16 at 15:55
  • @EmileBergeron then how do I get the contents of the file? I thought `content=file` would do that. – qarthandso Aug 17 '16 at 15:56
  • 1
    You put a string inside the variable `file`, so `content=file` put the string as the content of the response. Search how to read a file with python. – Emile Bergeron Aug 17 '16 at 15:57
  • `static` is your static storage directory? – dhke Aug 17 '16 at 16:03
  • If by the off chance you are trying to just serve a directory of files there's a reeeeally simple way to do it with python: go to the directory you're serving from and run `python -m SimpleHTTPServer `... there are lots of limitations but if you have python installed it takes literally seconds. From a web browser you'll be able to navigate and download – s g Aug 17 '16 at 18:51

2 Answers2

5

os.path.join returns a string, it's why you get a path in the content of the response. You need to read the file at that path first.

For a static file

If the file is static and on disk, you could just return it using the webserver and avoid using python and django at all. If the file needs authenticating to be downloaded, you could still handle that with django, and return a X-Sendfile header (this is dependant on the webserver).

Serving static files is a job for a webserver, Nginx and Apache are really good at this, while Python and Django are tools to handle application logic.

Simplest way to read a file

def serve(request):
    path = os.path.join(BASE_DIR, 'static', 'files', 'apple-app-site-association')
    with open(path , 'r') as myfile:
        data=myfile.read()
    response = HttpResponse(content=data)
    response['Content-Type'] = 'application/json'

This is inspired by How do I read a text file into a string variable in Python

For a more advanced solution

See dhke's answer on StreamingHttpResponse.

Additional information

Community
  • 1
  • 1
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • No need to serve the file from memory. File objects are iterable and `StreamingHttpResponse` takes care of the rest. – dhke Aug 17 '16 at 16:08
  • @dhke if the file is static and on disk, python and django wouldn't be needed at all. Just a config in the webserver should be enough. But since OP doesn't know how to read a file, I just provided the simplest way to achieve that. – Emile Bergeron Aug 17 '16 at 16:11
  • 1
    Then OP will learn ;-). Nonetheless thanks for reminding me, that a simple redirect will probably do. – dhke Aug 17 '16 at 16:21
  • @dhke I added a link to your answer for a more advanced solution. – Emile Bergeron Aug 17 '16 at 16:23
4

If you feed HttpResponse a string a content you tell it to serve that string as HTTP body:

content should be an iterator or a string. If it’s an iterator, it should return strings, and those strings will be joined together to form the content of the response. If it is not an iterator or a string, it will be converted to a string when accessed.

Since you seem to be using your static storage directory, you might as well use staticfiles to handle content:

from django.contrib.staticfiles.storage import staticfiles_storage
from django.http.response import StreamingHttpResponse

file_path = os.path.join('files', 'apple-app-site-association')

response = StreamingHttpResponse(content=staticfiles_storage.open(file_path))
return response

As noted in @Emile Bergeron's answer, for static files, this should already be overkill, since those are supposed to be accessible from outside, anyway. So a simple redirect to static(file_path) should do the trick, too (given your webserver is correctly configured).

To serve an arbitrary file:

from django.contrib.staticfiles.storage import staticfiles_storage
from django.http.response import StreamingHttpResponse

file_path = ...

response = StreamingHttpResponse(content=open(file_path, 'rb'))
return response

Note that from Django 1.10 and on, the file handle will be closed automatically.

Also, if the file is accessible from your webserver, consider using django-sendfile, so that the file's contents don't need to pass through Django at all.

dhke
  • 15,008
  • 2
  • 39
  • 56
  • And why do you recommend using `StreamingHttpResponse` instead of `HttpResponse`? – qarthandso Aug 17 '16 at 16:11
  • 1
    @qarthandso Depends on your use case. If you use `StreamingHttpResponse` the file will be read from the filesystem chunk by chunk as it is written out to the client. From my experience, you might end up with the file in memory, if you use `HttpResponse` (e.g. ETag generation). For small files: Don't bother. – dhke Aug 17 '16 at 16:17
  • 1
    @qarthandso with this answer and mine, you have everything you need to solve the problem at hand. You should consider upvoting and accepting one to mark the question as answered. – Emile Bergeron Aug 17 '16 at 17:02