Версии статических файлов django
Я работаю над универсальным решением проблемы со статическими файлами и обновлениями в нем.
Пример: допустим, что был сайт с файлом /static/styles.css
- и сайт использовался в течение длительного времени - поэтому многие посетители кэшировали этот файл в браузере
Сейчас мы делаем изменения в этом CSS файле и обновляем на сервере, но у некоторых пользователей все еще есть старая версия (несмотря на дату модификации, возвращаемую сервером)
Очевидное решение заключается в добавлении какой-либо версии в файл /static/styles.css?v=1.1
, но в этом случае разработчик должен отслеживать изменения в этом файле и вручную увеличивать версию
Второе решение - подсчитать хэш md5 файла и добавить его в URL /static/styels.css/?v={mdp5hashvalue}
, который выглядит намного лучше, но md5 должен каким-то образом рассчитываться автоматически.
Возможно, я так понимаю - создайте шаблонный тег, подобный этому
{% static_file "style.css" %}
который будет рендерить
<link src="/static/style.css?v=md5hash">
НО, я не хочу, чтобы этот тег вычислял md5 при каждой загрузке страницы, и я не хочу хранить хеш в django-кеше, потому что тогда нам придется очищать после обновления файла...
есть идеи?
Ответы
Ответ 1
Django 1.4 теперь включает CachedStaticFilesStorage
который делает именно то, что вам нужно (ну... почти).
Так как Django 2.2 ManifestStaticFilesStorage
следует использовать вместо CachedStaticFilesStorage
.
Вы используете его с задачей manage.py collectstatic
. Все статические файлы собираются из ваших приложений, как обычно, но этот менеджер хранилища также создает копию каждого файла с добавленным к имени хешем MD5. Например, скажем, у вас есть файл css/styles.css
, он также создаст что-то вроде css/styles.55e7cbb9ba48.css
.
Конечно, как вы упомянули, проблема в том, что вы не хотите, чтобы ваши представления и шаблоны, вычисляющие хеш MD5, все время находили нужные URL-адреса для генерации. Решение кеширования. Хорошо, вы просили решение без кеширования, извините, вот почему я почти сказал. Но на самом деле нет причин отказываться от кеширования. CachedStaticFilesStorage
использует определенный кэш с именем staticfiles
. По умолчанию он будет использовать вашу существующую систему кеша, и вуаля! Но если вы не хотите, чтобы он использовал ваш обычный кэш, возможно, потому что это распределенный memcache и вы хотите избежать накладных расходов сетевых запросов только для получения статических имен файлов, тогда вы можете настроить определенный кэш RAM только для staticfiles
. Это проще, чем кажется: посмотрите этот отличный пост в блоге. Вот как это будет выглядеть:
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'
}
}
Ответ 2
Я бы предложил использовать что-то вроде django-compressor. В дополнение к автоматической обработке этого типа вещей для вас он также автоматически объединяет и уменьшает ваши файлы для быстрой загрузки страницы.
Даже если вы не используете его полностью, вы можете проверить их код для руководства по настройке чего-то подобного. Это было лучше проверено, чем все, что вы когда-либо получили от простого ответа StackOverflow.
Ответ 3
Я использую свой собственный templatetag, который добавляет дату изменения файла для URL: https://bitbucket.org/ad3w/django-sstatic
Ответ 4
Разве это плохо - изобретать велосипед и создавать собственную реализацию? Кроме того, я хотел бы, чтобы низкоуровневый код (например, nginx) служил моим статическим файлам в производственной среде, а не в приложении на python, даже с бэкэндом. И еще одна вещь: я хотел бы, чтобы ссылки оставались неизменными после пересчета, поэтому браузер выбирает только новые файлы. Итак, вот моя точка зрения:
template.html:
{% load md5url %}
<script src="{% md5url "example.js" %}"/>
вне HTML:
static/example.js?v=5e52bfd3
settings.py:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(PROJECT_DIR, 'static')
имя_приложения /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)
Обратите внимание: чтобы применить изменения, приложение uwsgi (а точнее процесс) должно быть перезапущено.
Ответ 5
Django 1.7 добавил ManifestStaticFilesStorage
, лучшую альтернативу CachedStaticFilesStorage
, которая не использует систему кэширования и решает проблему хеша, вычисляемого во время выполнения.
Вот выдержка из документации:
CachedStaticFilesStorage не рекомендуется - почти во всех случаях ManifestStaticFilesStorage является лучшим выбором. При использовании CachedStaticFilesStorage есть несколько проблем с производительностью, поскольку из-за промахов кэша во время выполнения требуются хэшированные файлы. Для удаленного хранения файлов требуется несколько циклов обхода для хеширования файла при отсутствии кеша, так как требуется несколько обращений к файлу, чтобы гарантировать, что хеш файла является правильным в случае вложенных путей к файлам.
Чтобы использовать его, просто добавьте следующую строку в settings.py
:
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
А затем запустите python manage.py collectstatic
; он добавит MD5 к имени каждого статического файла.
Ответ 6
Главное преимущество этого решения: вам не нужно ничего изменять в шаблонах.
Это добавит версию сборки в STATIC_URL
, а затем веб-сервер удалит ее с помощью правила Rewrite
.
settings.py
# build version, it increased with each build
VERSION_STAMP = __versionstr__.replace(".", "")
# rewrite static url to contain the number
STATIC_URL = '%sversion%s/' % (STATIC_URL, VERSION_STAMP)
Таким образом, конечным URL-адресом будет, например, следующее:
/static/version010/style.css
И тогда у Nginx есть правило, чтобы переписать его обратно на /static/style.css
location /static {
alias /var/www/website/static/;
rewrite ^(.*)/version([\.0-9]+)/(.*)$ $1/$3;
}
Ответ 7
Как насчет того, что у вас всегда есть параметр URL в вашем URL-адресе с версией, и всякий раз, когда у вас есть основной выпуск, вы изменяете версию в своем URL-параметре. Даже в DNS. Поэтому, если www.yourwebsite.com
загружает www.yourwebsite.com/index.html?version=1.0
, то после основной версии браузер должен загрузить www.yourwebsite.com/index.html?version=2.0
Я думаю, это похоже на ваше решение 1. Вместо отслеживания файлов вы можете отслеживать целые каталоги? Например, ratehr чем /static/style/css?v=2.0
, вы можете сделать /static-2/style/css
или сделать его даже зернистым /static/style/cssv2/
.
Ответ 8
Обновлено для кода @deathangel908. Теперь он хорошо работает и с хранилищем S3 (и с любым другим хранилищем, которое я думаю). Разница заключается в использовании статического хранилища файлов для получения содержимого файла. Оригинал не работает на S3.
имя_приложения/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)
Ответ 9
Простой templatetag vstatic
, который создает URL-адреса с версиями статических файлов, которые расширяют поведение Django:
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
Если вы хотите автоматически установить STATIC_VERSION в текущий хэш-код git commit, вы можете использовать следующий фрагмент кода (при необходимости отредактируйте код Python3):
import subprocess
def get_current_commit_hash():
try:
return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).strip().decode('utf-8')
except:
return ''
В settings.py
вызов get_current_commit_hash()
, поэтому это будет вычисляться только один раз:
STATIC_VERSION = get_current_commit_hash()
Ответ 10
Я использую глобальный базовый контекст во всех моих представлениях, где я устанавливаю статическую версию равной миллисекунде (таким образом, она будет новой версией при каждом перезапуске приложения):
# 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 расширяет мой шаблон base.html, где я использую его следующим образом:
<link rel="stylesheet" type="text/css" href="{% static 'style.css' %}?v={{ static_version }}">
довольно просто и делает работу