一. 数据库的表设计
数据库表设计
用户表(利用auth_user, 额外扩张几个字段,不用添加auth_user原表里的字段)
phone
avatar
create_time
blog # 一对一个人站点表 用户表与个人站点表
个人站点表
site_name # 站点名称
site_title # 每个人站点的名言警句,随便写的
site_theme # 站点的样式, 存css的路径
# 个人站点表经常不会出现外接字段
标签表 # 存到底有几个标签,名字是谁呢
name
blog # 与个人站点表是一对多的关系,每个站点看似有多个标签,每个标签看似在每个站点都有,实则不是,标签虽然名字一样,但是是不同的人创建的
分类表
name
blog # 一对多个人站点
文章表
title
desc # 文章摘要,独白
content # 文章内容
create_time # 发布时间
blog # 文章和个人站点一对多
tag # 多对多
category # 文章分类, 一对多的关系
如果想统计某个文章的点赞点踩数, 到个人站点有好多文章,如果我们要跨表查询的话,无疑是给数据库增加压力,那我们可以在文章表里建几个普通字段
# 数据库设计优化
comment_num # 普通字段
up_num # 普通字段
down_num # 普通字段
建立这几个普通字段,我们可以和点赞点踩表,评论表开个事务,之后在想看评论数的话直接点comment_num就可以拿到评论数,不再需要跨表查
点赞点踩表
user # 一对多用户表
article # 一对多文章表
is_up
针对点赞来说,赞可以有多个人来点,赞对人来说是一对多的关系
评论表
user # 一对多用户表
article # 一对多文章表
comment # 评论的内容
create_time
parent # 一对多评论表 ,自己跟自己关联 根评论,子评论 这个是根评论的id 如果有值说明是子评论, 如果没值就是根评论
数据库同步
models里面的配置
1 from django.db import models 2 from django.contrib.auth.models import AbstractUser 3 # Create your models here. 4 class UserInfo(AbstractUser): 5 phone = models.BigIntegerField(null=True) 6 # avatar存的是用户头像文件路径 用户上传的头像会自动保存到avatar文件夹下 7 avatar = models.FileField(upload_to='avatar/',default='avatar/default.jpg') 8 create_time = models.DateField(auto_now_add=True) 9 10 blog = models.OneToOneField(to='Blog',null=True) # 用户创建出来可以不绑定主页 11 12 13 class Blog(models.Model): 14 site_title = models.CharField(max_length=32) 15 site_name = models.CharField(max_length=32) 16 site_theme = models.CharField(max_length=255) 17 18 19 class Category(models.Model): 20 name = models.CharField(max_length=32) 21 blog = models.ForeignKey(to='Blog') 22 23 24 class Tag(models.Model): 25 name = models.CharField(max_length=32) 26 blog = models.ForeignKey(to='Blog') 27 28 29 class Article(models.Model): # 多对多的时候建议用第三个方法也就是半自动化创建第三张表 30 title = models.CharField(max_length=255) 31 desc = models.CharField(max_length=255) 32 content = models.TextField() # 存大段文本 33 create_time = models.DateField(auto_now_add=True) 34 35 # 数据库优化字段 36 comment_num = models.IntegerField(default=0) 37 up_num = models.IntegerField(default=0) 38 down_num = models.IntegerField(default=0) 39 40 # 外键字段 41 blog = models.ForeignKey(to='Blog',null=True) 42 category = models.ForeignKey(to='Category',null=True) 43 tag = models.ManyToManyField(to='Tag',through='Article2Tag',through_fields=('article','tag')) 44 45 class Article2Tag(models.Model): 46 article = models.ForeignKey(to='Article') 47 tag = models.ForeignKey(to='Tag') 48 49 50 class UpAndDown(models.Model): 51 user = models.ForeignKey(to='UserInfo') 52 article = models.ForeignKey(to='Article') 53 is_up = models.BooleanField() # 传布尔值 存0/1 54 55 56 class Comment(models.Model): 57 user = models.ForeignKey(to='UserInfo') 58 article = models.ForeignKey(to='Article') 59 content = models.CharField(max_length=255) 60 create_time = models.DateField(auto_now_add=True) 61 parent = models.ForeignKey(to='self',null=True)
注册功能
1 from django import forms 2 from django.forms import widgets 3 from app01 import models 4 5 6 class MyRegForm(forms.Form): 7 username = forms.CharField(max_length=8, min_length=3, label='用户名', 8 error_messages={ 9 'max_length': '用户名最大八位', 10 'min_length': '用户名最小三位', 11 'required': '用户名不能为空' 12 }, widget=widgets.TextInput(attrs={'class': 'form-control'}) 13 ) 14 password = forms.CharField(max_length=8, min_length=3, label='密码', 15 error_messages={ 16 'max_length': '密码最大八位', 17 'min_length': '密码最小三位', 18 'required': '密码不能为空' 19 }, widget=widgets.PasswordInput(attrs={'class': 'form-control'}) 20 ) 21 confirm_password = forms.CharField(max_length=8, min_length=3, label='确认密码', 22 error_messages={ 23 'max_length': '确认密码最大八位', 24 'min_length': '确认密码最小三位', 25 'required': '确认密码不能为空' 26 }, widget=widgets.PasswordInput(attrs={'class': 'form-control'}) 27 ) 28 email = forms.EmailField(label='邮箱', error_messages={ 29 'required': "邮箱不能为空", 30 'invalid': "邮箱格式错误" 31 }, widget=widgets.EmailInput(attrs={'class': 'form-control'})) 32 33 # 局部钩子 校验用户名是否已存在 34 def clean_username(self): 35 username = self.cleaned_data.get('username') 36 is_user = models.UserInfo.objects.filter(username=username) 37 if is_user: 38 self.add_error('username', '用户名已存在') 39 return username 40 41 # 全局钩子 校验密码是否一致 42 def clean(self): 43 password = self.cleaned_data.get('password') 44 confirm_password = self.cleaned_data.get('confirm_password') 45 if not password == confirm_password: 46 self.add_error('confirm_password', '两次密码不一致') 47 return self.cleaned_data
前端代码:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> 7 {% load static %} 8 <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}"> 9 <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script> 10 </head> 11 <body> 12 <div class="container"> 13 <div class="row"> 14 <div class="col-md-8 col-md-offset-2"> 15 <h2 class="text-center">注册</h2> 16 {# novalidate是告诉前端不要让前端去校验#} 17 <form id="myform" novalidate> 18 {% csrf_token %} 19 {% for foo in form_obj %} 20 <div class="form-group"> 21 {#foo.auto_id获取foo渲染的input框的id值,效果就是在标签上点动就会在input框里有光标在晃动#} 22 <label for="{{ foo.auto_id }}">{{ foo.label }}</label> 23 {{ foo }} 24 <span class="errors pull-right" style="color: red;"></span> 25 </div> 26 {% endfor %} 27 {# form-group的效果就是让input框的距离稍远一点,不加的话就两个input框就会挨的近一点#} 28 <div class="form-group"> 29 <label for="myfile">头像 30 {# 将头像的这个放在label里面就会出现点击头像图片的话就会弹出选择文件的页面#} 31 <img src="/static/img/default.jpg" alt="" height="80" style="margin-left: 20px" id="img"> 32 </label> 33 {# 将选择文件的那几个字消失#} 34 <input type="file" name="avatar" id="myfile" style="display: none"> 35 </div> 36 <input type="button" class="btn btn-primary pull-right" value="注册" id="id_submit"> 37 </form> 38 </div> 39 </div> 40 </div> 41 42 <script> 43 将头像展示在前端 44 $('#myfile').change(function () { 45 // 获取用户上传的头像 然后替换到img标签中 46 // 1 获取用户上传的文件对象 47 var fileObj = $(this)[0].files[0]; 拿到当前文件所存储的对象 48 // 2.利用内置对象FileReader 49 var fileReader = new FileReader(); //文件阅读器 50 // 3.将文件对象交由文件阅读器读取 文件内容 51 fileReader.readAsDataURL(fileObj); // IO操作速度较慢,读取文件较慢,还没等到这条语句执行完就执行下调语句, 52 // 会出现选中头像后什么都不显示的现象,所以在下条语句加onload等待语句,等待我读完再去执行下条语句 53 // 4.找到img标签 修改src属性 54 // 等待文件阅读器完全读取完文件数据之后 才做下面的操作 onload 55 fileReader.onload = function(){ $('#img').attr('src',fileReader.result)} 56 }); 57 58 // 绑定点击事件 59 $('#id_submit').click(function () { 60 // 1. 产生内置对象formdata 61 var formData = new FormData(); 62 // 2. 循环添加普通键值对 63 {#console.log($('#myform').serializeArray())#} 64 $.each($('#myform').serializeArray(),function (index,obj) { 65 formData.append(obj.name,obj.value) 66 }); 67 // 3. 手动添加文件 68 formData.append('myfile',$('#myfile')[0].files[0]) ; 69 // 4. 发送ajax请求 70 $.ajax({ 71 url:'', 72 type:'post', 73 data:formData, 74 75 // 传文件需要指定两个参数 76 contentType:false, 77 processData:false, 78 79 success:function (data) { 80 if (data.code==100){ 81 location.href = data.url 82 }else{ 83 // 如果没有成功 说明用户输入的数据不合法 你需要展示错误信息 84 // console.log(data.msg) 85 // 能够将对应的错误信息准确无误的渲染到对应的input下面的span标签中 86 // 手动拼接input的id值 87 $.each(data.msg,function (index,obj) { 88 {#console.log(index,obj)#} 89 var targetId = '#id_' + index; 90 $(targetId).next().text(obj[0]).parent().addClass('has-error') 91 }) 92 } 93 } 94 95 96 }) 97 }); 98 {#绑定焦点事件,当鼠标点进input框时,让input框不在飘红#} 99 $('input').focus(function () { 100 $(this).next().text('').parent().removeClass('has-error') 101 }) 102 </script> 103 </body> 104 </html>
后端代码:
1 from django.shortcuts import render,HttpResponse 2 from django.http import JsonResponse 3 from app01 import myforms 4 from app01 import models 5 from django.contrib import auth 6 # Create your views here. 7 8 9 def register(request): 10 form_obj = myforms.MyRegForm() 11 if request.method == 'POST': 12 back_dic = {'code':100,'msg':''} 13 # 校验用户信息是否合法 14 form_obj = myforms.MyRegForm(request.POST) 15 if form_obj.is_valid(): 16 clean_data = form_obj.cleaned_data # clean_data = {'username':'','password':'','confirm_password':'','email':''} 17 clean_data.pop('confirm_password') # clean_data = {'username':'','password':'','email':''} 18 # 手动获取用户头像 19 user_file = request.FILES.get('myfile') 20 if user_file: # 一定要判断用户是否传头像了 如果传了才往字典里面添加 没传不用添加 因为有默认 21 clean_data['avatar'] = user_file 22 # 创建数据 23 models.UserInfo.objects.create_user(**clean_data) 24 back_dic['msg'] = '注册成功' 25 back_dic['url'] = '/login/' 26 else: 27 back_dic['code'] = 101 28 back_dic['msg'] = form_obj.errors # 数据不合法,错误数据全都在errors里面,将全部错误信息返回前端 29 return JsonResponse(back_dic) # ajax通常返回的都是一个字典,先在上面定义一个字典 30 31 return render(request,'register.html',locals())