详解 view.py

登录注册

完善视图函数

将之前写的注册函数改得完善亿点:

from django.shortcuts import render
import json
from user.models import User
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
# Create your views here.

@csrf_exempt
def register(request):
    if request.method == 'POST':
        # 检测 POST 类型的请求

        data_json = json.loads(request.body)
        # 此时 data_json 已经转为了字典类型

        username = data_json['username']
        password1 = data_json['password1']
        password2 = data_json['password2']
        email = data_json.get('email', '')
        # 不确定是否有这个参数,使用get

        if password1 != password2:
            return JsonResponse({'result': 0, 'message': '两次密码不一致!'})
        # 虽然密码一致性与合法性检测一般是前端判断
        # 但是后端最好也加上,以防万一

        user = User(username=username, password=password1, email=email)
        # id 是自动赋值,不需要指明
        # post 不赋值会使用默认值0

        user.save()
        return JsonResponse({'result': 1, 'message': '注册成功!'})
        # 正常结束后需要给一个反馈信息

前后端在传递数据时,通常使用json格式。不了解json的同学看这里。前端把要传的数据打包成 json 发给后端,后端解析这个 json 得到字典后,进行相应的处理,最后返回给前端一个 json 信息来告知处理结果。

{
    'result': 1,
    'message': '注册成功!'
}

这是我个人经常用的一个返回信息模板,第一个参数0或1告诉前端是否正确处理了,后面一个参数则告诉详细的处理结果。比如由于两次密码不一致导致注册失败,前端就可以直接把 message 里的内容弹窗弹出来。

之所以选择 POST 类型的请求,是因为其更安全,数据内容不会携带在 URL中。同时它支持携带任意格式任意长度的请求参数,相对较为灵活。为了省事一点,可以要求前端只传 POST 类型的请求。POST 和 GET 的区别见这里

完善了注册函数后,再次启动项目(其实也不用每次都启动, 在启动状态下保存文件,Django 会自动检测并更新),用 postman 测一下注册功能是否正确。这次的请求因为带上了参数,所以需要在 body 中添加相应内容:

image-20220124113023535

随便填一个最简单的管理员账号:

image-20220124113118076

不出所料,得到了两次密码不一致的结果:

image-20220124113154896

改成一致后再次发送请求,就可以看到正确的反馈信息:

image-20220124113244159

在 Datagrip 中刷新一下 user 表,就能看到刚刚写入到数据库中的内容:

image-20220124113326286

增删改查中的“增“,到这里已经结束了。

继续完善登录函数:

@csrf_exempt
def login(request):
    if request.method == 'POST':
        data_json = json.loads(request.body)
        username = data_json['username']
        password = data_json['password']
        user = User.objects.get(username=username)
        # 查询语句,根据括号中的条件,返回唯一一条结果
        # 如果有多个表项匹配这一条件,则报错

        if user.password == password:
            return JsonResponse({'result': 1, 'message': '登录成功!'})
        else:
            return JsonResponse({'result': 0, 'message': '密码错误!'})

同时在user/urls.py中注册:

from django.urls import path
from user.views import *

urlpatterns = [
    path('register', register),
    path('login', login),
]

postman 中新建一个 login 请求,进行测试:

image-20220124114126103

image-20220124114147873

两种情况都可以正常识别。但是,如果是下面这个情况,就会报错:

image-20220124114407048

返回了一个 500 错误(恭喜你解锁了一个以后最常看见的错误代码)。仔细看下 Django 的终端输出:

image-20220124155823649

查询结果不存在,这代表着User.objects.get(username=username)的获取结果为空。所以在不确定结果是否存在前,不能直接用get去查询,应该先检查存在性。查询部分应该修改为如下格式:

if User.objects.filter(username=username).exists() == True:
    # 使用 filter 先检查存在性
    user = User.objects.get(username=username)
else:
    return JsonResponse({'result': 0, 'message': '用户不存在!'})

此时就可以正常处理用户不存在的情况了:

image-20220124160326896

之前那个报错,渲染成 HTML 格式,长下面这样:

image-20220124160500737

image-20220124160508513

settings.py中有一个参数,表示是否开启 DEBUG 模式,默认为 True。在这种情况下,报错会返回详细信息。在测试环境中不建议将其关闭,前端在浏览器中抓取到 500 错误后,可以查看返回信息,根据类似上面的报错信息提醒后端修 Bug。前后端对接的 debug 流程大概就是这样。

使用 session

在测试 login 接口时,发现存在一个问题,那就是后端无法检测登录状态,每次都认为是一个未登录用户在执行登录操作。这会带来很多问题,比如如果一个登录用户想查看自己所发表的博客,后端就无法进行正确的查询操作。

为解决这个问题,有两种办法:

  1. 使用 token,也就是身份令牌。在登录成功后,后端会将一些信息加密传递给前端,加密后的结果就称为 token。每次前端在向后端发送请求时,都要携带这个token,后端在解密之后,就会知道用户名等各项信息。这种办法不需要前后端进行过多配置,实现原理简单;但每次前端传参时都需要手动加上 token。有关 token 的更多介绍,可以看这里
  2. 使用 session。这是 Django 官方推荐的方法。session 可以看做是一个存储信息的仓库,后端可以向 session 中写入、读取信息。在每次返回前端请求时,会自动捎带上 session id,前端将其存在cookie里,每次发请求就会自动发给后端。后端拿到id,先是自动去数据库中根据 id 找到session,然后就可以支持各种操作了。不过在跨域的情况下,前端如果想要保存 cookie,需要加很多额外的配置信息,但是后端用起来十分方便(我只负责出后端教程,肯定后端怎么方便怎么来,就让前端去解决这个问题吧)。

下面介绍 session 的使用。对登录函数做一些修改:

@csrf_exempt
def login(request):
    if request.method == 'POST':
        id = request.session.get('id', 0)
        # 从 session 中获取信息
        if id != 0:
            return JsonResponse({'result': 0, 'message': '用户已登录!'})
        data_json = json.loads(request.body)
        username = data_json['username']
        password = data_json['password']
        if User.objects.filter(username=username).exists() == True:
            user = User.objects.get(username=username)
        else:
            return JsonResponse({'result': 0, 'message': '用户不存在!'})
        if user.password == password:
            request.session["id"] = user.id
            # 登录成功后就把 id 存进去

            return JsonResponse({'result': 1, 'message': '登录成功!'})
        else:
            return JsonResponse({'result': 0, 'message': '密码错误!'})

通过 session 来记忆当前登录的用户的 id 是什么。此时在进行多次登录测试:

image-20220124163448542

就会发现已经可以正确识别登录状态了。

顺带加一个登出功能:

@csrf_exempt
def logout(request):
    if request.session.get('id', 0) != 0:
        request.session.flush()
        # 清空所有的信息
        return JsonResponse({'result': 1, 'message': r'已登出!'})
    else:
        return JsonResponse({'result': 0, 'message': r'请先登录!'})

注册路由:

from django.urls import path
from user.views import *

urlpatterns = [
    path('register', register),
    path('login', login),
    path('logout', logout),
]

测试:

image-20220124163834559

发表博客

完善了登录注册这些基本功能后,就要开始着手实现博客相关的接口了。首先是创建一个新的APP,命名为post,表示博客相关模块:

python manage.py startapp post

然后去settings.py中注册,再在post/models.py里初始化一下博客数据库:

from django.db import models

# Create your models here.

class Post(models.Model):
    id = models.AutoField(primary_key=True)
    # 主键
    uid = models.IntegerField()
    # 发表该博客的用户id
    # 也可以选择用外键连接到User表中
    title = models.CharField(max_length=50)
    # 博客标题
    article = models.TextField(null=True, blank=True)
    # 博客正文
    # 因为可能很长,所以使用 TextField 字段
    time = models.DateTimeField()
    # 发表时间

然后三板斧迁移一下,同步到本地的数据库文件里:

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

image-20220206201841087

DataGrip 中刷新就可以看到新建的表了:

image-20220206201934933

接下来是完善views.py,实现发表博客的功能。参考代码如下:

from django.shortcuts import render
import json
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from post.models import *
from user.models import *
from datetime import datetime
# Create your views here.

@csrf_exempt
def create(request):
    if request.method != 'POST':
        return JsonResponse({'result':0, 'message':'Error'})

    id = request.session.get('id', 0)
    if id == 0:
        return JsonResponse({'result':0, 'message':r'请先登录'})

    data_json = json.loads(request.body)
    post = Post(
        uid = id,
        title = data_json['title'],
        article = data_json['article'],
        time = datetime.now()
    )
    post.save()
    # 存储新博客文章信息

    user = User.objects.get(id = id)
    user.post += 1
    user.save()
    # 更新发表文章数量

关于 URL 的配置,同user模块,这里只给出修改后的结果:

hello/urls.py

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('user/', include(('user.urls', 'user'), namespace="user")),
    path('post/', include(('post.urls', 'post'), namespace="post")),
]

post.urls.py

from django.urls import path
from post.views import *

urlpatterns = [
    path('create', create),
]

运行,在 Postman 中简单测试一下:

image-20220206203056714

就可以在数据库中看到相应的结果了:

image-20220206203116498

发表完博客后,下一步是查看。由于不知道用户会点开哪一篇博客,所以要先给前端当前用户所发表的博客列表,用户点击之后,再去精确获取详细的正文信息。涉及到的函数如下:

@csrf_exempt
def get_list(request):
    if request.method != 'POST':
        return JsonResponse({'result':0, 'message':'Error'})

    data_json = json.loads(request.body)
    id = int(data_json['id'])
    # 这里是用户id,获取这个用户所发表的所有文章

    posts = [{
        'id': x.id,
        'title': x.title,
        'time': x.time
    } for x in Post.objects.filter(uid = id)]
    # filter 的结果是一个类似列表的可迭代元素
    # 故可以使用 for .. in 去遍历
    # 这其实是一种压行写法

    return JsonResponse({'result':1, 'message':r'获取成功', 'posts':posts})

@csrf_exempt
def get_post(request):
    if request.method != 'POST':
        return JsonResponse({'result':0, 'message':'Error'})
    data_json = json.loads(request.body)
    id = int(data_json['id'])
    # 这里是文章id

    post = Post.objects.get(id = id)
    return JsonResponse({'result':1, 'message':r'获取成功', 'post': post.to_dic()})
    # 注意这里使用了to_dic()函数

Django 中的数据库定义是使用的类的形式,既然是类,就可以在里面声明函数。to_dic的定义如下:

class Post(models.Model):
    id = models.AutoField(primary_key=True)
    # 主键
    uid = models.IntegerField()
    # 发表该博客的用户id
    # 也可以选择用外键连接到User表中
    title = models.CharField(max_length=50)
    # 博客标题
    article = models.TextField(null=True, blank=True)
    # 博客正文
    # 因为可能很长,所以使用 TextField 字段
    time = models.DateTimeField()
    # 发表时间

    def to_dic(self):
        return {
            'id': self.id,
            'title': self.title,
            'time': self.time,
            'article': self.article,
            'uid': self.uid
        }

之所以要加一步到字典的转换,这是因为 get 得到的结果是 Post 类型,如果直接放到 json 中传递给前端,会在打包转换的时候出现格式错误而报错。json 只能认字典,列表等基础类型,所以一定要加这么一个步骤。

测试结果如下:

image-20220206204336221

image-20220206204442993

最后顺手补一个删除博客的功能:

@csrf_exempt
def delete_post(request):
    if request.method != 'POST':
        return JsonResponse({'result':0, 'message':'Error'})
    data_json = json.loads(request.body)
    id = int(data_json['id'])
    # 这里是文章id

    post = Post.objects.get(id = id)
    user = User.objects.get(id = post.uid)
    user.post -= 1
    user.save()
    # 记得先更新发表文章数量
    post.delete()

    return JsonResponse({'result':1, 'message':r'删除成功'})

到这里,数据库最基本的增,删,改,查四种情况都已经覆盖到了。之后实现的视图函数,基本上就是以上的各种变种,只不过业务逻辑上可能复杂一些, 比如一次涉及到多个表的查询,表之间信息同步更加复杂,数据库中的原始数据有时候不能直接给后端要先本地处理(例如要根据某个统计数据让前端画图等)。

同一个功能的实现是多样的。比如上面获取发表文章列表,其实可以直接把这个文章的所有详细信息全部返回给前端,这样前端在点击某个博客的页面的时候,就可以根据已有的数据直接渲染,不再向后端请求,可以大大提升相应速度。但是在发表博客数很多,博客正文很长的情况下,这么实现会导致在获取列表时相应时间变长,同时一次返回过多数据也会给带宽带来很大的压力。这就需要前后端对业务做一个合理的切割,来得到一个多方面平衡的接口实现方案。

到这里视图函数讲解过程就已经结束了。相关代码已经提交到了 Github 的示例项目上,最后一次 commit 的注释为“Finish view”。

Copyright © Marvolo 2021 all right reserved,powered by Gitbook该文章修订时间: 2022-02-22 12:01:22

results matching ""

    No results matching ""