跬步 On Coding

Tastypie入门小结

Tastypie是什么

Tastypie是基于Django的RESTful api开发框架,如果你有一个通过Django实现的网站,那么通过Tastypie写少许的代码就能实现一个全功能的REST api。

我要写些什么

这不是一篇Tastypie的入门教程,如果需要教程,可以从官方文档开始,我写的只在探索Tastypie的过程中我认为值得记录的地方。

Getting Started with Tastypie

认证 authorization

认证是指谁在访问该api,Tastypie的默认认证是Authorization(匿名只读认证),由于我们的业务场景api的使用对象是WEB网站以及APP,而且是以WEB网站优先的,所以计划使用SessionAuthentication与ApiKeyAuthentication。

SessionAuthentication是使用在网站上的,ApiKeyAuthentication可能需要定制,因为我们的OAUTH的token格式需要解密解析,待后续APP开发再进行。

另外,在调试api的时候可能需要使用BasicAuthentication用用户名/密码来认证,requests库可以方便的使用BasicAuthentication,所以写一个Meta基类来实现。

class BaseMeta:
    '''
    Resource Meta 基类,把Resource的常用设置写在这里
    '''
    list_allowed_methods = ['get', 'post']
    detail_allowed_methods = ['get', 'post', 'put', 'delete', 'patch']
    limit = 20 # 默认列表显示数量
    authentication = SessionAuthentication()
    always_return_data = True

if settings.DEBUG:
    BaseMeta.serializer = PrettyJSONSerializer()
    BaseMeta.authentication = MultiAuthentication(SessionAuthentication(), BasicAuthentication())

在定义Resource类时Meta类通过继承BaseMeta来写。

授权 authorization

授权,用户是否有权限访问该api,在已有的权限设计中,我们对每个Django的View都设计了权限,而Tastypie的DjangoAuthorization的是通过在Model中定义的权限来进行授权的。所以定义了一个业务授权的授权类。

class BaseAuthorization(Authorization):
    '''
    Tastypie 权限验证基类
    '''

    def __init__(self, *args, **kwargs):
        '''
        read_list_perm 读列表权限
        read_detail_perm 读详情页权限
        create_detail_perm 创建对象权限
        update_list_perm 更新列表权限
        update_detail_perm 更新详情权限
        delete_detail_perm 删除对象权限
        '''
        for perm_name in ('read_list_perm', 'read_detail_perm', 'create_detail_perm', 
            'update_list_perm', 'update_detail_perm', 'delete_detail_perm'):
            setattr(self, perm_name, kwargs.pop(perm_name, None))
        super(BaseAuthorization, self).__init__(*args, **kwargs)

    def read_list(self, object_list, bundle):
        if self.read_list_perm and bundle.request.user.has_perm(self.read_list_perm):
            return object_list
        raise Unauthorized("You are not allowed to access that resource.")

    def read_detail(self, object_list, bundle):
        if self.read_detail_perm and bundle.request.user.has_perm(self.read_detail_perm):
            return True
        raise Unauthorized("You are not allowed to access that resource.")

    def create_detail(self, object_list, bundle):
        if self.create_detail_perm and bundle.request.user.has_perm(self.create_detail_perm):
            return True
        raise Unauthorized("You are not allowed to access that resource.")

    def update_list(self, object_list, bundle):
        if self.update_list_perm and bundle.request.user.has_perm(self.update_list_perm):
            return object_list
        raise Unauthorized("You are not allowed to access that resource.")

    def update_detail(self, object_list, bundle):
        if self.update_detail_perm and bundle.request.user.has_perm(self.update_detail_perm):
            return True
        raise Unauthorized("You are not allowed to access that resource.")

    def delete_list(self, object_list, bundle):
        raise Unauthorized("Sorry, no deletes.")

    def delete_detail(self, object_list, bundle):
        if self.delete_detail_perm and bundle.request.user.has_perm(self.delete_detail_perm):
            return True
        raise Unauthorized("You are not allowed to access that resource.")

# 这样使用
class TestResource(ModelResouce):
    class Meta(BaseMeta):
        authorization = BaseAuthorization(
            read_list_perm='auth.employee_view',
            read_detail_perm='auth.employee_view',
            create_detail_perm='auth.employee_create',
            update_detail_perm='auth.employee_update',
            delete_detail_perm='auth.employee_delete'
        )

这样在读取或者写入数据的权限就跟业务在View中使用的权限一致了。

捆对象 Bundles

在定制Resource的过程中,不可避免的会使用捆对象,这是一个抽象的概念,代表了获取资源,或者写入资源过程中对单个资源的封装,它有这些属性。

  1. obj: Model对象
    • GET方法中obj是根据id来得到的Model对象
    • POST方法中obj是生成的id为空等待保存的对象
    • PUT/PATCH中obj是某些字段已改变但是没有保存的对象
  2. data: 数据字典
    • GET方法中data是等待序列化的字典数据
    • POST/PUT/PATCH法中data是从客户端发送过来的数据
  3. request: Django request对象

其它还有related_obj, related_name暂时未用到

脱水 dehydrate

类似于Django Form类中clean以及clean_FOO方法的使用,用于处理即将被序列化的数据,即在GET方法中会调用。如果需要对发送给客户端的数据进行加工,则需要定义相关的脱水方法。

class TestResource(ModelResouce):
	def dehydrate(self, bundle):
        # 修改数据
        bundle.data['title'] = buuidle.data['title'].upper()
        # 增加数据
        bundle.data['version'] = 'v1'
    return bundle

hydrate 注水

与脱水相反,注水是指在POST/PUT/PATCH方法中对客户端传过来的数据进行加工。

class TestResource(ModelResouce):
	def hydrate(self, bundle):
        # 修改数据
        city_id = bundle.data.pop('insrance_city')
        city = City.object.get(pk=city_id)
        bundle.obj.city = city
    return bundle

过滤查询 filtering

类似于我们业务中的搜索,使用Django Model自带的filter功能来过滤列表。使用方法可以参考文档:

http://django-tastypie.readthedocs.org/en/latest/resources.html#basic-filtering

对于在我们业务中类似于关键字搜索,需要同时查询多个字段的需求,可以考虑重写Resouce中的方法,或者增加方法,增加Meta属性,期望能达到我们正在使用的SearchMixin的效果。一个重写方法的例子:

class EmployeeInfoResource(ModelResource):
    def apply_filters(self, request, applicable_filters):
        qs = super(EmployeeInfoResource, self).apply_filters(request, applicable_filters)
        keyword = request.GET.get('keyword')
        if keyword:
            keyword = keyword.strip()
            qs = qs.filter(Q(credentials_no__icontains=keyword) | Q(emp_name__icontains=keyword))
        return qs

以上就是我在探索Tastypie的过程中一点心得,在实际的开发中,相信除了以资源为核心的api外,还有一些操作类的业务需求api,如果这些api基于Tastypie来写可能会得不偿失,所以还需要对操作类api进行规范。

操作类api

可以考虑返回的Response直接输出JsonResponse,或者直接使用Tastypie定义的各种HTTP状态码Response对象,可以在这里看到。

from tastypie.http import *