13

I would like to implement a private download area on a website powered by django. The user would have to be logged in with the appropriate rights in order to be able to get some static files.

What would you recommend for writing this feature. Any tips or tricks?

Thanks in advance

Update: Maybe because of my bad english or my lack of knowledge about this architecture (that's why I am asking) but my question is: how to make sure that static files (served by the regular webserver without any need of django) access is controlled by the django authentication. I will read the django docs more carefully but i don't remember of an out-of-the-box solution for that problem.

Update2: My host provider only allows FastCgi.

luc
  • 41,928
  • 25
  • 127
  • 172

3 Answers3

11

So, searching I found this discussion thread.

There were three things said you might be interested in.

First there is the mod_python method
Then there is the mod_wsgi method

Both of which don't seem all that great.

Better is the X-Sendfile header which isn't fully standard, but works at least within apache, and lighttpd.

kibbitzing from here, we have the following.

@login_required
def serve_file(request, context):
    if <check if they have access to the file>:
        filename = "/var/www/myfile.xyz" 
        response = HttpResponse(mimetype='application/force-download') 
        response['Content-Disposition']='attachment;filename="%s"'%filename
        response["X-Sendfile"] = filename
        response['Content-length'] = os.stat("debug.py").st_size
        return response
    return <error state>

and that should be almost exactly what you want. Just make sure you turn on X-Sendfile support in whatever you happen to be using.

emeryc
  • 825
  • 7
  • 8
  • I'm using X-Sendfile in a project right now and it's working well. I think I got my code from the same place also. – Edward Dale Oct 22 '09 at 20:44
  • Thansk for the answer. I am using FastCGI. Does that method work? – luc Oct 22 '09 at 21:07
  • @luc that method is based on either using apache or lighttpd, If you are using either of those then the X-Sendfile will work without a problem, regardless of how you are getting requests to python. – emeryc Oct 22 '09 at 23:19
  • As well as X-Sendfile, for nginx front end their is X-Accel-Redirect with private URL namespace mapping to private files. In Apache/mod_wsgi 3.0 there is Location header in daemon mode that behaves just like 200/Location in CGI. You would though in the latter need to possibly use mod_rewrite to effectively make URL subset where static files are only accessible to Apache internal subrequest trigger by Location and not be public. – Graham Dumpleton Oct 23 '09 at 03:58
  • @emeryc: It seems to be the right approach but apache/FastCgi returns a blank file every time. Any idea? Please also not that the len(f) is not correct. I've replaced it by f.seek(0, 2) f.tell() – luc Oct 26 '09 at 10:22
  • So, there was a dangling / after filename= that was left from the copied ruby. Also, I changed that len(f) to be the actually working os.stat I think that dangling / might have been the problem. The only other problem that I can think of is that you need to make sure Apache has read access to the file, and that it might need to be a relative file with regards to an apache path, but I would try stripping the / first. – emeryc Oct 26 '09 at 18:07
  • I've accepted your answer because i think it is the better approach in most cases. In my context, I've chosen a different path which is valid as well. See my answer below. – luc Oct 28 '09 at 08:24
3

The XSendfile seems to be the right approach but It looks to be a bit complex to setup. I've decided to use a simpler way.

Based on emeryc answer and django snippets http://www.djangosnippets.org/snippets/365/, I have written the following code and it seems to make what I want:

@login_required
def serve_file(request, filename):
    fullname = myapp.settings.PRIVATE_AREA+filename
    try:
        f = file(fullname, "rb")
    except Exception, e:
        return page_not_found(request, template_name='404.html')
    try:
        wrapper = FileWrapper(f)
        response = HttpResponse(wrapper, mimetype=mimetypes.guess_type(filename)[0])
        response['Content-Length'] = os.path.getsize(fullname)
        response['Content-Disposition'] = 'attachment; filename={0}'.format(filename)
        return response
    except Exception, e:
        return page_not_found(request, template_name='500.html')
luc
  • 41,928
  • 25
  • 127
  • 172
  • So, the reason that this might be bad, is in the case of using mod_python this will take up a lot of memory (if I remember correctly). On anything else the only downside is that it ties up the "expensive" threads going into django, instead of the much lighter file serving threads. This is only really a problem when you get to the point that requests/second being a whole number. – emeryc Oct 29 '09 at 16:34
  • If you like me are too lazy to look at the snippet: "from django.core.servers.basehttp import FileWrapper" – Arahman Jun 28 '12 at 08:28
1

There's tons of tutorials on how to enable authentication in Django. Do you need help with that? If so, start here.

The next step is to create a View which lists your files. So do that, this is all basic Django. If you have problems with this step, go back and go through the Django tutorial. You'll get this.

Finally, refer back to the first link (here is is again: authentication docs) and take a close look at the LOGIN_REQUIRED decorator. Protect your view with this decorator.

This is all pretty basic Django stuff. If you've done this and have a specific question, post it here. But you put a pretty open ended question on SO and that's not a great way to get assistance.

marcc
  • 12,295
  • 7
  • 49
  • 59
  • What's with the downvotes? Clearly I answered his question in the best possible way when the question was asked. This answer helped and prompted the original asker to change his question a little. – marcc Sep 23 '10 at 20:25