DRF视图

请求参数

利用HTTP请求报文传递参数有3种方式

  1. 查询字符串,GET方法,参数在URL中,http://127.0.0.1:8000/emp/?x=123x=abc
  2. 表单,前端网页中填写表单,一般使用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']}>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

image

二、JSON数据

image

#输出
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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__'