目录
BBS
项目开发基本流程
- 需求分析
- 架构设计
- 分组开发
- 提交测试
- 交付上线
项目分析(表)
bbs项目主要是仿照博客园的页面来设计的。核心就是文章的增删改查
-
表分析
首先我们得先确定几个表(表的数量),其次确定表得基础字段,最后确定表的外键字段
1.用户表 2.个人站点表 3.文章表 4.文章分类表 5.文章标签表 6.点赞点踩表 7.文章评论表
-
基本字段分析
用户表
我们基于auth_user表并扩展额外的字段
auth_user 用户名、密码、邮箱、是否是超级管理员 基于auth_user表并扩展额外的字段 电话表、头像、注册时间
个人站点表
1.站点名称(kimi/kiki) 2.站点标题(我博客的The Road of Learning kimi) 3.站点样式(CSS样式)
文章表
文章标题、文章简介、文章内容、发布时间
文章分类表
分类名称
文章标签表
标签名称
点赞点踩表
此表是记录哪个用户给哪篇文章点了推荐(赞)反对(踩) 用户字段(用户主键):外键字段 文章字段(文章主键):外键字段 点赞点踩
文章评论表
此表是记录哪个用户给哪篇文章评论了什么内容 用户字段(用户主键):外键字段 文章字段(文章主键):外键字段 评论内容 评论时间 外键字段(自关联) """ id user_id article_id content parent_id 1 1 1 哈哈哈 null 2 2 1 你笑傻 1 3 3 1 他疯了 2 """
-
外键字段
用户表 用户与个人站点时一对一外键关系 个人站点表 文章表 文章表与个人站点表时一对多外键关系 文章表与文章分类表时一对多外键关系 文章与文章标签表时多对多外键关系 文章评论数 文章点赞数 文章点踩数 """ 数据库字段优化设计:我们想统计文章的评论数、点赞数 通过文章数据跨表查询到文章评论表中对应的数据统计,但是文章需要频繁的展示,每次都跨表查询的话效率极低,基于上述的表述,我们优化处理,只需要在文章表中再创建三个普通字段,之后只需要确保每次操作评论表或者点赞点踩表时同时修改上述的三个普通字段即可""" 文章分类表 文章分类与个人站点时一对多外键关系 文章标签表 文章标签与个人站点是一对多外键关系
表关系图如下
项目注册功能
用户注册:利用form组件、modelform组件、ajax组件等编写
1.渲染前端标签
2.校验用户数据
3.展示错误提示
注意:form组件单独开设py文件编写 解耦合!!!
项目登录功能
img标签的src属性
1.直接填写图片地址
2.填写路由,会自动朝该路由发送get请求
如果结果是图片的二进制数据,那么会自动渲染图片
终端下载pillow模块
pip install pillow -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
项目实操
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser # 要继承auth_user做扩展,引入AbstractUser
""" 要在settings配置一下AUTH_USER_MODEL = 'app01.UserInfo'
"""
class UserInfo(AbstractUser):
"""用户表"""
phone = models.BigIntegerField(verbose_name='手机号', null=True)
avatar = models.FileField(upload_to='avatar/', default='avatar/default.jpg', 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):
"""个人站点表"""
site_name = models.CharField(verbose_name='站点名称', max_length=32)
site_title = models.CharField(verbose_name='站点标题', max_length=32)
site_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)
category = models.ForeignKey(to='Category', on_delete=models.CASCADE, null=True)
tags = models.ManyToManyField(to='Tag',
through='Article2Tag',
through_fields=('article', 'tag'),
null=True
)
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):
"""文章点赞点踩表"""
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
is_up = models.BooleanField(verbose_name='点赞点踩') # 传布尔值存 0或者1
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)
注册功能
校验数据利用的是form组件。
views.py
from app01 import myforms # 引入myforms
from app01 import models # 引入models
from django.http import JsonResponse # 引入JsonResponse做数据序列化
# 注册功能
def register_func(request):
# 前后端ajax交互,通常采用字段作为交互对象
back_dict = {'code': 10000, 'msg': ''}
# 1.先产生一个空的form_obj
form_obj = myform.RegisterForm()
if request.method == 'POST':
form_obj = myform.RegisterForm(
request.POST) # username password confirm_password email csrfmiddlewaretoken四个键值对
if form_obj.is_valid(): # 判断是否符合
clean_data = form_obj.cleaned_data # 存储符合校验的数据 # username password confirm_password email
# 将confirm_password键值对移除
clean_data.pop('confirm_password') # {# username password email}
# 获取用户上传的对象文件
avatar_obj = request.FILES.get('avatar') # 用户有可能没有上次,下面的判断必须要的
if avatar_obj:
clean_data['avatar'] = avatar_obj # {username password email avatar }
# 创建用户数据
models.UserInfo.objects.create_user(**clean_data) # 上述处理字典的目的就是为了创建数据省事
back_dict['msg'] = '注册成功'
back_dict['url'] = '/login/'
""" 正常业务一般是先写正确的逻辑,后面慢慢添加错误的逻辑"""
else:
back_dict['code'] = 10001
back_dict['msg'] = form_obj.errors
return JsonResponse(back_dict)
return render(request, 'registerPage.html', locals())
register.html
本次虽用了form,是为了后面前端的数据反序化,前端提交数据是使用了ajax组件提交数据,本次还给input标签绑定获取焦点事件,移除错误样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
<div class="col-md-8 col-md-offset-2">
<h2 class="text-center">用户注册</h2>
<form id = 'form'> <!--不使用form表单提交数据,但是用一下form表单,他有一个序列化功能-->
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group"> <!-- 目的是让多个获取用户数据的标签上下间距更大一点-->
<label for="{{ form.auto_id }}">{{ form.label }}</label> <!--form.auto_id自动获取渲染的标签id值-->
{{ form }}
<span style="color: red" class="pull-right"></span>
</div>
{% endfor %}
<!--用户头像自己编写相关标签获取-->
<div class="form-group">
<label for="myfile">头像
<img src="/static/img.jpg" alt="" width="120" id="myimg"> <!-- 把img放在lable里面,因为label和input框已经绑定了-->
</label>
<input type="file" id ='myfile' style="display: none" >
</div>
<input type="button" id="subBtn" class="btn btn-primary btn-block" value="注册">
</form>
</div>
</div>
<script>
// 1.用户头像的实时展示 >>>这是前端的固定代码
$('#myfile').change(function (){
//1.产生一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 2.获取用户上传的头像文件
let fileObj = this.files[0];
//3.将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj)
//3.等待文件阅读器对象加载完毕之后再修改src
myFileReaderObj.onload = function (){
// 4.修改img标签的src属性展示图片
$('#myimg').attr('src',myFileReaderObj.result)
}
})
// 2.给注册按钮绑定点击事件 发送ajax 携带了文件数据
$('#subBtn').click(function (){
// 1.先产生一个内置对象
let myFormDataObj = new FormData();
// 2.添加普通数据(单个单个的编写效率极低)
{#console.log($('#form').serializeArray()) // 可以一次性获取form标签内所有的普通字段数据[{},{},{}]#}
$.each($('#form').serializeArray(),function (index,dataObj){ //对结果for循环,然后交给后面的函处理
myFormDataObj.append(dataObj.name,dataObj.value) // {'name':'','value':''}
})
// 3.添加文件数据
myFormDataObj.append('avatar',$('#myfile')[0].files[0]) //query对象转为标签对象
//4.发送ajax请求
$.ajax({
url:'',
type:'post',
data:myFormDataObj,
contentType:false,
processData:false,
success:function (args){
// 如果code=10000,直接跳转到登录页面,back_dict['url'] = '/login/'
if(args.code===10000){
window.location.href = args.url
} else{
{#console.log(args.msg)#}
let dataObj =args.msg;
//如何针对性的渲染错误提示 {'username'} id_username
$.each(dataObj,function(k,msgArray){
//拼接标签的id值
let eleId='#id_'+ k
//根据id查找标签 修改下面的span标签的内容 并给父标签添加错误样式
$(eleId).next().text(msgArray[0]).parent().addClass('has-error')
})
}
}
})
})
//3.给所有的input标签绑定获取焦点事件,移除错误样式
$('input').focus(function (){
$(this).next().text('').parent().removeClass('has-error')})
</script>
</body>
</html>
登录功能
views.py
# 校验组件一般只在篡创建的时候用到的
def login_func(request):
return render(request,'loginPage.html')
from PIL import Image,ImageFont,ImageDraw
"""
Image 产生图片
ImageFont 字体样式
ImageDraw 画笔对象
"""
from io import BytesIO,StringIO
"""
BytesIO 在内存中临时存储 读取的时候以bytes格式为准
StringIO 在内存中临时存储 读取的时候以字符串格式为准
"""
import random
def get_random():
return random.randint(0.255),random.randint(0.255),random.randint(0.255)
def get_code_func(request):
# 1.推导步骤1:直接读取图片文件返回
# with open(r'D:\PycharmProjects\BBS\avatar\头像2.png','rb')as f:
# data=f.read()
# return HttpResponse(data)
# 2.推导步骤2:随机产生图片动态返回 第三方pillow模块
# img_obj = Image.new('RGB',(350.35),'green')
# with open(r'xxx.png', 'wb') as f:
# img_obj.save(f, 'png')
# with open(r'xxx.png', 'rb') as f:
# data = f.read()
# return HttpResponse(data)
# 3.推导步骤3:针对图片的保存与读取做优化 内存管理器
# img_obj = Image.new('RGB', (350, 35), 'yellow')
# io_obj = BytesIo()
# img_obj.save(io_obj,'png')
# return HttpResponse(io_obj.getvalue())
# 4.推导步骤4:图片颜色是可以随机变换的
img_obj = Image.new('RGB', (350, 35), get_random())
io_obj = BytesIO()
img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
<div class="col-md-8 col-md-offset-2">
<h2 class="text-center">用户登录</h2>
<div class="form-group">
<label for="name">用户名</label>
<input type="text" id="name" class="form-control">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" class="form-control">
</div>
<div class="form-group">
<label for="code">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="code" class="form-control">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="" width="350" height="35">
</div>
</div>
</div>
<input type="button" class="btn btn-success btn-block" value="登录">
</div>
</div>
</body>
</html>
标签:obj,name,项目,models,True,文章,BBS,verbose
From: https://www.cnblogs.com/zhanglanhua/p/17020719.html