一、表设计
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>
三、登录功能
登录功能的技术点有以下