BBS系统(一)——项目开发流程,注册功能与登录功能
项目开发基本流程
1.需求分析 2.架构设计 3.分组开发 4.提交测试 5.交付上线
仿造博客园项目(bbs项目)分析
- 使用的技术:Django(2.2.22)、MySQL(5.6)、python3.8、jquery3.6.1、bootstrap3.4.1
- 实现的功能
-
- 注册 (forms校验,页面渲染,上传头像)
- 登录 (自定义图片验证码)
- 首页:文章展示、侧边栏过滤(分类,标签,时间)
- 文章详情:点赞点踩、评论(父评论和子评论)
- 后台管理:当前用户文章展示(文章增删改查)
- 发布文章
- 核心:文章的增删改查
表分析
先确定表的数量 再确定表的基础字段 最后确定表的外键字段
表数量分析
1.用户表
2.个人站点表
3.文章表
4.文章分类表
5.文章标签表
6.点赞点踩表
7.文章评论表
基础字段分析
ps:下列表字段设计仅供参考,我们可以有更多的想法(自行添加字段)
- 用户表
1.替换auth_user表并扩展额外的字段
2.添加电话号码、头像、注册时间三个字段
- 个人站点表
1.站点名称(jason\lili\kevin)
2.站点标题(努力奋斗去他妹的)
3.站点样式(css文件)
- 文章表
1.文章标题
2.文章简介
3.文章内容
4.发布时间
- 文章分类表
分类名称
- 文章标签表
标签名称
- 点赞点踩表:记录哪个用户给哪篇文章点了推荐(赞)还是反对(踩)
1.用户字段(用户主键)>>>:外键字段
2.文章字段(文章主键)>>>:外键字段
3.点赞点踩
- 文章评论表:记录哪个用户给哪篇文章评论了什么内容
1.用户字段(用户主键)>>>:外键字段
2.文章字段(文章主键)>>>:外键字段
3.评论内容
4.评论时间
5. 外键字段(自关联,用于实现针对评论的回复功能)
id user_id article_id content parent_id
1 1 1 哈哈哈 null
2 2 1 哈你妹 1
3 3 1 讲文明 2
外键字段
- 用户表
用户与个人站点是一对一外键关系
- 个人站点表
因为跟用户表是一对一的外键关系,我们可以看成两张表是一张表
- 文章表
1.文章表与个人站点表是一对多外键关系
2.文章表与文章分类表是一对多外键关系
3.文章表与文章标签表是多对多外键关系
- 文章分类表
文章分类与个人站点是一对多外键关系
- 文章标签表
文章标签与个人站点是一对多外键关系
数据库字段优化设计:
我们想统计文章的评论数、点赞数,怎么做?
通过文章数据跨表查询到文章评论表中对应的数据统计即可。
但是文章需要频繁的展示,如果每次都跨表查询的话效率极低,因此我们想到在文章表中再创建三个普通字段,之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三个普通字段即可(即进行加减操作进行统计)
- 这里是三个普通字段
1. 文章评论数
2. 文章点赞数
3. 文章点踩数
前期准备
- 步骤一:
使用pycharm创建Django项目,并且顺带让他自动创建一个应用(app01)
- 步骤二:
- 使用MySQL创建一个新的库
create database bbs1;
- 步骤三:
去settings.py文件中修改配置
首先是修改templates文件夹的注册路径(部分代码)
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] ,
接着是创建一个static文件夹,并进行路径注册
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ]
由于我们替换了auth组件的User表进行了字段扩展,所以需要在配置文件中修改使用的表
AUTH_USER_MODEL = 'app01.UserInfo'
接着是修改数据库的配置信息
DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bbs1', 'HOST': '127.0.0.1', 'POST': 3306, 'USER': 'root', 'PASSWORD': '123', 'CHARSET': 'utf8' } }
ps:注册信息配置好后还要连接数据库
- 步骤四:
在models.py文件中编写表信息(根据项目分析中的字段来写)
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class UserInfo(AbstractUser): """用户表""" phone = models.BigIntegerField(verbose_name='手机号', null=True) avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='用户头像') register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True) # 外键字段 site = models.OneToOneField(to='Site', on_delete=models.CASCADE, null=True) class Site(models.Model): """个人站点表""" siti_name = models.CharField(verbose_name='站点名称', max_length=32) siti_title = models.CharField(verbose_name='站点标题', max_length=32) siti_theme = models.CharField(verbose_name='站点样式', max_length=32, null=True) class Article(models.Model): """文章表""" title = models.CharField(verbose_name='文章标题', max_length=32) desc = models.CharField(verbose_name='文章简介', max_length=255) content = models.TextField(verbose_name='文章内容') create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) # 三个优化字段 comment_num = models.IntegerField(verbose_name='评论数', default=0) up_num = models.IntegerField(verbose_name='点赞数', default=0) down_num = models.IntegerField(verbose_name='点踩数', default=0) # 外键字段 site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True) catagory = models.ForeignKey(to='Category',on_delete=models.CASCADE, null=True) tags = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag'), ) class Category(models.Model): """文章分类表""" name = models.CharField(verbose_name='分类名称', max_length=32) # 外键字段 site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True) class Tag(models.Model): """文章标签表""" name = models.CharField(verbose_name='标签名称', max_length=32) # 外键字段 site = models.ForeignKey(to='Site',on_delete=models.CASCADE, null=True) class Article2Tag(models.Model): """这是文章表和标签表的多对多字段需要创建的表,这里我们使用半自动创建的方式创建多对多字段""" article=models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True) tag = models.ForeignKey(to='Tag', on_delete=models.CASCADE, null=True) class UpAndDown(models.Model): """文章点赞点踩表""" is_up = models.BooleanField(verbose_name='点赞点踩') # 外键字段 user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True) article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True) class Comment(models.Model): """文章评论表""" user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True) article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True) content = models.TextField(verbose_name='评论内容') comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间') # 外键字段 parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True) # 这里的self就是指代Comment,即指代自己所代表的表
编写好了之后执行数据库迁移操作。
用户头像实时展示
路由
首先我们在路由表中添加注册页面的路由
path('register/', views.register_func, name='register_view'),
在编写注册功能的视图函数时,我们想到可以使用forms组件进行校验并给出错误提示,这样大大节省了代码.同时我们回顾forms组件的知识点,forms组件需要创建一个模型表,但是放在视图层中不合适,因此我们app01下单独创建一个py文件(myforms.py)。
forms组件校验用户名、密码、邮箱,获取用户头像
注册校验类(myforms.py)
from django import forms from app01 import models class RegisterForm(forms.Form): """用户注册form类""" username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={ 'min_length': '用户名最短三位', 'max_length': '用户名最长八位', 'required': '用户名不能为空' }, widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) ) password = forms.CharField(min_length=3, max_length=8, label='密码', error_messages={ 'min_length': '密码最短三位', 'max_length': '密码最长八位', 'required': '密码不能为空' }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码', error_messages={ 'min_length': '密码最短三位', 'max_length': '密码最长八位', 'required': '密码不能为空' }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) email = forms.EmailField(label='邮箱', error_messages={ 'required': '邮箱不能为空', 'invalid': '邮箱格式不正确' }, widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}) ) # 用户头像单独校验 不使用校验类 其他字段自己根据需求自定义添加即可 # 钩子函数 # 局部钩子校验用户名是否已存在 def clean_username(self): username = self.cleaned_data.get('username') res = models.UserInfo.objects.filter(username=username) if res: self.add_error('username', '用户名已存在') return username # 全局钩子校验两次密码是否一致 def clean(self): password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if not password == confirm_password: self.add_error('confirm_password', '两次密码不一致') return self.cleaned_data
在创建好了forms模型表后我们在views.py文件中导入使用即可.
视图函数(views.py)
在编写注册的视图函数时我们需要根据forms组件校验的结果做出不同的返回结果。
前端(registerPage.html)
用户注册样式
上述代码写好之后点击头像并能让上传的图片展示出来,如果想让头像实时展示还需要添加script标签。
用户头像实时展示
效果展示
注册功能
注意:用的是ajax提交,不是form表单(form表单没有绑任何事件)
给注册按钮绑定点击事件
在form标签下添加{% csrf_token %}
视图函数
先写正确逻辑
效果展示
注册功能完善
后端补充错误逻辑
前端补充获取到的错误信息
我们的目的是想让错误提示信息渲染到输入框后面,该怎么做呢?
检查前端代码发现输入框的id都比上述键值对中的键前面多了一个id_(如下图)
将错误提示信息渲染到输入框后面
效果展示
优化
1. 出现错误提示信息的时候输入框也变成红色
2. 输入框再次获取焦点时将错误样式移除
登录功能
路由
# 用户登录功能 path('login/', views.login_func, name='login_view'),
前端样式
样式展示
登陆功能除了用户名和密码,我们还需要实现验证码的功能.
随机验证码之随机色图片的产生
路由
# 图片验证码相关功能 path('get_code/', views.get_code_func)
前端img标签的src属性
关于验证码的图片会用到img标签的src属性:
1 可以直接填写图片地址 2 还可以填写一个路由,会自动朝该路由发送get请求
如果返回的结果是图片的二进制数据,那么会自动渲染图片。
例如:将上述img标签的src属性改为我们设置的路由
后端返回一个具体的图片路径
效果展示
我们需要动态获取图片,就需要用到pillow模块。
补充pillow模块与io模块
pillow模块
pillow模块是用于生成图片的,我们产生的验证码就是用它产生的
导入代码如下:
from PIL import Image, ImageFont, ImageDraw """ Image 产生图片 ImageFont 字体样式 ImageDraw 画笔对象 """
io模块
from io import BytesIO, StringIO '通过下方代码中的推导流程我们可以得知pillow模块虽然可以创建图片,但是他需要先保存图片才能调用,因此我们使用io模块存储数据' """ BytesIO 在内存中临时存储 读取的时候以bytes格式为准 StringIO 在内存中临时存储 读取的时候以字符串格式为准 """
视图函数
效果展示
后端编写验证码终极方案——随机图片+字符
效果展示
此时还存在一个问题,我们在刷新页面的时候,前面填空的用户名和密码也跟着刷新了。
如何产生局部刷新的效果?
验证码局部刷新
img的src属性只要发生变化,就会重新发送get请求,产生不同的图片。所以我们可以考虑给图片绑定点击事件,每点击一次,src属性就变化一次。
给img标签绑定点击事件
先给img标签绑定一个id='d1'
登录按钮发送ajax请求
可以使用form标签序列化功能,也可以自己挨个获取。
先试一下form标签
script标签:
效果展示
发送ajax需要组织成大字典的形式,使用form组件的话不太好添加,所以将上面的form标签去掉,自己挨个获取。
挨个获取
后端校验信息:
前端发送ajax请求: