Private media with Django

I have often wanted user-uploaded files and images to be “private” or “secure”, i.e. require some authentication and authorisation to view, but haven’t known how to start. Now that I have a solution that works (e.g. for sites hosted by Webfaction), I’d like to share it with you.

The basic principles are:

  1. Serve your public “static” files (e.g. css and javascript) and any public user/admin-uploaded “media” files from your existing static webapp. This static webapp has no capability to selectively hide some files from view, so we will not use it for the private media.
  2. Serve your private media from a regular Django view:
    • This view will be accessed through a regular Django URL in a urls.py file.
    • The first step in this view is to authenticate and check for authorisation. Be aware that you will only have the request parameters (including the URL path) to do this with. One solution might be to use a directory structure with permissions varying by directory.
    • The second step is to serve the file. You could do this using Django directly, but it would be woeful. Webfaction uses Apache, which has a nice module called mod_xsendfile which lets Apache serve the contents (Django merely specifies the path in the response header). There is a solution if you use nginx too which I have not explored (see django-filer’s secure downloads feature for more). You need to explicitly install and activate this module however – see below for details.
  3. Store your private media somewhere different to your public media. You can do this by providing a custom storage for your private FileFields and ImageFields. If you need instance-specific permissions, you can do this by passing a method as the upload_to directory.

Note – before going down this path, check if the file system you are using already has a protection mechanism (e.g. Amazon’s S3 service), in which case you probably won’t need this.

An implementation

I packaged up an implementation of these principles in django-private-media. You can install it from PyPi with:

pip install django-private-media

This package draws significantly from the secure file download code of Stephan Foulis’s django-filer.  One key difference between the two is that django-filer replaces all file and image fields with a foreign key; in contrast, because my focus is solely on the permissioning, django-private-media just uses the standard Django file and image fields.  As a result, it should be fairly straightforward to convert an existing project to use it.  See the readme for more details.

Code snippets

You’ll find below some code snippets to point you in the right direction. They assume you add a PRIVATE_MEDIA_URL and PRIVATE_MEDIA_ROOT, analogous to Django’s MEDIA_URL and MEDIA_ROOT, to your settings.py file.

urls.py

# urls.py
from django.conf import settings
...
urlpatterns += patterns('appname.views',
    url(r'^{0}(?P.*)$'.format(settings.PRIVATE_MEDIA_URL.lstrip('/')), 'serve_private_file',),
)
...

views.py

# views.py
from django.conf import settings

def has_read_permission(self, request, path):
    "Only show to authenticated users - extend this as desired"
    # Note this could allow access to paths including ../..
    # Don't use this simple check in production!
    return request.user.is_authenticated()

def serve_private_file(request, path):
    "Simple example of a view to serve private files with xsendfile"
    if has_read_permission(request, path):
        fullpath = os.path.join(settings.PRIVATE_MEDIA_ROOT, path)
        response = HttpResponse()
        response['X-Sendfile'] = fullpath
        return response

models.py

# models.py

from django.db import models
from django.conf import settings

private_media = FileSystemStorage(location=settings.PRIVATE_MEDIA_ROOT,
                                  base_url=settings.PRIVATE_MEDIA_URL,
                                  )
# or you could define a custom subclass of FileSystemStorage

class Car(models.Model):
    photo = models.ImageField(storage=private_media)

settings.py

# settings.py
PRIVATE_MEDIA_ROOT = '/home/username/private-media' # for example
PRIVATE_MEDIA_URL = '/private/' # for example

Installing xsendfile

To install and activate xsendfile on Webfaction, follow the advice given by this post.

That’s all!

What have I missed?  Please let me know if you’ve done something similar and have another or better solution.

Otherwise, I hope this helps!

  

4 thoughts on “Private media with Django”

  1. Wow ! That’s exacly what I was searching. It’s a big lack of Django.
    Can you also serve videos with this module. Does it have problem with seeking in theim ?
    Thanks.
    Antoine;

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>