Версии статических файлов в Django - PullRequest
31 голосов
/ 03 февраля 2012

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

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

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

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

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

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

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

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

{% static_file  "style.css" %}

, который будет отображать

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

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

есть мысли?

Ответы [ 10 ]

31 голосов
/ 15 февраля 2013

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'
  }
}
14 голосов
/ 03 февраля 2012

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

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

8 голосов
/ 16 марта 2012

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

6 голосов
/ 18 июля 2018

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

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

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

Для использованияпросто добавьте следующую строку в settings.py:

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

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

6 голосов
/ 07 января 2016

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

2 голосов
/ 03 апреля 2016

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

Это добавит версию сборки в STATIC_URL, а затем веб-сервер удалит ее с помощью RewriteПравило/static/style.css

location /static {
    alias /var/www/website/static/;
    rewrite ^(.*)/version([\.0-9]+)/(.*)$ $1/$3;
}
1 голос
/ 03 мая 2017

Простой шаблонный тег 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, вы можете использовать следующий фрагмент (при необходимости откорректируйте код 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()
1 голос
/ 03 апреля 2016

Есть обновление для кода @ 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)
1 голос
/ 03 февраля 2012

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

0 голосов
/ 09 апреля 2019

Я использую глобальный базовый контекст во всех моих представлениях, где я устанавливаю статическую версию равной миллисекунде (таким образом, она будет новой версией при каждом перезапуске приложения):

# 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 }}">

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

...