BBS项目
项目的前期准备
1.django2.2 创建一个django目录
(需要配置环境变量和数据库)
'DIRS': [os.path.join(BASE_DIR, 'templates'), ]
2.数据库准备 我这里创建了一个库名为sql0107的数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'sql0107',
'USER': 'root',
'PASSWORD': '222',
'HOST': '127.0.0.1',
'PORT': 3306,
'CHARSET': 'utf8',
}
}
第一步是创建表
表分析
先确定表的数量 再确定表的基础字段 最后确定表的外键字段
1.用户表
2.个人站点表
3.文章表
4.文章分类表
5.文章标签表
6.点赞点踩表
7.文章评论表
基础字段分析
'''下列表字段设计仅供参考 你可以有更多的想法'''
用户表
替换auth_user表并扩展额外的字段
电话号码、头像、注册时间
个人站点表
站点名称(jason\lili\kevin)
站点标题(努力奋斗去他妹的)
站点样式(css文件)
文章表
文章标题
文章简介
文章内容
发布时间
文章分类表
分类名称
文章标签表
标签名称
点赞点踩表:记录哪个用户给哪篇文章点了推荐(赞)还是反对(踩)
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
点赞点踩
文章评论表:记录哪个用户给哪篇文章评论了什么内容
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
评论内容
评论时间
外键字段(自关联)
"""
id user_id article_id content parent_id
1 1 1 哈哈哈 null
2 2 1 哈你妹 1
3 3 1 讲文明 2
"""
外键字段
用户表
用户与个人站点是一对一外键关系
个人站点表
文章表
文章表与个人站点表是一对多外键关系
文章表与文章分类表是一对多外键关系
文章表与文章标签表是多对多外键关系
'''
数据库字段优化设计:我们想统计文章的评论数 点赞数
通过文章数据跨表查询到文章评论表中对应的数据统计即可
但是文章需要频繁的展示 每次都跨表查询的话效率极低
我们在文章表中再创建三个普通字段
之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三 个普通字段即可
'''
文章评论数
文章点赞数
文章点踩数
文章分类表
文章分类与个人站点是一对多外键关系
文章标签表
文章标签与个人站点是一对多外键关系
模型层代码:models.py
特别重要:
创建表之前因为用户表继承AbstractUser 要在设置里面加上
AUTH_USER_MODEL = 'app01.UserInfo'
模型层代码:
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, blank=True)
avatar = models.FileField(upload_to='media/avatar/', default='media/avatar/default.jpg', verbose_name='用户头像')
register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)
"""用户表与个人站点一对一外键"""
site = models.OneToOneField(to='Site', on_delete=models.CASCADE, null=True)
# 修改admin 后天管理的表名
# class Meta:
# verbose_name_plural = '用户表'
def __str__(self):
return f'用户对象:{self.username}'
class Site(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=32, null=True) # 模拟站点样式文件
# 修改admin 后台管理的表名
# class Meta:
# verbose_name_plural = '个人站点表'
def __str__(self):
return f'个人站点:{self.site_theme}'
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)
# 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章表'
"""文章与个人站点一对多外键"""
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'), )
def __str__(self):
return f'文章对象:{self.title}'
class Category(models.Model):
"""文章分类表"""
name = models.CharField(verbose_name='分类名称', max_length=32)
"""个人站点与文章分类的外键"""
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章分类表'
def __str__(self):
return f'文章分类:{self.name}'
class Tag(models.Model):
"""文章标签表"""
name = models.CharField(verbose_name='标签名称', max_length=32)
"""个人站点和文章标签的外键"""
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章标签表'
def __str__(self):
return f'文章标签:{self.name}'
"""文章与标签表是多对多 半自动创建"""
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)
# 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章表与标签表关系'
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='点赞点踩')
# # 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章点赞点踩表'
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(auto_now_add=True, verbose_name='评论时间')
parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True)
# # 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章评论表'
然后执行数据库迁移命令 添加数据库 (这里使用mysql)
注册功能
1.先开启路由
2.返回一个用户的注册页面
3.引入静态文件 创建static 引入bootstrap文件
4.构建前端注册页面
1.渲染前端标签
2.获取用户数据
3.校验用户数据
4.展示错误提示
ps: 用forms组件、modelform组件
单独开设接口进行编写 解耦合
forms校验文件代码
单独创建一个myforms.py文件
from django import forms
from app01 import models
from django.forms import widgets
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
以下前端校验的截图 有几个点需要注意一下:
forms校验文件代码
from django import forms
from app01 import models
from django.forms import widgets
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表单提交数据 但是用一下form标签 它有一个序列化功能-->
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group"> <!--目的是让多个获取用户数据的标签上下间距更大一些-->
<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/default.jpg" alt="" id="myimg" width="120">
</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>
//1.用户头像的实时展示
$('#myfile').change(function (){
//1.产生一个文件阅读器对象
let myFilReaderObj = new FileReader();
// 2.获取用户上传的头像文件
let fileObj = this.files[0];
//3.将文件对象交给阅读器对象读取
myFilReaderObj.readAsDataURL(fileObj); //异步
// 等待文件阅读器对象加载完毕之后再修改src
myFilReaderObj.onload = function (){
//4.修改img标签的src 属性展示图片
$('#myimg').attr('src',myFilReaderObj.result)
}
})
//2.给注册按钮绑定点击事件 发送ajax 携带了文件数据
$('#subBtn').click(function (){
//1.先产生一个内置对象
let myFormDataObj = new FormData();
//2.添加普通数据(单个单个的编写效率极低)
{#console.log($('#form').serializeArray()) // 可以一次性获取form标签内所有普通字段数据 [{数组},{数组},{数组}]#}
$.each($('#form').serializeArray(),function (index,dataObj){ // 对结果for循环 然后交给后面的函数处理
myFormDataObj.append(dataObj.name,dataObj.value) // {'name':'','value':'',}
})
//3.添加文件数据
myFormDataObj.append('avatar',$('#myfile')[0].files[0])
//4.发送ajax请求
$.ajax({
url:'',
type: 'post',
data:myFormDataObj,
contentType:false,
processData: false,
success: function (args){
if(args.code === 10000){
window.location.href = args.url
}else{
{#console.log(args.msg)#}
let dataObj = args.msg;
// 如何针对性的渲染错误提示 {'username'} id_username
$.each(dataObj,function (k,msgArray){
// 拼接标签的id值
let eleId = '#id_' + k
// 根据id查找标签 修改下面span标签的内容 并给父标签添加错误样式
$(eleId).next().text(msgArray[0]).parent().addClass('has-error')
})
}
}
})
})
//3.给所有的input标签绑定获取焦点事件 移除错误样式
$('input').focus(function (){
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
注册功能后端代码
from django.shortcuts import render, HttpResponse, redirect
from app01 import models
from app01 import myforms
from django.http import JsonResponse
def register_func(request):
# 前后端ajax交互 通常采用字段作为交互对象
back_dict = {'code': 10000, 'msg': ''}
# 1.先产生一个空的form_obj
form_obj = myforms.RegisterForm()
if request.method == 'POST':
form_obj = myforms.RegisterForm(request.POST) # username password confirm_password email csrfmiddlewaretoken
if form_obj.is_valid():
clean_data = form_obj.cleaned_data # 存储符合校验的数据 {username password confirm_password email}
# 将confirm_password键值对移除
clean_data.pop('confirm_password') # {username password email}
# 获取用户上传的头像文件
avatar_obj = request.FILES.get('avatar') # 用户有可能没有上传
if avatar_obj:
clean_data['avatar'] = avatar_obj # {username password email avatar}
# 创建用户数据
models.UserInfo.objects.create_user(**clean_data) # 上述处理字典的目的就是为了创建数据省事
# return HttpResponse('注册成功!!!!')
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())