在使用Django REST framework时,对接口加上权限限制是必不可少的,例如对于一篇文章,只有管理员和作者才有删除的权限,其它人只能有读取权限。此时Permissions就派上用场了。

REST framework提供了很多种权限,如IsAuthenticated,IsAdminUser等等,要定制permissions, 也是很容易的一件事。要定制permissions, 只需继承BasePermission,然后实现其中一个或者两个方法

  • has_permission(self, request, view)
  • has_object_permission(self, request, view, obj)

其中,has_permission是相对接口而言的,也就是在访问这个接口时,会进行权限检测。而has_object_permission是相对于对象而言的,只有访问对象时才会进行权限检测。

还有一个问题是,当实现自己的get_object并且需要进行权限检查时,不要忘记调用self.check_object_permissions(self.request, obj)

1
2
3
4
def get_object(self):
obj = get_object_or_404(self.get_queryset())
self.check_object_permissions(self.request, obj)
return obj

而对于访问对象进行权限检测时,一个好的方法是如果是安全方法如GET, HEAD等,则运行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `owner` attribute.
"""


def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True

# Instance must have an attribute named `owner`.
return obj.owner == request.user

联系作者

kill命令主要用来杀进程,以前不懂,一只都用kill -9, 现在才发现打错特错。

最近在学习使用Supervisor管理进程,测试Supervisor被杀死之后的情况。发现用kill -9杀死Supervisor后,管理的进程会变成孤儿进程。于是请教the5fire, 他提供了一篇no use kill 9, 顿时解决了疑惑。

在Mac上 man kill看到如下说明

1
2
3
4
5
6
7
1       HUP (hang up)
2 INT (interrupt)
3 QUIT (quit)
6 ABRT (abort)
9 KILL (non-catchable, non-ignorable kill)
14 ALRM (alarm clock)
15 TERM (software termination signal)

kill -9的主要弊端是被杀的进程来不及善后处理就已经死了,这回留下很多问题。所以强烈建议不要使用kill -9来杀死进程, 而是使用kill -15

联系作者

用Supervisor管理进程里说过,当supervisor挂了之后,它管理的进程就给了init进程,之后supervisor再次启动,端口已经被绑定了,怎么破?目前还没有找到解决的办法。现在继续说说这个问题。

这里说当supervisor挂了之后,它管理的进程就给了init进程,这并不完全对。这里其实是我主动用kill命令把supervisor杀掉,而且是用kill -9, 此时supervisor来不及将被杀的信息告诉管理的进程就死了,于是管理的进程变成了孤儿进程。当使用kill -15杀死supervisor时,它管理的进程也会一起挂掉,这样进程所占的资源也来得及释放。supervisor下次就可以正常启动。

一篇错误的博客

linux 后台进程管理利器supervisor里看到

不带参数运行supervisord是以daemon方式运行
当supervisord以非daemon方式运行时,杀掉supervisord后,被监控的进程也退出了。
而以daemon方式运行,杀掉supervisord对被监控进程无影响

然后我在博客里找什么是带参数和不带参数

supervisord (以daemon方式启动)
或 supervisord -c /etc/supervisord.conf (非daemon)

后来看了supervisor的官方文档,知道supervisor是否以daemon方式启动,是在supervisord.conf的supervisord项里配置的。当配置了nodaemon=true时,就会以非daemon方式启动,而不是根据带参数和不带参数决定的。

而被监控的进程是否一起死掉,也是更加supervisor被杀的方式决定的。从这方面看,这篇博客真是错误连篇。

一些需要注意的地方

  • autorestart=true
    在program配置项里,最好加上这个配置项,让监控的程序在关闭后自动重启。我遇到过一个问题是,用kill -15把监控的进程杀掉,之后程序没有自动重启。原因是它的退出码是0, 而exitcodes的默认配置是0,2 此时程序不会自动重启。因为exitcodes里配置的是The list of “expected” exit codes for this program used with autorestart, 而autorestart默认配置是unexpected。

联系作者

一直卡在gopkg.in/inconshreveable/go-update.v0,
想在CentOS6.5上安装Ngrok, 按照搭建 ngrok 服务实现内网穿透上的步骤安装。遇到问题,记录一下。

  • no package golang available.

参考CentOS-6.x下搭建golang环境的三种方式

  • no package build-essential available

参考CentOS Install Build Essentials

  • 卡在gopkg.in/inconshreveable/go-update.v0 (download)

参考ngrok服务安装笔记, 知道是git版本太旧。更新之后就可以编译成功

在CentOS6.5上如何更新git, 可参考CentOS6.5升级git

联系作者

以前就用过Supervisor,但没有细看,这次认真的使用了之后,还是发现了一些问题。

  • 如何让Supervisor成为服务

如果是通过pip install supervisor这种方式安装的Supervisor, 如何让Supervisor成为系统的一种服务,让系统开机时自动启动,在这里给出了一些例子,然而,我的系统是Centos 6.5, 这些例子没有包括。于是通过yum install supervisor的方式安装,得到了/etc/init.d/supervisord下的服务脚本. 之后把通过yum安装得到的/usr/bin/supervisord和/usr/bin/supervisorctl替换成pip安装得到的/usr/local/bin/supervisord和/usr/local/bin/supervisorctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/bash
#
# supervisord This scripts turns supervisord on
#
# Author: Mike McGrath <mmcgrath@redhat.com> (based off yumupdatesd)
#
# chkconfig: - 95 04
#
# description: supervisor is a process control utility. It has a web based
# xmlrpc interface as well as a few other nifty features.
# processname: supervisord
# config: /etc/supervisord.conf
# pidfile: /var/run/supervisord.pid
#

# source function library
. /etc/rc.d/init.d/functions

RETVAL=0

start() {
echo -n $"Starting supervisord: "
daemon supervisord
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/supervisord
}

stop() {
echo -n $"Stopping supervisord: "
killproc supervisord
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/supervisord
}

restart() {
stop
start
}

case "$1" in
start)
start
;;
stop)
stop
;;
restart|force-reload|reload)
restart
;;
condrestart)
[ -f /var/lock/subsys/supervisord ] && restart
;;
status)
status supervisord
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart}"
exit 1
esac

exit $RETVAL
  • Supervisor无法管理daemon进程

在启动Shadowsock的时候,加了-d参数
/usr/local/bin/ssserver -p 443 -k password --user nobody -d start, 也就是让它成为daemon进程,于是Supervisor就无法管理了。所以之后把这个参数去掉。

  • 当supervisor挂了之后,它管理的进程就给了init进程,之后supervisor再次启动,端口已经被绑定了,怎么破?目前还没有找到解决的办法。

参考资料

联系作者

在Django中,使用select_relatedprefetch_related是两个很常见的优化手段。

举个例子最能说明问题。

准备工作

首先建立如下model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Category(models.Model):
name = models.CharField(max_length=30)
creat_time = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return u'%s' % self.name

class Tag(models.Model):
name = models.CharField(max_length=30)
creat_time = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return u'%s' % self.name

class Post(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=300, allow_unicode=True, unique=True)
content = models.TextField()
publish_time = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category)
tag = models.ManyToManyField(Tag, blank=True)

def __unicode__(self):
return u'%s' % self.title

之后编写序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category


class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag


class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer()
tag = TagSerializer(many=True)

class Meta:
model = Post
fields = ('id', 'title', 'slug', 'content', 'publish_time', 'category', 'tag', )

之后编写api

1
2
3
4
5
6
7
8
9

class PostListAPI(generics.ListAPIView):
serializer_class = PostSerializer
model = Post
paginate_by = 10


def get_queryset(self):
return Post.objects.all().order_by('-publish_time')

编写url

1
url(r'^api/posts/?$', PostListAPI.as_view(), name='post_list'),

之后在后台新建两篇文章,访问api, 可以看到访问的sql

1
2
3
4
5
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug", "blog_post"."content", "blog_post"."publish_time", "blog_post"."category_id" FROM "blog_post" ORDER BY "blog_post"."publish_time" DESC; args=()
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_category"."id", "blog_category"."name", "blog_category"."creat_time" FROM "blog_category" WHERE "blog_category"."id" = 1; args=(1,)
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" = 2; args=(2,)
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_category"."id", "blog_category"."name", "blog_category"."creat_time" FROM "blog_category" WHERE "blog_category"."id" = 1; args=(1,)
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" = 1; args=(1,)

这里有两篇文章,访问tag和category表都分别访问了两次,如果是10篇文章,那访问tag和category则分别要10次。此时select_related和prefetch_related派上了用场。

查看select_related的文档,在返回QuerySet时,对于ForeignKey和OneToOneField等字段,通过添加select_related,可以把相关的对象在一次查询中查出,之后使用时就不需要再次查数据库。

还是看例子容易明白。对于上面的Post中category字段,因为是ForeignKey, 所以可以通过select_related查出,修改api如下

1
2
3
4
5
6
7
8
9
10
11

class PostListAPI(generics.ListAPIView):
serializer_class = PostSerializer
model = Post
paginate_by = 10


def get_queryset(self):
queryset = Post.objects.all().order_by('-publish_time')
queryset = queryset.select_related('category')
return queryset

访问url, 查看sql

1
2
3
DEBUG [24/Jul/2016 13:58:29] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug", "blog_post"."content", "blog_post"."publish_time", "blog_post"."category_id", "blog_category"."id", "blog_category"."name", "blog_category"."creat_time" FROM "blog_post" INNER JOIN "blog_category" ON ("blog_post"."category_id" = "blog_category"."id") ORDER BY "blog_post"."publish_time" DESC; args=()
DEBUG [24/Jul/2016 13:58:29] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" = 2; args=(2,)
DEBUG [24/Jul/2016 13:58:29] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" = 1; args=(1,)

可以看到,对于category信息,在查询post的同时,也一起查出,减少了查询category表的操作。

查看Django的prefetch_related文档,了解到prefetch_related对于相关对象会进行一次独立的查询,然后在Python中把对象关联起来。所以prefetch_related可以用于many-to-many and many-to-one关系。

还是举例子,对于上面的tag, 可以使用prefetch_related来处理。修改api如下:

1
2
3
4
5
6
7
8
9
10
11
class PostListAPI(generics.ListAPIView):
serializer_class = PostSerializer
model = Post
paginate_by = 10


def get_queryset(self):
queryset = Post.objects.all().order_by('-publish_time')
queryset = queryset.select_related('category')
queryset = queryset.prefetch_related('tag')
return queryset

之后访问api,查看sql

1
2
DEBUG [24/Jul/2016 14:02:35] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug", "blog_post"."content", "blog_post"."publish_time", "blog_post"."category_id", "blog_category"."id", "blog_category"."name", "blog_category"."creat_time" FROM "blog_post" INNER JOIN "blog_category" ON ("blog_post"."category_id" = "blog_category"."id") ORDER BY "blog_post"."publish_time" DESC; args=()
DEBUG [24/Jul/2016 14:02:35] [django.db.backends:utils:execute:89] [None] (0.000) SELECT ("blog_post_tag"."post_id") AS "_prefetch_related_val_post_id", "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" IN (2, 1); args=(2, 1)

可以看到对于tag的查询也只有一次。

从上面的例子可以看到,掌握select_related和prefetch_related的用法非常重要。本文的代码见test-django的blog

参看资料:

联系作者

对于一个应用来说,日志是很重要的一部分。Django提供的logging功能,使用Python自带的logging模块来处理系统日志很方便。

下面是一份简单的配置。配置最主要的需求是能够看到Django查询数据库的sql语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format' : '%(levelname)s [%(asctime)s] [%(name)s:%(module)s:%(funcName)s:%(lineno)s] [%(exc_info)s] %(message)s',
'datefmt' : "%d/%b/%Y %H:%M:%S"
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'logs/debug.log',
'formatter': 'standard'
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'DEBUG',
'propagate': True,
},
},
}

联系作者

Django提供了Q和F算子来方便查询。

Q算子

在对queryset进行filter时,filter语句的关系是AND关系,要实现OR关系,则需要使用Q算子

例如

1
Q(question__startswith='Who') | Q(question__startswith='What')

相当于

1
WHERE question LIKE 'Who%' OR question LIKE 'What%'

F算子

在实际开发中,常常需要对数据库中的某一个字段进行加减操作,如评论数,访问量等等。一种做法是把这个字段读出来,然后进行操作,之后再保存到数据库中,而F算子提供了简便的方法,它使这些操作直接在数据库中执行。
例如,在下面的例子中需要对stories_filed字段进行加1操作。

1
2
3
4
# Tintin filed a news story!
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()

而使用F算子,则是

1
2
3
4
from django.db.models import F

reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)

F算子的优势在于不需要使用Python, 而只是使用数据库来完成操作。

而F算子的另外一个好处是减少多线程下的变量问题。

联系作者