Django学习笔记:DRF

1:熟悉Django

这里我们通过一个简单的用户管理系统来熟悉一下`Django`的运用,我们做这个管理系统的目的就是:

1:熟悉Django项目的创建流程
2:熟悉Django与HTML模板的渲染
3:熟悉Ajax前后端数据交互
4:熟悉ORM数据库操作

1.1:创建项目

image

image

image

这就是我们创建完成之后的样子

1.2:配置Django并测试启动

其实只要Pycharm没有问题,就可以直接启动,但是有些Pycharm有问题的可能导致部分包没有导入到某些py文件中,大家可以根据报错去导入一下就OK了,导入完成之后我们可以测试一下,如下图

image

image

从这里我们可以看到它已经启动了,然后我们尝试着去访问一下这个地址

image

看到这个界面证明我们的Django环境是OK的,然后后面就是我们可以根据自己的功能模块去创建相应的目录

1.3:创建App目录

# 我们在我们的项目目录下创建一个app的目录,它主要用以路由分发。

PS E:\code\user_data_manager> python .\manage.py startapp app

image

现在的目录结构是这样子的,然后因为我们这次的项目第一个是一个用户信息管理系统,所以肯定要有数据库,然后通过网页去显示我们的用户信息,然后再去对数据库进行增删改查的操作。所以我们第一个要做的就是需要去操作数据库,也就是需要去定义数据模型

1.4:定义数据模型

当然了,我们定义数据模型的话是在app/migrations/models.py下去定义我们的数据模型
from django.db import models


# Create your models here.

class User(models.Model):
    name = models.CharField(max_length=20)
    city = models.CharField(max_length=20)
    sex = models.CharField(max_length=10)
    age = models.IntegerField()

上图就是我们定义的一个数据模型,用于存储数据的,那么我们定义完成之后就是需要将这个数据模型同步一下,使用如下命令操作

PS E:\code\user_data_manager> python .\manage.py makemigrations
# 这个提示的意思是因为我们没有在settings.py内添加我们的app,所以我们去添加一下
No changes detected

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app'
]

添加完成之后我们需要去生成一下迁移文件
# 生成迁移文件
PS E:\code\user_data_manager> python .\manage.py makemigrations
# 执行迁移同步
PS E:\code\user_data_manager> python .\manage.py migrate 

这样就可以同步完成了,我们这里默认的数据库是sqlite3的数据库,然后我们后面就是去编写我们的视图函数

1.5:编写视图函数

我们在app/views.py下去编写视图函数
from django.shortcuts import render
from django.http import HttpResponse


# Create your views here.

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        return HttpResponse('这是一个创建用户的请求!')
    elif request.method == 'PUT':
        return HttpResponse('这是一个更新用户的请求!')
    elif request.method == 'DELETE':
        return HttpResponse('这是一个删除用户的请求!')
    else:
        return HttpResponse('这是一个未知的请求!')

然后我们需要去做一下路由的分发,是在项目目录下的urls.py文件下,但是我们不在这里写,我们导入include包,然后去app下创建一个子urls.py,然后将前面的urls.py复制到app/urls.py下面去
# 主项目目录下的urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),

]
# app/urls.py

from django.urls import path
from app import views

urlpatterns = [
    path('user/', views.user),
]
但是上面的做法,其实还不行,我们需要在主路由的文件中去包含一下我们app/urls.py这个子路由文件
# 包含完成后的主目录下的urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app/', include('app.urls')),
]
这样完成之后我们就可以去测试我们的Django项目是否可以识别我们的前面定义的四种请求了,这里我们启动一下Django之后我们涉及到一个工具,我这里是用的ApiPost工具来测试的,四种请求方式测试结果如下:

# 注:这里除了GET方法,其他的三个方法直接测试的话是403,因为Django默认自带了认证,所以我们需要传递Token的。当然我们可以临时绕过它,也就是在settings.py文件内将csrf的配置注释掉。

# 注释如下配置
'django.middleware.csrf.CsrfViewMiddleware',

image

image

image

image

image

我们前面定义的视图就只有四个请求方法和全否定的方法,对应图上的四种请求方式,其实这个时候懂得人就知道,这种操作叫做RestFul API风格,也就是通过请求一个API的不同请求方式实现不同的功能,就比如我们这个样例:

GET /app/user/
POST /app/user/
PUT /app/user/
DELETE /app/user/

等等等等,都对应了后端的一种操作,这个我们就称之为RestFul风格

1.6:编写HTML页面

我们之所以去编写这个HTML页面只是为了去显示我们的用户信息所以才去编写的,后面入手CMDB是使用Vue前端框架开发的。

我们在项目目录下的template下创建一个userlist.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理系统</title>
</head>
<body>
<table border="2">
    <thead>
         <tr>
             <th>姓名</th>
             <th>城市</th>
             <th>性别</th>
             <th>年龄</th>
         </tr>
    </thead>
    <tbody>
         <tr>
            <td>Layzer</td>
            <td>上海</td>
            <td>男</td>
            <td>25</td>
         </tr>
    </tbody>
</table>
</body>
</html>
这是一个简单到不能再简单的HTML表格页面,

image

然后我们在视图函数内去编写我们的视图,代码如下:
def user_list(request):
    return render(request, 'userlist.html')
这样写,就可以让Django去渲染我们的userlist.html的数据了其实就是Jinjia模板操作,然后我们需要去子urls.py内定义一下url
from django.urls import path
from app import views

urlpatterns = [
    path('user/', views.user),
    path('userlist/', views.user_list),
]
这个时候我们就可以通过Django的链接去访问我们的这个html页面了

http://127.0.0.1:8000/app/userlist/

image

因为这个数据暂时是我们写死的,所以我们如果想动态获取的话肯定是需要去数据库内去添加数据的,那么我们去数据库内添加几条数据。至于怎么去打开SQLite数据库,这里我用的是 Navicat。

image

从这里我们可以看到,app_user就是我们刚刚同步的数据库了,然后我们往这个表内添加一些数据

image

这里我们添加了三条数据,也就意味着这就是我们模拟出来的三条用户数据,然后我们现在就要思考,如何让数据动态的渲染给userlist.html了。

1.7:操作数据库回显信息

这里我们就需要在视图内去操作数据库获取信息了,具体的操作代码如下:
from django.shortcuts import render
from django.http import HttpResponse
# 导入数据模型
from app.models import User


# Create your views here.

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        return HttpResponse('这是一个创建用户的请求!')
    elif request.method == 'PUT':
        return HttpResponse('这是一个更新用户的请求!')
    elif request.method == 'DELETE':
        return HttpResponse('这是一个删除用户的请求!')
    else:
        return HttpResponse('这是一个未知的请求!')


def user_list(request):
    # 获取所有数据
    userlist = User.objects.all()
    # 通过模板方式渲染给userlist.html
    return render(request, 'userlist.html', {'userlist': userlist})
这样做完之后我们就可以去前端去获取数据了。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理系统</title>
</head>
<body>
<h1>用户信息管理</h1>
<table border="2">
    <thead>
         <tr>
             <th>姓名</th>
             <th>城市</th>
             <th>性别</th>
             <th>年龄</th>
         </tr>
    </thead>
    <tbody>
    {% for i in userlist %}
         <tr>
            <td>{{ i.name }}</td>
            <td>{{ i.city }}</td>
            <td>{{ i.sex }}</td>
            <td>{{ i.age }}</td>
         </tr>
    {% endfor %}
    </tbody>
</table>
</body>
</html>
这里就是我们定义好的模板了,然后我们再去看看我们的数据是否是动态刷新到了我们的页面上了。

image

从这里我们可以看出,的确数据是从数据库里面取出来了,然后我们下面再去完成一个创建用户的功能

1.8:编写创建用户视图

首先我们先把HTML写好,但是后面还是要改的,这个暂时是先写好我们的功能
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理系统</title>
</head>
<body>
<h1>用户信息管理</h1>
<button><a href="#" target="_blank">创建用户</a></button>
<table border="2">
    <thead>
         <tr>
             <th>姓名</th>
             <th>城市</th>
             <th>性别</th>
             <th>年龄</th>
         </tr>
    </thead>
    <tbody>
    {% for i in userlist %}
         <tr>
            <td>{{ i.name }}</td>
            <td>{{ i.city }}</td>
            <td>{{ i.sex }}</td>
            <td>{{ i.age }}</td>
         </tr>
    {% endfor %}
    </tbody>
</table>
</body>
</html>
然后我们再去创建一个html叫做useradd.html,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>创建用户</title>
</head>
<body>
<form action="#">
    <h1>创建用户</h1>
    姓名:<input type="text" name="name"><br>
    城市:<input type="text" name="city"><br>
    性别:<input type="text" name="sex"><br>
    年龄:<input type="text" name="age"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>
当然了,我们得去添加视图
def user_add(request):
    return render(request, 'useradd.html')
然后再去添加一下url
from django.urls import path
from app import views

urlpatterns = [
    path('user/', views.user),
    path('userlist/', views.user_list),
    path('useradd/', views.user_add),
]
访问:http://127.0.0.1:8000/app/useradd/

我们得到了如下的页面

image

我们的创建用户的地址确认之后我们去在获取用的html页面写入一下
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理系统</title>
</head>
<body>
<h1>用户信息管理</h1>
<!--主要更改了href跳转地址-->
<button><a href="/app/useradd/" target="_blank">创建用户</a></button>
<table border="2">
    <thead>
         <tr>
             <th>姓名</th>
             <th>城市</th>
             <th>性别</th>
             <th>年龄</th>
         </tr>
    </thead>
    <tbody>
    {% for i in userlist %}
         <tr>
            <td>{{ i.name }}</td>
            <td>{{ i.city }}</td>
            <td>{{ i.sex }}</td>
            <td>{{ i.age }}</td>
         </tr>
    {% endfor %}
    </tbody>
</table>
</body>
</html>
这个配置完成之后它就可以通过点击创建用户跳转到创建用户的视图了。

1.9:创建提交数据路由及操作

我们这里对html又做了一点点的修改,也就是修改了提交数据的路由。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>创建用户</title>
</head>
<body>
<form action="/app/user/" method="POST">
    <h1>创建用户</h1>
    姓名:<input type="text" name="name"><br>
    城市:<input type="text" name="city"><br>
    性别:<input type="text" name="sex"><br>
    年龄:<input type="text" name="age"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>
然后我们去视图内编写提交数据的操作。
from django.shortcuts import render
from django.http import HttpResponse
from app.models import User


# Create your views here.

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        # 这里是从前端获取传进来的数据然后赋值给变量
        name = request.POST.get('name')
        city = request.POST.get('city')
        sex = request.POST.get('sex')
        age = request.POST.get('age')
        # 操作ORM 入库对应数据
        User.objects.create(
            name=name,
            city=city,
            sex=sex,
            age=age
        )
    elif request.method == 'PUT':
        return HttpResponse('这是一个更新用户的请求!')
    elif request.method == 'DELETE':
        return HttpResponse('这是一个删除用户的请求!')
    else:
        return HttpResponse('这是一个未知的请求!')


def user_list(request):
    userlist = User.objects.all()
    return render(request, 'userlist.html', {'userlist': userlist})


def user_add(request):
    return render(request, 'useradd.html')

完成之后我们去前端进行添加数据的测试。

image

点击提交,之后返回 userlist的路由去查看是否有新增

image

当我们刷新一下页面之后就可以看到我们的新数据了,这个时候我们就确定了我们的增加用户的操作已经完成了,然后我们还需要去做的一个事情就是如何更新和删除用户呢?

1.10:实现更新和删除用户

通常我们会在用户列表页面去更新或者删除用户,我们来看看是什么样子的前端

image

然后我们需要去实现两个操作的视图,首先我们去实现删除操作,我们这里让它按照ID去删除,但是我们在前端没有打印ID,我们也改改打印出来
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理系统</title>
</head>
<body>
<h1>用户信息管理</h1>
<button><a href="/app/useradd/" target="_blank">创建用户</a></button>
<table border="2">
    <thead>
         <tr>
             <th>ID</th>
             <th>姓名</th>
             <th>城市</th>
             <th>性别</th>
             <th>年龄</th>
             <th>操作</th>
         </tr>
    </thead>
    <tbody>
    {% for i in userlist %}
         <tr>
             <td>{{ i.id }}</td>
             <td>{{ i.name }}</td>
             <td>{{ i.city }}</td>
             <td>{{ i.sex }}</td>
             <td>{{ i.age }}</td>
             <td>
                 <button>编辑</button>
                 <button>删除</button>
             </td>
         </tr>
    {% endfor %}
    </tbody>
</table>
</body>
</html>

image

然后我们去视图实现删除操作。
from django.shortcuts import render
from django.http import HttpResponse
from app.models import User
# 我们需要使用QueryDict去获取我们的数据
from django.http import QueryDict


# Create your views here.

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        name = request.POST.get('name')
        city = request.POST.get('city')
        sex = request.POST.get('sex')
        age = request.POST.get('age')
        User.objects.create(
            name=name,
            city=city,
            sex=sex,
            age=age
        )
        return HttpResponse('添加成功!')
    elif request.method == 'PUT':
        return HttpResponse('这是一个更新用户的请求!')
    elif request.method == 'DELETE':
        # 拿到字节
        data = QueryDict(request.body)
        # 取出数据
        id = data.get('id')
        # 操作ORM先查找数据,然后再删除数据
        User.objects.get(id=id).delete()
        return HttpResponse('这是一个删除用户的请求!')
    else:
        return HttpResponse('这是一个未知的请求!')


def user_list(request):
    userlist = User.objects.all()
    return render(request, 'userlist.html', {'userlist': userlist})


def user_add(request):
    return render(request, 'useradd.html')

不过我们在思考一个问题,就是前端如何将数据传递给我们的后端呢,这个时候其实我们需要用到Ajax了。所以我们需要用JQuery
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理系统</title>
    <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
</head>
<body>
<h1>用户信息管理</h1>
<button><a href="/app/useradd/" target="_blank">创建用户</a></button>
<table border="2">
    <thead>
         <tr>
             <th>ID</th>
             <th>姓名</th>
             <th>城市</th>
             <th>性别</th>
             <th>年龄</th>
             <th>操作</th>
         </tr>
    </thead>
    <tbody>
    {% for i in userlist %}
         <tr>
             <td>{{ i.id }}</td>
             <td>{{ i.name }}</td>
             <td>{{ i.city }}</td>
             <td>{{ i.sex }}</td>
             <td>{{ i.age }}</td>
             <td>
                 <button>编辑</button>
                 <button onclick="delUser(this)">删除</button>
             </td>
         </tr>
    {% endfor %}
    </tbody>
</table>
<script>
    function delUser(obj) {
        confirm = confirm('确定删除吗?');
        if (confirm) {
            id = $(obj).parent().parent().find("td:eq(0)").text();
            data = {'id': id};
            $.ajax({
                type: 'DELETE',
                url: '/app/user',
                data: data,
                success: function (result) {
                    alert('删除成功');
                },
                error: function () {
                    alert('删除失败');
                }
            })
        }
    }
</script>
</body>
</html>
这里我们运用到了一点点ajax的操作,使用ajax传递参数到url然后执行删除。

image

我这里测试的是删除第四个数据,然后我们刷新看看数据是否已经被我们删除掉了。

image

这个时候我们发现数据已经没了,证明数据已经被我们删除掉了。也就意味着我们的程序是执行成功的,不过我们其实py写的还是不太规范,真正的规范的操作时返回一个json串的,比如:
from django.shortcuts import render
from django.http import HttpResponse,JsonResponse
from app.models import User
from django.http import QueryDict


# Create your views here.

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        name = request.POST.get('name')
        city = request.POST.get('city')
        sex = request.POST.get('sex')
        age = request.POST.get('age')
        User.objects.create(
            name=name,
            city=city,
            sex=sex,
            age=age
        )
        return HttpResponse('添加成功!')
    elif request.method == 'PUT':
        return HttpResponse('这是一个更新用户的请求!')
    elif request.method == 'DELETE':
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            User.objects.get(id=id).delete()
            result = {'code': 200, 'msg': '删除成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '删除失败!'}
            return JsonResponse(result)
    else:
        return HttpResponse('这是一个未知的请求!')


def user_list(request):
    userlist = User.objects.all()
    return render(request, 'userlist.html', {'userlist': userlist})


def user_add(request):
    return render(request, 'useradd.html')

<!--删除用户的前端代码-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理系统</title>
    <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
</head>
<body>
<h1>用户信息管理</h1>
<button><a href="/app/useradd/" target="_blank">创建用户</a></button>
<table border="2">
    <thead>
         <tr>
             <th>ID</th>
             <th>姓名</th>
             <th>城市</th>
             <th>性别</th>
             <th>年龄</th>
             <th>操作</th>
         </tr>
    </thead>
    <tbody>
    {% for i in userlist %}
         <tr>
             <td>{{ i.id }}</td>
             <td>{{ i.name }}</td>
             <td>{{ i.city }}</td>
             <td>{{ i.sex }}</td>
             <td>{{ i.age }}</td>
             <td>
                 <button>编辑</button>
                 <button onclick="delUser(this)">删除</button>
             </td>
         </tr>
    {% endfor %}
    </tbody>
</table>
<script>
    function delUser(obj) {
        confirm = confirm('确定删除吗?');
        if (confirm) {
            id = $(obj).parent().parent().find("td:eq(0)").text();
            data = {'id': id};
            $.ajax({
                type: 'DELETE',
                url: '/app/user',
                data: data,
                success: function (result) {
                    if (result.code === 200) {
                        alert("删除成功");
                        window.location.reload();
                    } else {
                        alert("删除失败");
                    }
                },
                error: function () {
                    alert("删除失败");
                }
            })
        }
    }
</script>
</body>
</html>
from django.shortcuts import render
from django.http import HttpResponse,JsonResponse
from app.models import User
from django.http import QueryDict


# Create your views here.

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        try:
            name = request.POST.get('name')
            city = request.POST.get('city')
            sex = request.POST.get('sex')
            age = request.POST.get('age')
            User.objects.create(
                name=name,
                city=city,
                sex=sex,
                age=age
            )
            result = {'code': 200, 'msg': '创建成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '创建失败!'}
            return JsonResponse(result)
    elif request.method == 'PUT':
        return HttpResponse('这是一个更新用户的请求!')
    elif request.method == 'DELETE':
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            User.objects.get(id=id).delete()
            result = {'code': 200, 'msg': '删除成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '删除失败!'}
            return JsonResponse(result)
    else:
        return HttpResponse('这是一个未知的请求!')


def user_list(request):
    userlist = User.objects.all()
    return render(request, 'userlist.html', {'userlist': userlist})


def user_add(request):
    return render(request, 'useradd.html')
下面我们去实现更新用户的操作。我们去写一个新的页面去完成这个事情。template/useredit.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>编辑用户</title>
</head>
<body>
<h1>编辑用户</h1>
<form action="/app/user/" method="POST">
    姓名:<input type="text" name="name"><br>
    城市:<input type="text" name="city"><br>
    性别:<input type="text" name="sex"><br>
    年龄:<input type="text" name="age"><br>
    <input type="submit" value="提交">
    </form>
</body>
</html>
随后我们去添加一下视图和url
def user_edit(request):
    return render(request, 'useredit.html')
from django.urls import path
from app import views

urlpatterns = [
    path('user/', views.user),
    path('userlist/', views.user_list),
    path('useradd/', views.user_add),
    path('useredit', views.user_edit)
]
这里我们还需要针对前端去做一个操作,点击编辑按钮跳转到我们的编辑页面。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理系统</title>
    <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
</head>
<body>
<h1>用户信息管理</h1>
<button><a href="/app/useradd/" target="_blank">创建用户</a></button>
<table border="2">
    <thead>
         <tr>
             <th>ID</th>
             <th>姓名</th>
             <th>城市</th>
             <th>性别</th>
             <th>年龄</th>
             <th>操作</th>
         </tr>
    </thead>
    <tbody>
    {% for i in userlist %}
         <tr>
             <td>{{ i.id }}</td>
             <td>{{ i.name }}</td>
             <td>{{ i.city }}</td>
             <td>{{ i.sex }}</td>
             <td>{{ i.age }}</td>
             <td>
                 <!--使它可以点击跳转到我们的编辑视图并传递id过去-->
                 <button><a href="/app/useredit?id={{ i.id }}" target="_blank">编辑</a></button>
                 <button onclick="delUser(this)">删除</button>
             </td>
         </tr>
    {% endfor %}
    </tbody>
</table>
<script>
    function delUser(obj) {
        confirm = confirm('确定删除吗?');
        if (confirm) {
            id = $(obj).parent().parent().find("td:eq(0)").text();
            data = {'id': id};
            $.ajax({
                type: 'DELETE',
                url: '/app/user',
                data: data,
                success: function (result) {
                    if (result.code === 200) {
                        alert("删除成功");
                        window.location.reload();
                    } else {
                        alert("删除失败");
                    }
                },
                error: function () {
                    alert("删除失败");
                }
            })
        }
    }
</script>
</body>
</html>
但是话又说回来了,我们编辑的用户信息数据一定是存在的,总不能是个空表格编辑吧,所以这个时候我们还要想办法去获取一下这个数据,然后我们去视图里看看如何实现,我们可以看到上面我们通过href跳转的时候传递了id到目标路由,然后我们是不就可以在目标的视图函数内拿到这个参数了,那么我们后面实战一下
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from app.models import User
from django.http import QueryDict


# Create your views here.

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        try:
            name = request.POST.get('name')
            city = request.POST.get('city')
            sex = request.POST.get('sex')
            age = request.POST.get('age')
            User.objects.create(
                name=name,
                city=city,
                sex=sex,
                age=age
            )
            result = {'code': 200, 'msg': '创建成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '创建失败!'}
            return JsonResponse(result)
    elif request.method == 'PUT':
        return HttpResponse('这是一个更新用户的请求!')
    elif request.method == 'DELETE':
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            User.objects.get(id=id).delete()
            result = {'code': 200, 'msg': '删除成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '删除失败!'}
            return JsonResponse(result)
    else:
        return HttpResponse('这是一个未知的请求!')


def user_list(request):
    userlist = User.objects.all()
    return render(request, 'userlist.html', {'userlist': userlist})


def user_add(request):
    return render(request, 'useradd.html')


def user_edit(request):
    # 拿到ID
    id = request.GET.get('id')
    # 声明成对象
    user_obj = User.objects.get(id=id)
    # 渲染给前端
    return render(request, 'useredit.html', {'user_obj': user_obj})

上面的做完之后我们需要去前端改一下。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>编辑用户</title>
</head>
<body>
<h1>编辑用户</h1>
<form action="/app/user/" method="POST">
    姓名:<input type="text" name="name" value="{{ user_obj.name }}"><br>
    城市:<input type="text" name="city" value="{{ user_obj.city }}"><br>
    性别:<input type="text" name="sex" value="{{ user_obj.sex }}"><br>
    年龄:<input type="text" name="age" value="{{ user_obj.age }}"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>
我们通过Jinjia的模板参数拿到了后端传过来的变量并且从变量内取值拿到了我们的参数,那么我们来看看是什么样子的。

image

可以看到这里通过ID查询到了用户数据,并且设置给了input的默认值。根据前面我们路由方法的定义我们在视图内使用的是put来更新用户数据,那么我们还需要把上面的POST改成PUT,然后去视图内编写PUT相应的操作,但是form原生不支持PUT,所以我们需要借助ajax来实现。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>编辑用户</title>
    <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
</head>
<body>
<h1>编辑用户</h1>
<form action="#">
    姓名:<input type="text" name="name" value="{{ user_obj.name }}"><br>
    城市:<input type="text" name="city" value="{{ user_obj.city }}"><br>
    性别:<input type="text" name="sex" value="{{ user_obj.sex }}"><br>
    年龄:<input type="text" name="age" value="{{ user_obj.age }}"><br>
    <input type="button" id="btn" value="提交">
</form>
<script>
    // 点击提交获取输入框的值通过PUT请求发送给服务器
    $('#btn').click(function (){
        // 获取输入的值
        var id = $('input[name="id"]').val();
        var name = $('input[name="name"]').val();
        var city = $('input[name="city"]').val();
        var sex = $('input[name="sex"]').val();
        var age = $('input[name="age"]').val();
        // 组合数据成为一个字典
        data = {'id': id, 'name': name, 'city': city, 'sex': sex, 'age': age}
        $.ajax({
            type: 'PUT',
            url: 'app/user/',
            data: data,
            success: function (result) {
                if (result.code === 200) {
                    alert("更新成功");
                    window.location.reload();
                } else {
                    alert("更新失败");
                }
            },
            error: function () {
                alert("更新失败");
            }
        })
    })

</script>
</body>
</html>
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from app.models import User
from django.http import QueryDict


# Create your views here.

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        try:
            name = request.POST.get('name')
            city = request.POST.get('city')
            sex = request.POST.get('sex')
            age = request.POST.get('age')
            User.objects.create(
                name=name,
                city=city,
                sex=sex,
                age=age
            )
            result = {'code': 200, 'msg': '创建成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '创建失败!'}
            return JsonResponse(result)
    elif request.method == 'PUT':
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            # 用户更新
            user_obj = User.objects.get(id=id)
            user_obj.name = data.get('name')
            user_obj.city = data.get('city')
            user_obj.sex = data.get('sex')
            user_obj.age = data.get('age')
            user_obj.save()
            result = {'code': 200, 'msg': '更新成功!'}
            return HttpResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '更新失败!'}
            return JsonResponse(result)
    elif request.method == 'DELETE':
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            User.objects.get(id=id).delete()
            result = {'code': 200, 'msg': '删除成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '删除失败!'}
            return JsonResponse(result)
    else:
        return HttpResponse('这是一个未知的请求!')


def user_list(request):
    userlist = User.objects.all()
    return render(request, 'userlist.html', {'userlist': userlist})


def user_add(request):
    return render(request, 'useradd.html')


def user_edit(request):
    # 拿到ID
    id = request.GET.get('id')
    user_obj = User.objects.get(id=id)
    return render(request, 'useredit.html', {'user_obj': user_obj})

这些完成之后我们去测试一下。

image

更新前的张三的年龄是21,我们改成30

image

我们提交一下看看是什么结果。

image

更新成功,然后我们去看看userlist的页面是否真的更新成功了。

image

可以清楚的看到,更新完成了。然后我们大概看一看其实这就是一个简单的Django的CRUD的操作,通过前端或者API去操作我们的数据库的增删改查,不过有些需要完善的地方大家可以去改改,比如全部通过Ajax的方式进行增删改查,这个私下大家去坐一下吧,不过这里还有一点知识点,下面教大家写一个类视图,因为我们目前写的是一个函数视图,其实函数视图本身就是一个很容易上手的,不过为了面对更复杂的业务逻辑,我们有必要学习一下类视图。

1.11:类视图

我们把下面的方法基于类重写一下看看区别
# 原版

def user(request):
    if request.method == 'GET':
        return HttpResponse('这是一个获取用户的请求!')
    elif request.method == 'POST':
        try:
            name = request.POST.get('name')
            city = request.POST.get('city')
            sex = request.POST.get('sex')
            age = request.POST.get('age')
            User.objects.create(
                name=name,
                city=city,
                sex=sex,
                age=age
            )
            result = {'code': 200, 'msg': '创建成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '创建失败!'}
            return JsonResponse(result)
    elif request.method == 'PUT':
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            # 用户更新
            user_obj = User.objects.get(id=id)
            user_obj.name = data.get('name')
            user_obj.city = data.get('city')
            user_obj.sex = data.get('sex')
            user_obj.age = data.get('age')
            user_obj.save()
            result = {'code': 200, 'msg': '更新成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '更新失败!'}
            return JsonResponse(result)
    elif request.method == 'DELETE':
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            User.objects.get(id=id).delete()
            result = {'code': 200, 'msg': '删除成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '删除失败!'}
            return JsonResponse(result)
    else:
        return HttpResponse('这是一个未知的请求!')
# 类视图

class UserView(View):
    def get(self, request):
        return HttpResponse('这是一个获取用户的请求!')

    def post(self, request):
        try:
            name = request.POST.get('name')
            city = request.POST.get('city')
            sex = request.POST.get('sex')
            age = request.POST.get('age')
            User.objects.create(
                name=name,
                city=city,
                sex=sex,
                age=age
            )
            result = {'code': 200, 'msg': '创建成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '创建失败!'}
            return JsonResponse(result)

    def put(self, request):
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            # 用户更新
            user_obj = User.objects.get(id=id)
            user_obj.name = data.get('name')
            user_obj.city = data.get('city')
            user_obj.sex = data.get('sex')
            user_obj.age = data.get('age')
            user_obj.save()
            result = {'code': 200, 'msg': '更新成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '更新失败!'}
            return JsonResponse(result)

    def delete(self, request):
        try:
            data = QueryDict(request.body)
            id = data.get('id')
            User.objects.get(id=id).delete()
            result = {'code': 200, 'msg': '删除成功!'}
            return JsonResponse(result)
        except Exception as e:
            result = {'code': 500, 'msg': '删除失败!'}
            return JsonResponse(result)
# 当然了,跟随改造,url也是需要改造的

from django.urls import path
from app import views

urlpatterns = [
    # path('user/', views.user),
    path('user/', views.UserView.as_view()),  # 类视图
    path('userlist/', views.user_list),
    path('useradd/', views.user_add),
    path('useredit', views.user_edit)
]
这样改造其实测试功能也都是正常的。

2:Django REST Framework初识

Django REST Framework简称(DRF)是一个强大且灵活的Web API工具,遵循RestFul API风格,功能完善,可快速开发API平台

官档地址:https://www.django-rest-framework.org

DRF使用要求:
Python:3.x
Django:2.2 ~ 4.0

安装DRF
pip3 install djangorestframework

在INSTALLED_APPS中引入

INSTALLED_APPS = [
......
'rest_framework',
......
]

2.1:使用DRF实现用户增删改查

目录
创建APP
定义数据模型并同步数据库
编写序列化器文件
编写视图
添加API路由

2.2:创建APP

# 因为需要区别新的app,所以我们就去创建一个新的app_api

PS E:\code\user_data_manager> python .\manage.py startapp app_api

2.3:定义数据模型并同步数据库

我们还是操作models.py,当然我们可以直接复制app下的那个models.py的内容,当然前提是我们需要在主项目目录内导入我们的这个子项目哦
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'app',
    'app_api'
]
from django.db import models

# Create your models here.


class User(models.Model):
    name = models.CharField(max_length=20)
    city = models.CharField(max_length=20)
    sex = models.CharField(max_length=10)
    age = models.IntegerField()
然后就是去同步我们的数据

PS E:\code\user_data_manager> python .\manage.py makemigrations  
PS E:\code\user_data_manager> python .\manage.py migrate

image

2.4:编写序列化器文件

在app_api目录下创建一个叫做serializers.py的文件,内容如下(它主要去做API数据的增删改查)
from app_api.models import User
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

2.5:编写视图

同样的在views.py内定义视图。
from rest_framework import viewsets
from .serializers import UserSerializer
from app_api.models import User


class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

2.6:添加API路由

同样的,我们使用包含的方式去分发一下路由,首先是主项目目录下的urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app/', include('app.urls')),
    path('app_api/', include('app_api.urls'))
]
然后复制内容到app_api/urls.py内,这个文件需要创建的
from django.urls import path, include
from app_api import views
from rest_framework import routers

# 启动自动路由
router = routers.DefaultRouter()
# 注册路由
router.register(r'user', views.UserViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]
其实经过这一系列的操作,我们的接口就写好了,然后我们启动看看效果。

http://127.0.0.1:8000/app_api/api/

image

这个页面是DRF自带的调试页面,下面的地址就是我们的路由地址,我们可以通过访问这个路由地址然后去填写参数进行各种请求测试。

image

当然它也有两种传递数据的方式,一种是通过POST,另一种是通过JSON数据传递。

2.7:测试API

image

下面我们测试一下,这里我们获取数据使用的是ApiPost

image

这里其实我们已经看到数据了,然后去ApiPost查看一下

image

其实这里我们就在针对 http://127.0.0.1:8000/app_api/api/user/ 这个API做增,查的操作,然后我们下面看看更新。、

更新数据是通过ID去更新,比如我们去更新第一条数据的age为30,那么我们需要先访问api

http://127.0.0.1:8000/app_api/api/user/1/

image

image

然后它就会把数据展示给我们供我们去改,然后还给我们提供了PUT请求,那么我们去改一下它的age为30

image

更新完成

image

image

当然删除数据就可以直接点击那个红色的DELETE

image

image

这就是针对API的增删改查了。

3:Django DRF 序列化器

目录
序列化与反序列化介绍
以前常用的三种序列化方式
DRF的序列化器三种类型
DRF序列化器关联表显示
改变序列化和反序列化行为

3.1:序列化与反序列化介绍

日常开发中,会从别的API获取数据或者自己写API提供数据,数据格式一般都是采用JSON格式,这期间就会涉及两个术语:

序列化:将Python对象转换为JSON
反序列化:将JSON转换为Python对象

3.2:常用的列化方式:JSON

之前常用的JSON模块序列化与反序列化操作:
1:序列化应用场景:使用ORM查询数据,采用JSON格式API返回数据
2:反序列化场景:从别的API获取数据,在Python里面处理
import json

# 序列化
data = {"name": "zhangsan", "age": 18, "sex": "男"}
print(type(json.dumps(data)))        # <class 'str'>

# 反序列化
json_str = '{"name": "zhangsan", "age": 18, "sex": "男"}'
print(type(json.loads(json_str)))    # <class 'dict'>


# 运行结果
PS E:\code\user_data_manager> python .\main.py
<class 'str'>
<class 'dict'>

3.2:常用的序列化方式:Django内置Serializers模块

Serializers是Django内置的一个序列化器,可直接将Python QuertSet对象转换为JSON格式,但不支持反序列化,也可以说它支支持查询数据库的操作中才会产生QuerySet对象。
from django.core import serializers
obj = User.jobects.all()
data = serializers.serialize('json', obj)

3.3:常用的序列化方式:Django内置的JsonResponse模块

JsonResponse模块会自动将Python对象转换为Json对象并响应
...
def delete(self, request):
    try:
        data = QueryDict(request.body)
        id = data.get('id')
        User.objects.get(id=id).delete()
        result = {'code': 200, 'msg': '删除成功!'}
        return JsonResponse(result)
    except Exception as e:
        result = {'code': 500, 'msg': '删除失败!'}
        return JsonResponse(result)

3.4:DRF序列化器

DRF中有一个serializers模块专门负责数据序列化,DRF提供的方案更先进,更高级别的序列化方案

序列化器支持三种类型:

1:Serializer:对Model(数据模型)进行序列化,需要自定义字段映射
2:ModelSerializer:对Model进行序列化,会自动生成字段和验证规则,默认还包含简单的create()和update()方法
3:HyperlinkdModelSerializer:与ModelSerializer类似,只不过使用超链接来表示关系而不是主键ID

3.5:序列化器:Serializer

第一步我们需要先定义序列化器app_api/serialzer.py,数据模型还是那个数据模型

根据我们上面的描述,这里的字段需要与数据库的字段一一对应。
from rest_framework import serializers


# 定义序列化器
class UserSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    city = serializers.CharField(max_length=20)
    sex = serializers.CharField(max_length=10)
    age = serializers.IntegerField()
后面就是去定义视图了app_api/views.py,当然这个目前只是测试。
from .serializers import UserSerializer
from app_api.models import User
from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):
    def get(self, request):
        return Response("This is GET Method!")

    def post(self, request):
        return Response("This is POST Method!")

    def put(self, request):
        return Response("This is PUT Method!")

    def delete(self, request):
        return Response("This is DELETE Method!")

然后我们还需要去定义路由app_api/urls.py
from django.urls import path
from app_api import views

urlpatterns = [
    path('api/user/', views.UserView.as_view()),
]
这样做好之后我们尝试启动访问一下。

http://127.0.0.1:8000/app_api/api/user/

image

测试没问题之后我们就开始使用这个序列化器,那么其实这个序列化器是在视图内使用的,具体使用方法如下:
from .serializers import UserSerializer
from app_api.models import User
from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):
    def get(self, request):
        # 首先获取数据库中的所有数据
        queryset = User.objects.all()
        # 使用序列化器对数据进行序列化
        serializer = UserSerializer(queryset, many=True)  # 多条数据的话需要使用many=True
        # 返回序列化后的数据
        return Response(serializer.data)

    def post(self, request):
        return Response("This is POST Method!")

    def put(self, request):
        return Response("This is PUT Method!")

    def delete(self, request):
        return Response("This is DELETE Method!")

上面的序列化器就定义好了,然后我们就可以再次去访问API看看是否可以取到我们的数据了。

image

这里发现数据是可以取到了,证明我们的序列化器是生效的,可用的,当然这里面有一个问题,就是目前我们是以获取全部数据来做的,那么会有人问,我们如何获取单条数据呢?那么我们下面来看看怎么写,首先我们去更改url文件
from django.urls import path, re_path
from app_api import views

urlpatterns = [
    path('api/user/', views.UserView.as_view()),
    # 正则匹配id,这里因为我们要用到正则,所以就要用re_path匹配了
    re_path(r'^api/user/(?P<pk>\d+)$', views.UserView.as_view()),
]

然后我们去更改视图,我们通过判断api后是否有传值来取数据,如果有就根据ID取取数据,如果没有就取全部数据。
from .serializers import UserSerializer
from app_api.models import User
from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):
    def get(self, request,pk=None):
        if pk:
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True)
            # 返回序列化后的数据
        return Response(serializer.data)

    def post(self, request):
        return Response("This is POST Method!")

    def put(self, request):
        return Response("This is PUT Method!")

    def delete(self, request):
        return Response("This is DELETE Method!")

image

我们这里实现的是查询的操作,那么接下来我们去完成增删改的操作看看。我们先规范下增删改查的API接口如何写
增:POST: /app_api/api/user/
删:DELETE:/app_api/api/user/<id>
改:PUT:/app_api/api/user/<id>
查(单):GET:/app_api/api/user/<id>
查(全):GET:/app_api/api/user/
我们前面实现了查全和查单,后面就是增删改的操作了,第一个是创建
第一步我们需要去增加一下创建的视图
from .serializers import UserSerializer
from app_api.models import User
from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):
    def get(self, request,pk=None):
        if pk:
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True)
            # 返回序列化后的数据
        return Response(serializer.data)

    def post(self, request):
        # 接收用户提交的数据
        data = request.data
        # 调用序列化器对数据进行反序列化
        serializer = UserSerializer(data=data)
        # 验证数据
        if serializer.is_valid():
            # 保存数据
            # 它调用了下面的create方法
            serializer.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request):
        return Response("This is PUT Method!")

    def delete(self, request):
        return Response("This is DELETE Method!")

这样写完之后我们还需要去一下创建的序列化器。
from app_api.models import User
from rest_framework import serializers


# 定义序列化器
class UserSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=20)
    city = serializers.CharField(max_length=20)
    sex = serializers.CharField(max_length=10)
    age = serializers.IntegerField()

    # 创建重写create方法实现入库操作。
    # validated_data就是提交过来的数据
    def create(self, validated_data):
        return User.objects.create(**validated_data)
完成后我们再去提交然后就会发现创建完成,然后我们可以根据ID去查询一下。

image

image

这样创建用户的操作已经完成了,也就是增的操作就完成了,下面是更新用户的操作。
from .serializers import UserSerializer
from app_api.models import User
from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):
    def get(self, request,pk=None):
        if pk:
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True)
            # 返回序列化后的数据
        return Response(serializer.data)

    def post(self, request):
        # 接收用户提交的数据
        data = request.data
        # 调用序列化器对数据进行反序列化
        serializer = UserSerializer(data=data)
        # 验证数据
        if serializer.is_valid():
            # 保存数据
            serializer.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request, pk):
        # 接收传入ID
        user_obj = User.objects.get(id=pk)
        # 将查询数据与传入数据进行合并
        serializers = UserSerializer(instance=user_obj, data=request.data)
        # 验证数据
        if serializers.is_valid():
            # 保存数据
            serializers.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request):
        return Response("This is DELETE Method!")

同样我们还需要去重写update的方法
from app_api.models import User
from rest_framework import serializers


# 定义序列化器
class UserSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=20)
    city = serializers.CharField(max_length=20)
    sex = serializers.CharField(max_length=10)
    age = serializers.IntegerField()

    # 创建重写create方法
    def create(self, validated_data):
        return User.objects.create(**validated_data)

    # 创建重写update方法
    def update(self, instance, validated_data):
        return User.objects.filter(id=instance.id).update(**validated_data)
写完之后我们去重试

image

image

增改查都实现了,那么最后我们来实现删除数据的操作,
from .serializers import UserSerializer
from app_api.models import User
from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):
    def get(self, request, pk=None):
        if pk:
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True)
            # 返回序列化后的数据
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        # 接收用户提交的数据
        data = request.data
        # 调用序列化器对数据进行反序列化
        serializer = UserSerializer(data=data)
        # 验证数据
        if serializer.is_valid():
            # 保存数据
            serializer.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request, pk):
        # 接收传入ID
        user_obj = User.objects.get(id=pk)
        # 将查询数据与传入数据进行合并
        serializers = UserSerializer(instance=user_obj, data=request.data)
        # 验证数据
        if serializers.is_valid():
            # 保存数据
            serializers.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request, pk):
        user_obj = User.objects.get(id=pk)
        try:
            user_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})

image

总结:序列化工作流程

序列化(读数据):视图里通过ORM从数据库获取数据查询集对象 --> 数据传入序列化器 --> 序列化器将数据进行序列化 --> 调用序列化器的data获取数据 --> 响应数据返回

反序列化(写数据):视图获取前端提交的数据 --> 数据传入序列化器 --> 调用序列化器的is_valid方法校验数据是否合规 --> 调用序列化器的save方法保存数据。


序列化器常用方法与属性:
1:serializer.is_valid():调用序列化器验证是否通过,传入raise_exeception=True可以在验证失败时由DRF响应400异常
2:serializer.errors:获取反序列化器验证的错误信息
3:serializer.data:获取序列化器返回的数据
4:serializer.save():将验证通过的数据保存到数据库(ORM操作)

3.6:序列化器参数

名称 作用
max_length 最大长度,适用于字符串,列表,文件
min_length 最小长度,适用于字符串,列表,文件
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
max_value 最大值,适用于数值
min_value 最小值,适用于数值

3.6.1:同用参数

名称 作用
read_only 说明该字段仅用于序列化,默认为False,若为True,反序列化可以不传
write_only 该字段仅用于反序列化,默认为False
required 该字段在反序列化时必须输入,默认为True
default 反序列化时使用的默认值
allow_null 是否允许为Null,默认为False
validators 指定自定义验证器
error_message 包含错误编号与错误信息的字典
具体的添加是在序列化器内操作,我们看看如何操作添加的。
from app_api.models import User
from rest_framework import serializers


# 定义序列化器
class UserSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=20, error_messages={
        "blank": "请输入用户名",
        "required": "用户名不能为空",
        "max_length": "用户名长度不能超过20个字符"})
    city = serializers.CharField(max_length=20, error_messages={
        "blank": "请输入城市",
        "required": "城市不能为空",
        "max_length": "城市长度不能超过20个字符"})
    sex = serializers.CharField(max_length=10, error_messages={
        "blank": "请输入性别",
        "required": "性别不能为空",
        "max_length": "性别长度不能超过10个字符"})
    age = serializers.IntegerField(min_value=16, max_value=100, error_messages={
        "blank": "请输入年龄",
        "required": "年龄不能为空",
        "min_value": "年龄不能小于16岁",
        "max_value": "年龄不能大于100岁"})

    # 创建重写create方法
    def create(self, validated_data):
        return User.objects.create(**validated_data)

    # 创建重写update方法
    def update(self, instance, validated_data):
        return User.objects.filter(id=instance.id).update(**validated_data)

imageimage

大概我们也能看出来,它可以自动帮我们处理我们想处理的错误,比如上图这些关于反序列化的操作,其实我们可以理解为这是基于反序列化的一种规则,由于传入的数据触发了规则,所以会被返回我们定义的响应的提示。

3.6.2:扩展验证规则

如果上面的常用参数无法实现我们的验证需求时,可以通过钩子方法来扩展验证规则。

局部钩子:validate_字段名(self, 字段值)
全局钩子:validate(self, 所校验的数据字典)
# 局部钩子
# 性别只能为男或女
def validate_sex(self, attrs):
    if attrs != "男" and attrs != "女":
        raise serializers.ValidationError("性别只能为男或女")
    else:
        return attrs
# 全局钩子
# 不允许账户名为admin
def validate(self, attrs):
    # 这里的attrs其实是个字典,我这里只是处理了name
    if attrs["name"] == "admin":
        raise serializers.ValidationError("用户名不能为admin")
    else:
        return attrs

image

image

3.6.3:自定义验证器

from app_api.models import User
from rest_framework import serializers


# 自定义验证器
def check_name(data):
    if data.startswith('a'):
        raise serializers.ValidationError('用户名不能以a开头')
    return data

# 定义序列化器
class UserSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=20, validators=[check_name], error_messages={
        "blank": "请输入用户名",
        "required": "用户名不能为空",
        "max_length": "用户名长度不能超过20个字符"})
    city = serializers.CharField(max_length=20, error_messages={
        "blank": "请输入城市",
        "required": "城市不能为空",
        "max_length": "城市长度不能超过20个字符"})
    sex = serializers.CharField(max_length=10, error_messages={
        "blank": "请输入性别",
        "required": "性别不能为空",
        "max_length": "性别长度不能超过10个字符"})
    age = serializers.IntegerField(min_value=16, max_value=100, error_messages={
        "blank": "请输入年龄",
        "required": "年龄不能为空",
        "min_value": "年龄不能小于16岁",
        "max_value": "年龄不能大于100岁"})

    # 创建重写create方法
    def create(self, validated_data):
        return User.objects.create(**validated_data)

    # 创建重写update方法
    def update(self, instance, validated_data):
        return User.objects.filter(id=instance.id).update(**validated_data)

    # 局部钩子
    def validate_sex(self, attrs):
        if attrs != "男" and attrs != "女":
            raise serializers.ValidationError("性别只能为男或女")
        else:
            return attrs

    # 全局钩子
    def validate(self, attrs):
        if attrs["name"] == "admin":
            raise serializers.ValidationError("用户名不能为admin")
        else:
            return attrs

不过这个场景其实用的也不多,但是我们还是来测试一下吧

image

3.7:序列化器:ModelSerializer

ModelSerializer类型不需要自定义字段映射和定义create,update方法,使用起来会方便很多。
from app_api.models import User
from rest_framework import serializers


# 定义序列化器
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User # 指定序列化器对应的模型类
        fields = '__all__' # 指定序列化器中包含的字段

3.7.1:Meta常用属性

名称 作用
fields 显示所有或指定字段
exclude 排除某个字段,元组格式,不能用fiedls同时用
read_only_fields 只读字段,即只用于序列化,不支持修改
extra_kwargs 添加或修改原有的字段参数,字典格式
depath 根据关联的数据递归显示,一般是多表
from app_api.models import User
from rest_framework import serializers


# 定义序列化器
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User  # 指定序列化器对应的模型类
        fields = '__all__'  # 指定序列化器中包含的字段
        read_only_fields = ('id',)  # 指定序列化器中只读的字段
        extra_kwargs = {
            'name': {'max_length': 10, 'required': True},
            'city': {'max_length': 10, 'required': True},
            'sex': {'max_length': 10, 'required': True},
            'age': {'min_value': 18, 'max_value': 100, 'required': True},
        }

image

根据我们的定义,这里最小时18,但是我们用了15,所以它就帮我们自动报错了。

image

这是我们整体的一个触发效果。

3.7:HperModelSerializer

与ModelSerializer使用方法一致,只不过它使用超链接来表示关系而不是主键ID
from app_api.models import User
from rest_framework import serializers


# 定义序列化器
class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User  # 指定序列化器对应的模型类
        fields = '__all__'  # 指定序列化器中包含的字段

3.7.1:更改视图

from .serializers import UserSerializer
from app_api.models import User
from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):
    def get(self, request, pk=None):
        if pk:
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True, context={'request': request})
            # 返回序列化后的数据
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        # 接收用户提交的数据
        data = request.data
        # 调用序列化器对数据进行反序列化
        serializer = UserSerializer(data=data)
        # 验证数据
        if serializer.is_valid(raise_exception=True):
            # 保存数据
            serializer.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request, pk):
        # 接收传入ID
        user_obj = User.objects.get(id=pk)
        # 将查询数据与传入数据进行合并
        serializers = UserSerializer(instance=user_obj, data=request.data)
        # 验证数据
        if serializers.is_valid(raise_exception=True):
            # 保存数据
            serializers.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request, pk):
        user_obj = User.objects.get(id=pk)
        try:
            user_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})

3.7.2:更改URL

from django.urls import path, re_path
from app_api import views

urlpatterns = [
    re_path('api/user/$', views.UserView.as_view(), name="user-detail"),
    # 正则匹配id
    re_path(r'^api/user/(?P<pk>\d+)$', views.UserView.as_view(), name="user-detail"),
]

image

我们可以看到它把URL也返回给我们了,这个URL就是主键ID,不过这种做法我们是不太习惯的,我们还是习惯于直接显示ID,所以我们后面还是使用ModelSerializer

3.8:DRF序列化:多表显示

例如我们后面涉及到的workflow项目就涉及到的多表。

一对多:一个项目对应多个应用,一个应用只能属于一个项目。
多对多:一个应用部署到多台服务器,一个服务器能部署多个应用。

image

3.8.1:关联表数据显示

第一步还是去定义数据模型
from django.db import models


from django.db import models


# Create your models here.
# 这里可以忽视

"""
class User(models.Model):
    name = models.CharField(max_length=20)
    city = models.CharField(max_length=20)
    sex = models.CharField(max_length=10)
    age = models.IntegerField()
"""

class Project(models.Model):
    name = models.CharField(max_length=100)
    describe = models.CharField(max_length=200, null=True)


class App(models.Model):
    name = models.CharField(max_length=30)
    project = models.ForeignKey(Project, on_delete=models.CASCADE)  # 表示一对多的关系
    describe = models.CharField(max_length=200, null=True)


class Server(models.Model):
    hostname = models.CharField(max_length=100)
    ip = models.CharField(max_length=100)
    describe = models.CharField(max_length=200, null=True)
    app = models.ManyToManyField(App)  # 表示多对多的关系
定义好了之后我们去同步数据库

PS E:\code\user_data_manager> python .\manage.py makemigrations
Migrations for 'app_api':
  app_api\migrations\0002_app_project_server_app_project.py
    - Create model App
    - Create model Project
    - Create model Server
    - Add field project to app

PS E:\code\user_data_manager> python .\manage.py migrate       
Operations to perform:
  Apply all migrations: admin, app, app_api, auth, contenttypes, sessions
Running migrations:
  Applying app_api.0002_app_project_server_app_project... OK

image

完成之后我们需要去编写相应的序列化器了。
from app_api.models import User, Project, App, Server
from rest_framework import serializers


# 定义序列化器
""" (这里可以暂时不用了)
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User  # 指定序列化器对应的模型类
        fields = '__all__'  # 指定序列化器中包含的字段
        read_only_fields = ('id',)  # 指定序列化器中只读的字段
        extra_kwargs = {
            'name': {'max_length': 10, 'required': True},
            'city': {'max_length': 10, 'required': True},
            'sex': {'max_length': 10, 'required': True},
            'age': {'min_value': 18, 'max_value': 100, 'required': True},
        }
"""

class ProjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Project
        fields = '__all__'


class AppSerializer(serializers.ModelSerializer):
    class Meta:
        model = App
        fields = '__all__'


class ServerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Server
        fields = '__all__'
我们还需要去定义一下视图
from .serializers import UserSerializer, ProjectSerializer, AppSerializer, ServerSerializer
from app_api.models import User, Project, App, Server
from rest_framework.views import APIView
from rest_framework.response import Response

""" (暂时不用)
class UserView(APIView):
    def get(self, request, pk=None):
        if pk:
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True)
            # 返回序列化后的数据
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        # 接收用户提交的数据
        data = request.data
        # 调用序列化器对数据进行反序列化
        serializer = UserSerializer(data=data)
        # 验证数据
        if serializer.is_valid(raise_exception=True):
            # 保存数据
            serializer.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request, pk):
        # 接收传入ID
        user_obj = User.objects.get(id=pk)
        # 将查询数据与传入数据进行合并
        serializers = UserSerializer(instance=user_obj, data=request.data)
        # 验证数据
        if serializers.is_valid(raise_exception=True):
            # 保存数据
            serializers.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request, pk):
        user_obj = User.objects.get(id=pk)
        try:
            user_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})
"""

class ProjectView(APIView):
    def get(self, request):
        queryset = Project.objects.all()
        serializer = ProjectSerializer(queryset, many=True)
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        serializer = ProjectSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request):
        pass

    def delete(self, request):
        pass


class AppView(APIView):
    def get(self, request):
        queryset = App.objects.all()
        serializer = AppSerializer(queryset, many=True)
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        serializer = AppSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request):
        pass

    def delete(self, request):
        pass


class ServerView(APIView):
    def get(self, request):
        queryset = Server.objects.all()
        serializer = ServerSerializer(queryset, many=True)
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        serializer = ServerSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request):
        pass

    def delete(self, request):
        pass
最后我们还需要去配置一下路由,这里我没写关于put和delete的方法。
from django.urls import path, re_path
from app_api import views

urlpatterns = [
    re_path('api/user/$', views.UserView.as_view()),
    re_path(r'^api/user/(?P<pk>\d+)$', views.UserView.as_view()),
    # 上面两条路由可以暂时不用。
    re_path('^api/project/$', views.ProjectView.as_view()),
    re_path('^api/app/$', views.AppView.as_view()),
    re_path('^api/server/$', views.ServerView.as_view()),
]
这里没有针对单个进行匹配,这里目前只针对测试用,然后我们测试一下

API如下

/app_api/api/project/
/app_api/api/app/
/app_api/api/server

image

image

image

POST

/app_api/api/project/
/app_api/api/app/
/app_api/api/server

image

image

注:这里创建数据的时候需要指定项目ID,也就是说它和Project的库是一个一对多的关系。

image

image

这里其实有个坑就是前面配置表关系的时候在Server里面APP其实应该可以为空的,否则每次创建一个服务器都要关联一个应用就有点不太符合逻辑了。

image

这里就表示了Server与App之间的关系映射,当然,这体现不出一对多对多,那么我们再添加一个。

image

这样或许就能看的很明白了,1,2,3项目都部署在了名为cloud-1的机器上。
当然了这里其实也只是捋顺了我们的思路,给一对多,多对多进行了实现,后面我们还想针对数据进行进一步的优化。
# 完善后的视图

from .serializers import UserSerializer, ProjectSerializer, AppSerializer, ServerSerializer
from app_api.models import User, Project, App, Server
from rest_framework.views import APIView
from rest_framework.response import Response

"""
class UserView(APIView):
    def get(self, request, pk=None):
        if pk:
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True)
            # 返回序列化后的数据
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        # 接收用户提交的数据
        data = request.data
        # 调用序列化器对数据进行反序列化
        serializer = UserSerializer(data=data)
        # 验证数据
        if serializer.is_valid(raise_exception=True):
            # 保存数据
            serializer.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request, pk):
        # 接收传入ID
        user_obj = User.objects.get(id=pk)
        # 将查询数据与传入数据进行合并
        serializers = UserSerializer(instance=user_obj, data=request.data)
        # 验证数据
        if serializers.is_valid(raise_exception=True):
            # 保存数据
            serializers.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request, pk):
        user_obj = User.objects.get(id=pk)
        try:
            user_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})
"""

class ProjectView(APIView):
    def get(self, request):
        queryset = Project.objects.all()
        serializer = ProjectSerializer(queryset, many=True)
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        serializer = ProjectSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request):
        project_obj = Project.objects.get(id=request.data['id'])
        serializers = ProjectSerializer(instance=project_obj, data=request.data)
        if serializers.is_valid(raise_exception=True):
            serializers.save()
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request):
        project_obj = Project.objects.get(id=request.data['id'])
        try:
            project_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})


class AppView(APIView):
    def get(self, request):
        queryset = App.objects.all()
        serializer = AppSerializer(queryset, many=True)
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        serializer = AppSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request):
        app_obj = App.objects.get(id=request.data['id'])
        serializers = AppSerializer(instance=app_obj, data=request.data)
        if serializers.is_valid(raise_exception=True):
            serializers.save()
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request):
        app_obj = App.objects.get(id=request.data['id'])
        try:
            app_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})


class ServerView(APIView):
    def get(self, request):
        queryset = Server.objects.all()
        serializer = ServerSerializer(queryset, many=True)
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        serializer = ServerSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request):
        server_obj = Server.objects.get(id=request.data['id'])
        serializers = ServerSerializer(instance=server_obj, data=request.data)
        if serializers.is_valid(raise_exception=True):
            serializers.save()
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request):
        server_obj = Server.objects.get(id=request.data['id'])
        try:
            server_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})
完善序列化器显示的数据。
from app_api.models import User, Project, App, Server
from rest_framework import serializers


# 定义序列化器
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User  # 指定序列化器对应的模型类
        fields = '__all__'  # 指定序列化器中包含的字段
        read_only_fields = ('id',)  # 指定序列化器中只读的字段
        extra_kwargs = {
            'name': {'max_length': 10, 'required': True},
            'city': {'max_length': 10, 'required': True},
            'sex': {'max_length': 10, 'required': True},
            'age': {'min_value': 18, 'max_value': 100, 'required': True},
        }


class ProjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Project
        fields = '__all__'


class AppSerializer(serializers.ModelSerializer):
    # 一对多展示详情
    project = ProjectSerializer(read_only=True)

    class Meta:
        model = App
        fields = '__all__'


class ServerSerializer(serializers.ModelSerializer):
    # 多对多展示详情
    app = AppSerializer(read_only=True, many=True)

    class Meta:
        model = Server
        fields = '__all__'

image

image

这样我们的详细信息就出来了。通过App都可以查询到它属于哪儿个项目,通过Server也可以查到上面的App属于哪儿个项目
序列化器返回的是当前模型中的字段,如果字段是外键时,返回的时外键对应的ID,如果想要返回外键对应的详细信息,那就需要按照上面的配置,这个配置被称之为

1:定义字段为外键对应序列类:例如:project = ProjectSerializer(read_only=True),这种适合针对某个外键字段。
2:序列化类中Meta类启用depth: 深度获取关联表数据,这种所有外键都会显示出来。

3.9:DRF序列化:SerializerMethodField

DRF序列化器默认仅返回数据模型中已存在的资源,如果想新增返回字段或者二次处理,该怎么操作呢?那么我们会使用serializermethodfield,下面我们来看看示例
class ProjectSerializer(serializers.ModelSerializer):
    app_count = serializers.SerializerMethodField()
    class Meta:
        model = Project
        fields = '__all__'

    def get_app_count(self, obj):
        return obj.app_set.count()

image

从这里我们可以看出,新增一个返回值比并且还带了数据在我们的额返回信息中。

3.10:DRF序列化器:改变序列化和反序列化的行为

可以通过重写下面的两个方法改变序列化和反序列化的行为

1:to_internal_value():处理反序列化的输入数据,自动转换Python对象,方便处理
2:to_representation():处理序列化数据的输出
如果提交API的数据与序列化器要求格式不符合,序列化器就会出现错误,这时就可以重写to_internal_value()方法只提取我们需要的数据
{
    "extra_info" : {
        "msg": "Hello"
    },
    "project": {
        "name": "测试项目-4",
        "describe": "假装有描述",
    }
}

image

这里就会发现它并没有找到我们要创建的项目字段,但是其实时有的,只不过是JSON级别不一样,然后我们就可以重写一下

imageimage

其实说白了就是我们在传递的数据中找到我们需要的数据进行转换成Python对象去创建数据。
希望给返回数据添加一个统计应用数量的字段,这个我们前面其实一种方法实现过了,当然这个方法也可以实现
def to_representation(self, instance):
    data = super().to_representation(instance)
    data['app_count_2'] = instance.app_set.count()
    return data

image

我们可以看到,这两种方法都是可以实现的。

4:Django DRF:视图

目录
DRF类视图介绍
APIView类
Request与Response
GenerAPIView类
ViewSet类
ModelViewSet类

4.1:DRF类视图介绍

在DRF框架中提供了众多的通用视图基类与扩展类,以简化视图的编写。

1:View:Django默认的视图基类,负责将视图连接到URL,HTTP请求方法的基本调度,最开始我们就是用它写的。
2:APIView:DRF提供的所有视图的基类,继承View并扩展,具备了身份验证,权限检查,流量控制等功能。
3:GenericAPIView:对APIView更高层次的封装,例如增加分页,过滤器。
4:GenericViewSet:继承GenericAPIView和ViewSet。
5:ViewSet:继承APIView,并集合router自动映射路由。
6:ModelViewSet:继承GenericAPIView和五个扩展类,封装好各种请求,更加完善,业务逻辑基本不用自己写。

image

4.2:APIView类

APIView:DRF提供的所有视图的基类,继承View并扩展,具备了身份验证,权限检查,流量控制等功能,不过这个缺点就是我们需要自己一条条的去定义URL并绑定给View视图。
class UserView(APIView):
    def get(self, request, pk=None):
        if pk:
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True)
            # 返回序列化后的数据
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def post(self, request):
        # 接收用户提交的数据
        data = request.data
        # 调用序列化器对数据进行反序列化
        serializer = UserSerializer(data=data)
        # 验证数据
        if serializer.is_valid(raise_exception=True):
            # 保存数据
            serializer.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    def put(self, request, pk):
        # 接收传入ID
        user_obj = User.objects.get(id=pk)
        # 将查询数据与传入数据进行合并
        serializers = UserSerializer(instance=user_obj, data=request.data)
        # 验证数据
        if serializers.is_valid(raise_exception=True):
            # 保存数据
            serializers.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})

    def delete(self, request, pk):
        user_obj = User.objects.get(id=pk)
        try:
            user_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})

4.3:Request和Response

DRF传入视图的request对象不再是Django默认的HttpRequest对象,而是基于HttpRequest类扩展的Request类的对象。
Request对象的数据是自动根据前端发送的数据统一解析数据格式。

常用属性:

1:request.data:返回POST提交的数据,与request.POST类似
2:request.query_params:返回GET URL参数,与request.GET类似
class UserView(APIView):

    def post(self, request):
        # 接收用户提交的数据
        data = request.data
        print(data)  # 我们打印request.data这个数据看看
        # 调用序列化器对数据进行反序列化
        serializer = UserSerializer(data=data)
        # 验证数据
        if serializer.is_valid(raise_exception=True):
            # 保存数据
            serializer.save()
            # 返回序列化后的数据
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})
        
    def get(self, request, pk=None):
        if pk:
            print(request.query_params)  # 获取query传递的参数
            user_obj = User.objects.get(id=pk)
            serializer = UserSerializer(user_obj)
        else:
            # 首先获取数据库中的所有数据
            queryset = User.objects.all()
            # 使用序列化器对数据进行序列化
            serializer = UserSerializer(queryset, many=True)
            # 返回序列化后的数据
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

image

POST:{'name': '张三', 'city': '上海', 'sex': '男', 'age': 24}
GET:<QueryDict: {'age': ['15']}>
DRF提供了一个响应类Response,响应的数据会自动转换符合前端的JSON数据格式
from rest_framework.response import Response

# 如下格式
Response(data, status=None, template_name=None, headers=None, centent_type=None)
# data:响应序列化处理后的数据,传递Python对象
# status:状态码,默认200
# template_name:模板名称
# headers:用于响应头信息的字典
# centent_type:响应数据类型
不过我们习惯是自己定义一个返回,也就是自己自定义的字段

return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})
不过rest_framework提供了一系列的状态码供我们使用

return Response({'status': status.HTTP_200_OK, 'msg': '查询成功', 'data': serializer.data})

# HTTP_200_OK:请求成功
# HTTP_301_MOVED_PERMANENTLY:永久重定向
# HTTP_302_FOUND:临时重定向
# HTTP_304_NOT_MODIFIED:请求资源未修改
# HTTP_403_FORBIDDEN:没有权限访问
# HTTP_404_NOT_FOUND:页面没有发现
# HTTP_500_INTERNAL_SERVER_ERROR:服务器内部错误
# HTTP_502_BAD_GATEWAY:网关错误
# HTTP_503_SERVICE_UNAVAILABLE:服务器不可达
# HTTP_504_GATEWAY_TIMEOUT:网关超时

4.4:GenericAPIView类

GenericAPIView是对APIView的更高层次的封装,实现了如下功能:

1:增加queryset属性,指定操作的数据,不用再将数据传给序列化器,会自动实现
2:增加serializer_class属性,直接指定使用的序列化器
3:增加过滤属性:filter_backends
4:增加分页属性:pagination_class
5:增加lookup_field属性和实现get_object()方法,用于获取单条数据,可自定义默认分组名
from rest_framework.generics import GenericAPIView

class UserView(GenericAPIView):
    queryset = User.objects.all()  # 指定操作的数据
    serializer_class = UserSerializer  # 指定序列化器

    def get(self, request, pk=None):
        if pk:
            user_obj = self.get_object()  # 用于调用指定数据
            serializer = self.get_serializer(instance=user_obj)  # 从类方法中调用序列化器
        else:
            users = self.get_queryset()  # 从类方法中调用数据
            serializer = self.get_serializer(users, many=True)  # 从类方法中调用序列化器
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    def put(self, request, pk=None):
        user_obj = self.get_object()  # 用于调用指定数据
        user_serializer = self.get_serializer(instance=user_obj, data=request.data)
        if user_serializer.is_valid():
            user_serializer.save()
            return Response({'code': 200, 'msg': '修改成功'})
        else:
            return Response({'code': 400, 'msg': '修改失败'})

image

image

image

这就是我们重写的一个UserView的视图,基本上功能是一样的,只是使用的方法不同而已

4.5:ViewSet类

GenericAPIView已经完成了许多功能,但是会有一个问题,获取所有用户列表和单个用户需求要分别定义两个视图和URL路由,使用ViewSet可以很好的解决这个问题,并且实现了路由自动映射。

ViewSet视图集不再实现get(),post()等方法,而是实现以下请求方法动作:

1:list():获取所有数据
2:retrieve():获取单个数据
3:create():创建数据
4:update():更新数据
5:destory():删除数据

下面我们来看看demo
from rest_framework.viewsets import ViewSet

class UserView(ViewSet):
    # 获取单个数据
    def retrieve(self, request, pk=None):
        user_obj = User.objects.get(id=pk)
        serializer = UserSerializer(user_obj)
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    # 获取所有数据
    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response({'code': 200, 'msg': '查询成功', 'data': serializer.data})

    # 创建数据
    def create(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功'})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

    # 更新数据
    def update(self, request, pk=None):
        user_obj = User.objects.get(id=pk)
        serializer = UserSerializer(instance=user_obj, data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '更新成功'})
        else:
            return Response({'code': 400, 'msg': '更新失败'})
        
    # 删除数据
    def destroy(self, request, pk=None):
        user_obj = User.objects.get(id=pk)
        try:
            user_obj.delete()
            return Response({'code': 200, 'msg': '删除成功'})
        except Exception as e:
            return Response({'code': 400, 'msg': '删除失败'})
路由内需要如下定义

re_path('api/user/$', views.UserView.as_view({'get': 'list', 'post': 'create', 'put': 'update', 'delete': 'destroy'})),
# 正则匹配id
re_path(r'^api/user/(?P<pk>\d+)$', views.UserView.as_view({'get': 'retrieve', 'post': 'create', 'put': 'update', 'delete': 'destroy'})),

这里的这个路由映射了我们ViewSet的方法,也就意味着,我们可以定义这个视图应用哪儿些方法

image
image
image
image

这样我们基于ViewSet的增删改查就完成了。
其实这里路由的定义方法和之前一样,每个API都要写一条路由,但是实际还是那个我们使用了ViewSet后,就不用自己去设计URL路由以及绑定HTTP方法了,会自动处理URL映射,我们来看看demo。
from rest_framework import routers
from django.urls import include

# 自动生成URL路由
router = routers.DefaultRouter()
router.register(r'users', views.UserView, basename='users')

urlpatterns += [
    path('api/', include(router.urls)),
]

image

image

image
image
image

GET:/app_api/api/			    # 查看自动注册的API
GET:/app_api/api/users/			# 查全部数据
POST:/app_api/api/users/13/      # 查单条数据
PUT:/app_api/api/users/19/       # 更新数据
DELETE:/app_api/api/users/19/    # 删除数据
以上操作我们都可以通过一条路由注册就OK了

4.6:ModeViewSet类

ModeViewSet继承了GenericAPIView和五个扩展类,封装好各种请求,更加完善,业务逻辑基本不用自己写,只需要指定serializer_class和queryset,就可以直接进行增删改查,下面我们来看看Demo
from rest_framework.viewsets import ModeViewSet

class UserView(ModeViewSet):
    queryset = User.object.all()  # 指定操作的数据
    serializer_class = UserSerializer  # 指定序列化器
就配置这么多,路由我们都不用配置,还是用ViewSet的自动配置路由就OK了,然后我们继续去测试增删改查,我们可以看到,这里的逻辑我们完全没有写,但是我们可以尝试去试一下增删改查的逻辑

image
image
image
image
image
image
image

这里其实我们可以轻松的看到,其实API都实现了,只是我们没有写任何的处理,所以它没有任何的回显操作。

image

image

动作 类名 HTTP方法 说明 URL示例
retrieve mixins.RetrieveModelMixin GET 获取单数据,需要携带pk http://1270.0.0.1/app_api/api/user/12
list mixins.ListModelMixin GET 获取多条数据 http://1270.0.0.1/app_api/api/user/
create mixins.CreateModelMixin POST 创建数据 http://1270.0.0.1/app_api/api/user/
update mixins.UpdateModelMixin PUT 更新数据,需要携带pk http://1270.0.0.1/app_api/api/user/12
destroy mixins.DestroyModelMixin DELETE 删除数据,需要携带pk http://1270.0.0.1/app_api/api/user/12
当我们去深追那些类的定义的时候我们会发现,它的实现方法和我们前面的实现方法基本上是一样的,大差不差。
由于ModelViewSet比较高的抽象,实现自动增删改查的功能,对于增,改在很多场景无法满足需求,这就需要我们去重写方法了,我们来看看demo
def create(self, request, *args, **kwargs):
    request.data['age'] += 1
    serializer = self.get_serializer(data=request.data)
    if serializer.is_valid(raise_exception=True):
        serializer.save()
        return Response({'code': 200, 'msg': '创建成功', 'data': serializer.data})
    else:
        return Response({'code': 400, 'msg': '创建失败'})
这里其实我们是针对数据做了一个简单的处理,也就是基于我们创建用户的时候给它的age+1,那么后面我们来测试一下是否真的如我们所愿

image

如果预想和我们的一样,那么它的age应该是33

image

我这里直接将代码返回了,在python程序返回中指定了data字段。看到了是和我们预想的一样的,这就是重写方法的作用了。

5:Django DRF:常用共能

目录
主流认证方式
DRF认证
限流
过滤
搜索和排序
分页
自动生成接口文档

5.1:主流认证方式

1:session
2:Token
3:JWT

5.1.1:Session认证

HTTP是一个无状态系恶意,每次访问都是新的,早期主要用于浏览网页,随着隋代发展,像在线购物网站的兴起,就面临着需要记录哪儿些人登录系统,哪儿些人购物车存放了商品,也就是每个人必须分开,所以就有了哟用户标识,但每次访问都需要登陆,非常的麻烦,这就有了会话保持,然而Cookie+Session就实现了会话保持技术。

image

5.1.2:Token认证

Cookie+Session通常在浏览器作为客户端的情况下比较通用,随着前后端分离的开发模式的普及,会涉及到很多(PC,APP,Pad),特别是手机端,对Cookie非常不友好,并且Cookie不支持跨域,因此局限性是非常高的,所以就有了Token

image

5.1.3:JWT认证

与Token认证一样,都是访问资源令牌,区别是普通的Token服务端验证Token信息要查询数据库,JWT验证Token不需要查询数据库,只需要在服务端使用密钥校验即可。

image

5.2:DRF认证与权限

目前我们做的DRF是可以任意访问的,没有任何的限制,是不符合上线标准的,因此接下来我们要实现的就是访问控制。

DRF支持如下四种认证方式:
1:BasicAuthentication:基于用户名密码的认证,适用于测试。
2:SessionAuthentication:基于Session的认证。
3:TokenAAuthentication:基于Token的认证。
4:RemoteUserAuthentication:基于远程用户的认证。

DRF支持权限:
1:IsAuthenticated:只有登录用户才能访问所有API
2:AllowAny:允许所有用户
3:IsAdminUser:仅管理员用户
4:IsAuthenticatedOnReadOnly:登录用户可读写API,未登录只读。

5.2.1:Session认证实现

我们可以根据视图级别来选择启用认证的范围,它支持全局启动和视图级别启动,那么我们来看看demo,这个是写在settings.py的配置下的
# 全局

# DRF 基于Session的认证
REST_FRAMEWORK = {
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
    # 权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

image

我们可以看到,我们的权限现在已经不足了,这个时候我们访问的时候就可以使用Django自带的认证系统就可以了。也就是/admin

image

但是我们这个时候是不知道密码的,所以我们在数据库应该也看不到密码,所以我们需要设置一下。

PS E:\code\user_data_manager> python .\manage.py createsuperuser 
System check identified some issues:

WARNINGS:
app_api.Server.app: (fields.W340) null has no effect on ManyToManyField.
Username (leave blank to use 'administrator'): layzer
Email address: layzer@kudevops.cn
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y                 
Superuser created successfully.

# 但是这里的密码在生产上一定要规范,我这里设置的不规范所以会提示我,但是依旧可以创建,然后我们拿着这个账号密码就可以登录了

imageimage

image

这样就OK了,就实现了我们的Session的全局认证了,我们也可以看到我们的认证会被返回一个Sessionid,然后我们再去看看数据库。

image

不过对于这种认证方式不太适合我们使用API,比较适合使用浏览器的时候使用这种方式,那么我们再来看。其实它还还可基于视图曾,那么我们来看看Demo
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated

class UserView(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]

    def create(self, request, *args, **kwargs):
        request.data['age'] += 1
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功', 'data': serializer.data})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

image

image

从这里我们可以看出,这是基于视图级别的认证,我们访问这个视图才会去触发认证,所以这个时候我们再去登录一下就OK了,不过还是那句话,Session只适用于浏览器

5.2.2:Token认证

Token认证我们分为4步

1:安装模块
2:启用Token认证
3:生成数据库
4:配置Token认证接口URL
# settings.py下配置

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'app',
    'app_api',
    'rest_framework.authtoken',  # 这个就是Token模块了
]
# settings.py下配置

# DRF 基于Session的认证
REST_FRAMEWORK = {
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    # 权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}
PS E:\code\user_data_manager> python .\manage.py migrate       
System check identified some issues:

WARNINGS:
app_api.Server.app: (fields.W340) null has no effect on ManyToManyField.
Operations to perform:
  Apply all migrations: admin, app, app_api, auth, authtoken, contenttypes, sessions
Running migrations:
  Applying authtoken.0001_initial... OK
  Applying authtoken.0002_auto_20160226_1747... OK
  Applying authtoken.0003_tokenproxy... OK

随后我们查看一下数据库

image

from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app_api/', include('app_api.urls')),
    path('api-token-auth/', views.obtain_auth_token),
]

# 路由我们完全可以丢到公共的url内,因为Token既然用肯定是全局的。
这样就OK了,下面我们来测试一下

image

测试我们发现,它让我们提供账号密码。这个时候我们再次尝试传入账号密码试一下。

image

登陆完成之后它就会给我们返回Token,也就证明了我们登录成功了,然后我们去数据库看一下。

image

可以看到它和我们的用户的id做了一个绑定,也就是说我们以后用这个Token登录他就会识别到对应的用户,然后我们可以带着个这个用户的Token再去访问一下信息,看看是否有权限。

image

我们通过Headler传递Token到后端,但是测试的时候它是有一个标准的,就是参数值前有一个`Token`和`一个空格`后面才是我们的Token,然后我们发现这样我们就有权限了,并且可以看到我们的数据了,这就证明Token起作用了,并且它是可以访问到我们的数据的。
当然,它也是支持视图验证的,我们来看看绑定视图级别的验证,同样的,注释掉settings.py的配置,然后放到视图内
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

class UserView(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def create(self, request, *args, **kwargs):
        request.data['age'] += 1
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功', 'data': serializer.data})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

image

可以看到视图外的API是有权限的,但是我们再去看看视图

image
image

可以看到我关掉Token的时候是不行的,但是我打开Token就OK了,这就证明了Token的验证在视图级别是生效的。
当然了,默认的obtain_auth_token视图返回的数据是比较简单的,只有Token一项,如果想返回更多的信息,例如:用户名,可以重写obtainAuthToken类的方法实现,下面我们看看Demo,我们在app_api下新建一个叫做obtain_auth_token.py的文件
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response


class CustomAuthToken(ObtainAuthToken):

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data, context={'request': request})
        if serializer.is_valid():
            user = serializer.validated_data['user']
            token, created = Token.objects.get_or_create(user=user)
            return Response({'code': 200, 'msg': '登录成功', 'token': token.key, 'username': user.username})
        else:
            return Response({'code': 400, 'msg': '登录失败'})
然后还需要去url指定自定义模块
from django.contrib import admin
from django.urls import path, include
from app_api import obtain_auth_token

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app_api/', include('app_api.urls')),
    path('api-token-auth/', obtain_auth_token.CustomAuthToken.as_view()),
]

image

这样我们就可以自定义回显请求了。

5.3:限流

我们可以针对接口的访问频率进行限制,以减轻服务器的压力,主要应用于高并发的场景,比如提交订单,投票等场景,我们下面来看看如何配置,它的配置和认证一样配置在全局的。
# DRF 基于Session的认证
REST_FRAMEWORK = {
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    # 权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    # 限流 : 范围
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',  # 匿名用户
        'rest_framework.throttling.UserRateThrottle'  # 登录用户
    ],
    # 限流 : 速率
    'DEFAULT_THROTTLE_RATES': {
        # 周期 second, minute, hour, day
        'anon': '3/minute',  # 针对未登录用户进行限制,每分钟3次,基于IP区分用户
        'user': '5/minute'  # 针对登录用户进行限制,每分钟5次,基于用户区分用户
    },
}

image

这个时候我去访问就被限流了,它告诉我多久不可用,这个时候我们的访问也就不会再生效了。

5.4:过滤

对于列表数据,可能需要根据字段进行过滤,我们可以通过添加Django-filter扩展来增强支持。

文档:https://www.django-rest-framework.org/api-guide/filtering

首先我们需要安装这个APP

pip3 install django-filter

其次就是导入

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'app',
    'app_api',
    'rest_framework.authtoken',
    'django_filters'
]

然后就是添加到配置内

# DRF 基于Session的认证
REST_FRAMEWORK = {
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    # 权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    # 限流 : 范围
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',  # 匿名用户
        'rest_framework.throttling.UserRateThrottle'  # 登录用户
    ],
    # 限流 : 速率
    'DEFAULT_THROTTLE_RATES': {
        # 周期 second, minute, hour, day
        'anon': '3/minute',  # 针对未登录用户进行限制,每分钟3次,基于IP区分用户
        'user': '5/minute'  # 针对登录用户进行限制,每分钟5次,基于用户区分用户
    },

    # 过滤
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend'
    ]
}

然后我们就可以去应用这个过滤器了,当然这个配置是全局生效的,所以我们去配置一下。
from django_filters.rest_framework import DjangoFilterBackend

class UserView(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    # 过滤器,基于sex过滤
    filter_backends = [DjangoFilterBackend]  # 这里是基于视图级别配置过滤,当然我们前面配置了全局,就不需要配置局部了
    filterset_fields = ['sex']

    def create(self, request, *args, **kwargs):
        request.data['age'] += 1
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功', 'data': serializer.data})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

imageimage

可以看到它只过滤出了性别为女的数据,这就是过滤器的作用

5.5:搜索和排序

from rest_framework import filters

class UserView(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    # 过滤器,基于sex过滤
    filterset_fields = ['sex']
	
    # 指定过滤器和排序器,然后指定字段过滤
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    # 指定搜索字段生效的数据key,也就是说我们搜索的字段必须是name内的,当然,它支持设置多个。
    search_fields = ['name']

    def create(self, request, *args, **kwargs):
        request.data['age'] += 1
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功', 'data': serializer.data})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

image
image

过滤和排序配置其实也就是引入了两个配置,demo如下
class UserView(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    # 过滤器,基于sex过滤
    filterset_fields = ['sex']

    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    # 基于name字段进行搜索
    search_fields = ['name']
    # 基于age字段进行排序,默认是升序,如果需要用降序可以在过滤前加-
    ordering_fields = ['age']

    def create(self, request, *args, **kwargs):
        request.data['age'] += 1
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功', 'data': serializer.data})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

image-20221106003840234

当然两者还可以结合起来使用,即搜索而且还排序。
class UserView(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    # 过滤器,基于sex过滤
    filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend]
    # 基于sex字段进行过滤
    filterset_fields = ['sex']
    # 基于name字段进行搜索
    search_fields = ['name']
    # 基于age字段进行排序(默认升序)
    ordering_fields = ['age']

    def create(self, request, *args, **kwargs):
        request.data['age'] += 1
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response({'code': 200, 'msg': '创建成功', 'data': serializer.data})
        else:
            return Response({'code': 400, 'msg': '创建失败'})

image

三者可结合在一起使用,然后过滤我们想要的数据。

5.6:分页

分页是数据表格必备的功能,可以在前端实现,也可以在后端实现,为了避免响应数据过大的问题,造成前端服务的压力,我们一般在后端去实现这个功能。
# DRF 基于Session的认证
REST_FRAMEWORK = {
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication'
    ],
    # 权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    # 限流 : 范围
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',  # 匿名用户
        'rest_framework.throttling.UserRateThrottle'  # 登录用户
    ],
    # 限流 : 速率
    'DEFAULT_THROTTLE_RATES': {
        # 周期 second, minute, hour, day
        'anon': '100/minute',  # 针对未登录用户进行限制,每分钟3次,基于IP区分用户
        'user': '100/minute'  # 针对登录用户进行限制,每分钟5次,基于用户区分用户
    },

    # 过滤
    # 'DEFAULT_FILTER_BACKENDS': [
    #     'django_filters.rest_framework.DjangoFilterBackend'
    # ]

    # 分页
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',  # 分页
    'PAGE_SIZE': 3  # 每页显示的数量
}

image

根据图上显示,我们其实是有6条数据,但是这也页只显示了三条,因为这个在我们的分页配置定死了,然后上面就有一个下一页的操作了。我们可以点击第二页再看看

image

这其实就是我们的分页功能了,当然它页可以结合前面的三者来使用。
但是默认的分页器灵活度是不高的,例如不能动态传递每页条数,我们可以通过重写PageNumberPagination类属性改变默认配置。当然这个我们需要去自己写一个文件来做了app_api/pagination.py
from rest_framework.pagination import PageNumberPagination

class MyPageNumberPagination(PageNumberPagination):
    page_size = 2  # 每页显示的数据条数(默认)
    page_query_param = 'page_number'  # 查询固定页数的关键字
    page_size_query_param = 'page_size'  # 指定查询每页数据条数的关键字
    max_page_size = 20  # 指定每页最大显示的数据条数
然后我们需要去指定这个模块去使用
# DRF 基于Session的认证
REST_FRAMEWORK = {
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication'
    ],
    # 权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    # 限流 : 范围
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',  # 匿名用户
        'rest_framework.throttling.UserRateThrottle'  # 登录用户
    ],
    # 限流 : 速率
    'DEFAULT_THROTTLE_RATES': {
        # 周期 second, minute, hour, day
        'anon': '100/minute',  # 针对未登录用户进行限制,每分钟3次,基于IP区分用户
        'user': '100/minute'  # 针对登录用户进行限制,每分钟5次,基于用户区分用户
    },

    # 过滤
    # 'DEFAULT_FILTER_BACKENDS': [
    #     'django_filters.rest_framework.DjangoFilterBackend'
    # ]

    # 分页
    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',  # 分页
    'DEFAULT_PAGINATION_CLASS': 'app_api.pagination.MyPageNumberPagination',  # 自定义分页器
    # 'PAGE_SIZE': 3  # 每页显示的数量
}

image

根据自定义分页器配置来看,默认显示两条数据。

image

可以看到我们根据我们的配置,显示第二页,并显示三条数据,这些都是根据我们的自定义分页器来做的。
但是我们会发现默认返回的数据是一个固定格式的JSON字符串,但是这个格式与我们平时的格式不太一样,所以我们希望把它改一下,当然我们还是利用重写pagination的文件来做这件事。
class MyPageNumberPagination(PageNumberPagination):
    page_size = 2  # 每页显示的数据条数(默认)
    page_query_param = 'page_num'  # 查询固定页数的关键字
    page_size_query_param = 'page_size'  # 指定查询每页数据条数的关键字
    max_page_size = 20  # 指定每页最大显示的数据条数

    # 重写分页器响应数据
    def get_paginated_response(self, data):
        if not data:
            return Response({'code': 404, 'msg': '没有数据了'})
        else:
            return Response(OrderedDict([
                ('code', 200),
                ('msg', '查询成功'),
                ('count', self.page.paginator.count),
                ('next', self.get_next_link()),  # 可以不要
                ('previous', self.get_previous_link()),  # 可以不要
                ('data', data)
            ]))

image

那我们这样看数据就亲民多了,能够清楚的看到我们的数据了,并且可以让前端更好的调用了,基本上我们的Django DRF的学习就到这里了,后面我们进入Vue的学习,也就是开展前端的学习了。

5.7:自动生成API文档

由于项目开发经验欠缺或者着急上线,需求不断改动,项目设计阶段定义的接口已经是面目全非,这个前端人员带来了巨大的困难,那么我们如何改善这个问题呢?

Swagger可以帮助我们来解决这个问题,它是一个应用非常广泛的REST API文档自动生成工具,它可以生成文档给前端人员查看。

文档:https://django-rest-swagger.readthedocs.io/en/latest

首先需要安装这个包

pip3 install django-rest-swagger

然后引入包

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'app',
    'app_api',
    'rest_framework.authtoken',
    'django_filters',
    'rest_framework_swagger'
]

然后再DRF配置

# DRF 基于Session的认证
REST_FRAMEWORK = {
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication'
    ],
    # 权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    # 限流 : 范围
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',  # 匿名用户
        'rest_framework.throttling.UserRateThrottle'  # 登录用户
    ],
    # 限流 : 速率
    'DEFAULT_THROTTLE_RATES': {
        # 周期 second, minute, hour, day
        'anon': '100/minute',  # 针对未登录用户进行限制,每分钟3次,基于IP区分用户
        'user': '100/minute'  # 针对登录用户进行限制,每分钟5次,基于用户区分用户
    },

    # 过滤
    # 'DEFAULT_FILTER_BACKENDS': [
    #     'django_filters.rest_framework.DjangoFilterBackend'
    # ]

    # 分页
    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',  # 分页
    'DEFAULT_PAGINATION_CLASS': 'app_api.pagination.MyPageNumberPagination',  # 自定义分页器
    # 'PAGE_SIZE': 3  # 每页显示的数量
    
    # 引入swagger
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
}


最后引入一下路由就OK了

from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views
from app_api import obtain_auth_token
from rest_framework_swagger.views import get_swagger_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app_api/', include('app_api.urls')),
    path('api-token-auth/', obtain_auth_token.CustomAuthToken.as_view()),
    path('docs/', get_swagger_view(title='API文档'))
]
但是这中间可能大家会遇到一个错误就是刷新页面会出问题,但是不用慌,我们先看看问题

image

如果是这个问题,我们根据路径去找到一个叫index.html的文件将{% load staticfiles %} 改成 {% load static %}就OK了,当然还有另一种手法解决,也就是在配置添加如下配置


TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates']
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
            # 添加如下内容即可解决
            'libraries': {
                'staticfiles': 'django.templatetags.static',
            },
        },
    },
]

image

posted @ 2022-11-13 20:56  Layzer  阅读(99)  评论(0编辑  收藏  举报