本文主要是介绍DRF的接口使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
【一】如何看cbv源码
需要看源码。 在看python源码的时候 一定要时刻提醒自己面向对象属性方法查找顺序
- 先从对象自己找
- 再去产生对象的类里面找
- 之后再去父类找
总结: 看源码只要看到了self点一个东西 一定要问你自己当前这个self到底是谁
cbv的源码 : 突破口在urls.py
from django.contrib import admin
from django.urls import path,re_path
from app01 import viewsurlpatterns = [# CBV源码re_path('r^login/',views.MyLogin.as_view()),
]
# re_path('r^login/',views.view), 这里CBV跟FBV 一模一样
# '''这里可以看出CBV跟FBV在路由匹配上本质是一样的 都是路由 对应 函数内存地址'''# 函数名/方法名 加括号优先级最高
# 这里我们可以假设一下as_view()要么是被@staticmethod修饰的静态方法要么是被@classmethod修饰的类的方法 ✓@classonlymethoddef as_view(cls, **initkwargs):"""Main entry point for a request-response process."""for key in initkwargs:pass
# cls 就是我们自己写的类 MyCBV
def view(request, *args, **kwargs):# cls 就是我们自己写的类# self = MyLogin(**initkwargs) 产生一个我们自己写的类的对象self = cls(**initkwargs)self.setup(request, *args, **kwargs)
'''需要看源码。 在看python源码的时候 一定要时刻提醒自己面向对象属性方法查找顺序- 先从对象自己找- 再去产生对象的类里面找- 之后再去父类找总结: 看源码只要看到了self点一个东西 一定要问你自己当前这个self到底是谁
'''
# CBV的精华
def dispatch(self, request, *args, **kwargs):# Try to dispatch to the right method; if a method doesn't exist,# defer to the error handler. Also defer to the error handler if the# request method isn't on the approved list.# 获取的当前请求的小写格式 如何对比当前请求方式是否合法# get请求为例子# post请求if request.method.lower() in self.http_method_names:handler = getattr(self, request.method.lower(), self.http_method_not_allowed)'''反射:通过字符串来操作对象的数学或者方法handler = getattr(自己写的类产生的对象,'get',当找不到get数学或者方法的时候机会用到第三个参数)handler = 我们自己写的类里面的get方法'''else:handler = self.http_method_not_allowedreturn handler(request, *args, **kwargs)'''自动调用get方法'''
1
2
3
4
cbv源码的执行流程
CBV(Class-Based Views)是一种处理HTTP请求的方式,相比FBV(Function-Based Views)更加灵活和易于维护。CBV源码的执行流程大致如下:
- URL匹配和路由分发:当一个HTTP请求到达Django应用时,Django会根据URL配置文件中的路由规则将请求分发到相应的视图函数。
- 视图函数调用:对于CBV,路由规则通常会指向一个继承自Django提供的特定类的视图类,而不是一个普通的函数。当请求到达相应的视图类时,Django会实例化该类,并调用其中的
dispatch
方法。 - dispatch方法:
dispatch
方法是CBV执行的入口。在dispatch
方法中,Django会根据请求的HTTP方法(GET、POST、PUT等)调用相应的处理方法,例如get
、post
、put
等。 - 请求处理方法:在视图类中,通常会定义一系列处理请求的方法,如
get
、post
等。这些方法接收请求对象作为参数,并根据请求的具体情况执行相应的逻辑。 - Mixin扩展:CBV可以通过Mixin类来扩展功能。Mixin类是一种特殊的类,可以通过多重继承的方式将其添加到视图类中,从而为视图类提供额外的功能,例如身份验证、缓存等。
- 模板渲染:在视图类中,可以通过调用
render
方法来渲染模板并生成HTML响应。render
方法接收模板名称和上下文数据作为参数,并返回渲染后的HTML内容。 - 响应返回:最后,视图类会生成一个HTTP响应对象,并将其返回给客户端。这个响应对象通常包含HTTP状态码、响应头和响应体等信息。
总的来说,CBV的执行流程可以简单概括为:URL匹配和路由分发 → 视图函数调用 → dispatch方法 → 请求处理方法 → 模板渲染 → 响应返回。
【二】在视图层View编写5个接口
-
什么是csrf
- CSRF 就是攻击者利用一个用户已经登录的身份,在用户不知情的情况下对另一个网站或应用发起恶意请求。
-
防止的措施
- CSRF Token:在用户登录时生成一个随机的 token,并将其嵌入到表单或请求的参数中,在后续的请求中验证这个 token 的有效性,确保请求来源合法。
- SameSite Cookie 属性:设置 Cookie 的 SameSite 属性,限制 Cookie 只能在相同站点请求中发送,防止跨站点请求。
- 验证 Referer:验证请求的 Referer 头部,确保请求来源合法。
- 双重提交 Cookie:将一个随机值存储在 Cookie 中,并在表单中将这个值作为参数发送,在服务器端验证这个值与 Cookie 中的值是否匹配。
基于View编写5个接口--csrf-序列化自己做--》JsonResponse多条--》for 字典--》追加到列表中单体新增修改删除
【0】前提条件
继承View编写修改接口,让他使用json格式提交数据,修改成功
写一个装饰器,装饰视图函数FBV,使得request有data属性,无论哪种编码和请求方式,都是字典
【1】准备Book的数据
-
创建一个名为Book表跟book的数据库
-
启动MySQL
net start mysql
- 输入密码的两种方式
---mysql -u root -P 3306 -p
--- mysql -u root -p
********************
- 在model.py文件中创建数据库
from django.db import models# Create your models here.
class Book(models.Model):name = models.CharField(max_length=88)price = models.IntegerField()publish = models.CharField(max_length=66)
- 前提是先在settings.py文件中先定义
DATABASES = {'default': {# 数据库引擎选择使用MySQL'ENGINE': 'django.db.backends.mysql',# 指定数据库名字,需提前创建'NAME': 'books',# 指定数据库用户名'USER': 'root',# 指定数据库用户密码'PASSWORD': '123123',# 指定数据库连接IP'HOST': '127.0.0.1',# 指定数据库端口'PORT': 3306,# 指定数据库默认编码集(可选)'CHARSET': 'utf8mb4',}
}
- 数据迁移
''' python manage.py makemigrationspython manage.py migrate '''
【2】在views
from django.http import JsonResponse
import re
import json
from urllib.parse import unquote
# Create your views here.
from django.utils.decorators import method_decorator
from django.views import View
from .models import Book
class BookView(View):def get(self, request):res_list = Book.objects.all()data_list = []for res in res_list:data_list.append({'name': res.name, 'price': res.price, 'publish': res.publish})return JsonResponse({'code': 100, 'msg': '查询成功', 'results': data_list})def post(self, request):# 前端无论以什么编码格式--》提交到后端你的数据,都会被解析到 request.data 中,是个字典# 如果是urlencoded或form-data编码--》不能直接这样写,要写成如下data = request.POSTbook = Book.objects.create(name=data.get('name'), price=data.get('price'), publish=data.get('publish'))return JsonResponse({'code': 100, 'msg': '新增成功'})# 如果是json格式编码,下面没问题# Book.objects.create(**request.data)print(request.POST)print(request.data)# 本质原因是?''' APIView帮咱们呢---》看POST中有没有数据,如果有,直接赋值给data,如果没有,他去body中转成字典再给datajson格式:POST没有东西 QueryDictdata有数据 dicturlencoded或form-dataPOST有东西 QueryDictdata有数据 QueryDict'''return JsonResponse({'code': 100, 'msg': '新增成功'})# 127.0.0.1:8000/app01/books/1
@method_decorator(outter,'post')
# @method_decorator(outter,'delete')
class BookDetailView(View):def get(self, request, pk):res = Book.objects.filter(pk=pk).first()# 加个逻辑判断return JsonResponse({'code': 100, 'msg': '查询成功', 'result': {'name': res.name, 'price': res.price, 'publish': res.publish}})def delete(self, request, pk):try:# 尝试从数据库中获取指定主键的对象book = Book.objects.get(pk=pk)except Book.DoesNotExist:# 如果对象不存在,则返回相应的 JSON 响应return JsonResponse({'code': 200, 'msg': '没有数据!!!'})# 如果对象存在,则删除它book.delete()# 返回删除成功的 JSON 响应return JsonResponse({'code': 100, 'msg': '删除成功'})def put(self, request, pk):# 跟post新增数据一样,编码格式不是json就会报错print(request.body)data = request.bodyres_data = json.loads(data)print(res_data)name = res_data.get('name')price = res_data.get('price')publish = res_data.get('publish')Book.objects.filter(pk=pk).update(name=name,price=price,publish=publish)return JsonResponse({'code': 100, 'msg': '修改成功'})def post(self, request):# body gen body的使用方法# print(request.body)# ta = request.body# da = json.loads(ta)name = request.POST.get('name')price = request.POST.get('price')publish = request.POST.get('publish')# 保存任务到数据库res = Book.objects.create(name=name,price=price,publish=publish,)return JsonResponse({'code': 100, 'msg': '添加成功', 'result': {'name': res.name, 'price': res.price, 'publish': res.publish}})
添加一个装饰器才能实现无论哪种编码和请求方式,都是字典
def outter(func):def inner(*args, **kwargs):data = Nonerequest = args[0]content_type = request.META.get('CONTENT_TYPE')if 'json' in content_type:data = json.loads(request.body)elif 'urlencoded' in content_type:data = request.body.decode('utf-8')# 将查询字符串拆分为键值对列表body_list = data.split('&')decode = {}for body in body_list:decode[body.split("=")[0]] = unquote(body.split("=")[1])print(decode)# 最终得到的解析后的字典data = decodeelif 'form-data' in content_type:data = request.body.decode('utf8')pattern = r'Content-Disposition: form-data; name="([^"]+)"\s*\r\n\s*\r\n(.*?)(?=\r\n|--.*?--|\Z)'res = re.findall(pattern, string=data)data = {i[0]: i[1] for i in res}request.data = datares = func(*args, **kwargs)return resreturn inner
这个装饰器的内部函数 inner
接收任意数量的位置参数 args
和关键字参数 kwargs
,然后提取第一个位置参数(即 HTTP 请求对象)并赋值给 request
变量。接下来,它获取请求的 Content-Type,根据不同的 Content-Type 类型对请求的 body 数据进行解析,并将解析后的数据存储在 data
变量中。
- 如果 Content-Type 中包含 ‘json’,则将请求的 body 数据解析为 JSON 格式。
- 如果 Content-Type 中包含 ‘urlencoded’,则将请求的 body 数据解析为 URL 编码格式,并将其拆分为键值对列表,然后使用列表推导式和解码函数
unquote
将每个键值对进行解码,并构建成字典。 - 如果 Content-Type 中包含 ‘form-data’,则使用正则表达式从 body 数据中提取键值对,并将其存储为字典格式。
第一句代码 data = {i[0]: unquote(i[-1]) for i in [info.split('=') for info in data.split('&')]}
是用来解析 URL 编码格式的数据。具体解释如下:
data.split('&')
: 这一部分将请求的 body 数据按照 ‘&’ 符号进行拆分,得到一个包含键值对的列表。[info.split('=') for info in data.split('&')]
: 这一部分使用列表推导式,对上一步得到的列表中的每个键值对进行拆分,按照 ‘=’ 符号将键值对分成键和值,并构成一个新的列表。{i[0]: unquote(i[-1]) for i in [info.split('=') for info in data.split('&')]}:
这一部分再次使用列表推导式,对拆分后的键值对列表中的每个元素进行处理。其中,i[0]
表示取每个键值对中的键,而i[-1]
表示取每个键值对中的值。然后使用unquote
函数对值进行 URL 解码,最终构建成一个字典,键为解码后的键,值为解码后的值。
第二句代码 pattern = r'Content-Disposition: form-data; name="([^"]+)"\s*\r\n\s*\r\n(.*?)(?=\r\n|--.*?--|\Z)'
是用来解析 form-data 格式的数据。具体解释如下:
-
r'Content-Disposition: form-data; name="([^"]+)"\s*\r\n\s*\r\n(.*?)(?=\r\n|--.*?--|\Z)'
: 这是一个正则表达式,用于匹配 form-data 类型的数据。在 form-data 中,每个键值对由两部分组成:名称(name)和值。这个正则表达式的主要作用是匹配 form-data 中的名称和值。
Content-Disposition: form-data; name="([^"]+)"
: 这一部分匹配了 form-data 中的名称,使用了括号捕获组([^"]+)
来匹配名称的值,即键。\s*\r\n\s*\r\n
: 这一部分匹配了 form-data 中键值对之间的空行,即键值对之间的分隔符。(.*?)
: 这一部分匹配了 form-data 中的值,使用了非贪婪匹配.*?
来匹配任意字符,直到下一个分隔符或者结束符。(?=\r\n|--.*?--|\Z)
: 这一部分是一个前瞻断言,用于确定值的结束位置。它匹配了一个空行\r\n
或者 form-data 结束符--.*?--
或者字符串的结尾\Z
。
【3】在urls
先在主路由先实现一个路由分发
from django.contrib import admin
from django.urls import path,includeurlpatterns = [path('admin/', admin.site.urls),path('Api01/', include('Api01.urls')),
]
子路由
from django.urls import path,include
# BookView、BookDetailView 这个就是我在views.py 定义的类
from Api01.views import BookView,BookDetailView
urlpatterns = [path('books/', BookDetailView.as_view()),path('books/<int:pk>/', BookDetailView.as_view()),]
【4】在postman进行检验
具体其他的自己练
【5】注意事项
在下面的实例来说这里的在Task表的取值就是就是用request.body
就是只能使用python中反序列化
因为它返回的是一个字典的形式
'''b'{"task_name":"jian","task_desc":"666"}'
{'task_name': 'jian', 'task_desc': '666'}
<class 'dict'>
Task object (3)
jian '''
例如:
import json# 假设 request.body 就是你提供的字节串
body_bytes = b'{"task_name":"jian","task_desc":"666"}'# 将字节串解析为 Python 字典对象
body_dict = json.loads(body_bytes)# 现在可以访问 body_dict 中的数据了
print(body_dict['task_name']) # 输出 'jian'
print(body_dict['task_desc']) # 输出 '666'
def put(self, request, id):try:print(request.body)data = request.bodycalssdatda = json.loads(data)print(calssdatda)print(type(calssdatda))# <class 'dict'>task = Task.objects.get(id=id)print(task)task_name = calssdatda.get('task_name')print(task_name)task_desc = calssdatda.get('task_desc')task_time = time.strftime('%Y-%m-%d')# 更新任务信息task.task_name = task_nametask.task_desc = task_desctask.task_time = task_timetask.save()return JsonResponse({'message': 'Task 更新成功'})except Task.DoesNotExist:return JsonResponse({'error': '未找到数据'}, status=404)
示例二
# models.py
from django.db import modelsclass Book(models.Model):name = models.CharField(max_length=88)price = models.IntegerField()publish = models.CharField(max_length=66)
# views.py
class BookView(APIView):def get(self, request):res_list = Book.objects.all()data_list = []for res in res_list:data_list.append({'name': res.name, 'price': res.price, 'publish': res.publish})return JsonResponse({'code': 100, 'msg': '查询成功', 'results': data_list})def post(self, request):# 前端无论以什么编码格式--》提交到后端你的数据,都会被解析到 request.data 中,是个字典# 如果是urlencoded或form-data编码--》不能直接这样写,要写成如下data = request.POSTbook = Book.objects.create(name=data.get('name'), price=data.get('price'), publish=data.get('publish'))return JsonResponse({'code': 100, 'msg': '新增成功'})# 如果是json格式编码,下面没问题# Book.objects.create(**request.data)print(request.POST)print(request.data)# 本质原因是?''' APIView帮咱们呢---》看POST中有没有数据,如果有,直接赋值给data,如果没有,他去body中转成字典再给datajson格式:POST没有东西 QueryDictdata有数据 dicturlencoded或form-dataPOST有东西 QueryDictdata有数据 QueryDict'''return JsonResponse({'code': 100, 'msg': '新增成功'})
- 这个是我写的装饰器
- 这个装饰器根据请求的 Content-Type 头部信息来判断请求发送的数据类型,然后将数据解析为相应的格式,并存储在请求对象的
data
属性中。解析的数据包括 JSON 格式、urlencoded 格式和 form-data 格式。 - 白话文就是可以让我随便使用JSON格式、urlencoded格式和form-data格式。
def outter(func):def inner(*args, **kwargs):data = Nonerequest = args[0]content_type = request.META.get('CONTENT_TYPE')if 'json' in content_type:data = json.loads(request.body)elif 'urlencoded' in content_type:data = request.body.decode('utf-8')# 将查询字符串拆分为键值对列表body_list = data.split('&')decode = {}for body in body_list:decode[body.split("=")[0]] = unquote(body.split("=")[1])print(decode)# 最终得到的解析后的字典data = decodeelif 'form-data' in content_type:data = request.body.decode('utf8')pattern = r'Content-Disposition: form-data; name="([^"]+)"\s*\r\n\s*\r\n(.*?)(?=\r\n|--.*?--|\Z)'res = re.findall(pattern, string=data)data = {i[0]: i[1] for i in res}request.data = datares = func(*args, **kwargs)return resreturn inner
- urls.py
from django.urls import path,include
# BookView、BookDetailView 这个就是我在views.py 定义的类
from Api01.views import BookView,BookDetailView
urlpatterns = [path('books/', BookDetailView.as_view()),path('books/<int:pk>/', BookDetailView.as_view()),]
- 这个是View方法
@method_decorator(outter,'post')
# @method_decorator(outter,'delete')
class BookDetailView(View):def get(self, request, pk):res = Book.objects.filter(pk=pk).first()# 加个逻辑判断return JsonResponse({'code': 100, 'msg': '查询成功', 'result': {'name': res.name, 'price': res.price, 'publish': res.publish}})def delete(self, request, pk):try:# 尝试从数据库中获取指定主键的对象book = Book.objects.get(pk=pk)except Book.DoesNotExist:# 如果对象不存在,则返回相应的 JSON 响应return JsonResponse({'code': 200, 'msg': '没有数据!!!'})# 如果对象存在,则删除它book.delete()# 返回删除成功的 JSON 响应return JsonResponse({'code': 100, 'msg': '删除成功'})def put(self, request, pk):# 跟post新增数据一样,编码格式不是json就会报错Book.objects.filter(pk=pk).update(**request.data)return JsonResponse({'code': 100, 'msg': '修改成功'})def post(self, request):# body gen body的使用方法# print(request.body)# ta = request.body# da = json.loads(ta)name = request.POST.get('name')price = request.POST.get('price')publish = request.POST.get('publish')# 保存任务到数据库res = Book.objects.create(name=name,price=price,publish=publish,)return JsonResponse({'code': 100, 'msg': '添加成功', 'result': {'name': res.name, 'price': res.price, 'publish': res.publish}})
【三】APIView编写5个接口
- 去除了csrf
- 由于 RESTful API 通常是无状态的,不需要使用 CSRF 防护机制,因此 DRF 的 APIView 默认情况下去除了 CSRF 防护。
- 包装新的request
- 包装新的 request:APIView 提供了更灵活的请求(request)对象的访问方式,允许开发者更方便地访问请求中的数据以及请求的元数据。
- 三大认证
- APIView 支持多种身份验证方式,包括基于 Token 的认证、基于 Session 的认证、基于 OAuth 的认证等,使得开发者可以根据需求选择适合的认证方式。
- 全局异常捕获
- APIView 具有全局异常捕获的功能,可以捕获视图中发生的异常,并返回自定义的错误响应,提高了代码的可维护性和可靠性。
【0】注意事项
视图(View)和API视图(APIView)都可以包含处理请求的逻辑,并且它们都可以执行相似的操作,比如从请求中获取数据、执行业务逻辑、与数据库交互等。
然而,它们之间仍然存在一些区别:
- 设计目的:视图通常用于渲染HTML页面,而API视图则用于处理API请求并返回数据,通常以JSON格式。
- 功能和便利性:API视图(特别是DRF中的APIView)通常提供了更多的功能和便利性,比如内置的请求处理方法(如
get
、post
等)、序列化、验证、权限控制、分页等。这些功能可以帮助简化API开发并提高开发效率。 - 返回响应的方式:API视图通常使用DRF提供的
Response
对象来返回响应,而视图可能使用Django的render
函数或者HttpResponse
对象。
虽然视图和API视图在处理请求的逻辑上可能非常相似,但是选择合适的工具(视图或API视图)取决于你的具体需求和项目的特点。如果你正在构建一个RESTful API,那么使用API视图可能会更加合适和便捷。
【1】主路由urls.py
from django.contrib import admin
from django.urls import path,include # 路由分发urlpatterns = [path('admin/', admin.site.urls),path('task_api/', include('task_api.urls')),
]
【2】子路由urls.py
from django.urls import path,include,re_path
from task_api.views import TaskAPIView ,TaskDetailView # TaskViewurlpatterns = [# re_path(r'(?P<id>\d+)?/?$', TaskAPIView.as_view()),re_path(r'(?P<pk>\d+)?/?$', TaskDetailView.as_view()),
]
【3】views.py
from django.http import JsonResponse
from .models import Task
import time
import uuidfrom rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class TaskAPIView(APIView):def post(self, request):# 获取POST请求中的数据task_name = request.POST.get('task_name')task_desc = request.POST.get('task_desc')# 生成任务时间task_time = time.strftime('%Y-%m-%d')# 生成任务IDtask_id = uuid.uuid4()# 保存任务到数据库Task.objects.create(task_name=task_name,task_desc=task_desc,task_time=task_time,task_id=task_id)# 返回JSON响应 是一个HTTP状态码返回成功则是"201 Created"return Response({'message': 'Task 添加成功!'}, status=status.HTTP_201_CREATED)def get(self,request):res_list = Task.objects.all()data_list = []for res in res_list:data_list.append({'task_name': res.task_name,'task_desc':res.task_desc,'task_time':res.task_time,'task_id':res.task_id})return JsonResponse({'code': 100, 'msg': '查询成功', 'results': data_list})class TaskDetailView(APIView):def get(self,request,pk):if pk:try:task = Task.objects.get(pk=pk)print(task)data = {'task_name': task.task_name,'task_desc': task.task_desc,'task_time': task.task_time,'task_id': task.task_id}print(data)return JsonResponse(data)except Task.DoesNotExist:return JsonResponse({'error': 'Task 未能发现数据'}, status=404)def delete(self, request, pk):if pk:try:Task.objects.filter(pk=pk).delete()return JsonResponse({'code': 100, 'msg': '删除成功'})except Task.DoesNotExist:return JsonResponse({'error': 'Task 未能发现数据'}, status=404)def put(self, request, pk):if pk:try:Task.objects.filter(pk=pk).update(**request.data)return JsonResponse({'code': 100, 'msg': '修改成功!!!'})except Task.DoesNotExist:return JsonResponse({'error': 'Task 未能发现数据'}, status=404)
问:
这个**request.data按你所说就是一个将Task表中的数据渲染到前端然后在解包打印到后端的过程对不对
答:
不完全正确。request.data
并不是将数据库中的数据直接渲染到前端,而是在Django REST Framework中用于获取HTTP请求中发送的数据的方式之一。具体来说,request.data
是一个包含了HTTP请求主体中发送的数据的字典,它可能是通过POST、PUT或PATCH等HTTP方法发送的。
在你的情况下,假设你使用的是PUT方法来更新任务的数据,那么request.data
将包含前端发送的要更新的任务数据。而**request.data
这个语法是将这个字典中的键值对解包为关键字参数,然后将这些参数传递给update()
函数,以更新数据库中对应任务的数据。
所以,request.data
不是将数据库中的数据直接渲染到前端,而是用于获取HTTP请求中发送的数据,并在后端进行处理和操作。
request.data-->请求体的数据--》方法包装成了数据属性
这篇关于DRF的接口使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!