Referencing Django's versioned static resources - django

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'

Related

django: how to include a static file in a template?

In a Django project, I would like to include a static file in a template. The following is the the project structure:
proj/
main/
static/
main/
js/
main.js
news/
templates/
news/
news.html
In news.html, I would like to include the main.js with the following hypothetical format:
{% load staticfiles %}
...
{% include {% static 'main/js/main.js' %} %}
How do I do it?
The include directive only searches file in template directories. What you could do (although I wouldn't) is to change your settings to also include the static files:
TEMPLATE_DIRS = [
...
"path/to/your/statics",
]
I have a couple of ideas.
The easiest thing to do would be to ensure you have the filesystem loader enabled, and you include the relevant directory containing static files in TEMPLATES[0]['DIRS'] (previously TEMPLATE_DIRS). The caveats are that it will not pick up static files contained inside your installed applications automatically, unless you list them in DIRS, too. Another option would be to use STATIC_ROOT, but that will only work if you run collectstatic every time you change the static file, which you normally don't have to do in development. Another pitfall is that it will only work with local static storage, not if you use any CDN or otherwise host your static files on a different host / network / whatever.
The other thing you can do is to write your own template loader which will use the static files API to load statics as templates. This is a bit more involved, but it should be able to access static files regardless of how they are stored and served.
Should you choose either option, you still have to be careful. For instance, you'll have to ensure that the static files you're including as templates are safe to include into HTML or whatever other context you're using them in. There will be no escaping going on.
In case you're trying to optimize the number of requests the client has to make, there are better ways. You're most likely better off implementing some pipeline that will pre-process and minify your static files. Including any significant chunk of CSS / JS into the HTML will make it impossible for clients to take advantage of caching, forcing them to re-download the same static content over and over again, likely impacting the performance negatively.
Edit: Since you want to just include some dynamic JavaScript variables, that's not a static file at all. What you really want is to create a template containing JavaScript code, and not mess with handling static files as templates. There is no rule that would say every javascript needs to be a static file. If it's dynamic, it's not a static file.
For the record, this was a typical instance of the XY problem. Problem X was dynamically assigning values to JavaScript variables when rendering templates. You came up with including static files in templates as a potential solution, but then got stuck with problem Y, which was that you didn't know how to include static files in templates.

Django: Make JS source maps compatible with staticfiles filename hashing

In our Django project we are using Gulp to compile our assets, then UglifyJS to minify them. During this whole process we are generating sourcemaps, which appear to be working correctly.
The problem comes when we use the Django static template tag to include our minified files. Say we have a minified JS file called ourapp.min.js. In our template we would write:
<script src="{% static 'ourapp.min.js %}"></script>
which would be compiled into something like:
<script src="/ourstaticroot/ourapp.min.0123456789ab.js"></script>
(where 0123456789ab is a hash of the file contents)
The problem now is that, although the file has been renamed, our sourcemap still points to the old filename, so suddenly becomes invalid. If we then need to debug this page (say, using Sentry) it cannot find the source file and we are left to debug the uglified file instead, which becomes much more of a task.
Does anyone know of a good way to get around this? We would like to continue using Gulp for our assets, and also continue using the hashed filenames, as this prevents issues caused by caching of stale asset files.
it does not really matter, since the original files are being kept. So if your file points to a map without a hash it should be served by Django. Of course you need to be careful with long term expiration headers. Should you be using whitenoise are are fine, since they handle expiration properly and long term expiration headers are only set on hashed files.
Cheers
-Joe
I see two options available to you. It's not clear where exactly the cache busting suffix gets added to your files, but at that point you could:
Use some string processing, to manipulate the assets and set their sourceMapUrl with a url that is generated by your system and includes the expected suffix //# sourceMappingURL=/path/to/file.js.<suffix>.map. This could be a simple bash command in a gulp step (if that's where it happens)
Alternatively, browsers will also accept an http header SourceMap: /path/to/file.js.map which you could instruct your asset server to set for files with headers. This may be more difficult depending on your infrastructure.

Why do I need to define both STATIC_URL and STATICFILES_DIRS?

If I define only one, the page displays without any CSS/JS. If I define both, the pages load fine. Why is that? I thought STATICFILES_DIRS is extraneous?
There's an answer here but it doesn't answer why I need both. It simply says what both does, which in my eyes is the same thing. What's the difference?
List of definitions for STATIC/STATIC_ROOT/STATICFILES_DIR
From what I have scoured on StackOverflow, I think I can sum it up as follows:
STATIC_ROOT is referenced as the static server, be it the default Django static server or the static server made on Heroku/etc. You point to this in urls.py
STATICFILES_DIR is referencing the stuff within static assets so if there are separate folders, one for CSS, one for JS, etc.
Feel free to add!
STATICFILES_DIRS is extraneous. You only need to use it if you are using a static files directory outside of the static files directories inside of your apps. Without seeing more of your code, and the structure of your project I can't even begin to guess why your pages aren't loading, but you definitely do not need to specify a STATICFILES_DIRS.

Django: preventing direct access to files

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.

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.