Django的bbs项目开发
一、表设计
1.用户表
继承AbstractUser 扩展 phone 电话号码 avatar 用户头像 create_time 创建时间 外键字段 一对一个人站点表
2.个人站点表
site_name 站点名称 site_title 站点标题 site_theme 站点样式
3.文章标签表
name 标签名 外键字段 一对多个人站点
4.文章分类表
name 分类名 外键字段 一对多个人站点
5.文章表
title 文章标题 desc 文章简介 content 文章内容 create_time 发布时间
数据库字段设计优化(******)
(虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率)
up_num 点赞数
down_num 点踩数
comment_num 评论数
外键字段
一对多个人站点
多对多文章标签
一对多文章分类
6.点赞点踩表
记录哪个用户给哪篇文章点了赞还是点了踩 user ForeignKey(to="User") article ForeignKey(to="Article") is_up BooleanField()
7.文章评论表
记录哪个用户给哪篇文章写了哪些评论内容 user ForeignKey(to="User") article ForeignKey(to="Article") content CharField() comment_time DateField() # 自关联 parent ForeignKey(to="Comment",null=True) # ORM专门提供的自关联写法 parent ForeignKey(to="self",null=True) 7.评论表 根评论和子评论 to=‘self‘
modles.py
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/default.png‘,verbose_name=‘用户头像‘)
"""
给avatar字段传文件对象 该文件会自动存储到avatar文件下 然后avatar字段只保存文件路径avatar/default.png
"""
create_time = models.DateField(auto_now_add=True)
blog = models.OneToOneField(to=‘Blog‘,null=True)
class Blog(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=64) # 存css/js的文件路径
class Category(models.Model):
name = models.CharField(verbose_name=‘文章分类‘,max_length=32)
blog = models.ForeignKey(to=‘Blog‘,null=True)
class Tag(models.Model):
name = models.CharField(verbose_name=‘文章标签‘,max_length=32)
blog = models.ForeignKey(to=‘Blog‘, null=True)
class Article(models.Model):
title = models.CharField(verbose_name=‘文章标题‘,max_length=64)
desc = models.CharField(verbose_name=‘文章简介‘,max_length=255)
# 文章内容有很多 一般情况下都是使用TextField
content = models.TextField(verbose_name=‘文章内容‘)
create_time = models.DateField(auto_now_add=True)
# 数据库字段设计优化
up_num = models.BigIntegerField(verbose_name=‘点赞数‘,default=0)
down_num = models.BigIntegerField(verbose_name=‘点踩数‘,default=0)
comment_num = models.BigIntegerField(verbose_name=‘评论数‘,default=0)
# 外键字段
blog = models.ForeignKey(to=‘Blog‘, null=True)
category = models.ForeignKey(to=‘Category‘,null=True)
tags = models.ManyToManyField(to=‘Tag‘,
through=‘Article2Tag‘,
through_fields=(‘article‘,‘tag‘)
)
class Article2Tag(models.Model):
article = models.ForeignKey(to=‘Article‘)
tag = models.ForeignKey(to=‘Tag‘)
class UpAndDown(models.Model):
user = models.ForeignKey(to=‘UserInfo‘)
article = models.ForeignKey(to=‘Article‘)
is_up = models.BooleanField() # 传布尔值 存0/1
class Comment(models.Model):
user = models.ForeignKey(to=‘UserInfo‘)
article = models.ForeignKey(to=‘Article‘)
content = models.CharField(verbose_name=‘评论内容‘,max_length=255)
comment_time = models.DateTimeField(verbose_name=‘评论时间‘,auto_now_add=True)
# 自关联
parent = models.ForeignKey(to=‘self‘,null=True) # 有些评论就是根评论二、注册功能
注册功能用到的技术点有以下
forms组件
用户头像前端实时展示
ajax
我们之前写forms代码是直接写到views里面的
为了贯彻面向对象思想,我们会把forms组件代码写到单独的一个py文件里,如果说这个项目从头到位就使用一个form组件,直接写在app下面的任意一个自己写的文件里就行,不过记得不要叫forms,这样在导入Django库里的form时会出现问题,可以叫myforms.py或者其他,只要补交forms就行
如果你项目中用到多个不同的forms组件时,那最好还是自己建一个文件夹,然后把对应的forms组件写上对应的名字,然后放在这个文件夹下面,这样也比较清晰
myforms.py
from django import forms
from app01 import models
class MyRegForm(forms.Form):
username = forms.CharField(label=‘用户名‘, min_length=3, max_length=8,
error_messages={
‘required‘: ‘用户名不能为空‘,
‘min_length‘: "用户名最少3位",
‘max_length‘: "用户名最大8位"
},
# 还需要让标签有bootstrap样式
widget=forms.widgets.TextInput(attrs={‘class‘: ‘form-control‘})
)
password = forms.CharField(label=‘密码‘, min_length=3, max_length=8,
error_messages={
‘required‘: ‘密码不能为空‘,
‘min_length‘: "密码最少3位",
‘max_length‘: "密码最大8位"
},
# 还需要让标签有bootstrap样式
widget=forms.widgets.PasswordInput(attrs={‘class‘: ‘form-control‘})
)
confirm_password = forms.CharField(label=‘确认密码‘, min_length=3, max_length=8,
error_messages={
‘required‘: ‘确认密码不能为空‘,
‘min_length‘: "确认密码最少3位",
‘max_length‘: "确认密码最大8位"
},
# 还需要让标签有bootstrap样式
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‘)
# 去数据库中校验
is_exist = models.UserInfo.objects.filter(username=username)
if is_exist:
# 提示信息
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_dataviews.py的register视图函数
def register(request):
form_obj = MyRegForm()
if request.method == ‘POST‘:
back_dic = {"code": 1000, ‘msg‘: ‘‘}
# 校验数据是否合法
form_obj = MyRegForm(request.POST)
# 判断数据是否合法
if form_obj.is_valid():
# print(form_obj.cleaned_data) # {‘username‘: ‘jason‘, ‘password‘: ‘123‘, ‘confirm_password‘: ‘123‘, ‘email‘: ‘‘}
clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量
# 将字典里面的confirm_password键值对删除
clean_data.pop(‘confirm_password‘) # {‘username‘: ‘jason‘, ‘password‘: ‘123‘, ‘email‘: ‘‘}
# 用户头像
file_obj = request.FILES.get(‘avatar‘)
"""针对用户头像一定要判断是否传值 不能直接添加到字典里面去"""
if file_obj:
clean_data[‘avatar‘] = file_obj
# 直接操作数据库保存数据
models.UserInfo.objects.create_user(**clean_data)
back_dic[‘url‘] = ‘/login/‘
else:
back_dic[‘code‘] = 2000
back_dic[‘msg‘] = form_obj.errors
return JsonResponse(back_dic)
return render(request,‘register.html‘,locals())register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center">注册</h1>
<form id="myform"> <!--这里我们不用form表单提交数据 知识单纯的用一下form标签而已-->
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group">
<label for="{{ form.auto_id }}">{{ form.label }}</label>
{{ form }}
<span style="color: red" class="pull-right"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="myfile">头像
{% load static %}
<img src="{% static ‘img/default.png‘ %}" id=‘myimg‘ alt="" width="100" style="margin-left: 10px">
</label>
<input type="file" id="myfile" name="avatar" style="display: none" >
</div>
<input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">
</form>
</div>
</div>
</div>
<script>
$("#myfile").change(function () {
// 文件阅读器对象
// 1 先生成一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 2 获取用户上传的头像文件
let fileObj = $(this)[0].files[0];
// 3 将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作
// 4 利用文件阅读器将文件展示到前端页面 修改src属性
// 等待文件阅读器加载完毕之后再执行
myFileReaderObj.onload = function(){
$(‘#myimg‘).attr(‘src‘,myFileReaderObj.result)
}
})
$(‘#id_commit‘).click(function () {
// 发送ajax请求 我们发送的数据中即包含普通的键值也包含文件
let formDataObj = new FormData();
// 1.添加普通的键值对
{#console.log($(‘#myform‘).serializeArray()) // [{},{},{},{},{}] 只包含普通键值对#}
$.each($(‘#myform‘).serializeArray(),function (index,obj) {
{#console.log(index,obj)#} // obj = {}
formDataObj.append(obj.name,obj.value)
});
// 2.添加文件数据
formDataObj.append(‘avatar‘,$(‘#myfile‘)[0].files[0]);
// 3.发送ajax请求
$.ajax({
url:"",
type:‘post‘,
data:formDataObj,
// 需要指定两个关键性的参数
contentType:false,
processData:false,
success:function (args) {
if (args.code==1000){
// 跳转到登陆页面
window.location.href = args.url
}else{
// 如何将对应的错误提示展示到对应的input框下面
// forms组件渲染的标签的id值都是 id_字段名
$.each(args.msg,function (index,obj) {
{#console.log(index,obj) // username ["用户名不能为空"]#}
let targetId = ‘#id_‘ + index;
$(targetId).next().text(obj[0]).parent().addClass(‘has-error‘)
})
}
}
})
})
// 给所有的input框绑定获取焦点事件
$(‘input‘).focus(function () {
// 将input下面的span标签和input外面的div标签修改内容及属性
$(this).next().text(‘‘).parent().removeClass(‘has-error‘)
})
</script>
</body>
</html>三、登录功能
登录功能的技术点有以下
实现图片验证码
ajax
