改造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)