基于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代码是直接写到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_data
views.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\': \'123@qq.com\'}
- clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量
- # 将字典里面的confirm_password键值对删除
- clean_data.pop(\'confirm_password\') # {\'username\': \'jason\', \'password\': \'123\', \'email\': \'123@qq.com\'}
- # 用户头像
- 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>
三、登录功能
登录功能的技术点有以下