跳转到主要内容

Django模型保存时的URL缓存失效

项目描述

https://img.shields.io/pypi/v/django-dumper.svg?style=flat-square:target:https://pypi.python.org/pypi/django-dumper..image::https://img.shields.io/travis/saulshanabrook/django-dumper.svg?style=flat-square:target:https://travis-ci.org/saulshanabrook/django-dumper/

django-dumper 提供了类似于Django的完整站点缓存,以及基于模型保存的路径缓存失效。它将永久缓存不在 DUMPER_PATH_IGNORE_REGEX 中的每个页面。因此,为了使其有效,所有页面在必要时都必须通过指定在模型中哪些路径应该在模型保存时失效来进行失效处理。

为什么...?

django-dumper 的创建是为了解决一个非常具体的问题。我在尝试减少包含大量图片的页面加载时间时遇到了麻烦。默认情况下,如果您想在模板中渲染图片的URL、宽度和高度,则每个图片会调用存储后端三次。对于像S3这样的远程后端,这会创建漫长且不可靠的页面加载时间。如果您更聪明,创建了 缓存高度和宽度字段,那么可以将此减少到一次调用。这仍然不是包含100+张图片的页面的理想选择。所以我想到,这些图片唯一变化的时候是模型保存时。然后我开始思考,实际上,我的页面唯一变化的时候是模型保存时。当然,我还想尽量在缓存之前让页面加载时间尽可能低,但为什么我要在每个请求上重新渲染这些页面,如果对于每个访问者来说,它们在有人更改模型之前都是相同的呢?

所以我着手开发了一个应用,可以让我做到这一点。它将无限期地缓存每个响应的全部内容。然后,每当模型被保存时,它会基于路径无效化某些响应。例如,如果/ice-cream/页面显示了每个口味的链接,而/ice-cream/<flavor-name>/提供了某个口味的详细信息,那么每次保存口味时,它不仅应该使该口味的详细信息页面无效,还应该使通用列表页面无效。这确实是一个蛮力方法,但我觉得这种方法是安全的。你可能会过度无效化,但如果设置正确,你将永远不会有陈旧的缓存。

这绝对不是一个全能的缓存应用。你网站上渲染的每个页面都必须由模型确定。详细视图和列表视图是模型确定页面的例子。此外,如果你的网站根据请求头(cookie、语言等)进行区分,那么这将不起作用,因为它将向所有访客提供相同的版本。

安装

安装就像这样简单

pip install django-dumper

设置

配置类似于Django的每个站点缓存

您需要将'dumper.middleware.cache.UpdateCacheMiddleware''dumper.middleware.cache.FetchFromCacheMiddleware'添加到您的MIDDLEWARE_CLASSES设置中,如下例所示

MIDDLEWARE_CLASSES = (
    'dumper.middleware.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'dumper.middleware.FetchFromCacheMiddleware',
)

然后,将以下可选设置添加到您的Django设置文件中

  1. DUMPER_CACHE_ALIAS – 要使用的存储缓存别名。默认为'default'

  2. DUMPER_KEY_PREFIX – 如果缓存跨越多个使用相同Django安装的站点,则设置为此站点的名称或其他唯一字符串,以防止键冲突。默认为'dumper.cached_path.'

  3. DUMPER_PATH_IGNORE_REGEX – 如果路径匹配,则这些页面将不会被缓存。默认情况下,不会缓存^/admin/的admin页面

用法

要在模型保存和删除时使某些路径无效,请使用dumper.register注册该模型。它将使dependent_paths方法返回的每个路径无效。

from django.db import models

import dumper


class IceCream(models.Model):
    slug = models.CharField(max_length=200)

    def get_absolute_url(self):
        return '/' + self.slug

    def dependent_paths(self):
        '''Returns a list of paths to invalidate when this model is updated'''
        return [self.get_absolute_url()]

dumper.register(IceCream)

dependent_paths还可以返回要使它们无效的相关对象的路径。例如,如果每个IceCream都有一些相关的Sizes,那么如果其中一个尺寸被修改,那么应该使该IceCream无效。

from django.db import models

import dumper


class IceCream(models.Model):
    slug = models.CharField(max_length=200)
    sizes = models.ManyToManyField(Size, related_name='ice_creams')

    def get_absolute_url(self):
        return '/' + self.slug

    def dependent_paths(self):
        '''Returns a list of paths to invalidate when this model is updated'''
        return [self.get_absolute_url()]


class Size(models.Model):
    slug = models.CharField(max_length=200)

    def get_absolute_url(self):
        return '/' + self.slug

    def dependent_paths(self):
        for ice_cream in self.ice_creams:
            yield ice_cream.get_absolute_url()
        yield self.get_absolute_url()

dumper.register(IceCream)
dumper.register(Size)

调试

dumper包已经为中间件和使无效化放置了DEBUG日志。要启用此功能,只需确保显示来自dumper的任何级别为DEBUG的日志。

最简单的方法是在您的settings.py中这样做

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'name': {
            'format': '%(name)s: %(message)s'
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'name'
        },
    },
    'loggers': {
        'dumper': {
            'level': 'DEBUG',
            'handlers': ['console', ]
        }
    }
}

建议

我建议启用ETags。这样,整个响应就不必发送给用户,只需发送标题,如果ETAG相同。

Django文档没有连贯地描述你的中间件应该如何排序,然而这个stack overflow讨论做得很好。

内部

缓存中间件 dumper/middleware.py

我的缓存基于Django的按站点缓存,但更加简单。最初我只是使用了他们的缓存,但这大大增加了我的代码的复杂性,并使其更难以理解。这是因为他们的缓存根据Vary HTML头为相同的URL创建不同的缓存版本。如果除了路径之外还使用了其他东西来生成缓存键,那么基于路径的无效化实现将更加复杂。例如,当我在支持Django中间件时,我必须找出一种方法来删除路径的所有缓存版本。

如果你的页面除了路径和HTTP方法之外还根据其他内容变化,那么你不应该用django-dumper来缓存它们。要么使用DUMPER_PATH_IGNORE_REGEX设置忽略它们,要么如果你的所有页面都落在这个类别中,就完全不用这个项目。

无效化路径 dumper/invalidation.py

为了在模型保存时进行无效化,我们从模型中获取应该无效化的路径,然后删除对应这些路径的缓存键。每个缓存键由一个路径和一个HTTP方法组成。

模型保存时进行无效化:dumper/site.py

当你将一个无效化函数注册到三个信号时。该函数从模型中获取路径,然后使用dumper/invalidation.py来删除它们。它注册的三个信号是post_savepre_deletem2m_changed。最后一个信号在添加、删除或更改关系的任何成员时被调用。如果更改了多对多关系,它可能多次调用无效化函数,但这无害,除了稍微影响缓存后端的性能之外。

贡献

如果你发现问题或者想要看到一个新特性被支持,请转到问题部分并报告它。不要害怕,大胆去做吧!

要以任何形式贡献代码,请分支并本地克隆存储库。为你的特性创建一个新分支

git commit -b feature/whatever-you-like

然后确保所有测试通过(并为任何新特性编写新的测试)

使用Docker Compose和Docker

docker-compose run tests
# run `docker-compose build` if you change the required packages before testing again

通常

pip install -e .
pip install -r requirements-dev.txt
django-admin.py test --settings=test.settings

检查README.rst是否正确

restview --long-description

然后将完成的功能推送到github,并从分支打开一个pull request。

新版本

要创建新版本

  1. 将更改添加到CHANGES.txt

  2. setup.py中更改版本

  3. python setup.py register

  4. python setup.py sdist upload

Bitdeli badge

项目详情


下载文件

下载适用于您的平台文件。如果您不确定要选择哪个,请了解更多关于安装包的信息。

源分布

django-dumper-0.2.7.tar.gz (17.2 kB 查看哈希)

上传

支持

AWSAWS云计算和安全赞助商DatadogDatadog监控FastlyFastlyCDNGoogleGoogle下载分析MicrosoftMicrosoftPSF赞助商PingdomPingdom监控SentrySentry错误日志StatusPageStatusPage状态页面