django静态文件版本控制

问题描述 投票:30回答:10

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

示例:假设有一个带有/static/styles.css文件的站点 - 并且站点已经使用了很长时间 - 所以很多访问者在浏览器中缓存了这个文件

现在我们在这个css文件中做了更改,并在服务器上更新,但是一些用户仍然有旧版本(尽管服务器返回了修改日期)

显而易见的解决方案 - 在文件/static/styles.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-cache中存储hash,因为那样我们将在更新文件后清除..

有什么想法吗 ?

python django django-staticfiles
10个回答
31
投票

Django 1.4现在包括CachedStaticFilesStorage,它完全符合你的需要(好吧......差不多)。

因为Django 2.2 ManifestStaticFilesStorage应该用来代替CachedStaticFilesStorage

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

当然,正如您所提到的,问题是您不希望您的视图和模板一直计算MD5哈希值,以找出要生成的相应URL。解决方案是缓存。好的,你问了一个没有缓存的解决方案,对不起,这就是我几乎说的原因。但是真的没有理由拒绝缓存。 CachedStaticFilesStorage使用名为staticfiles的特定缓存。默认情况下,它将使用您现有的缓存系统,并且vo!但是如果你不想让它使用常规缓存,也许是因为它是一个分布式内存缓存而你想避免网络查询的开销只是为了得到静态文件名,那么你可以为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'
  }
}

0
投票

我在所有视图中使用全局基本上下文,我将静态版本设置为毫秒时间(这样,每次重新启动应用程序时它都是新版本):

STATIC_VERSION = get_current_commit_hash()

我的page.html扩展了我的base.html模板,我在这里使用它:

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

相当简单并完成工作


14
投票

我建议使用像django-compressor这样的东西。除了自动处理这类东西外,它还会自动组合和缩小文件以加快页面加载速度。

即使您最终没有完全使用它,您也可以检查其代码以获得类似设置的指导。它比你从简单的StackOverflow答案得到的任何东西都要好。


8
投票

我使用自己的模板标签,将文件修改日期添加到url:qazxsw poi


6
投票

是重新发明轮子并创建自己的实施那么糟糕?此外,我希望低级代码(例如nginx)在生产中而不是python应用程序中提供我的静态文件,即使使用后端也是如此。还有一件事:重新计算后,我希望链接保持不变,因此浏览器只提取新文件。所以https://bitbucket.org/ad3w/django-sstatic矿的观点:

template.html:

here's

出html:

{% load md5url %}
<script src="{% md5url "example.js" %}"/>

settings.朋友:

static/example.js?v=5e52bfd3

APPNAME / templatetags / md5url.py:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(PROJECT_DIR, 'static')

注意,要应用更改,应重新启动uwsgi应用程序(特定于进程)。


6
投票

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) 添加了Django 1.7ManifestStaticFilesStorage的一个更好的替代品,它不使用缓存系统并解决了在运行时计算哈希的问题。

这是摘录CachedStaticFilesStorage

建议不要使用CachedStaticFilesStorage - 几乎在所有情况下,ManifestStaticFilesStorage都是更好的选择。使用CachedStaticFilesStorage时会有一些性能损失,因为缓存未命中需要在运行时散列文件。远程文件存储需要多次往返才能在缓存未命中时散列文件,因为需要多次文件访问以确保在嵌套文件路径的情况下文件散列是正确的。

要使用它,只需将以下行添加到from the documentation

settings.py

然后,运行STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' ;它会将MD5附加到每个静态文件的名称。


2
投票

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

这会将构建版本添加到python manage.py collectstatic中,然后Web服务器将使用STATIC_URL规则将其删除。

settings.朋友

Rewrite

所以最终的网址就是这样的:

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

然后Nginx有一个规则将它重写回/static/version010/style.css

/static/style.css

1
投票

你的URL中的URL参数总是带有版本,并且只要你有主要版本,就可以更改URL参数中的版本。即使在DNS中。因此,如果location /static { alias /var/www/website/static/; rewrite ^(.*)/version([\.0-9]+)/(.*)$ $1/$3; } 加载www.yourwebsite.com然后在主要版本后浏览器应该加载www.yourwebsite.com/index.html?version=1.0

我想这与您的解决方案类似1.您可以跟踪整个目录而不是跟踪文件吗?例如,速度比www.yourwebsite.com/index.html?version=2.0你可以做/static/style/css?v=2.0或使其甚至粒状/static-2/style/css


1
投票

@ deathangel908代码有更新。现在它也适用于S3存储(和我认为的任何其他存储)。不同之处在于使用静态文件存储来获取文件内容。原始版本不适用于S3。

APPNAME / templatetags / md5url.py:

/static/style/cssv2/

1
投票

简单的templatetag 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) 创建了扩展Django行为的版本化静态文件URL:

vstatic

如果要自动将STATIC_VERSION设置为当前git commit hash,可以使用以下代码段(必要时调整Python3代码):

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

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()
© www.soinside.com 2019 - 2024. All rights reserved.