управление версиями статических файлов django

Я работаю над универсальным решением проблемы со статическими файлами и обновлениями в нем

пример: допустим, был сайт с/static / styles.css файл - и сайт использовался долгое время-поэтому многие посетители кэшировали этот файл в браузере

теперь мы делаем изменения в этом файле css и обновление на сервере, но у некоторых пользователей все еще есть старая версия (несмотря на дату модификации, возвращенную сервером)

очевидное решение-добавьте некоторую версию в файл / статические / стили.css?v=1.1

но в этом случае разработчик должен отслеживать изменения в этом файле и вручную увеличивать версию

решение 2-подсчитайте md5 хэш файла и добавьте в url /static/styels.в CSS/?v={mdp5hashvalue}

что выглядит намного лучше, но md5 должен быть рассчитан как-то автоматически..

они возможно, как я это вижу-создать некоторый тег шаблона, как это

{% static_file  "style.css" %}

чему

<link src="/static/style.css?v=md5hash">

НО, Я не хочу, чтобы этот тег вычислял md5 при каждой загрузке страницы, и я не хочу хранить хэш в django-кэше, потому что тогда нам придется очистить файл после обновления..

какие мысли ?

9 ответов


Django 1.4 теперь включает в себя CachedStaticFilesStorage что делает именно то, что вам нужно (ну... почти).

вы используете его с manage.py collectstatic задач. Все статические файлы собираются из ваших приложений, как обычно, но этот менеджер хранения также создает копию каждого файла с хэшем MD5, добавленным к имени. Например, скажем, у вас есть css/styles.css файл, он также создаст что-то вроде css/styles.55e7cbb9ba48.css.

конечно, как вы отметили, проблема это то, что вы не хотите, чтобы ваши представления и шаблоны вычисляли хэш MD5 все время, чтобы узнать соответствующие URL-адреса для создания. Решение заключается в кэшировании. Хорошо, вы попросили решение без кэширования, извините, поэтому я сказал почти. Но на самом деле нет причин отказываться от кэширования. CachedStaticFilesStorage использует определенный кэш с именем staticfiles. По умолчанию, он будет использовать существующую систему кэша, и вуаля! Но если вы не хотите использовать ваш обычный кэш, возможно, потому, что это распределенный memcache и вы хотите избежать накладных расходов сетевых запросов только для получения статических имен файлов, то вы можете настроить определенный кэш ОЗУ только для 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'
  }
}

Я бы предложил использовать что-то вроде django-компрессор. В дополнение к автоматической обработке этого типа материалов для вас он также автоматически объединит и уменьшит ваши файлы для быстрой загрузки страницы.

даже если вы не используете его полностью, вы можете проверить их код для руководства в настройке чего-то подобного. Это было лучше проверено, чем все, что вы когда-либо получите от простого ответа StackOverflow.


Я использую свой собственный templatetag, который добавляет дату изменения файла в url:https://bitbucket.org/ad3w/django-sstatic


изобретает колесо и создает собственную реализацию, что плохо? Кроме того, я хотел бы, чтобы код низкого уровня (например, nginx) служил моим статическим файлам в производстве вместо приложения python, даже с бэкэндом. И еще одно: я бы хотел, чтобы ссылки оставались неизменными после пересчета, поэтому браузер извлекает только новые файлы. Так что здесь моя точка зрения:

шаблон.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')

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)

Примечание, чтобы применить изменения, приложение uwsgi (чтобы быть конкретным процессом) должно быть перезапущено.


главное преимущество этого решения: вам не нужно ничего изменять в шаблонах.

это добавит версию сборки в STATIC_URL, а затем веб-сервер удалит его с помощью Rewrite правило.

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)

таким образом, окончательный url будет, например, следующим:

/static/version010/style.css

и тогда у Nginx есть правило, чтобы переписать его обратно в /static/style.css

location /static {
    alias /var/www/website/static/;
    rewrite ^(.*)/version([\.0-9]+)/(.*)$ /;
}

как насчет того, что у вас всегда есть параметр 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/.


существует обновление для кода @deathangel908. Теперь он хорошо работает и с хранилищем S3 (и с любым другим хранилищем, я думаю). Разница заключается в использовании статического хранилища файлов для получения содержимого файла. Оригинал не работает на 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)

простой templatetag vstatic это создает версионные статические файлы urls, которые расширяют поведение 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 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 вызов get_current_commit_hash(), поэтому это будет рассчитано только один раз:

STATIC_VERSION = get_current_commit_hash()

Джанго 1.7 добавил ManifestStaticFilesStorage, лучшая альтернатива CachedStaticFilesStorage это не использует систему кэша и решает проблему хэша, вычисляемого во время выполнения.

вот выдержка из документации:

CachedStaticFilesStorage не рекомендуется - почти во всех случаях ManifestStaticFilesStorage является лучшим выбором. Существует несколько штрафов за производительность, когда используя CachedStaticFilesStorage с кэша требует хеширование файлов во время выполнения. Удаленное хранилище файлов требует нескольких обходов, чтобы хэшировать файл на пропуске кэша, так как требуется несколько обращений к файлам, чтобы убедиться, что хэш файла верен в случае вложенных путей к файлам.

чтобы использовать это, просто добавьте следующую строку settings.py:

STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

и затем, run python manage.py collectstatic; Он добавит MD5 к имени каждого статического файла.