在 Django 中,我们有
ManifestStaticFilesStorage
用于缓存静态文件,但它可以在 Django 和浏览器之间工作,但我希望在用户和浏览器之间有正确的缓存。我想要:每次更改静态文件时,都会重新计算文件的哈希值,浏览器缓存会失效,并且用户无需 F5 adn 即可看到新的静态文件,无需运行
--collectstatic --no-input
。
我的代码现在无法运行:
settings.py
STATICFILES_STORAGE = 'auth.utils.HashPathStaticFilesStorage'
CACHES = {
'staticfiles': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'staticfiles',
'TIMEOUT': 3600 * 24 * 7,
'MAX_ENTRIES': 100,
}
}
和
auth.utils.py
:
# -*- coding: utf-8 -*-
import time
from hashlib import sha384
from django.conf import settings
from django.core.cache import cache
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
try:
ACCURACY = settings.STATICFILES_HASH_ACCURACY
except AttributeError:
ACCURACY = 12
try:
KEY_PREFIX = settings.STATICFILES_HASH_KEY_PREFIX
except AttributeError:
KEY_PREFIX = 'staticfiles_hash'
class HashPathStaticFilesStorage(ManifestStaticFilesStorage):
"""A static file storage that returns a unique url based on the contents
of the file. When a static file is changed the url will also change,
forcing all browsers to download the new version of the file.
The uniqueness of the url is a GET parameter added to the end of it. It
contains the first 12 characters of the SHA3 sum of the contents of the
file.
Example: {% static "image.jpg" %} -> /static/image.jpg?4e1243
The accuracy of the hash (number of characters used) can be set in
settings.py with STATICFILES_HASH_ACCURACY. Setting this value too low
(1 or 2) can cause different files to get the same hash and is not
recommended. SHA3 hashes are 40 characters long so all accuracy values
above 40 have the same effect as 40.
The values can be cached for faster performance. All keys in the cache have
the prefix specified in STATICFILES_HASH_KEY_PREFIX in setings.py. This
value defaults to 'staticfiles_hash'
"""
@property
def prefix_key(self):
return "%s:%s" % (KEY_PREFIX, 'prefix')
def invalidate_cache(self, nocache=False):
"""Invalidates the cache. Run this when one or more static files change.
If called with nocache=True the cache will not be used.
"""
value = int(time.time())
if nocache:
value = None
cache.set(self.prefix_key, value)
def get_cache_key(self, name):
hash_prefix = cache.get(self.prefix_key)
if not hash_prefix:
return None
key = "%s:%s:%s" % (KEY_PREFIX, hash_prefix, name)
return key
def set_cached_hash(self, name, the_hash):
key = self.get_cache_key(name)
if key:
cache.set(key, the_hash)
def get_cached_hash(self, name):
key = self.get_cache_key(name)
if not key:
return None
the_hash = cache.get(key)
return the_hash
def calculate_hash(self, name):
path = self.path(name)
try:
the_file = open(path, 'rb')
the_hash = sha384(the_file.read()).hexdigest()[:ACCURACY]
the_file.close()
except IOError:
return ""
return the_hash
def get_hash(self, name):
the_hash = self.get_cached_hash(name)
if the_hash:
return the_hash
the_hash = self.calculate_hash(name)
self.set_cached_hash(name, the_hash)
return the_hash
def url(self, name):
base_url = super(HashPathStaticFilesStorage, self).url(name)
the_hash = self.get_hash(name)
if "?" in base_url:
return "%s&%s" % (base_url, the_hash)
return "%s?%s" % (base_url, the_hash)
我只是用这个非常简单的想法
<img src="{{ company.logo.url }}?v={% now 'U' %}" />
使用
?v=
强制使用版本并将版本设置为当前时间戳 {% now 'U' %}
,以便它会随着每个请求而更改
避免用户必须重新加载页面才能获取新静态内容的一种常见且简单的方法是在 HTML 标记中包含静态文件时附加一些可变值,如下所示:
<script src="{% static 'js/library.js' %}?{{ version }}"></script>
这样,当变量version采用不同的值时,浏览器将被迫从服务器下载新版本的静态文件。
您可以使用自定义上下文处理器设置版本,例如从设置中读取项目版本。像这样的东西:
from django.conf import settings
def version(request):
return {
'version': settings.VERSION
}
如果您使用 git 作为 VCS,另一种方法是在将修改推送到服务器时将项目的最后一次提交哈希写入文件中。该文件应采用 Python 可读的格式。这样你就可以使用 git commit hash 作为前面提到的 version 变量。 您可以使用 GIT post-receive hook:
来完成此操作#!/bin/bash
WORKDIR=/path/to/project/
VERSION_MODULE=${WORKDIR}django_project/project/version.py
# for every branch which has been pushed
while read oldrev newrev ref
do
# if branch pushed is master, update version.py file in the django project
if [[ $ref =~ .*/master$ ]]; then
GIT_WORK_TREE=$WORKDIR git checkout -f master
echo "GIT_REF = 'master'" > $VERSION_MODULE
echo "GIT_REV = '$newrev'" >> $VERSION_MODULE
fi
done
那么你的上下文处理器可能是:
from project.version import GIT_REV
def version(request):
return {
'version': GIT_REV[:7]
}
超级简单的想法:更改 Django 设置中的
STATIC_URL
变量。
STATIC_URL = /static/ # before
STATIC_URL = /static/v2/ # after
这将更改所有静态文件的路径,迫使浏览器重新加载内容。
这是@Namgyu Ho 答案的改进:
首先在根目录下创建
__version__
文件,然后编辑settings.py.STATIC_URL
和settings.py.STATIC_ROOT
设置:
with open(os.path.join(BASE_DIR, "__version__")) as f:
VERSION = f.read().strip()
STATIC_URL = f"static/{VERSION}/"
STATIC_ROOT = os.path.join(BASE_DIR, f"static/{VERSION}")
通过这种方式,无需编辑模板中的每个静态文件路径,无需创建特殊的过滤器或标签,生产服务器将通过
collectstatic
命令获取更改。