Serve protected media files with django
<p>I'd like Django to serve some media files (e.g. user-uploaded files) only for logged-in users. Since my site is quite low-traffic, I think I will keep things simple and do not use <code>django-sendfile</code> to tell Nginx when to serve a file. Instead I'll let Django/Gunicorn do the job. To me this seems a lot simpler and for a low traffic site this maybe more secure.</p>

<p>But what is the best way to organize the file storage location? Media files are all stored below <code>MEDIA_ROOT</code> and this directory is served by Nginx in production. If I upload my files to <code>MEDIA_ROOT/protected/</code> I have to tell Nginx not to serve the files in the subdirectory <code>protected</code>.</p>

<p>But is this a good idea? It seems a litte risky to me to allow Nginx access <code>/media/</code> in the first place and then protect the subdirectory <code>/media/protected/</code>. Wouldn't it be better not to use a subdirectory of <code>MEDIA_ROOT</code> to store protected files? </p>

<p>But if I try something like this quick-and-dirty in my model:</p>

<pre><code>upload_to='../protected/documents/%Y/%m/'
</code></pre>

<p>Django complains:</p>

<pre><code>SuspiciousFileOperation at /admin/core/document/add/
The joined path (/home/me/projects/project/protected/documents/2016/09/test.file) is located outside of the base path component (/home/me/projects/project/media)
</code></pre>

<p>So I thing it is not good practice to "leave" the <code>MEDIA_ROOT</code>.</p>

<p>What is the best solution to store and serve protected media files?</p>

<p>I now came up with the following solution:</p>

<p>I have this in my Django settings:</p>

<pre><code>MEDIA_ROOT = "/projects/project/media/"
MEDIA_URL = "/media/
</code></pre>

<p>In my models I do either:</p>

<pre><code>document = models.FileField(upload_to="public/documents")
</code></pre>

<p>or</p>

<pre><code>document = models.FileField(upload_to="protected/documents")
</code></pre>

<p>This way, I now have the two subdirectories 'public' and 'protected' in my media files directory.</p>

<p>Nginx or Djangos development server only serves the files in the 'public' subdirectory.</p>

<p>For Djangos development server:</p>

<pre><code>if os.environ["ENVIRONMENT_TYPE"] == 'development':
    urlpatterns += static(settings.MEDIA_URL + "public/", document_root=settings.MEDIA_ROOT + "public/")
</code></pre>

<p>And for Nginx (used in production):</p>

<pre><code>location /media/public/ {
    alias   /projects/project/media/public/;
}
</code></pre>

<p>When I want to serve a protected document, I do the following:</p>

<p>In urls.py:</p>

<pre><code>url(r'^media/protected/documents/(?P&lt;file&gt;.*)$', core.views.serve_protected_document, name='serve_protected_document'),
</code></pre>

<p>And in views.py:</p>

<pre><code>@login_required()
def serve_protected_document(request, file):
    document = get_object_or_404(ProtectedDocument, file="protected/documents/" + file)

    # Split the elements of the path
    path, file_name = os.path.split(file)

    response = FileResponse(document.file,)
    response["Content-Disposition"] = "attachment; filename=" + file_name

    return response
</code></pre>

<p>I would appreciate any comments! Are there better ways to implement this?</p>

python django python-3.x nginx nginx-location