38

我正在为静态文件和其中的更新问题开发一些通用解决方案。

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

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

显而易见的解决方案是向文件添加一些版本,/static/styles.css?v=1.1但在这种情况下,开发人员必须跟踪此文件中的更改并手动增加版本

第二种解决方案是计算文件的 md5 哈希并将其添加到/static/styels.css/?v={mdp5hashvalue}看起来更好的 url 中,但 md5 应该以某种方式自动计算。

他们可能的方式我看到它 - 创建一些这样的模板标签

{% static_file  "style.css" %}

这将呈现

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

但是,我不希望这个标签在每次页面加载时都计算 md5,我也不想在 django-cache 中存储哈希,因为这样我们就必须在更新文件后清除......

有什么想法吗 ?

4

10 回答 10

40

Django 1.4 现在包含CachedStaticFilesStorage了您所需要的功能(嗯...几乎)。

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

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

当然,正如您所提到的,问题在于您不希望您的视图和模板一直计算 MD5 哈希以找出要生成的适当 URL。解决方案是缓存。好的,您要求提供不带缓存的解决方案,对不起,这就是我说差不多的原因。但是没有理由拒绝缓存,真的。 CachedStaticFilesStorage使用名为staticfiles. 默认情况下,它将使用您现有的缓存系统,瞧!但是如果你不希望它使用你的常规缓存,可能是因为它是一个分布式内存缓存,并且你想避免网络查询的开销只是为了获取静态文件名,那么你可以为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'
  }
}
于 2013-02-15T16:21:23.283 回答
16

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

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

于 2012-02-03T15:52:50.923 回答
8

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

于 2012-03-16T18:02:12.470 回答
8

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

模板.html:

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

出html:

static/example.js?v=5e52bfd3

设置.py:

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

应用程序名/模板标签/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 应用程序(具体来说是一个进程)。

于 2016-01-06T22:29:26.670 回答
7

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

这是文档的摘录:

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

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

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

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

于 2018-07-18T10:25:05.833 回答
2

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

于 2012-02-03T15:08:48.137 回答
1

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

应用程序名/模板标签/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)
于 2016-04-03T03:08:02.917 回答
1

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

这会将构建版本添加到 中STATIC_URL,然后网络服务器将使用Rewrite规则将其删除。

设置.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;
}
于 2016-04-03T03:21:00.177 回答
1

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 提交哈希,可以使用以下代码段(必要时调整 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.pycall get_current_commit_hash(),所以这将只计算一次:

STATIC_VERSION = get_current_commit_hash()
于 2017-05-03T14:28:55.320 回答
1

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

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

相当简单并且可以完成工作

于 2019-04-09T01:58:27.987 回答