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

1
2
3
4
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 类。

阅读更多

蓝桥杯 算法训练——审美课(JAVA满分)

问题描述
  《审美的历程》课上有n位学生,帅老师展示了m幅画,其中有些是梵高的作品,另外的都出自五岁小朋友之手。老师请同学们分辨哪些画的作者是梵高,但是老师自己并没有答案,因为这些画看上去都像是小朋友画的……老师只想知道,有多少对同学给出的答案完全相反,这样他就可以用这个数据去揭穿披着皇帝新衣的抽象艺术了(支持帅老师^_^)。
  答案完全相反是指对每一幅画的判断都相反。
输入格式
  第一行两个数n和m,表示学生数和图画数;
  接下来是一个n*m的01矩阵A:
  如果aij=0,表示学生i觉得第j幅画是小朋友画的;
  如果aij=1,表示学生i觉得第j幅画是梵高画的。
输出格式
  输出一个数ans:表示有多少对同学的答案完全相反。
样例输入
  3 2
  1 0
  0 1
  1 0
样例输出
  2
样例说明
  同学1和同学2的答案完全相反;
  同学2和同学3的答案完全相反;
  所以答案是2。
数据规模和约定
  对于50%的数据:n<=1000;
  对于80%的数据:n<=10000;
  对于100%的数据:n<=50000,m<=20。

这道题困扰了我很久,原理上看不难,但是注意有时长限制。

时间限制:1.0s

尝试了很多次都无法拿到满分,问题都出在运行超时上。

阅读更多

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字段属性进行修改,可以实现我们想要的效果。

1
2
3
4
5
6
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自带的session和auth应用实现Token认证

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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字段,则调用父类方法。

阅读更多

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

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

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

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

阅读更多

记录近期在 Android 开发上遇到的一些深坑

最近在开发 App 测试的时候遇到了一些坑,在这里记录一下,防止以后再遇到时又要浪费时间。

1. Fragment 中的 onAttach 方法没有被调用

在 Android 5.0 (API 21) 系统手机上测试时 App 抛出了空指针异常,而在其他测试机(均等于或高于 Android 6.0)上运行正常。
经排查发现时 Fragment 中的 OnAttach 方法没有被调用。

原来 Android 在 API 23 以后重载了 Fragment 中的 OnAttach 方法。
之前的

1
2
3
4
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}

被重载为了

1
2
3
4
@Override
public void onAttach(Context context) {
super.onAttach(context);
}

而我在继承 Fragment 的类中只重写了第二个方法,也就是 API 23 以后的方法。
所以我们复写的 OnAttach 方法在 API 21 的手机上没有被调用。

阅读更多

Android — 判断点是否位于多边形内

最近参与一个室内AP定位的项目做Android客户端的开发。

自己写了一个静态地图控件,遇到了需要判断触摸点是否位于多边形指定区域内的问题。

网上资料很多,主流方法是利用光投射算法。Point in polygon - Wikipedia

CSDN上有一篇文章把原理讲的非常详细 点在多边形内算法——判断一个点是否在一个复杂多边形的内部

参考 StackOverFlow 上一个回答的写法,翻译成 JAVA 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 利用光投射算法计算点是否在多边形内
*
* @param point 需要判断的点的坐标
* @param vertices 多边形按顺时针或逆时针顺序的顶点坐标集合
* @return 点是否在多边形内
*/
public static boolean isPointInPolygon(PointF point, List<PointF> vertices) {
boolean contains = false;
for(int i = 0, j = vertices.size() - 1; i < vertices.size(); j = i++) {
if(((vertices.get(i).y >= point.y) != (vertices.get(j).y >= point.y)) &&
(point.x <= (vertices.get(j).x - vertices.get(i).x) * (point.y - vertices.get(i).y) / (vertices.get(j).y - vertices.get(i).y) + vertices.get(i).x))
contains = !contains;
}
return contains;
}
阅读更多

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

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

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

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

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

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

阅读更多

微信小程序 canvas 动画踩坑——requestAnimationFrame 帧渲染

在浏览器上绘制 canvas 动画的时候经常使用 window.requestAnimationFrame 这个 Api 的回调来进行循环的帧渲染。

使用 requestAnimationFrame 将会把动画帧渲染周期交给浏览器统一调度,实现更好的性能优化。

可是微信小程序并没有支持这个 Api,在模拟器上测试可以正常运行,可在真机上运行时会报错,没有这个方法。

所以我们只得想办法自己来模拟这个 Api 所做的工作。

requestAnimationFrame 能够将原本零散的帧渲染序列进行梳理,使得页面上所有动画帧都在同一周期进行渲染,以最大化利用系统资源。

一般浏览器的渲染周期为 60 次每秒,即每次间隔大约 16 毫秒。因此我们只需要写一段代码,来将渲染周期控制在 16 毫秒,就能够模拟出 requestAnimationFrame 的效果了。

1
2
3
4
5
6
7
8
9
10
11
12
13
var lastFrameTime = 0;
// 模拟 requestAnimationFrame
var doAnimationFrame = function (callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastFrameTime));
var id = setTimeout(function () { callback(currTime + timeToCall); }, timeToCall);
lastFrameTime = currTime + timeToCall;
return id;
};
// 模拟 cancelAnimationFrame
var abortAnimationFrame = function (id) {
clearTimeout(id)
}

现在就可以像使用 requestAnimationFrame 和 cancelAnimationFrame 一样使用 doAnimationFrame 和 abortAnimationFrame 了。

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

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

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

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

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

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

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

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