DRF

走远了吗. 提交于 2019-12-04 07:06:40

1.简介

drf 是django rest framewok的简称,主要用于前后端分离项目,用于前端调取接口,后端返回json

drf是一个基于django开发的组件,本质是一个django的app。
drf可以办我们快速开发出一个遵循restful规范的程序。

简单说 只是一个项目而已,然后调取

涉及restful规范 是约束接口的规则

1.给别人提供一个URL,根据URL请求方式的不同,做不同操作。
    get,获取
    post,增加
    put,全部更新
    patch,局部更新
    delete,删除
2.数据传输基于json格式。

3. 建议用https代替http
4. 在URL中体现api,添加api标识
    https://www.cnblogs.com/xwgblog/p/11812244.html   # 错误
    https://www.cnblogs.com/api/xwgblog/p/11812244.html  # 正确
    https://api.cnblogs.com/xwgblog/p/11812244.html # 正确
    
    建议:https://www.cnblogs.com/api/...
5. 在URL中要体现版本
    https://www.cnblogs.com/api/v1/userinfo/
    https://www.cnblogs.com/api/v2/userinfo/
6. 一般情况下对于api接口,用名词不用动词。
    https://www.cnblogs.com/api/v1/userinfo/
7. 如果有条件的话,在URL后面进行传递。
    https://www.cnblogs.com/api/v1/userinfo/?page=1&category=2
8. 返回给用户状态码(code)
9. 对下一个请求返回一些其他接口

当然根据Django也可以实现遵循restful规范的接口开发:

其中两种视图模式

  • FBV,可以实现比较麻烦。
  • CBV,相比较简答根据method做的了不同的区分。

drf组件的功能

  • 解析器,解析请求体中的数据,将其变成我们想要的格式。request.data request.query_params.get('article')

    在进行解析时候,drf会读取http请求头 content-type.
    如果content-type:x-www-urlencoded,那么drf会根据 & 符号分割的形式去处理请
    求体。
    user=wang&age=19
    如果content-type:application/json,那么drf会根据 json 形式去处理请求体。
    {"user":"wang","age":19}

  • 序列化,可以对QuerySet进行序列化,也可以对用户提交的数据进行校验。

  • 视图,继承APIView(在内部apiview继承了django的View)

  • 渲染器,可以帮我们把json数据渲染到页面上进行友好的展示。(内部会根据请求设备不同做不同的
    展示)

  • 分页,筛选

2.安装

pip3 install djangorestframework

3.使用

3.1注册

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework'  #添加项目
]

3.2路由

from django.conf.urls import url
from django.contrib import admin
from api import views

urlpatterns = [
    url(r'^drf/info/', views.DrfInfoView.as_view()),
]

3.3视图

注意 APIView中没有JsonResponse

使用要使用Response

from rest_framework.views import APIView
from rest_framework.response import Response

class DrfInfoView(APIView):

    def get(self,request,*args,**kwargs):
        data = [
            {'id': 1, 'title': '震惊了...你居然吃...', 'content': '...'},
            {'id': 2, 'title': '震惊了...他居然吃...', 'content': '...'},

        ]
        return Response(data)    

4.基于接口的增删改查low版本

from django.urls import path,re_path
from api import  views
urlpatterns = [
    re_path('drf/category$', views.DrfCategoryView.as_view()),
    re_path('drf/category/(?P<pk>\d+)/$', views.DrfCategoryView.as_view()),
]
from api import models
from django.forms.models import model_to_dict
class DrfCategoryView(APIView):
    def get(self,request,*args,**kwargs):
        """获取所有文章分类/单个文章分类"""
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Category.objects.all().values('id','name')
            data_list = list(queryset)
            return Response(data_list)
        else:
            category_object = models.Category.objects.filter(id=pk).first()
            data = model_to_dict(category_object)
            return Response(data)

    def post(self,request,*args,**kwargs):
        """增加一条分类信息"""
        models.Category.objects.create(**request.data)
        return Response('成功')

    def delete(self,request,*args,**kwargs):
        """删除"""
        pk = kwargs.get('pk')
        models.Category.objects.filter(id=pk).delete()
        return Response('删除成功')

    def put(self,request,*args,**kwargs):
        """更新"""
        pk = kwargs.get('pk')
        models.Category.objects.filter(id=pk).update(**request.data)
        return Response('更新成功')
from django.db import models

class Category(models.Model):
    """
    文章分类
    """
    name = models.CharField(verbose_name='分类',max_length=32)


class Article(models.Model):
    """
    文章表
    """
    title = models.CharField(verbose_name='标题',max_length=32)
    summary = models.CharField(verbose_name='简介',max_length=255)
    content = models.TextField(verbose_name='文章内容')
    cates = models.ForeignKey(to=Category,blank=True,null=True,on_delete=models.CASCADE)

5.序列化

(可以直接看6)

drf的 serializers帮助我们提供了

  • 数据校验
  • 序列化
    url(r'^new/category/$', views.NewCategoryView.as_view()),
    url(r'^new/category/(?P<pk>\d+)/$', views.NewCategoryView.as_view()),
from rest_framework import serializers
from rest_framework.views import APIView
class NewCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Category
        # fields = "__all__"
        fields = ['id','name']

class NewCategoryView(APIView):
    def get(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Category.objects.all()
            ser = NewCategorySerializer(instance=queryset,many=True)
            return Response(ser.data)
        else:
            model_object = models.Category.objects.filter(id=pk).first()
            ser = NewCategorySerializer(instance=model_object, many=False)
            return Response(ser.data)

    def post(self,request,*args,**kwargs):
        ser = NewCategorySerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def put(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        category_object = models.Category.objects.filter(id=pk).first()
        ser = NewCategorySerializer(instance=category_object,data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def delete(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        models.Category.objects.filter(id=pk).delete()
        return Response('删除成功')

6.跨表展示,跨字典展示,一对多

from django.db import models

class Category(models.Model):
    """
    文章分类
    """
    name = models.CharField(verbose_name='分类',max_length=32)


class Article(models.Model):
    """
    文章表
    """
    status_choices = (
        (1,'发布'),
        (2,'删除'),
    )
    status = models.IntegerField('状态',choices=status_choices,default=1) #字典
    title = models.CharField('标题',max_length=32)
    summary = models.CharField('简介',max_length=255)
    content = models.TextField('文章内容')
    cates = models.ForeignKey(to=Category,on_delete=models.CASCADE)  #跨表
urlpatterns = [
    re_path('drf/text/$', views.DrfNewText.as_view()),
    re_path('drf/text/(?P<pk>\d+)/$', views.DrfNewText.as_view()),
]
from rest_framework.views import APIView
from  rest_framework.response import Response
from   api import  models
from  api.serializer import NewText



class DrfNewText(APIView):
    def get(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        if not pk:
            obj = models.Article.objects.all()
            ser = NewText(instance=obj,many=True)
            return Response(ser.data)
        else:
            obj = models.Article.objects.filter(id=pk).first()
            ser = NewText(instance=obj,many=False)
            return Response(ser.data)


    def post(self,request,*args,**kwargs):
        ser = NewText(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        obj = models.Article.objects.filter(id=pk).first()
        ser = NewText(instance=obj, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        models.Article.objects.filter(id=pk).delete()
        return Response('删除成功')
from   rest_framework import serializers
from api import models

class NewText(serializers.ModelSerializer):
    #跨表查询出name,名字与数据库字段名相同时,会覆盖,需要加read_only=True
    cates_l = serializers.CharField(source='cates.name', required=False)
    status_l = serializers.CharField(source='get_status_display',required=False)
    # x1 = serializers.SerializerMethodField()
    class Meta:
        model = models.Article
        # fields = '__all__'
        fields = ['id','title','summary','content','cates','cates_l','status','status_l']
##方法定义x1  设置get_x1方法
    # def get_x1(self,obj):
    #     return obj.cates.name

postman请求

只需要提交数据库有的字段

    {
        "title": "今天1w23",
        "summary": "明天",
        "content": "后天",
        "cates": 1,
        "status": 2
    }

7.多对多

可写两个serializers ,显示和提交分开

from django.db import models

class Category(models.Model):
    """
    文章分类
    """
    name = models.CharField(verbose_name='分类',max_length=32)


class Article(models.Model):
    """
    文章表
    """
    status_choices = (
        (1,'发布'),
        (2,'删除'),
    )
    status = models.IntegerField('状态',choices=status_choices,default=1)
    title = models.CharField('标题',max_length=32)
    summary = models.CharField('简介',max_length=255)
    content = models.TextField('文章内容')
    cates = models.ForeignKey(to=Category,on_delete=models.CASCADE)
    tag = models.ManyToManyField(verbose_name='标签', to='Tag', blank=True)

class Tag(models.Model):
    """标签"""
    title = models.CharField(verbose_name='标签',max_length=32)
from   rest_framework import serializers
from api import models

class NewText(serializers.ModelSerializer):
    #跨表查询出name,并替换了fields的cates
    cates = serializers.CharField(source='cates.name', required=False)
    status = serializers.CharField(source='get_status_display',required=False)
    tag = serializers.SerializerMethodField()

    class Meta:
        model = models.Article
        fields = ['id','title','summary','content','cates','status','tag']
    def get_tag(self,obj):

            query_set = obj.tag.all()
            return [({'id':obj.id,'title':obj.title} )for obj in query_set]

class Newpost(serializers.ModelSerializer):
    class Meta:
        model =models.Article
        fields = '__all__'
from rest_framework.views import APIView
from  rest_framework.response import Response
from   api import  models
from  api.serializer import NewText,Newpost



class DrfNewText(APIView):
    def get(self,request,*args,**kwargs):
        pk = kwargs.get('pk')
        if not pk:
            obj = models.Article.objects.all()
            ser = NewText(instance=obj,many=True)
            return Response(ser.data)
        else:
            obj = models.Article.objects.filter(id=pk).first()
            ser = NewText(instance=obj,many=False)
            return Response(ser.data)


    def post(self,request,*args,**kwargs):
        ser = Newpost(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        obj = models.Article.objects.filter(id=pk).first()
        ser = Newpost(instance=obj, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        models.Article.objects.filter(id=pk).delete()
        return Response('删除成功')

8.PageNumberPagination 分页

REST_FRAMEWORK = {
    'PAGE_SIZE':3,
    # 'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination"
}   #指定分页方式
from rest_framework.pagination import PageNumberPagination
from rest_framework import serializers
from   api import  models
class PageArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        fields = "__all__"
class  Page(APIView):
    def get(self,request,*args,**kwargs):
        queryset  = models.Article.objects.all()
        #方法一,仅数据
        #分页对象
        page_object = PageNumberPagination()
        # 调用 分页对象.paginate_queryset方法进行分页,得到的结果是分页之后的数据
        # result就是分完页的一部分数据
        result = page_object.paginate_queryset(queryset, request, self)
        # 序列化分页之后的数据
        ser = PageArticleSerializer(instance=result, many=True)
        return Response(ser.data)
        # 方式二:数据 + 分页信息
        return page_object.get_paginated_response(ser.data) #只有这行不一样
        #方式三: 自定义返回数据
        return Response({'count': page_object.page.paginator.count, 'result': ser.data})

9.LimitOffsetPagination 分页

from rest_framework.pagination import LimitOffsetPagination
from rest_framework import serializers


class PageArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        fields = "__all__"

class HulaLimitOffsetPagination(LimitOffsetPagination):
    max_limit = 2  #限定最大条数

class Page(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.Article.objects.all()

        page_object = HulaLimitOffsetPagination() #调用
        result = page_object.paginate_queryset(queryset, request, self)
        ser = PageArticleSerializer(instance=result, many=True)
        return Response(ser.data)

###  http://127.0.0.1:8000/page/article/?offset=0&limit=7

10.扩展用法

调取内部方法设置分页

REST_FRAMEWORK = {
    'PAGE_SIZE':3,
    'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination"
}
from rest_framework.generics import ListAPIView
from rest_framework import serializers
from   api import  models


class PageViewArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        fields = "__all__"


class PageViewArticleView(ListAPIView):


    queryset = models.Article.objects.all()
    serializer_class = PageViewArticleSerializer
#http://127.0.0.1:8000/page/article/?page=1

11.呼拉圈接口设置

1.设置表结构

不会经常变化的值放在内存:choices形式,避免跨表性能低。 如标题

分表:如果表中列太多/大量内容可以选择水平分表 如内容

from django.db import models

class UserInfo(models.Model):
    """ 用户表 """
    username = models.CharField(verbose_name='用户名',max_length=32)
    password = models.CharField(verbose_name='密码',max_length=64)


class Article(models.Model):
    """ 文章表 """
    category_choices = (
        (1,'咨询'),
        (2,'公司动态'),
        (3,'分享'),
        (4,'答疑'),
        (5,'其他'),
    )
    category = models.IntegerField(verbose_name='分类',choices=category_choices)
    title = models.CharField(verbose_name='标题',max_length=32)
    image = models.CharField(verbose_name='图片路径',max_length=128) # /media/upload/....
    summary = models.CharField(verbose_name='简介',max_length=255)

    comment_count = models.IntegerField(verbose_name='评论数',default=0)
    read_count = models.IntegerField(verbose_name='浏览数',default=0)

    author = models.ForeignKey(verbose_name='作者',to='UserInfo')
    date = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)

class ArticleDetail(models.Model):
    article = models.OneToOneField(verbose_name='文章表',to='Article')
    content = models.TextField(verbose_name='内容')


class Comment(models.Model):
    """ 评论表 """
    article = models.ForeignKey(verbose_name='文章',to='Article')
    content = models.TextField(verbose_name='评论')
    user = models.ForeignKey(verbose_name='评论者',to='UserInfo')
    # parent = models.ForeignKey(verbose_name='回复',to='self', null=True,blank=True)

2.url

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^article/', views.ArticleView.as_view()),
]

3.功能实现

1.增加文章(可以不写)一次增加两个表中的数据:

from . import models
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        # fields = '__all__'
        exclude = ['author',]

class ArticleDetaSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.ArticleDetail
        # fields = '__all__'
        exclude = ['article',]


class ArticleView(APIView):
    """文章的视图类"""

    def get(self,request,*args,**kwargs):
        """获取文章"""
        pass

    def post(self,request,*args,**kwargs):
        """新增文章"""
        ser = ArticleSerializer(data=request.data)
        ser_detail = ArticleDetaSerializer(data=request.data)
        if ser.is_valid() and ser_detail.is_valid():
            #增加文章
            article_obj = ser.save(author_id=1) #从session取出用户的ID存在数据库,这里没写,现编一个
            ser_detail.save(article=article_obj)
            return Response(ser.data)

        return  Response("错误")

{"category":"2","title":"梦啊","image":"www.zhuimengnan.com","summary":"梦在哪啊你走的那么决然","content":"人生路远,不是不爱了,是想给自己一条退路"}

2.get获取文章列表

class ArticlelistSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        fields = '__all__'

class ArticleView(APIView):
    """文章的视图类"""
    def post(self,request,*args,**kwargs):
        pass #看上边 这里不写了

    def get(self,request,*args,**kwargs):
        """获取文章列表"""
        #分页
        queryset = models.Article.objects.all().order_by('-date')
        page = PageNumberPagination()
        result = page.paginate_queryset(queryset,request,self)

        #序列化
        ser = ArticlelistSerializer(instance=result,many=True)
        return Response(ser.data)

3.文章详细

#文章详细钩子
class PageArticleSerializer(serializers.ModelSerializer):
    content = serializers.CharField(source='articledetail.content')
    author = serializers.CharField(source='author.username')
    category =serializers.CharField(source='get_category_display')
    date =serializers.SerializerMethodField()
    class Meta:
        model = models.Article
        fields = '__all__'

        def get_date(self,obj):
            return obj.date.strftime("%Y-%m-%d %H:%M:%S")

class ArticleView(APIView):
    """文章的视图类"""

    def get(self,request,*args,**kwargs):
        """获取文章列表"""
        pk = kwargs.get('pk')
        if not pk:
            #分页
            queryset = models.Article.objects.all().order_by('-date')
            page = PageNumberPagination()
            result = page.paginate_queryset(queryset,request,self)

            #序列化
            ser = ArticlelistSerializer(instance=result,many=True)
            return Response(ser.data)
        #文章详细
        article_obj = models.Article.objects.filter(id=pk).first()
        ser =PageArticleSerializer(instance=article_obj,many=False)
        return Response(ser.data)

4.评论列表

  • 查看评论列表
    访问时:http://127.0.0.1:8000/comment/?article=2

    http://127.0.0.1:8000/comment/
    
    {
      article:1,
      content:'xxx'
    }
    
url(r'^comment/$', views.CommentView.as_view()),
class CommentView(APIView):
    """评论接口"""

    def get(self,request):
        #取得的是url传过来的参数
        article_id = request.query_params.get('article') #return self._request.GET

        queryset = models.Comment.objects.filter(article_id=article_id)

        ser = CommentSerializer(instance=queryset,many=True)
        return  Response(ser.data)
    def post(self,request,*args,**kwargs):
        print(request.data)
        ser = PostCommentSerializer(data=request.data)
        if ser.is_valid():
            ser.save(user_id=2)
            return Response('sucess')
        return Response(ser.errors)

5.总结

from django.db import models

class UserInfo(models.Model):
    """ 用户表 """
    username = models.CharField(verbose_name='用户名',max_length=32)
    password = models.CharField(verbose_name='密码',max_length=64)


class Article(models.Model):
    """ 文章表 """
    category_choices = (
        (1,'咨询'),
        (2,'公司动态'),
        (3,'分享'),
        (4,'答疑'),
        (5,'其他'),
    )
    category = models.IntegerField(verbose_name='分类',choices=category_choices)
    title = models.CharField(verbose_name='标题',max_length=32)
    image = models.CharField(verbose_name='图片路径',max_length=128) # /media/upload/....
    summary = models.CharField(verbose_name='简介',max_length=255)

    comment_count = models.IntegerField(verbose_name='评论数',default=0)
    read_count = models.IntegerField(verbose_name='浏览数',default=0)

    author = models.ForeignKey(verbose_name='作者',to='UserInfo')
    date = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)

class ArticleDetail(models.Model):
    article = models.OneToOneField(verbose_name='文章表',to='Article')
    content = models.TextField(verbose_name='内容')


class Comment(models.Model):
    """ 评论表 """
    article = models.ForeignKey(verbose_name='文章',to='Article')
    content = models.TextField(verbose_name='评论')
    user = models.ForeignKey(verbose_name='评论者',to='UserInfo')
    # parent = models.ForeignKey(verbose_name='回复',to='self', null=True,blank=True)


from django.conf.urls import url
from django.contrib import admin
from api import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^article/$', views.ArticleView.as_view()),
    url(r'^article/(?P<pk>\d+)/$', views.ArticleView.as_view()),
    url(r'^comment/$', views.CommentView.as_view()),
]

from . import models
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        # fields = '__all__'
        exclude = ['author',]

class ArticleDetaSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.ArticleDetail
        # fields = '__all__'
        exclude = ['article',]

class ArticlelistSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Article
        fields = '__all__'

class PageArticleSerializer(serializers.ModelSerializer):
    content = serializers.CharField(source='articledetail.content')
    author = serializers.CharField(source='author.username')
    category =serializers.CharField(source='get_category_display')
    date =serializers.SerializerMethodField()
    class Meta:
        model = models.Article
        fields = '__all__'

    def get_date(self,obj):
        print(obj)
        return obj.date.strftime("%Y-%m-%d %H:%M:%S")

class ArticleView(APIView):
    """文章的视图类"""
    def post(self,request,*args,**kwargs):
        """新增文章"""
        ser = ArticleSerializer(data=request.data)
        ser_detail = ArticleDetaSerializer(data=request.data)
        if ser.is_valid() and ser_detail.is_valid():
            #增加文章
            article_obj = ser.save(author_id=1) #从session取出用户的ID存在数据库,这里没写,现编一个
            ser_detail.save(article=article_obj)
            return Response(ser.data)

        return  Response("错误")

    def get(self,request,*args,**kwargs):
        """获取文章列表"""
        pk = kwargs.get('pk')
        if not pk:
            #分页
            condition = {}
            category = request.query_params.get('category')
            if category:
                condition['category'] = category
            queryset = models.Article.objects.filter(**condition).order_by('-date')
            # queryset = models.Article.objects.all().order_by('-date')
            page = PageNumberPagination()
            result = page.paginate_queryset(queryset,request,self)

            #序列化
            ser = ArticlelistSerializer(instance=result,many=True)
            return Response(ser.data)
        article_obj = models.Article.objects.filter(id=pk).first()
        ser =PageArticleSerializer(instance=article_obj,many=False)
        return Response(ser.data)

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Comment
        fields = '__all__'

class PostCommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Comment
        exclude =['user',]

class CommentView(APIView):
    """评论接口"""

    def get(self,request):
        #取得的是url传过来的参数
        article_id = request.query_params.get('article') #return self._request.GET

        queryset = models.Comment.objects.filter(article_id=article_id)

        ser = CommentSerializer(instance=queryset,many=True)
        return  Response(ser.data)
    def post(self,request,*args,**kwargs):
        print(request.data)
        ser = PostCommentSerializer(data=request.data)
        if ser.is_valid():
            ser.save(user_id=2)
            return Response('sucess')
        return Response(ser.errors)

REST_FRAMEWORK = {
    'PAGE_SIZE':2,
    'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination"
}

12. 筛选

案例:在文章列表时候,添加筛选功能。

全部:http://127.0.0.1:8000/article/
筛选:http://127.0.0.1:8000/article/?category=2
class ArticleView(APIView):
    """ 文章视图类 """

    def get(self,request,*args,**kwargs):
        """ 获取文章列表 """
        pk = kwargs.get('pk')
        if not pk:
            condition = {}
            category = request.query_params.get('category')
            if category:
                condition['category'] = category
            queryset = models.Article.objects.filter(**condition).order_by('-date')
            
            pager = PageNumberPagination()
            result = pager.paginate_queryset(queryset,request,self)
            ser = ArticleListSerializer(instance=result,many=True)
            return Response(ser.data)
        article_object = models.Article.objects.filter(id=pk).first()
        ser = PageArticleSerializer(instance=article_object,many=False)
        return Response(ser.data)  

13.drf的组件:内置筛选

from rest_framework.views import APIView
from rest_framework.response import Response
from . import models
from rest_framework.filters import BaseFilterBackend
from rest_framework.pagination import PageNumberPagination


class NewFilterBackend(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):    
        val = request.query_params.get('cagetory')
        return queryset.filter(category_id=val)
    
class NewSerializers(serializers.ModelSerializer):
    class Meta:
        model = models.Tag
        fields = "__all__"

class NewView(ListAPIView):
    queryset = models.news.objects.all()
    filter_backends = [NewFilterBackend,]
    serializer_class = NewSerializers
    pagination_class = PageNumberPagination

14 .视图举例:

实现 表Tag的增删改查

from django.db import models

class Tag(models.Model):
    title =models.CharField(max_length=32)
from django.conf.urls import url,include
from django.contrib import admin
from shanxuan import views

urlpatterns = [
    url(r'^tag/$', views.Tagviews.as_view()),
    url(r'^tag/(?P<pk>\d+)/$', views.TagDetailviews.as_view()),
]

查询单条数据和全部的 不能写在一起,会被覆盖掉,所以写两个views

from  rest_framework import serializers
from  rest_framework.generics import  ListAPIView,RetrieveAPIView,CreateAPIView,UpdateAPIView,DestroyAPIView


from  . import models

class TagSer(serializers.ModelSerializer):
    class Meta:
        model = models.Tag
        fields = "__all__"

class Tagviews(ListAPIView,CreateAPIView):
    queryset = models.Tag.objects.all()
    serializer_class = TagSer

class TagDetailviews(RetrieveAPIView,UpdateAPIView,DestroyAPIView):
    queryset = models.Tag.objects.all()
    serializer_class = TagSer

分页写在配置文件中

REST_FRAMEWORK = {
    'PAGE_SIZE':2,
    'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination"
}

15.自定制方法

class TagSer(serializers.ModelSerializer):
    class Meta:
        model = models.Tag
        fields = "__all__"

class TagView(ListAPIView,CreateAPIView):
    queryset = models.Tag.objects.all()
    #serializer_class = TagSer

    def get_serializer_class(self):
        # self.request
        # self.args
        # self.kwargs
        #分两个序列化的类
        if self.request.method == 'GET':
            return TagSer
        elif self.request.method == 'POST':
            return OtherTagSer
        #提交自定制数据
    def perform_create(self,serializer):
        serializer.save(author=1)

class TagDetailView(RetrieveAPIView,UpdateAPIView,DestroyAPIView):
    queryset = models.Tag.objects.all()
    serializer_class = TagSer

16.类继承关系

class View(object):
    def dipatch(self):
        print(123)
    
class APIView(View):
    version_class = settings.xxx 
    parser_class = settings.sxx
    permision_classes = []
    
    def dipatch(self):
    
        self.initial()
    
        method = getattr(self,"get")
        return method()
        
    def initial(self):
        self.version_class()
        self.parser_class()
        for item in self.permision_classes:
            item()
    
class GenericAPIView(APIView):
    queryset = None
    serilizer_class = None 
    def get_queryset(self):
        return self.queryset
        
    def get_serilizer(self,*arg,**kwargs):
        cls = self.get_serilizer_class()
        return cls(*arg,**kwargs)
    
    def get_serilizer_class(self):
        return self.serilizer_class
class ListModelMixin(object):
    def list(self):
        queryset = self.get_queryset()
        ser = self.get_serilizer(queryset,many=True)
        return Reponse(ser.data)
        
class ListAPIView(ListModelMixin,GenericAPIView):
    def get(self):
        return self.list(...)
    
    
class TagView(ListAPIView):
    queryset = models.User.object.all()
    serilizer_class = TagSerilizer
    version_class = URLPathClass
    parser_class = JSONParser
    permission_classes = [Foo,Bar ]

obj = TagView()
x = obj.dispatch()
给用户返回x

17.版本

在url中显示版本信息,

http://127.0.0.1:8000/version/v1/index  
urlpatterns +=[
    url(r'^version/',include('version.urls'))
]
urlpatterns = [
    url(r'^(?P<version>\w+)/index/$',views.IndexViews.as_view()),
]
class IndexViews(APIView):
    #versioning_class = URLPathVersioning  #这是局部
    def get(self,request,*args,**kwargs):
        print(request.version)
        print(request.versioning_scheme)
        return Response("123")

REST_FRAMEWORK = {
    'PAGE_SIZE':2,
    'DEFAULT_PAGINATION_CLASS':"rest_framework.pagination.PageNumberPagination",
    'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning",
    'ALLOWED_VERSIONS':['v1','v2'],  #这是全局 #这是全局
    'VERSION_PARAM':'version',
}

18.认证

流程

1.新建项目,创建数据库

class UserInfo(models.Model):

    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    token = models.CharField(max_length=64,null=True,blank=True)

2.url

from django.conf.urls import url,include
from django.contrib import admin
from . import views
urlpatterns = [
    url(r'^login/$', views.LoginView.as_view()),
    url(r'^order/$', views.OrderView.as_view()),
]

3.views

import uuid
from django.shortcuts import render
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from rest_framework.versioning import URLPathVersioning
from rest_framework.views import APIView
from rest_framework.response import Response

from . import models


#创建token随机字符创,添加到数据库
class LoginView(APIView):
    def post(self,request,*args,**kwargs):
        user_obj = models.UserInfo.objects.filter(**request.data).first()
        if not user_obj:
            return Response('登录失败')
        strs =  str(uuid.uuid4())
        user_obj.token = strs
        user_obj.save()
        return  Response(strs)

#判断条件,用户是否存在
from rest_framework.authentication import BaseAuthentication
class TokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        user_obj = models.UserInfo.objects.filter(token=token).first()
        if user_obj:
            return (user_obj,token)
        return (None,None)

#登录验证后缀有token返回正常,无返回gun
class OrderView(APIView):
    authentication_classes = [TokenAuthentication, ]
    def get(self,request,*args,**kwargs):
        #print(request.user)
        #print(request.auth)
        if request.user:
            return Response("123")
        return Response('gun')

4. 简化代码

全局配置

需要把TokenAuthentication 单独提出放到auth.py中

setting中配置

    'DEFAULT_AUTHENTICATION_CLASSES':['renzheng.auth.TokenAuthentication',]

5.postman测试

http://127.0.0.1:8000/renzheng/order/?token=df2d6aba-187b-4e9c-af1b-fe025fdffde3
http://127.0.0.1:8000/renzheng/order/

总结

当用户发来请求时,  dispash 找到认证的所有类并实例化成为对象列表,然后将对象列表封装到新的request对象中。

以后在视同中调用request.user

在内部会循环认证的对象列表,并执行每个对象的authenticate方法,该方法用于认证,他会返回两个值分别会赋值给
request.user/request.auth 

19.权限

其他同上

from  rest_framework.views import APIView
from  rest_framework.response import Response
from rest_framework.permissions import BasePermission

#判断是否有权限
class MyPermission(BasePermission):
    message ={'error':"无权限"}  #方式一
    #多条数据
    def has_permission(self, request, view):
        if request.user:
            return True

        return False
    # from  rest_framework import exceptions
    # return exceptions.PermissionDenied({'error':"无权限"})
    #单条数据
    def has_object_permission(self, request, view, obj):

        return False



class OrderView(APIView):
    permission_classes = [MyPermission,]
    def get(self,request,*args,**kwargs):
        return Response('123')

全局的话

REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES":["",]
}

源码分析

class APIView(View):
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    
    def dispatch(self, request, *args, **kwargs):
        封装request对象
        self.initial(request, *args, **kwargs)
        通过反射执行视图中的方法

    def initial(self, request, *args, **kwargs):
        版本的处理
        # 认证
        self.perform_authentication(request)
        
        # 权限判断
        self.check_permissions(request)
        
        
        self.check_throttles(request)
        
    def perform_authentication(self, request):
        request.user
    
    def check_permissions(self, request):
        # [对象,对象,]
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(request, message=getattr(permission, 'message', None))
    def permission_denied(self, request, message=None):
        if request.authenticators and not request.successful_authenticator:
            raise exceptions.NotAuthenticated()
        raise exceptions.PermissionDenied(detail=message)
        
    def get_permissions(self):
        return [permission() for permission in self.permission_classes]
    
class UserView(APIView):
    permission_classes = [MyPermission, ]
    
    def get(self,request,*args,**kwargs):
        return Response('user')

20.跨域

由于浏览器具有“同源策略”的限制。
如果在同一个域下发送ajax请求,浏览器的同源策略不会阻止。
如果在不同域下发送ajax,浏览器的同源策略会阻止。
  • 域相同,永远不会存在跨域。
    • 非前后端分离,没有跨域。
    • 前后端分离,nginx分流不存在跨域。
  • 域不同时,才会存在跨域。
    • l拉勾网,前后端分离,存在跨域(设置响应头解决跨域)

1.解决跨域:CORS

本质在数据返回值设置响应头   #在服务端

from django.shortcuts import render,HttpResponse

def json(request):
    response = HttpResponse("JSONasdfasdf")
    response['Access-Control-Allow-Origin'] = "*"
    return response    

2.跨域时,发送了2次请求

在跨域时,发送的请求会分为两种:

条件:
    1、请求方式:HEAD、GET、POST
    2、请求头信息:
        Accept
        Accept-Language
        Content-Language
        Last-Event-ID
        Content-Type 对应的值是以下三个中的任意一个
                                application/x-www-form-urlencoded
                                multipart/form-data
                                text/plain
 
注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
  • 简单请求,发一次请求。

    设置响应头就可以解决
    from django.shortcuts import render,HttpResponse
    
    def json(request):
        response = HttpResponse("JSONasdfasdf")
        response['Access-Control-Allow-Origin'] = "*"
        return response
    
    
  • 复杂请求,发两次请求。

    • 预检
    • 请求
    from django.views.decorators.csrf import csrf_exempt
    
    @csrf_exempt
    def put_json(request):
        response = HttpResponse("JSON复杂请求")
        if request.method == 'OPTIONS':
            # 处理预检
            response['Access-Control-Allow-Origin'] = "*"
            response['Access-Control-Allow-Methods'] = "PUT"
            return response
        elif request.method == "PUT":
            return response
    

3.总结

  1. 由于浏览器具有“同源策略”的限制,所以在浏览器上跨域发送Ajax请求时,会被浏览器阻止。
  2. 解决跨域
    • 不跨域
    • CORS(跨站资源共享,本质是设置响应头来解决)。
      • 简单请求:发送一次请求
      • 复杂请求:发送两次请求

21.访问频率限制

用法

from rest_framework.views import APIView
from rest_framework.response import Response

from rest_framework.throttling import AnonRateThrottle,BaseThrottle####

class ArticleView(APIView):
    throttle_classes = [AnonRateThrottle,] #####
    def get(self,request,*args,**kwargs):
        return Response('文章列表')

class ArticleDetailView(APIView):
    def get(self,request,*args,**kwargs):
        return Response('文章详细')
REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning",
    "ALLOWED_VERSIONS":['v1',],
    "DEFAULT_THROTTLE_RATES":{  ##############
        "anon":'3/m'
    }
}
#全局DEFAULT_AUTHENTICATION_CLASSES=[rest_framework.throttling.AnonRateThrottle]

源码

class BaseThrottle:
    """
    Rate throttling of requests.
    """

    def allow_request(self, request, view):
        """
        Return `True` if the request should be allowed, `False` otherwise.
        """
        raise NotImplementedError('.allow_request() must be overridden')

    def get_ident(self, request):
        """
        Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
        if present and number of proxies is > 0. If not use all of
        HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
        """
        xff = request.META.get('HTTP_X_FORWARDED_FOR')
        remote_addr = request.META.get('REMOTE_ADDR')
        num_proxies = api_settings.NUM_PROXIES

        if num_proxies is not None:
            if num_proxies == 0 or xff is None:
                return remote_addr
            addrs = xff.split(',')
            client_addr = addrs[-min(num_proxies, len(addrs))]
            return client_addr.strip()

        return ''.join(xff.split()) if xff else remote_addr

    def wait(self):
        """
        Optionally, return a recommended number of seconds to wait before
        the next request.
        """
        return None


class SimpleRateThrottle(BaseThrottle):
    """
    A simple cache implementation, that only requires `.get_cache_key()`
    to be overridden.

    The rate (requests / seconds) is set by a `rate` attribute on the View
    class.  The attribute is a string of the form 'number_of_requests/period'.

    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')

    Previous request information used for throttling is stored in the cache.
    """
    cache = default_cache
    timer = time.time
    cache_format = 'throttle_%(scope)s_%(ident)s'
    scope = None
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

    def __init__(self):
        if not getattr(self, 'rate', None):
            self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)

    def get_cache_key(self, request, view):
        """
        Should return a unique cache-key which can be used for throttling.
        Must be overridden.

        May return `None` if the request should not be throttled.
        """
        raise NotImplementedError('.get_cache_key() must be overridden')

    def get_rate(self):
        """
        Determine the string representation of the allowed request rate.
        """
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)

    def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        if rate is None:
            return (None, None)
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        return (num_requests, duration)

    def allow_request(self, request, view):
        """
        Implement the check to see if the request should be throttled.

        On success calls `throttle_success`.
        On failure calls `throttle_failure`.
        """
        if self.rate is None:
            return True

        # 获取请求用户的IP
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True

        # 根据IP获取他的所有访问记录,[]
        self.history = self.cache.get(self.key, [])

        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()

    def throttle_success(self):
        """
        Inserts the current request's timestamp along with the key
        into the cache.
        """
        self.history.insert(0, self.now)
        self.cache.set(self.key, self.history, self.duration)
        return True

    def throttle_failure(self):
        """
        Called when a request to the API has failed due to throttling.
        """
        return False

    def wait(self):
        """
        Returns the recommended next request time in seconds.
        """
        if self.history:
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            remaining_duration = self.duration

        available_requests = self.num_requests - len(self.history) + 1
        if available_requests <= 0:
            return None

        return remaining_duration / float(available_requests)


class AnonRateThrottle(SimpleRateThrottle):
    """
    Limits the rate of API calls that may be made by a anonymous users.

    The IP address of the request will be used as the unique cache key.
    """
    scope = 'anon'

    def get_cache_key(self, request, view):
        if request.user.is_authenticated:
            return None  # Only throttle unauthenticated requests.

        return self.cache_format % {
            'scope': self.scope,
            'ident': self.get_ident(request)
        }

总结

  1. 如何实现的评率限制

    - 匿名用户,用IP作为用户唯一标记,但如果用户换代理IP,无法做到真正的限制。
    - 登录用户,用用户名或用户ID做标识。
    具体实现:
     在django的缓存中 = {
            throttle_anon_1.1.1.1:[100121340,],
            1.1.1.2:[100121251,100120450,]
        }
    
    
        限制:60s能访问3次
        来访问时:
            1.获取当前时间 100121280
            2.100121280-60 = 100121220,小于100121220所有记录删除
            3.判断1分钟以内已经访问多少次了? 4 
            4.无法访问
        停一会
        来访问时:
            1.获取当前时间 100121340
            2.100121340-60 = 100121280,小于100121280所有记录删除
            3.判断1分钟以内已经访问多少次了? 0
            4.可以访问
    

22..jwt

用于在前后端分离时,实现用户登录相关。

一般用户认证有2中方式:

  • token

    用户登录成功之后,生成一个随机字符串,自己保留一分+给前端返回一份。
    
    以后前端再来发请求时,需要携带字符串。
    后端对字符串进行校验。
    

    优势:

    • token只在前端保存,后端只负责校验。
    • 内部集成了超时时间,后端可以根据时间进行校验是否超时。
    • 由于内部存在hash256加密,所以用户不可以修改token,只要一修改就认证失败。
  • jwt

    用户登录成功之后,生成一个随机字符串,给前端。
      - 生成随机字符串
          {typ:"jwt","alg":'HS256'}     {id:1,username:'alx','exp':10}
          98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928374
          - 类型信息通过base64加密
          - 数据通过base64加密
          - 两个密文拼接在h256加密+加盐 base64url
      - 给前端返回
          98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928375
    
    前端获取随机字符串之后,保留起来。
    以后再来发送请求时,携带98qow39df0lj980945lkdjflo.saueoja8979284sdfsdf.asiuokjd978928375。
    
    
    后端接受到之后,
      1.先做时间判断
        2.字符串合法性校验。
    

安装

pip3 install djangorestframework-jwt

案例

  • app中注册

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'api.apps.ApiConfig',
        'rest_framework',
        'rest_framework_jwt'
    ]
    
  • 用户登录

    import uuid
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import URLPathVersioning
    from rest_framework import status
    
    from api import models
    
    class LoginView(APIView):
        """
        登录接口
        """
        def post(self,request,*args,**kwargs):
            #1.根据用户名和密码验证用户登录
            user = models.UserInfo.objects.filter(username=request.data.get('username'),password=request.data.get('password')).first()
            if not user:
                return  Response({'code':10001,'error':"用户名密码错误"})
            #2.根据user对象生成payload(中间值的数据)
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            payload = jwt_payload_handler(user)
    
            #3.构造前面数据,base64加密,中间数据base64加密,前两段拼接然后做hs256加密(加盐),在做base64加密.生成token
    
            jwt_encode_hander = api_settings.JWT_ENCODE_HANDLER
            token = jwt_encode_hander(payload)
    
            return Response({'code':10000,'data':token})
    
    
  • 用户认证

    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    # from rest_framework.throttling import AnonRateThrottle,BaseThrottle
    
    
    class ArticleView(APIView):
        # throttle_classes = [AnonRateThrottle,]
    
        def get(self,request,*args,**kwargs):
            # 获取用户提交的token,进行一步一步校验
            import jwt
            from rest_framework import exceptions
            from rest_framework_jwt.settings import api_settings
            jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
    
            jwt_value = request.query_params.get('token')
            try:
                payload = jwt_decode_handler(jwt_value)
            except jwt.ExpiredSignature:
                msg = '签名已过期'
                raise exceptions.AuthenticationFailed(msg)
            except jwt.DecodeError:
                msg = '认证失败'
                raise exceptions.AuthenticationFailed(msg)
            except jwt.InvalidTokenError:
                raise exceptions.AuthenticationFailed()
            print(payload)
    
            return Response('文章列表')
    
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!