BBS项目
项目开发基本流程
1.需求分析---2.架构设计---3.分组开发---4.提交测试---5.交付上线
项目分析
博客园项目我们可以参考博客园来编写程序,考虑其主要拥有哪些功能,经过我们浏览发现。其博客园项目主要是用户对文章的管理,其核心是文章的增删改查。
表分析
先确定表的数量。在确定表的基础字段,最后确定表的外键字段。
1.用户表
2.个人站点表
3.文章表
4.文章分类表
5.文章标签表
6.推荐反对表
7.文章评论表
基础字段分析
1.用户表可以直接继承使用 auth_user 表并扩展额外的字段
电话,头像,注册时间
2.个人站点表
站点名称(/个人标识/ID/姓名/)
站点标题(/标题/)
站点样式(/css文件/)
3.文章表
文章标题,文章简介,文章内容,文章的发布时间
4.文章分类表
分类名称
5.文章标签表
标签名称
6.推荐反对表
"记录哪个用户给哪篇文章点了推荐还是反对"
用户字段(用户主键)>>>: 外键字段
文章字段(文章主键)>>>: 外键字段
推荐反对字段 0或1
评论内容
评论时间
外键字段(自关联)
"因含有子评论所以需要根据根评论的ID来判断是否是子评论"
外键字段分析
用户表
用户与个人站点是一对一外键关系
个人站点表
文章表
文章表与个人站点表是一对多外键关系
文章表与文章分类表是一对多外键关系
文章表与文章标签表是多对多外键关系
'''
数据库字段优化设计:我们想统计文章的评论数点赞数通过文章数据跨表查询到文章评论表中对应的数据统计即可,但是文章需要频繁的展示每次都跨表查询的话效率极低,我们在文章表中再创建三个普通字段之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三个普通字段即可
'''
文章评论数
文章点赞数
文章点踩数
文章分类表
文章分类与个人站点是一对多外键关系
文章标签表
文章标签与个人站点是一对多外键关系
表的创建
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
"""用户表"""
phone = models.BigIntegerField(verbose_name='手机号', null=True)
avatar = models.FileField(upload_to='avatar/', default='avatar/111.jpg') # upload_to 是可以自动把获取到的文件存取到后面的路径中。
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_css = 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)
class Tag(models.Model):
"""文章标签表"""
name = models.CharField(verbose_name='标签名称', max_length=32)
# 文章表与标签表多对多第三方表(半自动创建方法)
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(verbose_name='评论时间', auto_now_add=True)
parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True) # 自关联字段
"记住 需要在settings内声明替换auth用户表"
AUTH_USER_MODEL = 'app01.UserInfo'
用户注册功能
form类验证层
使用forms组件
1.渲染前端标签
2.校验用户数据
3.展示错误信息
forms类单独放在一个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
模板层
<!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表单提交数据 但是它有序列化功能 得用一下 -->
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group"> <!-- form-group 让多个input标签上下间距更大一些 -->
<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/默认.png" alt="" width="120" id="myimg">
</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>
// 用户头像实时展示
$('#myfile').change(function (){
// 产生一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 获取用户上传的头像
let fileObj = this.files[0];
// 将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj); // 异步操作,需要等待完成
// 等待文件阅读器对象加载完毕后修改
myFileReaderObj.onload = function (){
// 修改img标签的src属性
$('#myimg').attr('src',myFileReaderObj.result)
}
})
// 给注册按钮绑定事件 发送ajax
$('#subBtn').click(function (){
// 产生一个内置对象
let myFormDataObj = new FormData();
// 添加普通数据
{#console.log($('#form').serializeArray()) // 一次性获取form表单所有普通字段 [{}.{}.{}]#}
$.each($('#form').serializeArray(), function (index, dataObj) { // 对结果for循环 然后交给后面的函数处理
myFormDataObj.append(dataObj.name, dataObj.value)
})
// 添加文件数据
myFormDataObj.append('avatar',$('#myfile')[0].files[0])
// 发送请求
$.ajax({
url:'',
type:'post',
data:myFormDataObj,
contentType:false,
processData:false,
success:function (args){
if(args.code ===10000){
window.location.href =args.url
}else{
// 根据input框显示错误信息
let dataObj = args.msg;
$.each(dataObj, function (k,msgArray){
// 拼接标签的id值
let eleId = '#id_' + k
// 根据id查找标签 修改下面span标签的内容并添加错误样式
$(eleId).next().text(msgArray[0]).parent().addClass('has-error')
})
}
}
})
})
// 给所有input标签绑定获取焦点事件 移除错误样式
$('input').focus(function (){
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
视图层
from django.shortcuts import render,HttpResponse,redirect
from app01 import myforms
from app01 import models
from django.http import JsonResponse
# Create your views here.
def register_func(request):
# 前后端交互通常使用字典
back_dict = {'code':10000,'msg':''}
# 产生一个空的form_obj
form_obj = myforms.RegisterForm()
if request.method == 'POST':
form_obj = myforms.RegisterForm(request.POST)
if form_obj.is_valid():
# 存储符合校验的数据
clean_date = form_obj.cleaned_data
# 删除用不到的数据
clean_date.pop('confirm_password')
# 获取用户上传的头像文件
avatar_boj = request.FILES.get('avatar')
if avatar_boj:
clean_date['avatar'] = avatar_boj
# 创建用户数据
models.UserInfo.objects.create_user(**clean_date)
# 登录成功添加后续操作
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())