42👍
Django 1.4 now includes CachedStaticFilesStorage
which does exactly what you need (well… almost).
Since Django 2.2 ManifestStaticFilesStorage
should be used instead of CachedStaticFilesStorage
.
You use it with the manage.py collectstatic
task. All static files are collected from your applications, as usual, but this storage manager also creates a copy of each file with the MD5 hash appended to the name. So for example, say you have a css/styles.css
file, it will also create something like css/styles.55e7cbb9ba48.css
.
Of course, as you mentioned, the problem is that you don’t want your views and templates calculating the MD5 hash all the time to find out the appropriate URLs to generate. The solution is caching. Ok, you asked for a solution without caching, I’m sorry, that’s why I said almost. But there’s no reason to reject caching, really. CachedStaticFilesStorage
uses a specific cache named staticfiles
. By default, it will use your existing cache system, and voilà! But if you don’t want it to use your regular cache, perhaps because it’s a distributed memcache and you want to avoid the overhead of network queries just to get static file names, then you can setup a specific RAM cache just for staticfiles
. It’s easier than it sounds: check out this excellent blog post. Here’s what it would look like:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '127.0.0.1:11211',
},
'staticfiles': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'staticfiles-filehashes'
}
}
17👍
I would suggest using something like django-compressor. In addition to automatically handling this type of stuff for you, it will also automatically combine and minify your files for fast page load.
Even if you don’t end up using it in entirety, you can inspect their code for guidance in setting up something similar. It’s been better vetted than anything you’ll ever get from a simple StackOverflow answer.
- [Django]-How can I handle Exceptions raised by dango-social-auth?
- [Django]-Django: Why do some model fields clash with each other?
- [Django]-Django REST Framework custom fields validation
8👍
I use my own templatetag which add file modification date to url: https://bitbucket.org/ad3w/django-sstatic
- [Django]-When to use or not use iterator() in the django ORM
- [Django]-Fastest way to get the first object from a queryset in django?
- [Django]-Pycharm error Django is not importable in this environment
8👍
Is reinventing the wheel and creating own implementation that bad? Furthermore I would like low level code (nginx for example) to serve my staticfiles in production instead of python application, even with backend. And one more thing: I’d like links stay the same after recalculation, so browser fetches only new files. So here’s mine point of view:
template.html:
{% load md5url %}
<script src="{% md5url "example.js" %}"/>
out html:
static/example.js?v=5e52bfd3
settings.py:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(PROJECT_DIR, 'static')
appname/templatetags/md5url.py:
import hashlib
import threading
from os import path
from django import template
from django.conf import settings
register = template.Library()
class UrlCache(object):
_md5_sum = {}
_lock = threading.Lock()
@classmethod
def get_md5(cls, file):
try:
return cls._md5_sum[file]
except KeyError:
with cls._lock:
try:
md5 = cls.calc_md5(path.join(settings.STATIC_ROOT, file))[:8]
value = '%s%s?v=%s' % (settings.STATIC_URL, file, md5)
except IsADirectoryError:
value = settings.STATIC_URL + file
cls._md5_sum[file] = value
return value
@classmethod
def calc_md5(cls, file_path):
with open(file_path, 'rb') as fh:
m = hashlib.md5()
while True:
data = fh.read(8192)
if not data:
break
m.update(data)
return m.hexdigest()
@register.simple_tag
def md5url(model_object):
return UrlCache.get_md5(model_object)
Note, to apply changes an uwsgi application (to be specific a process) should be restarted.
- [Django]-LEFT JOIN Django ORM
- [Django]-Django migration fails with "__fake__.DoesNotExist: Permission matching query does not exist."
- [Django]-TypeError: login() takes 1 positional argument but 2 were given
8👍
Django 1.7 added ManifestStaticFilesStorage
, a better alternative to CachedStaticFilesStorage
that doesn’t use the cache system and solves the problem of the hash being computed at runtime.
Here is an excerpt from the documentation:
CachedStaticFilesStorage isn’t recommended – in almost all cases ManifestStaticFilesStorage is a better choice. There are several performance penalties when using CachedStaticFilesStorage since a cache miss requires hashing files at runtime. Remote file storage require several round-trips to hash a file on a cache miss, as several file accesses are required to ensure that the file hash is correct in the case of nested file paths.
To use it, simply add the following line to settings.py
:
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
And then, run python manage.py collectstatic
; it will append the MD5 to the name of each static file.
- [Django]-Where can I find the error logs of nginx, using FastCGI and Django?
- [Django]-Django ORM – objects.filter() vs. objects.all().filter() – which one is preferred?
- [Django]-Why does django run everything twice?
2👍
How about you always have a URL Parameter in your URL with a version and whenever you have a major release you change the version in your URL Parameter. Even in the DNS. So if www.yourwebsite.com
loads up www.yourwebsite.com/index.html?version=1.0
then after the major release the browser should load www.yourwebsite.com/index.html?version=2.0
I guess this is similar to your solution 1. Instead of tracking files can you track whole directories? For example ratehr than /static/style/css?v=2.0
can you do /static-2/style/css
or to make it even granular /static/style/cssv2/
.
- [Django]-Django DetailView – how to use 'request' in get_context_data
- [Django]-Using JSON in django template
- [Django]-How to access request body when using Django Rest Framework and avoid getting RawPostDataException
1👍
There is an update for @deathangel908 code. Now it works well with S3 storage also (and with any other storage I think). The difference is using of static file storage for getting file content. Original doesn’t work on S3.
appname/templatetags/md5url.py:
import hashlib
import threading
from django import template
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
register = template.Library()
class UrlCache(object):
_md5_sum = {}
_lock = threading.Lock()
@classmethod
def get_md5(cls, file):
try:
return cls._md5_sum[file]
except KeyError:
with cls._lock:
try:
md5 = cls.calc_md5(file)[:8]
value = '%s%s?v=%s' % (settings.STATIC_URL, file, md5)
except OSError:
value = settings.STATIC_URL + file
cls._md5_sum[file] = value
return value
@classmethod
def calc_md5(cls, file_path):
with staticfiles_storage.open(file_path, 'rb') as fh:
m = hashlib.md5()
while True:
data = fh.read(8192)
if not data:
break
m.update(data)
return m.hexdigest()
@register.simple_tag
def md5url(model_object):
return UrlCache.get_md5(model_object)
- [Django]-How can I tell the Django ORM to reverse the order of query results?
- [Django]-Django model inheritance: create sub-instance of existing instance (downcast)?
- [Django]-Context in nested serializers django rest framework
1👍
The major advantage of this solution: you dont have to modify anything in the templates.
This will add the build version into the STATIC_URL
, and then the webserver will remove it with a Rewrite
rule.
settings.py
# build version, it's increased with each build
VERSION_STAMP = __versionstr__.replace(".", "")
# rewrite static url to contain the number
STATIC_URL = '%sversion%s/' % (STATIC_URL, VERSION_STAMP)
So the final url would be for example this:
/static/version010/style.css
And then Nginx has a rule to rewrite it back to /static/style.css
location /static {
alias /var/www/website/static/;
rewrite ^(.*)/version([\.0-9]+)/(.*)$ $1/$3;
}
- [Django]-Django.db.utils.IntegrityError: duplicate key value violates unique constraint "django_migrations_pkey"
- [Django]-How to get an array in Django posted via Ajax
- [Django]-Django Query That Get Most Recent Objects From Different Categories
1👍
Simple templatetag vstatic
that creates versioned static files urls that extends Django’s behaviour:
from django.conf import settings
from django.contrib.staticfiles.templatetags.staticfiles import static
@register.simple_tag
def vstatic(path):
url = static(path)
static_version = getattr(settings, 'STATIC_VERSION', '')
if static_version:
url += '?v=' + static_version
return url
If you want to automatically set STATIC_VERSION to the current git commit hash, you can use the following snippet (Python3 code adjust if necessary):
import subprocess
def get_current_commit_hash():
try:
return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).strip().decode('utf-8')
except:
return ''
At settings.py
call get_current_commit_hash()
, so this will be calculated only once:
STATIC_VERSION = get_current_commit_hash()
- [Django]-Django models without database
- [Django]-Cleanest & Fastest server setup for Django
- [Django]-How to automatically destroy django test database
1👍
I use a global base context in all my views, where I set the static version to be the millisecond time (that way, it will be a new version every time I restart my application):
# global base context
base_context = {
"title": settings.SITE_TITLE,
"static_version": int(round(time.time() * 1000)),
}
# function to merge context with base context
def context(items: Dict) -> Dict:
return {**base_context, **items}
# view
def view(request):
cxt = context({<...>})
return render(request, "page.html", cxt)
my page.html extends my base.html template, where I use it like this:
<link rel="stylesheet" type="text/css" href="{% static 'style.css' %}?v={{ static_version }}">
fairly simple and does the job
- [Django]-Django returning HTTP 301?
- [Django]-Uncaught TypeError: Cannot read property 'ownerDocument' of undefined
- [Django]-Django template can't see CSS files