Подтвердить что ты не робот

Версии статических файлов 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-кеше, потому что тогда нам придется очищать после обновления файла...

есть идеи?

4b9b3361

Ответ 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 }}">

довольно просто и делает работу