Django: preventing direct access to files - django

I want to serve files dynamically from behind the root of my web directory. Currently I'm serving all the files statically which I did just in order to include my javascripts and css files. I realize this is probably dumb but I'm not able to serve files any other way!
So, I want to serve files dynamically, without the user being able access at another time just by using a url.
My settings
ADMIN_MEDIA_PREFIX = '/media/'
MEDIA_ROOT = os.path.join( APP_DIR, 'site_media' )
MEDIA_URL = 'http://localhost:8000/site_media/'
My URLS
( r'^site_media/(?P<path>.*)$', 'django.views.static.serve', { 'document_root': settings.MEDIA_ROOT } )
Thanks!

What do you mean by "dynamically"? What is it that you're trying to achieve? Do you want to control access to the files?
If you want to run some Django-view that decides whether the user is allowed access to the file or not, you can use sendfile. See the following snippet for code on how to set the correct headers: http://djangosnippets.org/snippets/2226/
Your webserver also needs to support this header, at least apache and nginx which I've worked with does.
You also need to think about how to store and distribute the files as the server that is running the webserver needs access to the files. This will depend on your setup.

You could obfuscate the URLs in such a way that it'd be pretty hard to guess; this isn't at all secure but it would stop the casual poker-arounder. You'd also need to ensure that whatever you use to serve static media in production isn't set to display directory listings.
If you need something a little more secure, here's what I've done in the past:
In models.py:
my_storage = FileSystemStorage(location=settings.MY_FILES_ROOT, base_url='DO_NOT_USE')
class Resource (models.Model):
//...snip...
resource_file = models.FileField(storage = my_storage)
where settings.MY_FILES_ROOT is a path outside of where you keep your static files normally.
Then, in views.py:
def get_file(request, whatever_id):
//check if the user is allowed to access the file here
resource = Resource.objects.get(id=resid)
response = HttpResponse(resource.resource_file.read(), mimetype='text/whatever')
response['Content-Disposition'] = 'attachment; filename=whatever.ext'
return response
Of course, this approach only works if you know the MIME type and file extension of the file, whether it's always the same (the app I did this in always served PDFs) or you extract it from the file.

Related

Referencing Django's versioned static resources

When I run python manage.py collectstatic, it makes a copy of each image, JavaScript, and CSS file with a hash in the filename:
Post-processed 'css/theme.css' as 'css/theme.afeb1fc222a9.css'
Post-processed 'css/custom.css' as 'css/custom.585e1b29ff9a.css'
...
I'm assuming this is just a way of making a versioned filename for better caching; the client or CDN can be told to cache this file indefinitely, because if I make a change, the hash will differ, and I'll just reference the new version by the new name.
However, I'm not clear on how I'm supposed to reference this URL. The documentation on serving static files just says,
In your templates, either hardcode the url like /static/my_app/example.jpg or, preferably, use the static template tag to build the URL for the given relative path by using the configured STATICFILES_STORAGE storage (this makes it much easier when you want to switch to a content delivery network (CDN) for serving static files).
I went through my templates and dutifully switched every static resource (including the CSS files) from a hardcoded URL to a {% static "..." %} template tag, assuming it would map to the versioned filename where appropriate. But it doesn't.
I'm also using WhiteNoise for serving the resources, and I'm not entirely sure how it affects things, but it also says,
Want forever-cacheable files and compression support? Just add this to your settings.py: STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
But I have that in my settings file and it also doesn't seem to do anything with these versioned filenames.
If DEBUG is True then the static url will be 'css/theme.css' instead of
'css/theme.afeb1fc222a9.css'

To use Django FileField or FilePathField?

I'm a bit confused about which field to use. What I need is just a Field that will hold a file (audio and/or another one for an image)
The FileField seems to be specifically for uploading and I think it is a bit of overkill for what I need, I also don't like how it will automatically upload and rename the files to a unique filename (file_FHjgh758.txt) everytime something is uploaded.
The problem I have with the FilePathField is that it seems to require an absolute path, which would obviously be different from dev to production, I got around this by adding this to the model...
import os
class Foo(models.Model):
path = os.path.dirname(os.path.dirname(__file__))
path = os.join(path, 'media')
audio = models.FilePathField(path=path)
I have no idea if this is safe or proper, and there aren't many examples online or in the docs to draw from.
To be clear, I just want to have a field which I can point to a file (audio or image) somewhere in my system. What would be the best way to go about doing this?
If you want to refer to files that are already on your filesystem rather than user uploaded files, then FilePathField is the one that you want.
In this regard a few comments:
Don't point to a path that is inside your app's source tree. That is not safe.
You can use settings or environment variables to handle development vs. production paths, e.g., put a FILE_PATH_FIELD_DIRECTORY setting in your development/production settings files and refer to this setting from your app:
from django.conf import settings
class Foo(models.Model):
audio = models.FilePathField(path=settings.FILE_PATH_FIELD_DIRECTORY)

how to retrieve static file's contents in django view?

I have html files and I want to give the contents of the file to user from a django view.
Basically these files are data files whose internal sturcture is html.
** Edit **
So you want to render them (display them in the browser), or send them for download? Are they templates, or files the users have uploaded?
A: Clients will use the html(and other data as well) and replace certain part of the page with it. (I think how I'll use it is not that important though. We can treat html contents as a data)
Will you know the names of the files in question when you write the software, or will you only know their names at runtime?
A: I currently have a table which has the filepath as a field. ie. The filepath can be changed at runtime
You'll want to leverage the staticfiles storage system.
from django.contrib.staticfiles.storage import staticfiles_storage
file_handle = staticfiles_storage.open(your_filename)
contents = file_handle.read()
the filename (your_filename above) should be the relative path to the static file (not including the STATIC_URL (/static/[this part])
Edit: Finder vs Storage
If you want to find static files stored in app directories (or other configured static finders), you need to use a finder, not the storage to retrieve the file contents. Here's how to do that:
from django.contrib.staticfiles.finders import find
file_path = "relative/path/to/file.extension"
abs_path = find(file_path)
file(abs_path).read()

Django - how to reference paths of static files and can I use them in models?

I have a django model class with several images related to each instance.
Those images follow a certain pattern and can be determined by the name field of the model.
Those Images reside within the project static files folder.
So I have written a method for my model class to generate file paths for the images. It searches the static files folder for all files that follow the pattern *.jpg (the asterisks is necessary, because the filename has incrementing numbers).
Once it has found a file it transforms the absolute filesystem path into an url that is passed to a view and template via a list.
def getImages(self)
matches[]
for filename in fnmatch.filter(
os.listdir(os.path.join(settings.STATIC_ROOT_DIR,'images')), self.name + '*.jpg'):
matches.append(
os.path.join(settings.STATIC_URL, 'images', os.path.split(filename)[1]))
return matches
This method works fine, but doesn't leave me quite satisfied. Here are the reasons:
For development mode I am required to introduce a new variable called STATIC_ROOT_DIR, to obtain the path of the static files folder. I would like to use a consistent way to reference the static root folder for development and production. How can I achieve this? I would like to avoid development mode hackery as much as possible.
I have to build a URL by joining the static_url path with other strings that will eventually make up the URL for this static file. Is there a better way to construct URLs? Maybe some library function?
Last but not least: Is it good practice to do this in a model? Or is such a task better be done by a view?
There is a STATIC_ROOT variable in settings.py. Why not use it?
Personally, I follow your way - just concatenating paths. But just found a function for that:
from django.contrib.staticfiles.templatetags.staticfiles import static
print static('yourfile.jpg')
It works for me.
I think model is a good place for it. You store files in filesystem like you store model data in database. In other words, both of these are examples of storage which is a model level thing.
I would like to throw in another one:
Using STATIC_ROOT will break if you host your files externally.
You can use the django-storage-backend yourself (untested, just written):
from django.core.files.storage import get_storage_class
from django.conf import settings
def getImages(self)
static_storage = get_storage_class(settings.STATICFILES_STORAGE)()
directories, files = static_storage.listdir('images')
return [
static_storage.url('images/' + file)
for file in files
if file.startswith(self.name) and file.endswith('.jpg')
]
This will even return the correct URL if you use CachedStaticFileStorage or S3BotoStorage (from django-storages). And this will also be fine if you are in dev-mode.

Save FileField file uploads someplace other than MEDIA_ROOT?

Can I use FileField to handle a file, but store it someplace not under MEDIA_ROOT/MEDIA_URL but somewhere else entirely.
Seeking is to ensure it is not downloadable; although denying read permissions would do the trick here's hoping for something better... like a different directory altogether.
There are a few ways to do this.
First, you can take a look at handling uploaded files from the Django docs. If you read it over, basically you can handle the upload of the file within your view in the same part where you are processing your form.
Another option, and one which I think would be better is to use a custom file storage system. You could do this very simply using the existing one as a base but simply change the location, then use it as an argument in your FileField. For example:
from django.core.files.storage import FileSystemStorage
my_store = FileSystemStorage(location='/some/other/dir')
class SomeModel(models.Model):
file = models.FileField(storage=my_store)
Hope that helps!
Considering that the only actual use of MEDIA_ROOT in all of Django is to determine where uploaded files are stored, seems like it would make more sense to just point MEDIA_ROOT where you want your uploaded files, and then use a different setting for the path to your static assets. This is the approach taken by Pinax and django-staticfiles, which use STATIC_URL and STATIC_ROOT settings.
Note that even the documentation page on serving static assets in development no longer recommends using MEDIA_ROOT for that purpose, it demonstrates using your own STATIC_DOC_ROOT setting.