django 静态文件版本控制

Posted

技术标签:

【中文标题】django 静态文件版本控制【英文标题】:django static files versioning 【发布时间】:2012-02-26 04:23:58 【问题描述】:

我正在研究解决静态文件和更新问题的通用解决方案。

示例:假设有一个带有 /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-cache 中,因为那样我们将不得不在更新文件后清除...

有什么想法吗?

【问题讨论】:

这个问题已经有很多解决方案了,从那里开始,例如。 djangopackages.com/grids/g/static-builders 正如@ChrisPratt 建议的那样! 【参考方案1】:

您的 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/

【讨论】:

很好的解决方案。无需任何第三方模块即可轻松实现。【参考方案2】:

我建议使用django-compressor 之类的东西。除了自动为您处理此类内容外,它还会自动合并和缩小您的文件以实现快速页面加载。

即使您最终没有完全使用它,您也可以检查他们的代码以获取设置类似内容的指导。它比您从简单的 *** 答案中得到的任何东西都经过更好的审查。

【讨论】:

仅作记录,django-compressor 为此目的使用文件修改时间,类似于@ssbb 的回答中的建议 - github.com/django-compressor/django-compressor/blob/develop/… 即使您在其他地方手动进行 js 压缩,也可以使用此解决方案,方法是使用带有 WHITESPACE_ONLY 设置的 Closure Compiler 等带有 django 的轻量级压缩器,这样您就可以利用静态 /cache 文件命名. 我认为这是 Django 的最佳解决方案。【参考方案3】:

我使用自己的模板标签,将文件修改日期添加到 url:https://bitbucket.org/ad3w/django-sstatic

【讨论】:

与其自己构建路径,不如让django自己去找:from django.contrib.staticfiles.finders import find full_path = find(path)【参考方案4】:

Django 1.4 现在包含 CachedStaticFilesStorage,它可以满足您的需求(嗯...几乎)。

由于 Django 2.2 应该使用 ManifestStaticFilesStorage 而不是 CachedStaticFilesStorage

您将它与manage.py collectstatic 任务一起使用。像往常一样,所有静态文件都是从您的应用程序中收集的,但此存储管理器还会创建每个文件的副本,并在名称后附加 MD5 哈希。例如,假设您有一个css/styles.css 文件,它也会创建类似css/styles.55e7cbb9ba48.css 的内容。

当然,正如您所提到的,问题在于您不希望您的视图和模板一直计算 MD5 哈希以找出要生成的适当 URL。解决方案是缓存。好的,您要求没有缓存的解决方案,对不起,这就是我说几乎的原因。但是没有理由拒绝缓存,真的。 CachedStaticFilesStorage 使用名为 staticfiles 的特定缓存。默认情况下,它将使用您现有的缓存系统,瞧!但是如果你不希望它使用你的常规缓存,可能是因为它是一个分布式内存缓存并且你想避免网络查询的开销只是为了获取静态文件名,那么你可以为staticfiles设置一个特定的 RAM 缓存。 .这比听起来容易:查看this excellent blog post。这是它的样子:

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'
  

【讨论】:

对于未来的读者,this post 帮助我快速简单地实现了这一点【参考方案5】:

重新发明***并创建自己的实现有那么糟糕吗?此外,我希望低级代码(例如 nginx)在生产中而不是 python 应用程序中为我的静态文件提供服务,即使使用后端也是如此。还有一件事:我希望链接在重新计算后保持不变,所以浏览器只获取新文件。所以here's我的观点:

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')

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 应用程序(具体来说是一个进程)。

【讨论】:

这很好,但是这种方法不是让每个请求都计算 md5 吗? 不,结果被放入静态类字段。然后我们尝试从中获取:cls._md5_sum[file] 是的,在我发表评论后注意到了。谢谢。 当我更改 css 文件中的某些内容时如何更改哈希值,因为当前每次更改 css 文件中的内容时哈希值都保持不变? 哈希存储在静态字段中(意味着在内存中),因此您可以重新启动服务器或添加一些通过静态目录中的文件并重新评估新哈希的路由。【参考方案6】:

@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)

【讨论】:

【参考方案7】:

此解决方案的主要优点:您不必修改模板中的任何内容。

这会将构建版本添加到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]+)/(.*)$ $1/$3;

【讨论】:

【参考方案8】:

简单的模板标签 vstatic 创建扩展 Django 行为的版本化静态文件 url:

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 hash,可以使用如下 sn -p (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()

【讨论】:

【参考方案9】:

Django 1.7 添加了ManifestStaticFilesStorage,是CachedStaticFilesStorage 的更好替代方案,不使用缓存系统,解决了运行时计算哈希的问题。

这是摘录from the documentation:

不推荐使用 CachedStaticFilesStorage——在几乎所有情况下,ManifestStaticFilesStorage 都是更好的选择。使用 CachedStaticFilesStorage 时有几个性能损失,因为缓存未命中需要在运行时散列文件。远程文件存储需要多次往返才能在缓存未命中时对文件进行哈希处理,因为在嵌套文件路径的情况下需要多次文件访问以确保文件哈希正确。

要使用它,只需将以下行添加到settings.py

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

然后,运行python manage.py collectstatic;它会将 MD5 附加到每个静态文件的名称中。

【讨论】:

对于希望使用此技术但能够选择哪些文件类型被版本控制的未来读者,我写了an extension给它,允许您通过路径模式将文件列入白名单/黑名单.【参考方案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)

我的 page.html 扩展了我的 base.html 模板,我在其中像这样使用它:

<link rel="stylesheet" type="text/css" href="% static 'style.css' %?v= static_version ">

相当简单,做的工作

【讨论】:

这值得在项目中实现吗?或者它是否有助于解决缓存问题,例如有时 js 或 CSS 文件无法正确应用并需要硬刷新页面?提前致谢。 可能不会。不久前我切换到 ManifestStaticFilesStorage。请参阅上面的接受答案***.com/a/14898868/597548

以上是关于django 静态文件版本控制的主要内容,如果未能解决你的问题,请参考以下文章

Django 中 js 和 css 的版本控制

如何作为一个团队管理版本控制的 Django 迁移?

Gulp:静态资源(css,js)版本控制

Django路由控制

drf版本控制 django缓存

为啥需要将 django 迁移推送到版本控制系统