改造REST-Framework,自定义修饰器为ViewSet中每个Action指定单独的Serializer

在 REST-Framework 的 ViewSet 中,一般是通过 ViewSet 类的 serializer_class 属性来指定视图所使用的 Serializer。

但是很多时候我们需要为 ViewSet 中的某个 Action 指定不同的 Serializer。 比如在 UserViewSet中有一个修改密码的 Action:change_password,我们需要单独为它指定一个 ChangePasswordSerializer。

根据官方文档,我们可以这样做,重写 ViewSet 类的 get_serializer_class 方法,自己实现判断逻辑返回需要的 Serializer。

    def get_serializer_class(self):
        if self.action == "change_password":
            return ChangePasswordSerializer
        return super(UserViewSet, self).get_serializer_class()

这样在 change_password 方法中我们就可以直接通过 self.get_serializer() 得到 ChangePasswordSerializer 对象了。

但是这不是一个优雅的解决方法,我们应该劲量避免代码中出现硬编码!!!

为了优雅的解决这个问题,我们需要实现一个方法装饰器和一个 Mixin 类。

继续阅读改造REST-Framework,自定义修饰器为ViewSet中每个Action指定单独的Serializer

Django自定义认证User模型email字段不能保存为空的解决办法

在一个项目中直接使用了自定义Django AdminSite作为后台管理网站。

在修改自定义User模型的信息后,点击保存按钮提交Form时,提示email字段不能为空。

这是因为我们的UserAdmin类继承自django.contrib.auth.admin.UserAdmin,在父类UserAdmin的form中将email字段定义为了required,即必填字段。

通过继承django.contrib.auth.forms.UserChangeForm类,并在init方法中对email字段属性进行修改,可以实现我们想要的效果。

from django.contrib.auth.forms import UserChangeForm

class UserForm(UserChangeForm):
    def __init__(self, *args, **kwargs):
        super(UserForm, self).__init__(*args, **kwargs)
        self.fields["email"].required = False

这样在保存时就不会提示email字段不能为空了。

但是测试中发现,如果不填写email字段内容,保存后发现email字段的值变成了空字符串,而不是None,但是我们在自定义User模型中指定了email字段null=True,即我们期望的它应该是一个空值None。

经过调试后发现,在django.contrib.admin.options.ModelAdmin类的_changeform_view方法代码中,调用form.is_valid()方法后,email字段的值发生了变化。

继续阅读Django自定义认证User模型email字段不能保存为空的解决办法

使用Django自带的session和auth应用实现Token认证

Django的session和auth应用配合起来很方便的实现了身份认证和会话管理的功能。

现在我的项目在原有网站基础上需要另外提供一套API,只需要简单的一点拓展就可以在现有session和auth应用基础上实现基于Token的认证。

在自己的app下创建middleware.py文件,写一个自己的class,继承django.contrib.session.middleware模块下的SessionMiddleware类,并且重写process_request方法。

from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware

class SessionTokenMiddleware(SessionMiddleware):

    def process_request(self, request):
        # 尝试从请求头中获取 Session Key
        session_key = request.META.get("HTTP_" + getattr(settings, "ACCESS_TOKEN_NAME", "Access-Token").replace("-","_").upper())
        if not session_key:
            super().process_request(request)
        else:
            request.session = self.SessionStore(session_key)

在处理请求时,先尝试从请求头中对应字段获取Token,即Session Key,并通过Session引擎获取Session对象。
如果请求头中没有包含Token字段,则调用父类方法。

继续阅读使用Django自带的session和auth应用实现Token认证

selenium + PhantomJS 使用 WebDriverWait 实现延迟加载网页动态内容并截图

最近有个项目功能需要实现一个后台模拟登录网站,然后进入到二级页面进行网页截图并返回的 API。

听起来很有意思,打算使用 Python 的 selenium 包配合无界面浏览器 PhantomJS 来实现。

要注意较新版本的 selenium 已经不再支持 PhantomJS,需安装较老版本的 selenium。

继续阅读selenium + PhantomJS 使用 WebDriverWait 实现延迟加载网页动态内容并截图

在 Django HttpResponse 响应体中返回 openpyxl 生成的 Excel 文档——源码分析

现在我需要提供一个链接,访问时通过 openpyxl 动态生成一个 Excel 文档,并作为响应体返回。

在这里将动态生成的 Excel 文档保存到服务器,并重定向到静态文件链接显然不是一个合适的做法。

所以我们需要直接将 openpyx 生成的 Excel 文档写入到 Django 的 HttpResponse 对象响应体中。

查阅 openpyxl 的文档以后,并没有找到有用的信息,所以直接从源码入手。

在这篇文章中,我将把源码分析的过程详细的展现出来,并总结出完美的解决方案。

继续阅读在 Django HttpResponse 响应体中返回 openpyxl 生成的 Excel 文档——源码分析

Django 返回流文件时使用中文文件名的问题

最近有一个需求,需要根据请求参数,从数据库获取指定数据动态生成 Excel 表格文件,并以流的形式返回。

在测试的过程中发现了一个问题,无论我怎么在响应头中添加 Content-Disposition 指定文件名,在 Chrome 浏览器中访问的时候返回的文件名始终是按 url 的最后一段自动生成的文件名,而将文件名改成英文以后又是正常的。

猜测应该是字符串编码的问题,http 响应头中字符应该按照 url 百分号编码。

修改为以下代码后,终于能够正常显示中文文件名了。

# Python3
from urllib.parse import quote
...
response['Content-Disposition'] = 'attachment; filename={0}.xlsx'.format(quote(filename))

Python2 中的 quote 方法引入位置有所不同,代码如下:

# Python2
from urllib import quote
...
# 向下兼容 Python2.6 以前的格式化字符串
response['Content-Disposition'] = 'attachment; filename=%s.xlsx' % quote(filename)

Shell 脚本控制 uWSGI 服务器的启动、停止

现在有一个 Django 应用,通过 Nginx 接受外网请求后使用 uwsgi 协议转发至内网 uWSGI 服务器,uWSGI 服务器再和 Django 应用进行通信。

为了方便管理,我决定写一个 Shell 脚本来控制 uWSGI 服务器的启动和停止。

这样以后项目中如果需要同时启动别的进程,也可以通过简单修改 Shell 脚本来实现对整个项目的一键控制。

首先把 uWSGI 服务器的各项选项参数写入到 ini 文件,并设置守护进程。

然后编写 Shell 脚本如下(dino 为项目简称):

继续阅读Shell 脚本控制 uWSGI 服务器的启动、停止