1 开发模式

1.1前后端不分离

前端页面由后台服务提供,例如之前写的render、redirect返回html都是前后端不分离的项目
之前写的图书管理系统、学员管理系统、公司员工管理系统都是前后端不分离项目

1.2前后端分离

应用场景(需求):当一个公司既要开发一个网页端系统,又要开发一个手机app的时候,如果前后端不分离,需要后端服务写两套代码,而前后端分离的话可以只写一套代码用来提供数据,因为网页端和app都来自一个数据源,提高开发效率,降低成本。
前后端分离交互:
后端:返回json数据,不再写render和redirect,只写httpresponse
为前端提供url(api)

2 接口开发

2.1 以前版本

前端:
使用ajax或axios发送异步请求

后端:
url.py

  1. urlpatterns = [
  2. path('list_dep/', list_dep),
  3. path('add_dep/', add_dep),
  4. path('del_dep/<int:dep_id>/', del_dep),
  5. path('edit_dep/<int:dep_id>/', edit_dep),]

views.py

  1. def list_dep(request):
  2. return HttpRespose('')
  3. def add_dep(request):
  4. return HttpRespose('')
  5. def edit_dep(request, dep_id):
  6. return HttpRespose('')
  7. def del_dep(request, dep_id):
  8. return HttpRespose('')

缺点:url会随着表的增加极具增加,现在只有一个dep表 如果10张表就会有40个url

2.2 现在版本(让django程序遵循restful规范)

什么是restful规范:
是一套规则,用于前后端交互的约定,它规定了一些协议,以前增删改查需要四个接口,restful规范只需要一个接口,根据method不同,执行不同操作
restful规范还规定:数据在传输格式是json格式
建议url是:域名/api/版本号/接口地址
api接口不建议用动词
有条件的话,在url后面进行传递:域名/api/版本号/接口地址/?参数1&参数2
总结:

  • 建议用https代替http,保证数据的安全
  • url中添加api标识
  • 在接口中体现版本
  • 一般情况下对于api接口,用名词不用动词
  • 如果有筛选条件的话,可以添加在url中
  • 根据method不同执行不同操作
  • 返回给用户状态码
  • 返回值:
    • GET

返回查询到的数据,列表套字典

  1. - POST

返回新增数据

  1. - PUT

返回更新数据

  1. - Delete

返回空

  • 操作异常时,返回错误信息

标准url:https://www.cnblogs.com/api/v1/userinfo/?page=1&category=2
还可以:https://api.cnblogs.com/v1/userinfo/?page=1&category=2(存在跨域问题)

基于FBV

  1. urlpatterns = [
  2. path('dep/',dep)
  3. ]
  1. def dep(request):
  2. if request.method == "GET":
  3. return HttpResponse('查询')
  4. elif request.method == "POST":
  5. return HttpResponse('增加')
  6. elif request.method == "PUT":
  7. return HttpResponse('更新')
  8. elif request.method == "DELETE":
  9. return HttpResponse('删除')

基于CBV(推荐使用)—因为可以通过继承实现代码复用

  1. urlpatterns = [
  2. path('dep/',DepView.as_view())
  3. ]
  1. class DepView(View):
  2. def get(self,request,*args,**kwargs):
  3. return HttpResponse('')
  4. def post(self,request,*args,**kwargs):
  5. return HttpResponse('')
  6. def put(self,request,*args,**kwargs):
  7. return HttpResponse('')
  8. def delete(self,request,*args,**kwargs):
  9. return HttpResponse('')

3 初识drf

drf是一个基于django开发的组件,本质是一个django的app
帮助我们在django的基础上快速搭建遵循restful规范的接口程序

功能:

  • APIView
  • 解析器:根据用户请求体格式不同进行数据解析,解析之后放在request.data中
  • 序列化:serializers
  • 渲染器:可以帮我们把json数据渲染到页面进行友好展示

    3.1 drf使用

    1.注册app
    REST Framework - 图1
    2.写路由 ```python urlpatterns = [ path(‘drf/info/‘, views.DrfInfoView.as_view()) ]
  1. 3.写视图
  2. ```python
  3. #导入需要的类
  4. from rest_framework.views import APIView
  5. from rest_framework.response import Response
  6. class DrfInfoView(APIView): # 继承APIView类
  7. """
  8. 咨询接口
  9. """
  10. def get(self,request,*args,**kwargs):
  11. data = [
  12. {'id':1,'title':'刘学斌'},
  13. {'id':2,'title':'王淑贤'}
  14. ]
  15. return Response(data) # 返回rest_framework.views的Response对象

界面如下:
REST Framework - 图2
应用场景:前后端分离项目写接口时用到,用drf会很方便

4 小项目练习

4.1 创建程序并初始化数据库

1.修改相关配置
注册app
REST Framework - 图3
修改数据库迁移

  1. DATABASES = {
  2. 'default': {
  3. 'ENGINE': 'django.db.backends.mysql',
  4. 'NAME':'数据库名',
  5. 'USER': 'root',
  6. 'PASSWORD': '362514',
  7. 'HOST': 'localhost',
  8. 'PORT': '3306',
  9. }
  10. }

修改init.py

  1. import pymysql
  2. pymysql.version_info = (1, 4, 13, "final", 0)
  3. pymysql.install_as_MySQLdb() # 使用pymysql代替mysqldb连接数据库

2.写model

  1. from django.db import models
  2. class Category(models.Model):
  3. """
  4. 分类表
  5. """
  6. name = models.CharField(verbose_name='分类',max_length=32)
  7. class Tag(models.Model):
  8. """
  9. 标签表
  10. """
  11. title = models.CharField(verbose_name='标签名', max_length=32)
  12. class Article(models.Model):
  13. """
  14. 文章表
  15. """
  16. choices_status = (
  17. (1, '发布'),
  18. (2, '删除')
  19. )
  20. title = models.CharField(verbose_name='标题',max_length=32)
  21. summary = models.CharField(verbose_name='简介',max_length=255)
  22. content = models.TextField(verbose_name='文章内容')
  23. category = models.ForeignKey(to=Category,verbose_name='分类',on_delete=models.CASCADE)
  24. status = models.IntegerField(verbose_name='状态', choices=choices_status, default=1)
  25. tag = models.ManyToManyField(verbose_name='标签名', to=Tag)

3.写路由

  1. from django.contrib import admin
  2. from django.urls import path
  3. from huolala import views
  4. urlpatterns = [
  5. path('drf/category/', views.DrfCategoryView.as_view())
  6. ]

4.写视图

  1. from rest_framework.views import APIView
  2. from rest_framework.response import Response
  3. class DrfCategoryView(APIView):
  4. pass

5.数据库迁移

4.2 接口开发

开发完毕之后告诉前端:
http://127.0.0.1:8000/drf/category/
因为在公司你不可能自己开发一个前端页面去做测试,也不可能让前端开发者去测试你的功能,因为你可能需要测试好几十遍,前端开发者不可能浪费时间给你测试,所以需要自己去测试

所以要用工具模拟浏览器发请求:postman

REST Framework - 图4
如果以上述格式发送请求体,后端拿到的原始数据是
request.body :b’name=liuxuebin&age=19&gender=nan’
request.POST :

REST Framework - 图5
如果以上述原生JSON数据格式发送数据,后端拿到的是
request.body :b’{“name”:”wangshuxain”,”age”:18,”gender”:”nan”}’
request.POST :

现在大多数前端发的都是JSON格式数据

后端接收到的是字节类型数据,要转化成字典,首先要转化成字符串,然后用json.loads转化成字典
python中字节类型、字符串类型也就是json数据格式的字符串、字典 各自的样子:

b’{“name”:”wangshuxain”,”age”:18,”gender”:”nan”}’


{“name”:”wangshuxain”,”age”:18,”gender”:”nan”}


{‘name’: ‘wangshuxain’, ‘age’: 18, ‘gender’: ‘nan’}

但是request.data(drf提供的data属性)是字典类型,内部原理就是

  1. s = reqeust.body.decode('utf-8')
  2. request.data = json.loads(s)

4.2.1 向分类列表增加一条记录

1.前端发请求
REST Framework - 图6
2.写视图

  1. from rest_framework.views import APIView
  2. from rest_framework.response import Response
  3. from huolala import models
  4. import json
  5. class DrfCategoryView(APIView):
  6. """
  7. 对category表进行增删改查的接口
  8. """
  9. def post(self,request,*args,**kwargs):
  10. """
  11. 增加一条分类记录
  12. :param request: 接受前端请求
  13. :param args: 接受前端参数
  14. :param kwargs: 接受前端参数
  15. :return:
  16. """
  17. # request.data是字典类型{‘name’:'...'}
  18. models.Category.objects.create(**request.data)
  19. return Response('成功')

4.2.2 查询分类列表全部记录

  1. def get(self,request,*args,**kwargs):
  2. """
  3. 获取所有文章分类列表
  4. :param request:
  5. :param args:
  6. :param kwargs:
  7. :return:
  8. """
  9. quertyset = models.Category.objects.all().values('id','name')
  10. return Response(quertyset)

因为Response内部要进行序列化,传递的是Category对象是不能被序列化的,所以用values转化成字典,再放入到Response中会序列化之后变成JSON对象(本质是字符串只不过是JSON数据格式)返回给前端

4.2.3 查询分类列表中的一条记录

1.修改url

  1. urlpatterns = [
  2. path('drf/category/', views.DrfCategoryView.as_view()),
  3. path('drf/category/<int:id>/', views.DrfCategoryView.as_view())
  4. ]

2.修改视图

  1. def get(self,request,id=False,*args,**kwargs):
  2. """
  3. 获取 所有分类列表记录/单个记录
  4. :param request:
  5. :param args:
  6. :param kwargs:
  7. :return:
  8. """
  9. if not id:
  10. quertyset = models.Category.objects.all().values('id', 'name')
  11. return Response(quertyset)
  12. category_object = models.Category.objects.filter(id=id).first()
  13. data = model_to_dict(category_object) # Category对象直接转化成字典
  14. return Response(data)

4.2.4 删除和更新分类列表记录

  1. def delete(self,request,id,*args,**kwargs):
  2. """
  3. 删除记录
  4. :param request:
  5. :param id: 前台传来要删除的id
  6. :param args:
  7. :param kwargs:
  8. :return:
  9. """
  10. models.Category.objects.filter(id=id).delete()
  11. return Response('删除成功')
  12. def put(self,request,id,*args,**kwargs):
  13. """
  14. 更新记录
  15. :param request:
  16. :param id:
  17. :param args:
  18. :param kwargs:
  19. :return:
  20. """
  21. models.Category.objects.filter(id=id).update(**request.data)
  22. return Response('更新成功')

4.3 drf的序列化

引入serializers模块之后的增删改查
功能:

  • 数据校验
  • 序列化

    4.3.1 单表的增删改查

    serializer.py ```python from rest_framework import serializers from huolala import models

引入serializers模块之后对增删改查接口的改进

class NewCategorySerializers(serializers.ModelSerializer): “”” 对分类表进行序列化的类 “”” class Meta: model = models.Category

  1. # 设置返回时,要显示的字段
  2. fields = ['id', 'name']
  1. views.py
  2. ```python
  3. from rest_framework.views import APIView
  4. from rest_framework.response import Response
  5. class NewCategoryView(APIView):
  6. """
  7. 加入序列化之后改进的接口
  8. """
  9. def get(self, request, id=False, *args, **kwargs):
  10. """
  11. 获取 所有分类列表记录/单个记录
  12. :param request:
  13. :param args:
  14. :param kwargs:
  15. :return:
  16. """
  17. if not id:
  18. queryset = models.Category.objects.all()
  19. # 把要序列化的queryset对象传给instance,对queryset中的category序列化成字典
  20. ser = NewCategorySerializers(instance=queryset, many=True)
  21. return Response(ser.data)
  22. category_object = models.Category.objects.filter(id=id).first()
  23. ser = NewCategorySerializers(instance=category_object, many=False)
  24. return Response(ser.data)
  25. def post(self,request,*args,**kwargs):
  26. ser = NewCategorySerializers(data=request.data)
  27. if ser.is_valid(): # 数据校验
  28. ser.save() # 增加数据
  29. return Response(ser.data) # 增加成功,返回增加的记录
  30. else:
  31. return Response(ser.errors) # 返回错误信息
  32. def put(self, request, id=False, *args, **kwargs):
  33. category_obj = models.Category.objects.filter(id=id).first()
  34. ser = NewCategorySerializers(instance=category_obj,data=request.data)
  35. if ser.is_valid():
  36. ser.save() # 修改成功
  37. return Response(ser.data)
  38. else:
  39. return Response(ser.errors)
  40. def delete(self,request,id=False,*args,**kwargs):
  41. models.Category.objects.filter(id=id).delete()
  42. return Response('删除成功')

小补充:
模板查找顺序:先找自己创建目录里的templates再去找INSTALLED_APPS里面注册的程序里面的templates

4.3.2 一对多的增删改查

serializer.py

  1. class NewArticleSerializers(serializers.ModelSerializer):
  2. """
  3. 对文章表进行序列化的类
  4. """
  5. # 把外键中name字段值显示的方法
  6. category = serializers.CharField(source='category.name')
  7. # model中choice属性显示名称的方法
  8. status = serializers.CharField(source='get_status_display')
  9. class Meta:
  10. model = models.Article
  11. fields = ['id', 'title', 'content', 'category', 'status']

views.py

  1. class NewArticleView(APIView):
  2. """
  3. 加入序列化之后的改进的接口
  4. """
  5. def get(self, request, id=False, *args, **kwargs):
  6. if not id:
  7. queryset = models.Article.objects.all()
  8. ser = NewArticleSerializers(instance=queryset, many=True)
  9. return Response(ser.data)
  10. else:
  11. article_obj = models.Article.objects.filter(id=id).first()
  12. ser = NewArticleSerializers(instance=article_obj, many=False)
  13. return Response(ser.data)
  14. def post(self, request, *args, **kwargs):
  15. ser = NewArticleSerializers(data=request.data)
  16. if ser.is_valid():
  17. ser.save()
  18. return Response(ser.data)
  19. else:
  20. return Response(ser.errors)
  21. def put(self, request, id=False, *args, **kwargs):
  22. """
  23. 全部更新:提交全部字段
  24. :param request:
  25. :param id:
  26. :param args:
  27. :param kwargs:
  28. :return:
  29. """
  30. article_obj = models.Article.objects.filter(id=id).first()
  31. ser = NewArticleSerializers(instance=article_obj, data=request.data)
  32. if ser.is_valid():
  33. ser.save()
  34. return Response(ser.data)
  35. else:
  36. return Response(ser.errors)
  37. def patch(self,request,id=False,*args,**kwargs):
  38. """
  39. 局部更新,提交部分字段
  40. :param request:
  41. :param id:
  42. :param args:
  43. :param kwargs:
  44. :return:
  45. """
  46. article_obj = models.Article.objects.filter(id=id).first()
  47. ser = NewArticleSerializers(instance=article_obj, data=request.data, partial=True)
  48. if ser.is_valid():
  49. ser.save()
  50. return Response(ser.data)
  51. else:
  52. return Response(ser.errors)
  53. def delete(self,request,id=False,*args,**kwargs):
  54. models.Article.objects.filter(id=id).delete()
  55. return Response('删除成功')

4.3.3 多对多的增删改查

serilaizer.py

  1. class NewTagSerializers(serializers.ModelSerializer):
  2. """
  3. 查询多对多表的序列化类
  4. """
  5. category_name = serializers.CharField(source='category.name', required=False)
  6. tag_info = serializers.SerializerMethodField()
  7. class Meta:
  8. model = models.Article
  9. fields = ['title', 'summary', 'category_name', 'tag_info']
  10. def get_tag_info(self,obj):
  11. return [row for row in obj.tag.all().values('id', 'title')]
  12. class FormNewTagSerializers(serializers.ModelSerializer):
  13. """
  14. 增加、更新多对多表记录的序列化类
  15. """
  16. category_name = serializers.CharField(source='category.name', required=False)
  17. tag_info = serializers.SerializerMethodField()
  18. class Meta:
  19. model = models.Article
  20. fields = "__all__"
  21. def get_tag_info(self,obj):
  22. # obj.tag相当于拿到article中每条记录对应的tag的id和tag的title
  23. return [row for row in obj.tag.all().values('id', 'title')]

设定两个序列化器,因为展示数据和增加、更新数据字段是不一样的,展示数据可能只是一部分,而增加更新fields要设置成和模型类中属性相同,并且可能还要自己增加一些跨表属性
实际上对每个模型类序列化器不一定是只有一个,视情况而定

view.py

  1. class NewTagView(APIView):
  2. """
  3. 多对多的增删改查
  4. """
  5. def get(self, request, id=False, *args, **kwargs):
  6. if not id:
  7. queryset = models.Article.objects.all()
  8. ser = NewTagSerializers(instance=queryset, many=True)
  9. return Response(ser.data)
  10. else:
  11. article_obj = models.Article.objects.filter(id=id).first()
  12. ser = NewTagSerializers(instance=article_obj, many=False)
  13. return Response(ser.data)
  14. def post(self, request, *args, **kwargs):
  15. ser = FormNewTagSerializers(data=request.data)
  16. if ser.is_valid():
  17. ser.save()
  18. return Response(ser.data)
  19. else:
  20. return Response(ser.errors)
  21. def put(self, request, id=False, *args, **kwargs):
  22. article_obj = models.Article.objects.filter(id=id).first()
  23. ser = FormNewTagSerializers(instance=article_obj, data=request.data)
  24. if ser.is_valid():
  25. ser.save()
  26. return Response(ser.data)
  27. else:
  28. return Response(ser.errors)
  29. def patch(self, request, id=False, *args, **kwargs):
  30. article_obj = models.Article.objects.filter(id=id).first()
  31. ser = FormNewTagSerializers(instance=article_obj, data=request.data, partial=True)
  32. if ser.is_valid():
  33. ser.save()
  34. return Response(ser.data)
  35. else:
  36. return Response(ser.errors)
  37. def delete(self, request, id=False, *args, **kwargs):
  38. models.Article.objects.filter(id=id).first().tag.clear()
  39. return Response('删除成功')

models.Article.objects.all()拿到的记录应该是这个样子:
image.png

5 drf分页功能

5.1 基于页码

请求url:http://127.0.0.1:8000/page/article/?page=1 page是第几页
1.配置每页显示内容
setting.py

  1. # 配置每页显示数
  2. REST_FRAMEWORK = {
  3. "PAGE_SIZE": 5
  4. }

2.serializer.py

  1. class PageArticleSerializers(serializers.ModelSerializer):
  2. class Meta:
  3. model = models.Article
  4. fields = "__all__"

因为paginate_queryset方法拿到的是模型对象,需要序列化,所以需要定义序列化类

3.view.py
方式1:拿到的只有数据

  1. from rest_framework.pagination import PageNumberPagination
  2. class PageArticleView(APIView):
  3. def get(self, request, *args, **kwargs):
  4. queryset = models.Article.objects.all()
  5. # 分页对象
  6. page = PageNumberPagination()
  7. # 调用方法进行分页,得到的结果是分页之后的数据
  8. result = page.paginate_queryset(queryset, request, self)
  9. # 序列化
  10. ser = PageArticleSerializers(instance=result, many=True)
  11. return Response(ser.data)

方式2:数据+分页信息

  1. from rest_framework.pagination import PageNumberPagination
  2. class PageArticleView(APIView):
  3. def get(self, request, *args, **kwargs):
  4. queryset = models.Article.objects.all()
  5. page = PageNumberPagination()
  6. result = page.paginate_queryset(queryset, request, self)
  7. ser = PageArticleSerializers(instance=result, many=True)
  8. return page.get_paginated_response(ser.data)

5.2 基于位置

请求url:
http://127.0.0.1:8000/page/article/?offset=0&limit=3
offset是起始位置,0代表从第一条记录
limit是取的记录个数

  1. from rest_framework.pagination import LimitOffsetPagination # 引入需要的类
  2. class PageArticleView(APIView):
  3. def get(self, request, *args, **kwargs):
  4. queryset = models.Article.objects.all()
  5. page = PageNumberPagination()
  6. result = page.paginate_queryset(queryset, request, self)
  7. ser = PageArticleSerializers(instance=result, many=True)
  8. return page.get_paginated_response(ser.data)

6 实现呼啦圈项目

6.1 设计表结构

  1. from django.db import models
  2. class UserInfo(models.Model):
  3. """
  4. 用户表
  5. """
  6. username = models.CharField(verbose_name='用户名', max_length=32)
  7. password = models.CharField(verbose_name='密码', max_length=32)
  8. class Article(models.Model):
  9. """
  10. 文章表
  11. """
  12. """
  13. 为什么不创建分类表,使用外键关联?
  14. 因为类别是定死的,不需要创建表,用choices即可,这样查询的时候是去内存拿数据,而不是去数据
  15. 库拿,提高了查询效率
  16. """
  17. """
  18. 为什么不把content当作article的属性?
  19. 因为一般查询文章列表都不看content,只有点进去才显示content,如果把content当作属性,
  20. 查询文章列表会因为content文字多,影响查询速度,所以单独建一个文章详情表
  21. """
  22. category_choices = (
  23. (1, '咨询'),
  24. (2, '公司动态'),
  25. (3, '分享'),
  26. (4, '答疑'),
  27. (5, '其他')
  28. )
  29. category = models.IntegerField(verbose_name='分类', choices=category_choices)
  30. title = models.CharField(verbose_name='标题', max_length=32)
  31. summary = models.CharField(verbose_name='简介', max_length=255)
  32. date = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
  33. # 存放的只是图片路径
  34. image = models.CharField(verbose_name='图片', max_length=128)
  35. comment_count = models.IntegerField(verbose_name='评论数')
  36. read_count = models.IntegerField(verbose_name='浏览数')
  37. author = models.ForeignKey(verbose_name='作者',to=UserInfo)
  38. class ArticleDetail(models.Model):
  39. """
  40. 文章详情表
  41. """
  42. article = models.OneToOneField(verbose_name='文章', to=Article)
  43. content = models.TextField(verbose_name='内容')
  44. class Comment(models.Model):
  45. """
  46. 用户表
  47. """
  48. article = models.ForeignKey(verbose_name='文章', to=Article, on_delete=models.CASCADE)
  49. content = models.TextField(verbose_name='评论内容')
  50. user = models.ForeignKey(verbose_name='用户', to=UserInfo)
  51. # 自关联属性
  52. # parent = models.ForeignKey(verbose_name='回复', to='self', null=True, blank=True)

6.2 系统结构(类似于今日头条网站)

image.png
用户先通过浏览器访问url,会返回html、css、js代码,在浏览器进行渲染,当执行到发送ajax请求的代码时会向指定的api发送请求,api去连接数据库做查询拿到数据之后返回给浏览器然后渲染出来。
数据库的数据是通过后台管理系统来写入和读取的。
我们现在做的只是api部分,后台管理就不做了,通过数据库管理系统来操作数据库添加数据。

6.3 功能实现

6.3.1 增加文章

前端要发送的json数据

  1. {
  2. "category":1,
  3. "title":"python基础",
  4. "summary":"python的基础语法,开发,实践...",
  5. "image":"xxx/xxx.jpg",
  6. "content":"python变量、面向对象、内置方法的使用,web开发爬虫的实战......."
  7. }

前台发来的数据,会在两个表里面增加数据article和articledetail

serializer.py

  1. class ArticleSerializer(serializers.ModelSerializer):
  2. """文章列表序列化类"""
  3. class Meta:
  4. model = models.Article
  5. # 因为前端传来的json数据没有author所以不需要反序列化,author是通过读取session中的id获得
  6. exclude = ['author',]
  7. class ArticleDetailSerializer(serializers.ModelSerializer):
  8. """文章详情序列化类"""
  9. class Meta:
  10. model = models.ArticleDetail
  11. # 因为前端传来的json数据没有article所以不需要反序列化,article的值是当增加一条文章记录时,获取改记录id
  12. exclude = ['article',]

增加ArticleDetailSerializer的原因:因为只有一个ArticleSerializer,反序列化之后无法得到content的值,所以增加数据的时候,只有article表增加

而article_detail表没有增加,所以需要一个反序列化content的类

6.3.2 查看文章列表/文章详细

serializer.py

  1. class ArticleListSerializer(serializers.ModelSerializer):
  2. """查看文章列表序列化类"""
  3. class Meta:
  4. model = models.Article
  5. fields = "__all__"
  6. class ArticleDetailShowSerializer(serializers.ModelSerializer):
  7. """查看文章详情序列化类"""
  8. # 对传给instance的对象做一个连表操作,source后面的赋值相当于instance引用的对象.
  9. # article.外键就可以拿到外键关联的对象
  10. # 例如这里source='author.username' 相当于article.author.username 取得username属性值
  11. author = serializers.CharField(source='author.username')
  12. category = serializers.CharField(source='get_category_display')
  13. content = serializers.CharField(source='articledetail.content')
  14. class Meta:
  15. model = models.Article
  16. fields = ['id', 'title', 'summary', 'content','category','author']

因为文章列表和文章详情所展现的数据不同,所以用两个序列化类

view.py

  1. class ArticleView(APIView):
  2. """文章视图类"""
  3. def get(self, request, id=False, *args, **kwargs):
  4. """获取文章列表/文章详细"""
  5. if not id:
  6. queryset = models.Article.objects.all().order_by('-date')
  7. # 分页对象
  8. page = PageNumberPagination()
  9. # 调用方法进行分页,得到的结果是分页之后page对应页的数据
  10. result = page.paginate_queryset(queryset, request, self)
  11. # 序列化
  12. ser = ArticleListSerializer(instance=result, many=True)
  13. return Response(ser.data)
  14. else:
  15. article_obj = models.Article.objects.filter(id=id).first()
  16. ser = ArticleDetailShowSerializer(instance=article_obj, many=False)
  17. return Response(ser.data)

6.3.3 文章评论

查看评论列表
url:http://127.0.0.1:8000/hulaquan/comment/?article=2

添加评论两种添加方式:

  • article放请求体(建议用这一种)

url:http://127.0.0.1:8000/hulaquan/comment/

  1. {
  2. "content": "这篇文章写的不错",
  3. "article": 2,
  4. },
  • article放url上

url:http://127.0.0.1:8000/hulaquan/comment/?article=2

  1. {
  2. "content": "这篇文章写的不错",
  3. },

7 drf筛选

7.1 初识drf

查看文章列表,添加筛选功能:
全部:http://127.0.0.1:8000/hulaquan/article/
筛选:http://127.0.0.1:8000/hulaquan/article/?category=2

  1. # 4-9是筛选代码,自己写的
  2. def get(self, request, id=False, *args, **kwargs):
  3. """获取文章列表/文章详细"""
  4. if not id:
  5. condition = {}
  6. # get请求,获取url上的参数
  7. category = request.query_params.get('category')
  8. if category:
  9. condition['category'] = category
  10. queryset = models.Article.objects.filter(**condition).order_by('-date')
  11. # 分页对象
  12. page = PageNumberPagination()
  13. # 调用方法进行分页,得到的结果是分页之后page对应页的数据
  14. result = page.paginate_queryset(queryset, request, self)
  15. # 序列化
  16. ser = ArticleListSerializer(instance=result, many=True)
  17. return Response(ser.data)

7.2 drf筛选组件

  1. from rest_framework.filters import BaseFilterBackend
  2. # 运用筛选组件
  3. class MyFilterBackend(BaseFilterBackend):
  4. """筛选组件"""
  5. def filter_queryset(self, request, queryset, view):
  6. category_id = request.query_params.get('category')
  7. queryset = queryset.filter(category=category_id)
  8. return queryset
  9. class IndexView(APIView):
  10. def get(self,request,*args,**kwargs):
  11. queryset = models.Article.objects.all()
  12. obj = MyFilterBackend()
  13. data = obj.filter_queryset(request,queryset,self)

7.3 drf视图关系

  1. from rest_framework.generics import GenericAPIView
  2. from rest_framework.pagination import PageNumberPagination
  3. from rest_framework.filters import BaseFilterBackend
  4. class MyFilterBackend(BaseFilterBackend):
  5. """筛选组件"""
  6. def filter_queryset(self, request, queryset, view):
  7. category_id = request.query_params.get('category')
  8. quertset = queryset.filter(category=category_id)
  9. class NewsView(GenericAPIView):
  10. queryset = models.Article.objects.all() # 查询的全部数据
  11. filter_backends = [MyFilterBackend, ] # 配置筛选
  12. serializer_class = ArticleSerializer # 配置序列化
  13. pagination_class = PageNumberPagination # 配置分页
  14. def get(self, request, *args, **kwargs):
  15. v = self.get_queryset() # 拿到的是models.Article.objects.all()的queryset对象
  16. queryset = self.filter_queryset(v) # 拿到筛选之后的结果
  17. selected_result = self.paginate_queryset(queryset) # 拿到分页结果
  18. ser = self.get_serializer(instance=selected_result, many=True) # 创建序列化对象
  19. return Response(ser.data)

如果继承GenericAPIView就要这样写,可现在有人给你写了get方法了,你只需要继承ListAPIView即可,然后只需要自定义配置信息即可

ListAPIView类源代码

  1. class ListAPIView(mixins.ListModelMixin,
  2. GenericAPIView):
  3. """
  4. Concrete view for listing a queryset.
  5. """
  6. def get(self, request, *args, **kwargs):
  7. return self.list(request, *args, **kwargs)

其中的list方法源代码,可以发现和自己写的get方法一样

  1. def list(self, request, *args, **kwargs):
  2. queryset = self.filter_queryset(self.get_queryset())
  3. page = self.paginate_queryset(queryset)
  4. if page is not None:
  5. serializer = self.get_serializer(page, many=True)
  6. return self.get_paginated_response(serializer.data)
  7. serializer = self.get_serializer(queryset, many=True)
  8. return Response(serializer.data)

image.png
GennricAPIView里面的方法是来读取自定义配置类里面的参数的,ListAPIView,CreateAPIView,UpdateAPIView,DestroyAPIView,RetrieveAPIView是调用GennricAPIView里面的方法完成增删改查

现在用ListAPIView,CreateAPIView,UpdateAPIView,DestroyAPIView,RetrieveAPIView完成文章的增删改查

  1. class MyFilterBackend(BaseFilterBackend):
  2. """筛选组件"""
  3. def filter_queryset(self, request, queryset, view):
  4. category_id = request.query_params.get('category')
  5. data = queryset.filter(category=category_id)
  6. return data
  7. class NewArticleView(ListAPIView,CreateAPIView):
  8. """文章接口的配置类"""
  9. queryset = models.Article.objects.all() # 查询的全部数据
  10. filter_backends = [MyFilterBackend, ] # 配置筛选
  11. serializer_class = ArticleListSerializer # 配置序列化
  12. pagination_class = PageNumberPagination # 配置分页

RetrieveAPIView和ListAPIView都有get方法所,以取单个数据会产生冲突所以把RetrieveAPIView额ListAPIView分开,对单个数据的操作的类都可以放到一起,让别的类继承他们

7.4 视图扩展

假设访问re_path(r’^index/(?P\d+)/$’, views.NewArticleDetailView.as_view())这个url
当执行ListAPIView中list方法时,执行get_serializer内部
image.png
get_serializer_class执行的是NewArticleDetailView的get_serializer_class,但是如果NewArticleDetailView没有这个方法会顺着继承链向上找,最后调用的是GenericAPIView的,如果在NewArticleDetailView中定义get_serializer_class,则执行NewArticleDetailView类中的,所以现在在NewArticleDetailView类中定义get_serializer_class方法,就可以进行序列化器的选择,因为同一个接口增删改查用的序列化器不一定相同
perform_create方法和get_serializer_class原理相同

  1. class NewArticleView(ListAPIView,CreateAPIView):
  2. """文章接口的配置类"""
  3. queryset = models.Article.objects.all() # 查询的全部数据
  4. filter_backends = [MyFilterBackend, ] # 配置筛选
  5. # serializer_class = ArticleListSerializer # 配置序列化
  6. pagination_class = PageNumberPagination # 配置分页
  7. def get_serializer_class(self):
  8. """可以进行序列化器的选择"""
  9. if self.request.method == "GET":
  10. return ArticleListSerializer
  11. elif self.request.method == "POST":
  12. return ArticleSerializer
  13. def perform_create(self,serializer):
  14. """直接从内存获取的数据,不是来自于前端的发来的post数据放在这里传递给save"""
  15. serializer.save(author_id=1)

7.5 视图关系总结

类视图执行流程:
启动django程序,执行url里面的as_view()函数,返回view,当访问对应的url时,自动执行view函数,例如访问 views.NewArticleView.as_view()

  1. obj = NewArticleView()
  2. obj.dispatch()
  3. obj.get()
  4. obj.list()

8 drf请求封装执行流程

预备知识:

  1. class HttpRequest(object):
  2. def __init__(self):
  3. pass
  4. @property
  5. def GET(self):
  6. pass
  7. @property
  8. def POST(self):
  9. pass
  10. @property
  11. def body(self):
  12. oass
  13. class Request(self):
  14. def __init__(self,request):
  15. self._request = request
  16. def data(self):
  17. if content-type == "application/json"
  18. return json.loads(self._request.body.decode('utf-8'))
  19. elif content-type == 'x-www-...':
  20. return self._request.POST
  21. def query_params(self):
  22. return self._request.GET
  23. req = HttpRequest()
  24. request = Request(req)
  25. request.data
  26. request.query_params
  27. request._request.GET
  28. request._request.POST
  29. request._request.body

drf请求流程—源码分析:

  • 路由

django程序一启动,就会执行as_view(),此时请求还没来

  1. urlpatterns = [
  2. path('article/', views.ArticleView.as_view()), # 本质:path('article/', csrf_exempt(view)),
  3. # csrf_exempt(view)调用完返回的还是view
  4. ]
  • 视图关系 ```python class View(object): pass class APIView(View): pass class ArticleView(APIView): pass
  1. 因为ArticleView类没有as_view()方法,回去找父类,APIView类里面有as_view()<br />APIView.as_view方法源码:(此处不重要的省略)
  2. ```python
  3. @classmethod
  4. def as_view(cls, **initkwargs):
  5. view = super().as_view(**initkwargs)
  6. return csrf_exempt(view)

会去调用父类的as_view
View.as_view方法源码:

  1. @classonlymethod
  2. def as_view(cls, **initkwargs):
  3. def view(request, *args, **kwargs):
  4. self = cls(**initkwargs) # cls是APIView类
  5. self.setup(request, *args, **kwargs)
  6. if not hasattr(self, 'request'):
  7. raise AttributeError(
  8. "%s instance has no 'request' attribute. Did you override "
  9. "setup() and forget to call super()?" % cls.__name__
  10. )
  11. return self.dispatch(request, *args, **kwargs)
  12. return view

把view函数返回,再执行csrf_exempt(view),返回值就是view函数,所以路由后面的类本质是一个函数
然后前端发来请求,会给view加上括号,也就是进行函数调用,这里的view函数就是APIView里面的view

  1. def view(request, *args, **kwargs):
  2. self = cls(**initkwargs)
  3. self.setup(request, *args, **kwargs)
  4. if not hasattr(self, 'request'):
  5. raise AttributeError(
  6. "%s instance has no 'request' attribute. Did you override "
  7. "setup() and forget to call super()?" % cls.__name__
  8. )
  9. return self.dispatch(request, *args, **kwargs)

执行的dispatch是APIView里面的dispatch(因为ArticleView里面没有dispathch方法就去找父类的)
APIView.dispatch方法源码:

  1. def dispatch(self, request, *args, **kwargs):
  2. # 传递进来的是老的request
  3. # 新request内部包含老request,是因为新request的里面的_request属性是老request
  4. request = self.initialize_request(request, *args, **kwargs)
  5. self.request = request
  6. self.initial(request, *args, **kwargs)
  7. # 反射,假设请求是GET,则handler = self.get
  8. handler = getattr(self, request.method.lower())
  9. # 传入新的request,执行self.get()
  10. response = handler(request, *args, **kwargs)
  11. self.response = self.finalize_response(request, response, *args, **kwargs)
  12. return self.response

drf免除了csrf验证

9 drf版本实现

源码流程:

  1. class APIView(View):
  2. # 默认是空的
  3. versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
  4. def dispatch(self, request, *args, **kwargs):
  5. """
  6. request是django的request,它的内部有request.GET/request.POST/request.method
  7. args,kwwargs是在路由中匹配到的参数,如:re_path(r'^index/(?P<pk>\d+)/$', views.NewArticleDetailView.as_view())
  8. """
  9. ###############第一步#####################
  10. self.args = args
  11. self.kwargs = kwargs
  12. # request = Request(
  13. # request,
  14. # parsers=self.get_parsers(),
  15. # authenticators=self.get_authenticators(),
  16. # negotiator=self.get_content_negotiator(),
  17. # parser_context=parser_context
  18. # )
  19. # 是一个新的request对象,对象内部封装了一些值
  20. request = self.initialize_request(request, *args, **kwargs)
  21. self.request = request
  22. self.headers = self.default_response_headers # deprecate?
  23. try:
  24. ################第二步#############
  25. self.initial(request, *args, **kwargs)
  26. # Get the appropriate handler method
  27. # 执行视图函数
  28. self.response = self.finalize_response(request, response, *args, **kwargs)
  29. return self.response
  30. def initial(self, request, *args, **kwargs):
  31. ############## 2.1处理drf的版本 #################
  32. # version, scheme = v1,scheme
  33. version, scheme = self.determine_version(request, *args, **kwargs)
  34. # request.version, request.versioning_scheme = v1,scheme
  35. request.version, request.versioning_scheme = version, scheme
  36. def determine_version(self, request, *args, **kwargs):
  37. ############2.2##################
  38. if self.versioning_class is None:
  39. return (None, None)
  40. # 实例化对象
  41. # scheme = XXXXXXXX()
  42. scheme = self.versioning_class()
  43. # 假设scheme.determine_version(request, *args, **kwargs)返回值是v1
  44. return (scheme.determine_version(request, *args, **kwargs), scheme)
  45. class ArticleView(APIView):
  46. versioning_class = XXXXXXXXX
  47. def get(self, request, id=False, *args, **kwargs):
  48. pass

执行self.initial(request, args, **kwargs),先去ArticleView类里面找,没有就去APIView里面找
执行self.determine_version(request,
args, **kwargs),先去ArticleView类里面找,没有就去APIView里面找
执行self.versioning_class ,先去ArticleView类里面找,没有就去APIView里面找
现在在ArticleView定义versioning_class,访问的就是ArticleView里面的versioning_class

测试一下上面说的

  1. class MyView(object):
  2. def determine_version(request, *args, **kwargs):
  3. return 'v1'
  4. class OrderView(APIView):
  5. versioning_class = MyView
  6. def get(self,request,*args,**kwargs):
  7. print(request.version) # v1
  8. print(request.versioning_scheme)# <hulaquan.views.MyView object at 0x000001BFBFE7A048>
  9. return Response('OK')

第一种请求的url:http://127.0.0.1:8000/hulaquan/order/?version=v10

  1. class MyView(object):
  2. def determine_version(self,request, *args, **kwargs):
  3. return request.query_params.get('version')
  4. class OrderView(APIView):
  5. versioning_class = MyView
  6. def get(self,request,*args,**kwargs):
  7. print(request.version) # v10
  8. print(request.versioning_scheme)# <hulaquan.views.MyView object at 0x000001BFBFE7A048>
  9. return Response('OK')

第二种请求的url:http://127.0.0.1:8000/hulaquan/order/v9/(建议第二种,restful规范要求)

  1. re_path(r'^order/(?P<version>\w+)/$', views.OrderView.as_view())
  1. class MyVersion(object):
  2. def determine_version(self,request, *args, **kwargs):
  3. return kwargs.get('version')
  4. class OrderView(APIView):
  5. versioning_class = MyVersion
  6. def get(self,request,*args,**kwargs):
  7. print(request.version) # v1
  8. print(request.versioning_scheme)# <hulaquan.views.MyView object at 0x000001BFBFE7A048>
  9. return Response('OK')

对url进一步改进
一级路由

  1. re_path(r'^api/(?P<version>\w+)/',include('hulaquan.urls'))

二级路由

  1. path('order/', views.OrderView.as_view()),

所以访问的url符合restful规范: http://127.0.0.1:8000/api/v1/order/

现在好处是MyView这个类restframework给你写好了

  1. from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning
  2. class OrderView(APIView):
  3. # versioning_class = QueryParameterVersioning 和第一种请求方式一样
  4. versioning_class = URLPathVersioning # 和第二种请求方式一样
  5. def get(self,request,*args,**kwargs):
  6. print(request.version) # v1
  7. print(request.versioning_scheme)# <hulaquan.views.MyView object at 0x000001BFBFE7A048>
  8. return Response('OK')

总结:局部使用版本控制

  • 在url中写version

    1. urlpatterns = [
    2. re_path(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
    3. ]
  • 在视图中应用 ```python from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning

class OrderView(APIView): versioning_class = URLPathVersioning # 和第二种请求方式一样 def get(self,request,args,*kwargs): print(request.version) # v1

  1. print(request.versioning_scheme)# <hulaquan.views.MyView object at 0x000001BFBFE7A048>
  2. return Response('OK')
  1. - setting中配置
  2. ```python
  3. REST_FRAMEWORK = {
  4. "PAGE_SIZE": 2, # 配置每页显示数
  5. "ALLOWED_VERSIONS": ['v1', 'v2'], # 版本限制
  6. "ERSION_PARAM": 'version' # re_path(r'^order/(?P<version>\w+)/$' P后面<>的参数名
  7. }

局部使用也就是

  1. class OrderView(APIView):
  2. versioning_class = URLPathVersioning
  3. def get(self,request,*args,**kwargs):
  4. print(request.version) # v1
  5. print(request.versioning_scheme)# <hulaquan.views.MyView object at 0x000001BFBFE7A048>
  6. return Response('OK')
  7. class UserView(APIView):
  8. versioning_class = URLPathVersioning
  9. def get(self,request,*args,**kwargs):
  10. print(request.version) # v1
  11. print(request.versioning_scheme)# <hulaquan.views.MyView object at 0x000001BFBFE7A048>
  12. return Response('OK')

每个类里都要写versioning_class

全局使用(推荐使用)

  1. "DEFAULT_VERSIONING_CLASS": 'rest_framework.versioning.URLPathVersioning' # 全局使用版本控制

10 drf认证

本质就是用户登录之后,返回给用户token,用户之后携带token去访问某些接口,会得到访问用户得信息。
应用:如果想获取request的用户信息

  1. from rest_framework.views import APIView
  2. from rest_framework.response import Response
  3. from app01 import models
  4. import uuid
  5. from rest_framework.authentication import BaseAuthentication
  6. class LoginView(APIView):
  7. def post(self, request, *args, **kwargs):
  8. obj = models.UserInfo.objects.filter(**request.data).first()
  9. if not obj:
  10. return Response('登陆失败')
  11. random_str = str(uuid.uuid4())
  12. obj.token = random_str
  13. obj.save()
  14. return Response(random_str)
  15. class MyAuthentication(BaseAuthentication):
  16. """认证类"""
  17. def authenticate(self, request):
  18. # 取url传递的token
  19. token = request.query_params.get('token')
  20. # 根据token查询访问用户
  21. user_obj = models.UserInfo.objects.filter(token=token).first()
  22. if user_obj:
  23. return (user_obj,token)
  24. else:
  25. return (None,None)
  26. class UserView(APIView):
  27. # 配置认证类
  28. authentication_classes = [MyAuthentication]
  29. def get(self, request, *args, **kwargs):
  30. print(request.user) # 访问用户信息
  31. print(request.auth) # 用户携带的随机字符串
  32. if request.user:
  33. return Response('user')
  34. else:
  35. return Response('你是未登录用户,无法访问')

源码流程分析:
请求来了,执行dispatch

  1. def dispatch(self, request, *args, **kwargs):
  2. """
  3. request是django的request,它的内部有request.GET/request.POST/request.method
  4. args,kwwargs是在路由中匹配到的参数,如:re_path(r'^index/(?P<pk>\d+)/$', views.NewArticleDetailView.as_view())
  5. """
  6. """
  7. request = Request(
  8. request,
  9. parsers=self.get_parsers(),
  10. authenticators=self.get_authenticators(),
  11. negotiator=self.get_content_negotiator(),
  12. parser_context=parser_context
  13. )
  14. 是一个新的request对象,对象内部封装了一些值
  15. 内部封装了_request=老的request
  16. 内部封装了_authenticators = [MyAuthentication(),]
  17. """
  18. request = self.initialize_request(request, *args, **kwargs)

initialize_request方法源码

  1. def initialize_request(self, request, *args, **kwargs):
  2. return Request(
  3. request,
  4. parsers=self.get_parsers(),
  5. authenticators=self.get_authenticators(),# [MyAuthentication(),]
  6. negotiator=self.get_content_negotiator(),
  7. parser_context=parser_context
  8. )

返回的Request对象封装了一个authenticators属性,这个属性值就是get_authenticators的返回值,因为UserView没有get_authenticators方法,去调用APIView的get_authenticators方法

  1. def get_authenticators(self):
  2. return [auth() for auth in self.authentication_classes]

遍历UserView的authentication_classes属性值,然后返回一个列表,是 [MyAuthentication(),]
然后传递给了Response()对象的authenticators属性
然后执行request.user的时候

  1. def user(self)
  2. self._authenticate()
  3. return self._user
  1. def _authenticate(self):
  2. for authenticator in self.authenticators: # [MyAuthentication(),]
  3. try:
  4. user_auth_tuple = authenticator.authenticate(self) # 调用自己定义的认证类的authenticate方法
  5. except exceptions.APIException:
  6. self._not_authenticated()
  7. raise
  8. if user_auth_tuple is not None:
  9. self._authenticator = authenticator
  10. self.user, self.auth = user_auth_tuple
  11. return
  12. self._not_authenticated()

这是局部给视图加认证,如果是全局加认证,就配置settings

  1. REST_FRAMEWORK = {
  2. "DEFAULT_AUTHENTICATION_CLASSES":['app01.auth.MyAuthentication']
  3. }

总结 当用户请求时,找到认证的所有类并实例化称为对象列表,然后将对象列表封装到新的request中 如果以后在视图中,调用request.user 其内部会循环认证对象列表,并执行每个对象的authenticate方法,该方法用于认证,会返回两个值,两个值会分别赋值给request.user和request.auth

11 drf权限

认证和权限都可以在视图中完成,之所以需要这两个组件,是因为可以更加的分工明确,一个类对应一个功能
使用:(局部使用权限)

  1. class MyPermission(BasePermission):
  2. """权限类"""
  3. message = "你没有权限"
  4. code = "403" # code无法抛出
  5. def has_permission(self, request, view):
  6. if request.user:
  7. return True
  8. return False
  9. def has_object_permission(self, request, view, obj):
  10. """查询单个记录会调用"""
  11. return False
  12. class UserView(APIView):
  13. permission_classes = [MyPermission,]
  14. authentication_classes = [MyAuthentication,]
  15. def get(self, request, *args, **kwargs):
  16. return Response('user')

源码分析:

  1. def dispatch(self, request, *args, **kwargs):
  2. # 封装request对象
  3. self.initial(request, *args, **kwargs)
  4. # 通过反射执行视图方法

initial方法

  1. def initial(self, request, *args, **kwargs):
  2. ############## 处理drf的版本 #################
  3. version, scheme = self.determine_version(request, *args, **kwargs)
  4. request.version, request.versioning_scheme = version, scheme
  5. ##############################################
  6. self.perform_authentication(request) # 认证
  7. self.check_permissions(request) # 权限判断

执行 self.perform_authentication(request)会得到访问用户,

  1. def check_permissions(self, request):
  2. # self.get_permissions()返回一个对象列表:[MyPermission(),]
  3. for permission in self.get_permissions():
  4. # 调用MyPermission的has_permission方法
  5. if not permission.has_permission(request, self):
  6. self.permission_denied(
  7. request,
  8. message=getattr(permission, 'message', None),
  9. code=getattr(permission, 'code', None)
  10. )
  1. def get_permissions(self):
  2. # self是UserView对象
  3. return [permission() for permission in self.permission_classes]

12 总结

drf框架执行流程及内部提供组件

  1. 视图
  2. 版本处理
  3. 认证
  4. 权限
  5. 节流
  6. 解析器
  7. 序列化器
  8. 筛选器
  9. 分页
  10. 渲染

13 跨域

由于浏览器具有”同源策略”的限制。如果在同一个域下发送ajax请求,浏览器的同源策略不会阻止,如果在不同域下发送ajax请求,浏览器的同源策略会阻止。
总结:

  • 域相同,永远不会存在跨域
  • 域不同时,才会存在跨域
  • 域指的是端口或域名