DRF视图
DRF视图
请求参数
利用HTTP请求报文传递参数有3种方式
- 查询字符串,GET方法,参数在URL中,http://127.0.0.1:8000/emp/?x=123x=abc
- 表单,前端网页中填写表单,一般使用POST方法,参数在body中
POST /xxx/yyy?id=5&name=magedu HTTP/1.1
HOST: 127.0.0.1:9999
content-length: 26
content-type: application/x-www-form-urlencoded
age=5&weight=80&height=170
也可以POST、PUT提交JSON格式数据
3. URL本身就是数据的表达,http://127.0.0.1:8080/python/2010/u101
- 请求方法method表示CRUD
- 也可以使用查询字符串,例如分页?page=5
- 也可以提交数据
Django视图原理:
Django的视图都从View类开始,View.as_view()方法本质:
URL映射当中需要路径映射到视图函数,在as_view()方法中,它将View类包装成一个视图函数,一个请求一个View实例,内部通过dispatch方法通过request.method分发给同名小写的handler处理,返回HttpResponse实例。
APIView
在Django中,View是视图类基类,路由配置中需要把类通过as_view()伪装成视图哈桑农户,请求通过路由进入到这个视图函数中,内部为每一个请求实例化一个视图类实例,并根据request.method找到对应的handler。而这个基本流程已经被View类固定在其内部,我们只需要定义get、post等方法即可,简化了编程。
使用DRF,基于Django,也要使用View类,并不会改变请求和响应的处理流程。由于遵守REST要求,请求的参数使用JSON格式提交,返回的数据使用JSON格式返回。
请求的参数是JSON格式,先要反序列化它,然后验证,验证合格可以入库。响应的数据应该序列化成JSON格式。,也可以简化
参考:https://www.django-rest-framework.org/api-guide/views/
APIView
- APIView是DRF的视图类的基类,其基类是Django的View类
- as_view()调用基类的View的,但是使用了csrf_exempt(view)来排除CSRF保护
- 重写了dispath(),根据request.method.lower()找对应的handler。调用handler之前,封装request,处理调用handler的异常,最终处理这个response
- 重新定义了Request类来替代Django的,虽是重写。但是依然有联系
- 重新定义了Response类来增强替代Django的
- 异常类都是基于APIException类的
- 对请求进行认证和授权
APIView没有提供增删改查的 handler方法,也就是说和View一样,需要自己定义get、post、put、patch、delete方法
GET请求测试
GET请求:http://127.0.0.1:8000/emp/?x=123&x=abc&y=789
from rest_framework.views import APIView, Response, Request
from .models import Employees
from .serializers import EmployeeSerizlizer
class EmployeesView(APIView):
def get(self,request:Request,*args,**kwargs):
emp = Employees.objects.all()
serializer = EmployeeSerizlizer(instance=emp,many=True)
print(1,serializer.data)
print(request.data)
print(request.query_params)
print(request.content_type)
print(request.GET)
return Response(serializer.data)
#输出
1 [OrderedDict([('emp_no', 10001), ('birth_date', '1953-09-02'), ('first_name', 'Georgi'), ('last_name', 'Facello'), ('gender', 1), ('hire_date', '1986-06-26')]), OrderedDict([('emp_no', 10002), ('birth_dat
e', '1964-06-02'), ('first_name', 'Bezalel'), ('last_name', 'Simmel'), ('gender', 2), ('hire_date', '1985-11-21')]), OrderedDict([('emp_no', 10003), ('birth_date', '1959-12-03'), ('first_name', 'Parto'), ('
last_name', 'Bamford'), ('gender', 1), ('hire_date', '1986-08-28')]), OrderedDict([('emp_no', 10004), ('birth_date', '1954-05-01'), ('first_name', 'Chirstian'), ('last_name', 'Koblick'), ('gender', 1), ('hi
re_date', '1986-12-01')]), OrderedDict([('emp_no', 10005), ('birth_date', '1955-01-21'), ('first_name', 'Kyoichi'), ('last_name', 'Maliniak'), ('gender', 1), ('hire_date', '1989-09-12')]), OrderedDict([('em
p_no', 10006), ('birth_date', '1953-04-20'), ('first_name', 'Anneke'), ('last_name', 'Preusig'), ('gender', 2), ('hire_date', '1989-06-02')]), OrderedDict([('emp_no', 10007), ('birth_date', '1957-05-23'), (
'first_name', 'Tzvetan'), ('last_name', 'Zielinski'), ('gender', 2), ('hire_date', '1989-02-10')]), OrderedDict([('emp_no', 10008), ('birth_date', '1958-02-19'), ('first_name', 'Saniya'), ('last_name', 'Kal
loufi'), ('gender', 1), ('hire_date', '1994-09-15')]), OrderedDict([('emp_no', 10009), ('birth_date', '1952-04-19'), ('first_name', 'Sumant'), ('last_name', 'Peac'), ('gender', 2), ('hire_date', '1985-02-18
')]), OrderedDict([('emp_no', 10010), ('birth_date', '1963-06-01'), ('first_name', 'Duangkaew'), ('last_name', 'Piveteau'), ('gender', 2), ('hire_date', '1989-08-24')]), OrderedDict([('emp_no', 10011), ('bi
rth_date', '1953-11-07'), ('first_name', 'Mary'), ('last_name', 'Sluis'), ('gender', 2), ('hire_date', '1990-01-22')]), OrderedDict([('emp_no', 10012), ('birth_date', '1960-10-04'), ('first_name', 'Patricio
'), ('last_name', 'Bridgland'), ('gender', 1), ('hire_date', '1992-12-18')]), OrderedDict([('emp_no', 10013), ('birth_date', '1963-06-07'), ('first_name', 'Eberhardt'), ('last_name', 'Terkki'), ('gender', 1
), ('hire_date', '1985-10-20')]), OrderedDict([('emp_no', 10014), ('birth_date', '1956-02-12'), ('first_name', 'Berni'), ('last_name', 'Genin'), ('gender', 1), ('hire_date', '1987-03-11')]), OrderedDict([('
emp_no', 10015), ('birth_date', '1959-08-19'), ('first_name', 'Guoxiang'), ('last_name', 'Nooteboom'), ('gender', 1), ('hire_date', '1987-07-02')]), OrderedDict([('emp_no', 10016), ('birth_date', '1961-05-0
2'), ('first_name', 'Kazuhito'), ('last_name', 'Cappelletti'), ('gender', 1), ('hire_date', '1995-01-27')]), OrderedDict([('emp_no', 10017), ('birth_date', '1958-07-06'), ('first_name', 'Cristinel'), ('last
_name', 'Bouloucos'), ('gender', 2), ('hire_date', '1993-08-03')]), OrderedDict([('emp_no', 10018), ('birth_date', '1954-06-19'), ('first_name', 'Kazuhide'), ('last_name', 'Peha'), ('gender', 2), ('hire_dat
e', '1987-04-03')]), OrderedDict([('emp_no', 10019), ('birth_date', '1953-01-23'), ('first_name', 'Lillian'), ('last_name', 'Haddadi'), ('gender', 1), ('hire_date', '1999-04-30')]), OrderedDict([('emp_no',
10020), ('birth_date', '1952-12-24'), ('first_name', 'Mayuko'), ('last_name', 'Warwick'), ('gender', 1), ('hire_date', '1991-01-26')]), OrderedDict([('emp_no', 10030), ('birth_date', '1963-06-01'), ('first_
name', 'du'), ('last_name', 'jie'), ('gender', 2), ('hire_date', '1989-08-24')]), OrderedDict([('emp_no', 10040), ('birth_date', '1963-06-01'), ('first_name', 'www'), ('last_name', '333'), ('gender', 2), ('hire_date', '1989-08-24')])]
{}
<QueryDict: {}>
text/plain
<QueryDict: {}>
[30/Mar/2023 09:25:29] "GET /emp/ HTTP/1.1" 200 11576
QueryDict本质就是字典,对于同一个参数有多值情况使用了列表。
POST请求测试
POST请求:http://127.0.0.1:8000/emp/?x=123&x=abc&y=789
一、采用表单提交方式
from rest_framework.views import APIView, Request, Response
class TestIndex(APIView):
def post(self, request:Request):
print('~' * 30)
print(request.method) # HttpRequest的属性
print(request.GET) # HttpRequest的属性
print(request.query_params) # Request的属性,使用小写的
print(request.content_type)
print(request.POST) # HttpRequest的属性
print(request.data) # Request的属性,使用小写的
print('~' * 30)
return Response({})
#输出
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
POST
<QueryDict: {'dujie': ['123'], 'zhangsan': ['233']}>
<QueryDict: {'dujie': ['123'], 'zhangsan': ['233']}>
application/x-www-form-urlencoded
<QueryDict: {'dujie': ['123'], 'zzz': ['34445']}>
<QueryDict: {'dujie': ['123'], 'zzz': ['34445']}>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
二、JSON数据
#输出
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
POST
<QueryDict: {'dujie': ['123'], 'zhangsan': ['233']}>
<QueryDict: {'dujie': ['123'], 'zhangsan': ['233']}>
application/json
<QueryDict: {}>
{'dict': 4566, 'asdd': 3333}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
请求总结
- GET请求,查询字符串使用query_params提取
- POST请求,访问data属性
- 支持了POST、PUT、PATCH方法(增,改)
- 统一将请求处理放到data属性中
- 如果是Json数据,帮我们反序列化
响应
DRF对Django的响应也做了增强,使用更加简单方便
Response(data=None, status=None, template_name=None, headers=None,content_type=None)
- data将要序列化的数据,例如字典
- status 状态码,默认200
- headers响应报文头,字典
- content_type响应内容类型,有时候需要手动设置
from rest_framework.views import APIView, Request, Response
class TestIndex(APIView):
def get(self, request:Request):
print(request.query_params) # Request的属性,使用小写的
return Response({})
def post(self, request:Request):
print(request.query_params) # Request的属性,使用小写的
print(request.data) # Request的属性,使用小写的
return Response({
'host':'python', 'domain':'magedu.com'
}, status=201, headers={'X-Server':'Magedu'})
应用
需要构建两个类
- 列表页,返回列表和新增功能
- 详情页,基于主键的查看详情、修改、删除
路由
from django.urls import path
from .views import EmpsView, EmpView
urlpatterns = [
path('', EmpsView.as_view()), # /emp/
path('<int:pk>/', EmpView.as_view()), # /emp/10021/ 更换参数名称pk
]
列表页
# 使用APIView开发,列表页和详情页发现基本的CRUD具有通用性
#实际上其他业务开发就是换Model和序列化器就行
class EmpsView(APIView):
"""/emp"""
def get(self, request:Request, *args, **kwargs):
"""返回查询的列表"""
#1、 查询数据库 2、序列化器 3、data
emps = Employees.objects.all()
serializer = EmployeeSerizlizer(emps,many=True)
return Response(serializer.data)
def post(self, request:Request, *args, **kwargs):
"""新增数据校验后入库,返回201和新增的实例"""
data = request.data # 1、DRF请求封装,字典
serializer = EmployeeSerizlizer(data=data) #2、序列化器
serializer.is_valid(raise_exception=True) #3、校验
serializer.save() #入库
return Response(serializer.data,201)
详情页
class EmpView(APIView):
"""/emp/10001"""
def get(self, request:Request, *args, **kwargs):
print(1, self.args)
print(2, self.kwargs)
print(3, args, kwargs)
print(4, request.query_params)
emp = Employees.objects.get(pk=(kwargs['pk']))
serializer = EmployeeSerizlizer(emp)
return Response(serializer.data)
def put(self, request, *args, **kwargs):
"""先查后改"""
obj = Employees.objects.get(pk=(kwargs['pk']))
serializer = EmployeeSerizlizer(instance=obj, data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, 201)
def delete(self, request, *args, **kwargs):
"""先查后删"""
obj = Employees.objects.get(pk=(kwargs['pk']))
obj.delete()
return Response(status=204)
def patch(self, request, *args, **kwargs):
return Response()
异常处理
参考 https://www.django-rest-framework.org/api-guide/exceptions/
测试http://127.0.0.1:8000/emp/100,返回500服务器内部错误。
按平常做法,就应该出什么问题,返回什么出错信息。但这样做会有安全风险,且在浏览器端的普通用户根本不管什么错误,就认为网站出问题了。所以,返回更加友好的出错页面是有必要的,例如更换404页面。
我们的项目采用前后端分离,返回出错页面不合适,需要返回Json格式的出错信息,有必要拦截所有的异常做处理。
异常全局配置
自定义全局异常处理器,参考rest_framework.views.exception_handler 实现
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'utils.exceptions.global_exception_handler'
}
全局异常处理
exception_handler 会转化Django的Http404、PermissionDenied为DRF的基于APIException的类。
项目根目录下新建包utils,其中新建模块exceptions.py ,内容如下
from rest_framework.response import Response
from rest_framework.views import exception_handler
from rest_framework import exceptions
class MagBaseException(exceptions.APIException):
"""基类定义基本的异常"""
code = 10000 # code为0表示正常,非0表示错误
message = "非法请求" # 错误描述
@classmethod
def get_message(cls):
return {'code': cls.code, 'message': cls.message}
# 内部异常暴露细节,替换为自定义
exc_map = {
}
def global_exception_handler(exc, context):
"""
全局异常处理
不管什么异常这里统一处理。根据不同类型显示不同的信息
为了前端JS解析方便,这里响应的状态码采用默认的200
异常对应处理后返回对应的错误码和错误描述
异常找不到对应就返回缺省
"""
# 调用DRF缺省异常处理,返回Response对象或None
response = exception_handler(exc, context)
if response is not None: # 说明就是APIException,否则是None
# 如果是APIException,就利用exc的类型名称映射到自定义类型
errmsg = exc_map.get(exc.__class__.__name__,
MagBaseException).get_message()
return Response(errmsg, status=200) # 状态恒为200
return response
抛出异常后,拦截他们,这里做统一的处理
列表页和新增实现
from rest_framework.views import APIView, Request, Response
from .models import Employee
from .serializers import EmpSerializer
class EmpsView(APIView):
"""
实现列表页get、新增post
http://127.0.0.1:8000/emp/
"""
def get(self, request):
emps = Employee.objects.all()
return Response(EmpSerializer(emps, many=True).data)
def post(self, request):
serializer = EmpSerializer(data=request.data)
serializer.is_valid(True)
serializer.save()
return Response(serializer.data, 201)
POST测试数据
{
"emp_no": 10023,
"birth_date": "2000-06-01",
"first_name": "sam",
"last_name": "lee",
"gender": 1,
"hire_date": "2020-08-24"
}
详情页、修改和删除实现
from rest_framework.views import APIView, Request, Response
from .models import Employee
from .serializers import EmpSerializer
class EmpView(APIView):
"""
实现详情页get、修改put、删除delete
http://127.0.0.1:8000/emp/10021
"""
def get(self, request, pk:int):
obj = Employee.objects.get(pk=pk)
return Response(EmpSerializer(obj).data)
def put(self, request, pk:int):
# 必须先查后改
obj = Employee.objects.get(pk=pk)
serializer = EmpSerializer(obj, data=request.data)
serializer.is_valid(True)
serializer.save()
return Response(serializer.data, 201)
def delete(self, request, pk:int):
obj = Employee.objects.get(pk=pk)
obj.delete()
return Response(status=204)
PUT测试用Json
{
"emp_no": 10023,
"birth_date": "2000-06-01",
"first_name": "sam",
"last_name": "lee",
"gender": 2,
"hire_date": "2019-08-24"
}
完整参考代码
employee/views.py
from rest_framework.views import APIView, Request, Response
from .models import Employee
from .serializers import EmpSerializer
class EmpsView(APIView):
"""
实现列表页get、新增post
http://127.0.0.1:8000/emp/
"""
def get(self, request):
emps = Employee.objects.all()
return Response(EmpSerializer(emps, many=True).data)
def post(self, request):
serializer = EmpSerializer(data=request.data)
serializer.is_valid(True)
serializer.save()
return Response(serializer.data, 201)
class EmpView(APIView):
"""
实现详情页get、修改put、删除delete
http://127.0.0.1:8000/emp/10021
"""
def get(self, request, pk:int):
obj = Employee.objects.get(pk=pk)
return Response(EmpSerializer(obj).data)
def put(self, request, pk:int):
# 必须先查后改
obj = Employee.objects.get(pk=pk)
serializer = EmpSerializer(obj, data=request.data)
serializer.is_valid(True)
serializer.save()
return Response(serializer.data, 201)
def delete(self, request, pk:int):
obj = Employee.objects.get(pk=pk)
obj.delete()
return Response(status=204)
employee/models.py
from django.db import models
class Employee(models.Model):
class Gender(models.IntegerChoices): # 枚举类型,限定取值范围
MAN = 1, '男'
FEMALE = 2, '女'
class Meta:
db_table = 'employees'
verbose_name = '员工'
emp_no = models.IntegerField(primary_key=True, verbose_name='工号')
birth_date = models.DateField(verbose_name='生日')
first_name = models.CharField(max_length=14, verbose_name='名')
last_name = models.CharField(max_length=16, verbose_name='姓')
gender = models.SmallIntegerField(verbose_name='性别',
choices=Gender.choices)
hire_date = models.DateField()
class Salary(models.Model):
class Meta:
db_table = "salaries"
#id = models.AutoField(primary_key=True) # 额外增加的主键,Django不支持联合主
键
emp_no = models.ForeignKey(Employee, on_delete=models.CASCADE,
db_column='emp_no', related_name='salaries')
from_date = models.DateField()
salary = models.IntegerField(verbose_name='工资')
to_date = models.DateField()
employee/serizlizers.py
from rest_framework import serializers
from .models import Employee, Salary
class SalarySerializer(serializers.ModelSerializer):
class Meta:
model = Salary
fields = '__all__'
class EmpSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = '__all__'