Django中REST API的设计

最近学习了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/

--------EOF---------
微信分享/微信扫码阅读