标签 Django 下的文章

如果不缓存 access_token 应用很容易超出每天 2000 次的请求上限。
set 方法中的 ttl 可按实际需求就行调整,下面的代码中默认设置为 3600 秒。

from django.core.cache import cache
from wechatpy.session import SessionStorage


class DjangoDefaultStorage(SessionStorage):
    """
    使用django默认的缓存方法缓存请求微信接口的access_token
    wechatpy: https://docs.wechatpy.org/zh_CN/stable/quickstart.html#accesstoken
    微信官方:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
    """
    def __init__(self, *args, **kwargs):
        pass

    def get(self, key, default=None):
        return cache.get(key, default)

    def set(self, key, value, ttl=3600):
        cache.set(key, value, ttl)

    def delete(self, key):
        cache.delete(key)

编写以上方法后,在调用的代码部分使用以下方式调用:

session_interface = DjangoDefaultStorage()
client = WeChatClient(WECHAT_APP_ID, WECHAT_APP_SECRET, session=session_interface)

构建占位图片服务

构建一个简单的占位图片服务,使用Form来验证图片的尺寸:

class ImageForm(forms.Form):
    # valid height and width
    height = forms.IntegerField(min_value=1, max_value=1000)
    width = forms.IntegerField(min_value=1, max_value=1000)

    def generate(self, image_format='PNG'):
        height = self.cleaned_data['height']
        width = self.cleaned_data['width']
        image = Image.new('RGB', (width, height))
        content = BytesIO()
        image.save(content, image_format)
        content.seek(0)
        return content


def placeholder(request, width, height):
    form = ImageForm({'height': height, 'width': width})
    if form.is_valid():
        image = form.generate()
        return HttpResponse(image, content_type='image/png')
    else:
        return HttpResponseBadRequest('Invalid Image Request')


urlpatterns = (
    url(r'^image/(?P<width>\d+)x(?P<height>\d+)/$', placeholder, name='placeholder'),
)

加入缓存

先设置缓存key,先尝试读取key对应的缓存,如缓存不存在则生成图片并写缓存:

class ImageForm(forms.Form):
    # valid height and width
    height = forms.IntegerField(min_value=1, max_value=1000)
    width = forms.IntegerField(min_value=1, max_value=1000)


    def generate(self, image_format='PNG'):
        height = self.cleaned_data['height']
        width = self.cleaned_data['width']
        key = '{}x{}.{}'.format(height, width, image_format)
        content = cache.get(key)
        if content is None:
            image = Image.new('RGB', (width, height))
            content = BytesIO()
            image.save(content, image_format)
            content.seek(0)
            cache.set(key, content, 60*60)
        return content

设置Etag缓存

上述缓存的操作发生在服务器端,服务器端对缓存进行管理操作。同时可以进一步设置Etag使服务器对相同的资源返回304 Not Modified响应,以此来节约服务器运算资源和带宽。

def generate_etag(request, width, height):
    content = 'placeholder: {} x {}'.format(width, height)
    return hashlib.sha1(content.encode('utf-8')).hexdigest()


@etag(generate_etag)
def placeholder(request, width, height):
    form = ImageForm({'height': height, 'width': width})
    if form.is_valid():
        image = form.generate()
        return HttpResponse(image, content_type='image/png')
    else:
        return HttpResponseBadRequest('Invalid Image Request')

测试代码

启动测试服务器:
python placeholder.py runserver
根据构建的url模式,访问图片占位符地址:http://127.0.0.1:8000/image/200x200/
状态码为200,原始响应头如下:

HTTP/1.0 200 OK
Date: Sun, 29 Aug 2021 07:04:54 GMT
Server: WSGIServer/0.1 Python/2.7.18
Content-Length: 320
ETag: "3720c8007695a4c03152ef519310374e908ff60f"
Content-Type: image/png

再次请求该地址,状态码为304,原始响应头如下:

HTTP/1.0 304 Not Modified
Date: Sun, 29 Aug 2021 07:06:54 GMT
Server: WSGIServer/0.1 Python/2.7.18
Content-Length: 0
ETag: "3720c8007695a4c03152ef519310374e908ff60f"

同时可以注意到第二次的Content-Length为0,服务器只有headers的返回,没有body内容,节约了服务器带宽资源。

完整演示代码文件placeholder.pyplaceholder.py.txt 下载后删除.txt后缀即可。

上述演示代码存在的问题

  • 图片宽度过小是,文字会溢出,可将文字起始位置设置为(0, 0)
  • generate 方法与 ImageForm 耦合到一起了,难以在其他的地方复用
  • 图片缓存部分的逻辑,嵌入到了generate方法中,两者再次产生耦合,后续如果需要修改相关功能,可能比较困难,同时如果也会增加测试该代码的难度
  • 无法从外部(如通过URL参数)对generate方法指定image_format参数,如需实现此功能,需要同时修改urlpatternsgenerate_etag装饰器的代码,同时需要在ImageForm对图片格式参数进行限制。

包含模板文件的完整项目演示文件 placeholder.zip

《轻量级Django》笔记

一个最小的Django项目文件,包含 views,Url模式和配置即可。

文件 hello.py

import sys

from django.conf import settings
from django.conf.urls import url
from django.http import HttpResponse


# views
def index(request):
    return HttpResponse('Hello World')


# URL patterns
urlpatterns = (
    url(r'^$', index),
)


# settings
settings.configure(
    DEBUG=True,
    SECRET_KEY='SECRET_KEY_HERE',
    ROOT_URLCONF=__name__,
    MIDDLEWARE_CLASSES=(
        'django.middleware.common.CommonMiddleware',
        # 'django.middleware.csrf.CsrfViewMiddleware',
        # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ),
)


if __name__ == '__main__':
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

测试运行

在命令行中启动:python hello.py runserver
运行的结果:

>python hello.py runserver
Performing system checks...

System check identified no issues (0 silenced).
August 28, 2021 - 12:40:39
Django version 1.11.29, using settings None
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

运行环境软件版本

Python version : 2.7.18
Django version : 1.11.29