views:

337

answers:

3

Hello

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.

A: 

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
thanks for your answer. see my update.
luc
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
+2  A: 

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
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.
scompt.com
Thansk for the answer. I am using FastCGI. Does that method work?
luc
@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
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
@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
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.statI 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
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
A: 

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))
        response['Content-Length'] = os.path.getsize(fullname)
        return response
    except Exception, e:
        return page_not_found(request, template_name='500.html')
luc
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