专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
Python爱好者社区  ·  商汤科技,爆了。。。 ·  2 天前  
Python开发者  ·  Python即将成为TIOBE ... ·  3 天前  
Python爱好者社区  ·  ML书.pdf ·  5 天前  
Python爱好者社区  ·  42岁,讲师,因为评职称郁郁寡欢,吃了半年的 ... ·  6 天前  
Python爱好者社区  ·  商汤科技,彻底爆了 ·  5 天前  
51好读  ›  专栏  ›  Python开发者

Django 中 REST API 的设计

Python开发者  · 公众号  · Python  · 2017-05-03 20:06

正文

(点击上方蓝字,快速关注我们)


来源:伯乐在线专栏作者 - 王海波

如有好文章投稿,请点击 → 这里了解详情


最近学习了REST方面的知识,了解了REST 的基础、API 的设计规则等等。


在Django中,不需要自己去设计每一个API,因为djangorestframwork帮我们做了一些工作。其实设计Django REST API的框架不少,但是djangorestframwork风格更像Django,与django的集成度更高,更易上手。该框架分为model, serializer, views三层,支持权限许可等功能。


现在就边做边学习这方面的知识。 首先是准备工作: 1、安装djangorestframwork pip install djangorestframework; 2、安装django-filter;


配置:


  1. 在INSTALLED_APPS中添加: ‘rest_framwork’

  2. 设置 REST_FRAMEWORK


REST_FRAMEWORK = {

    # Use Django's standard django.contrib.auth permissions,

    # or allow read-only access for unauthenticated users.

    'DEFAULT_PERMISSION_CLASSES': [

        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',

    ],

    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',

    'PAGE_SIZE':2,

     'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)

}


在上面设置了权限,设置了结果分页,设置了过滤器。


上面配置完成,开始写代码。


一、Model就是数据库定义的模型本身 


Model有article,user,category三个


class Category(models.Model):

 

    name = models.CharField(max_length=150,unique=True,verbose_name=u'类名')

    alias = models.CharField(max_length=150,verbose_name=u'英文名称')

    status = models.IntegerField(default=0,choices=STATUS.items(),verbose_name=u'状态')

    #Automatically set the field to now when the object is first created.

    create_time = models.DateTimeField(auto_now_add=True)

    parent = models.ForeignKey('self',default=None,blank=True,null=True,verbose_name=u'上级分类')

 

class Article(models.Model):

 

    title = models.CharField(max_length=150,unique=True,verbose_name=u'标题')

    alias = models.CharField(max_length=150,verbose_name=u'英文标题')

    content = models.TextField(verbose_name=u'正文')

    #when editing article,content_html will be saved automatically from content.so it can be blank

    content_html = models.TextField(blank=True,verbose_name=u'正文html格式')

    abstract = models.TextField(blank=True,verbose_name=u'摘要')

    read_times = models.IntegerField(default=0,verbose_name=u'阅读次数')

    tags = models.CharField(max_length=100,verbose_name=u'标签',help_text='用逗号隔开')

    status = models.IntegerField(default=0,choices=STATUS.items(),verbose_name=u'文章状态')

    #Automatically set the field to now when the object is first created

    create_time = models.DateTimeField(auto_now_add=True)

    pub_time = models.DateTimeField(default=datetime.now())

    #Automatically set the field to now every time the object is saved.

    update_time = models.DateTimeField(auto_now=True)

    author = models.ForeignKey(User,verbose_name=u'作者')

    category = models.ForeignKey(Category,verbose_name=u'分类')

    access = models.IntegerField(default=100,choices=ACCESS.items(),verbose_name=u'文章权限,公开或者私人可见')


User Model是Django自带的Model。


二、序列化Serializer


摘自官网: 


Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.


The serializers in REST framework work very similarly to Django’s Form and ModelForm classes. We provide a Serializer class which gives you a powerful, generic way to control the output of your responses, as well as a ModelSerializer class which provides a useful shortcut for creating serializers that deal with model instances and querysets.


从以上文字可以看出,serializer的目的是将查询集或模型实例转化为Python数据类型,从而很方便得转变为Json,XML等多种格式。


我的项目中的Serializer设计:


from rest_framework import serializers

from .models import  Category,Article

from django.contrib.auth.models import User

from django.forms import widgets

 

class CategorySerializer(serializers.ModelSerializer):

    # Serializers define the API representation.

    class Meta:

        model = Category

        fields = ('id','name','alias','parent','create_time','status')

 

class UserSerializer(serializers.ModelSerializer):

    class Meta:

        model = User

        fields = ('username','email','last_login')

 

 

class ArticleSerializer(serializers.ModelSerializer):

    category = CategorySerializer()

    author = UserSerializer()

    class Meta:

        model = Article

        fields = ('title','content','author','category','pub_time')


在上面每个序列器都指定了Model,这样会自动生成序列化字段,这样比较方便。 在ArticleSerializer中,加入了author和category的序列化,这样做是因为当我们浏览article的信息时。会直接显示author和category的具体信息,否则就只是author和category的id。


三、View:


主要使用viewset。和route ViewSet为我们提供了默认的URL结构, 使得我们能更专注于API本身。而Route能够轻松的帮我们实现URL和ViewSet之间的关联。 源代码:


views.py

 

class CategoryViewset(viewsets.ModelViewSet):

 

     """

    This viewset automatically provides `list`, `create`, `retrieve`,

    `update` and `destroy` actions.

 

    Additionally we also provide an extra `highlight` action.

    """

 

    queryset = Category.objects.all()

    serializer_class = CategorySerializer

 

    def perform_create(self, serializer):

        serializer.save()

 

 

class UserViewset(viewsets.ModelViewSet):

    queryset = User.objects.all()

    permission_classes = (IsAdminUser,)

    serializer_class = UserSerializer

 

 

class ArticleViewset(viewsets.ModelViewSet):

    """

    the viewset let us can get data from api url

    like:curl - H 'application/json;indent=4' http://localhost:8080/api/articles/

    of course,we can get some author's article through:

    http://localhost:8080/api/articles/?author=2(author's id)

    Generally,people like to see json data,so when visit by browser,you should add

    ?format=json ,that is,http://localhost:8080/api/articles/?format=json

 

    """

    queryset = Article.objects.all()

    serializer_class = ArticleSerializer

    filter_backends = (filters.DjangoFilterBackend,)

    filter_fields = ('author','category','access','status')


上面使用了ModelSet是有原因的,因为它自动提供delete,create,update,list等操作,即增删该查等操作。


其次说下filter,一般都用默认的DjangoFilterBackend,此外rest-framework 提供了几个原生的 filter:


SearchFilter


filter_backends = (filters.SearchFilter,) search_fields = (‘username’, ’email’) # 指定搜索的域


请求 http://example.com/api/users?search=russell。


OrderingFilter


filter_backends = (filters.OrderingFilter,) ordering_fields = (‘username’, ’email’)


请求 http://example.com/api/users?ordering=account,-username。


再说下权限。


rest_framework 中提供了七种权限


AllowAny # 无限制

IsAuthenticated # 登陆用户

IsAdminUser # Admin 用户

IsAuthenticatedOrReadOnly # 非登录用户只读

DjangoModelPermissions # 以下都是根据 Django 的 ModelPremissions

DjangoModelPermissionsOrAnonReadOnly

DjangoObjectPermissions


把源码拿出来:


class IsAuthenticated(BasePermission):

    """

    Allows access only to authenticated users.

    """

 

    def has_permission(self, request, view):

        return request.user and request.user.is_authenticated()

 

 

class IsAdminUser(BasePermission):

    """

    Allows access only to admin users.

    """

 

    def has_permission(self, request, view):

        return request.user and request.user.is_staff


其实还是比较理解的,首先判断用户,如果满足就返回True。要注意我们在Viewset中设置的权限必须是一个元祖,否则会出现错误。正确写法:permission_classes = (IsAdminUser,)。元祖里可以写多个permission,但注意,元祖里的属于与的关系,即任何一个不满足都不行,而不是或的关系,具体可见如下源代码:


def check_permissions(self, request):

    """

    Check if the request should be permitted.

    Raises an appropriate exception if the request is not permitted.

    """

    for permission in self.get_permissions():

        if not permission.has_permission(request, self):

            self.permission_denied(

                request, message=getattr(permission, 'message', None)

            )


当然,除了默认的权限我们也可以自定义权限:


class UserPermisson(BasePermission):

    """

    define one permission,which can limit the user operation

    """

 

    def has_object_permission(self, request, view, obj):

        """

        only the creater can delete,put or post

        :param request:

        :param view:

        :param obj:

        :return:

        """

 

        return obj == request.user or request.user.is_staff

 

 

class ArticlePermisson(BasePermission):

    def has_object_permission(self, request, view, obj):

        """

        only the creater can delete,put or post

        :param request:

        :param view:

        :param obj:

        :return:

        """

        if request.method in SAFE_METHODS:

            #only the authorised user can get data

            return request.user.is_authenticated

 

        return request.user == obj.author


这里要注意,对于一个request,会优先检查permission的has_permission方法,它是对每一个request进行检查。然后才检查has_object_permission,这是对每一个object进行检查。两者的区别是前者是对每一个request请求设置的权限,后者是对数据库条目进行权限设置的,但我理解她也包含了对request的控制。


如果不满足权限要求,则返回403或者401等错误代码。


然后定义路由:


urls.py

from rest_framework import routers

from .views import CategoryViewset,ArticleViewset,UserViewset

 

router = routers.DefaultRouter()

 

router.register(r'categories',CategoryViewset)

router.register(r'articles',ArticleViewset)

router.register(r'users',UserViewset)


有几点说明:


  1. 如果在viewset设置了权限,那么必须得带上登录用户名,密码等信息;

  2. 如果没有设置权限,那不带用户名可以使用GET方法,但是其他操作如POST,PUT,DELETE等,也要带上登录用户名,密码等信息;


测试:


curl http://localhost:8080/api/categoriese/10/?format=json(默认为get)

curl http://localhost:8080/api/categorieses/

curl -u username:password -X PUT -d “name=dwd&alias=dw” http://localhost:8080/api/categories/10/

curl -u username:password -X delete http://localhost:8080/api/categoriese/10/

curl -u username:password -X POST -d “name=dwd&alias=dw” http://localhost:8080/api/categories/10/



看完本文有收获?请转发分享给更多人

关注「Python开发者」,提升Python技能