BBS(仿造博客园项目)
项目开发基本流程
1.需求分析
2.架构设计
3.分组开发
4.提交测试
5.交付上线
项目流程
仿造博客园项目
核心:文章的增删改查
表分析
先确定表的数量 再确定表的基础字段 最后确定表的外键字段
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
"""
评论点赞点踩表:记录哪个用户给那篇文章的哪条评论点赞或点踩
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
评论字段(评论主键)>>>:外键字段
点赞点踩
外键字段
用户表
用户与个人站点是一对一外键关系
个人站点表
文章表
文章表与个人站点表是一对多外键关系
文章表与文章分类表是一对多外键关系
文章表与文章标签表是多对多外键关系
'''
数据库字段优化设计:我们想统计文章的评论数 点赞数
通过文章数据跨表查询到文章评论表中对应的数据统计即可
但是文章需要频繁的展示 每次都跨表查询的话效率极低
我们在文章表中再创建三个普通字段
之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三 个普通字段即可
'''
文章评论数
文章点赞数
文章点踩数
文章分类表
文章分类与个人站点是一对多外键关系
文章标签表
文章标签与个人站点是一对多外键关系
注册功能
用户注册
1.渲染前端标签
2.校验用户数据
3.展示错误提示
ps:forms组件、modelform组件
单独开设py文件编写 解耦合!!!
登录功能
img标签的src属性
1.可以直接填写图片地址
2.还可以填写一个路由 会自动朝该路由发送get请求
如果结果是图片的二进制数据 那么自动渲染图片
pip install pillow -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
创建表相关代码
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
"""用户表,进行扩展,自定义字段"""
phone = models.CharField(max_length=32, verbose_name='手机号', null=True)
avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='头像')
register_time = models.DateTimeField(auto_now_add=True, verbose_name='注册时间')
"""外键字段"""
"""用户表与个人站点表一对一关系"""
site = models.OneToOneField(to='Site', on_delete=models.CASCADE, null=True)
class Site(models.Model):
"""个人站点表"""
site_name = models.CharField(max_length=32, verbose_name='站点名称')
site_title = models.CharField(max_length=32, verbose_name='站点标题')
site_theme_css = models.TextField(verbose_name='站点css', null=True)
site_theme_js = models.TextField(verbose_name='站点js', null=True)
site_theme_html = models.TextField(verbose_name='站点html', null=True)
site_publish_info = models.TextField(verbose_name='公告', null=True)
class Article(models.Model):
"""文章表"""
title = models.CharField(max_length=32, verbose_name='文章标题')
summary = models.TextField(verbose_name='文章摘要')
content = models.TextField(verbose_name='文章内容')
publist_time = models.DateTimeField(auto_now_add=True, verbose_name='文章发布时间')
"""数据库字段优化"""
"""因为经常要统计下面字段数量,虽然跨表可以查询,但是浪费数据库资源,所有在进行数据增加或删除数据,对下面字段进行数据的修改
以达到,减少数据库资源消耗,而进行优化的目的
"""
up_num = models.IntegerField(verbose_name='点赞数', default=0)
down_num = models.IntegerField(verbose_name='点踩数', default=0)
comment_num = models.IntegerField(verbose_name='评论数', default=0)
"""外键字段"""
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
classify = models.ForeignKey(to='Classify', on_delete=models.CASCADE, null=True)
"""多对多字段,一个文章可以有很多标签,一个标签页可以有很多文章
采用半自动创建多对多
"""
labels = models.ManyToManyField(to='Label',
through='Article2Label',
through_fields=('article', 'label'))
class Article2Label(models.Model):
"""多对多手动第三张表,后期可以扩展字段"""
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
label = models.ForeignKey(to='Label', on_delete=models.CASCADE, null=True)
class Classify(models.Model):
"""文章分类表"""
name = models.CharField(max_length=32, verbose_name='分类名称')
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
class Label(models.Model):
"""文章标签表"""
name = models.CharField(max_length=32, verbose_name='文章标签表')
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
class UpAndDownArticle(models.Model):
"""文章点赞点踩表"""
"""记录:哪个用户给那篇文章点赞或点踩"""
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE) # 外键字段关联userinfo表主键
article = models.ForeignKey(to='Article', on_delete=models.CASCADE) # 外键字段
is_up = models.BooleanField(verbose_name='点赞点踩') # 写True False 存 1 0
class Comment(models.Model):
"""文章评论表"""
"""记录:哪个用户给那片文章评论的内容与时间"""
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
content = models.TextField(verbose_name='评论内容')
comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
"""评论显示什么浏览器,以及评论者的ip"""
user_agent = models.CharField(max_length=64, verbose_name='用户评论浏览器')
user_ip = models.TextField(verbose_name='用户ip/ipv4/ipv6')
"""自关联字段"""
"""在有时侯,某些字段需要关联所在表的数据就需要使用到自关联字段
id user content parent
1 1 哈哈哈 null
2 2 不要 1
3 3 你怎么管这么多 2
"""
parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True)
"""数据库字段优化"""
up_num = models.IntegerField(verbose_name='评论被点赞数', default=0)
down_num = models.IntegerField(verbose_name='评论被点踩', default=0)
class UpAndDownComment(models.Model):
"""评论点赞点踩表"""
"""记录:哪个用户给那篇文章里的哪个评论点赞或点踩"""
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE) # 外键字段关联userinfo表主键
article = models.ForeignKey(to='Article', on_delete=models.CASCADE) # 外键字段
comment = models.ForeignKey(to='Comment', on_delete=models.CASCADE)
is_up = models.BooleanField(verbose_name='点赞点踩') # 写True False 存 1 0
注册功能相关代码
注册form组件
from django import forms
from django.forms import widgets
from BBS import models
class Register_from(forms.Form):
"""用户注册forms类"""
# validators正则匹配校验用的 required内容为空用的
username = forms.CharField(max_length=16, min_length=6, label='用户名',
error_messages={
'max_length': '用户名最长为16位',
'min_length': '用户名最短为6位',
'required': '用户名不能为空'
},
widget=widgets.TextInput(attrs={'class': 'form-control'})
)
password = forms.CharField(max_length=16, min_length=6, label='密码',
error_messages={
'max_length': '密码最长为16位',
'min_length': '密码最短为6位',
'required': '密码不能为空'
},
widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
confirm_password = forms.CharField(max_length=16, min_length=6, label='确认密码',
error_messages={
'max_length': '密码最长为16位',
'min_length': '密码最短为6位',
'required': '密码不能为空'
},
widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
email = forms.EmailField(label='邮箱', error_messages={
'required': '邮箱不能为空'
},
widget=widgets.EmailInput(attrs={'class': 'form-control'})
)
def clean_username(self):
"""局部钩子校验用户名是否已存在"""
username = self.cleaned_data.get('username')
user_obj = models.UserInfo.objects.filter(username=username)
if user_obj:
self.add_error('username', '用户已存在')
return username
def clean(self):
"""全局钩子,因为要使用到两个数据所以使用全局钩子,进行两次密码一致性的校验"""
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if password != confirm_password:
self.add_error('confirm_password', '两次密码不一致')
return self.cleaned_data
注册视图类相关代码
项目使用模块
from django.shortcuts import render, HttpResponse, redirect, reverse
from django.http import JsonResponse
# Create your views here.
from django.views import View
from BBS.myforms.register import Register_from
from django.contrib import auth # 校验模块
from django.contrib.auth.decorators import login_required # 登录装饰器
from django.views.decorators.csrf import csrf_exempt, csrf_protect # 取消或者校验csrf
from django.utils.decorators import method_decorator # 给视图类添加装饰器用的
from django.db.models import Q, F # 跟查询相关
后端代码
class Register_class(View):
"""用户注册视图类"""
def get(self, request):
form_obj = Register_from()
return render(request, 'bbs/registerPage.html', locals())
def post(self, request):
re_dict = {
'code': 10000,
'msg': ''
}
print(request.POST, request.FILES)
form_obj = Register_from(request.POST)
if form_obj.is_valid():
clean_data = form_obj.cleaned_data
clean_data.pop('confirm_password')
file_obj = request.FILES.get('avatar')
if file_obj:
clean_data['avatar'] = file_obj
models.UserInfo.objects.create_user(**clean_data)
re_dict['url'] = reverse('login_url')
else:
re_dict['code'] = 10001
re_dict['msg'] = form_obj.errors
return JsonResponse(re_dict)
html代码
<!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="row">
<div class="col-md-6 col-md-offset-3">
<h1 class="text-center h1">用户注册</h1>
<form id="form">
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group">
<label for="{{ form.auto_id }}">{{ form.label }}</label>
{{ form }}
<span class="pull-right" style="color: red"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="myfile">头像 <img src="{% static 'img/default.png' %}" alt="" id="myimg"
width="100"></label>
<input type="file" id="myfile" style="display: none">
</div>
<input type="button" id="mybtn" class="btn btn-block btn-success" value="注册">
</form>
</div>
</div>
</div>
<script>
$('#myfile').change(function () {
let redfile_obj = new FileReader()
redfile_obj.readAsDataURL(this.files[0])
redfile_obj.onload = function () {
$('#myimg').attr('src', redfile_obj.result)
}
})
$('#mybtn').click(function () {
let formdata_obj = new FormData()
let file_obj = $('#myfile')[0].files[0]
formdata_obj.append('avatar', file_obj)
$.each($('#form').serializeArray(), function (index, data_obj) {
formdata_obj.append(data_obj.name, data_obj.value)
})
console.log(formdata_obj)
$.ajax({
url: '',
type: 'post',
data: formdata_obj,
//取消属性与数据
contentType: false,
processData: false,
success: function (arg) {
if (arg.code === 10000) {
window.location.href = arg.url
} else {
$.each(arg.msg, function (name, value) {
let id_name = '#id_' + name
$(id_name).next().text(value).parent().addClass('has-error')
})
}
}
})
})
$('input').click(function () {
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
登录功能
后端代码
def login_func(request):
"""用户登录功能"""
if request.method == 'POST':
re_dict = {
'code': 10000,
'msg': ''
}
captcha: str = request.POST.get('captcha')
username = request.POST.get('username')
password = request.POST.get('password')
if request.session.get('captcha').lower() == captcha.lower():
user_obj = auth.authenticate(request, username=username, password=password)
if user_obj:
auth.login(request, user_obj)
re_dict['url'] = reverse('home_url')
else:
re_dict['code'] = 10001
re_dict['msg'] = {
'password': '用户名或密码错误'
}
else:
re_dict['code'] = 10002
re_dict['msg'] = {
'captcha': '验证码错误',
}
if not username:
re_dict['msg']['username'] = '用户名不能为空'
if not password:
re_dict['msg']['password'] = '密码不能为空'
return JsonResponse(re_dict)
return render(request, 'bbs/loginPage.html', locals())
前端代码
<!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="row">
<div class="col-md-6 col-md-offset-3">
<h1 class="text-center h1">用户登录</h1>
<form id="form">
{% csrf_token %}
<div class="form-group">
<label for="username">用户名</label>
<input type="text" name="username" id="username" class="form-control">
<span class="pull-right" style="color: red"></span>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="text" name="password" id="password" class="form-control">
<span class="pull-right" style="color: red"></span>
</div>
<div class="form-group">
<label for="captcha">验证码 </label>
<div class="row">
<div class="col-md-6">
<input type="text" name="captcha" class="form-control" id="captcha">
<span class="pull-right" style="color: red"></span>
</div>
<div class="col-md-6" id="div_captcha" onclick="$('#captcha_img').attr('src','{% url 'captcha_url' %}?'+Math.random())"><img src="{% url 'captcha_url' %}" alt="" width="260" height="35" id="captcha_img">
<p class="pull-right btn-link"><a href="javascript:void(0);"
id="change">看不清?点击换一张</a></p>
</div>
</div>
</div>
<input type="button" id="loginBtn" class="btn btn-block btn-success" value="登录">
</form>
</div>
</div>
</div>
<script>
$('#loginBtn').click(function () {
$.ajax({
url: '',
type: 'post',
dataType: 'json',
data: $('#form').serializeArray(),
success: function (args) {
console.log(args)
if (args.code === 10000) {
window.location.href = args.url
} else{
$('#div_captcha').click()
$.each(args.msg,function (name,value) {
let id_name = '#'+name
$(id_name).next().text(value).parent().addClass('has-error')
})
}
}
})
})
$('input').click(function () {
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
验证码功能代码
后端代码
from PIL import Image, ImageFont, ImageDraw
from io import BytesIO, StringIO # StringIO字符串内存管理对象 BytesIO字节内存管理对象
import random
def get_random_color():
"""获取随机rgb颜色"""
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
def captcha_func(request):
"""生成验证码存储到session中,返回验证码图片"""
imgobj = Image.new('RGB', (260, 35), get_random_color()) # 生成一个背景图片
imgfont = ImageFont.truetype(r'static/font/云峰静龙行书.ttf', size=32) # 生成字体
imgdraw = ImageDraw.ImageDraw(imgobj) # 生成一个画笔对象
code = ''
for num in range(5):
"""剩余5位数验证吗"""
choice_big = chr(random.randint(65, 90))
choice_small = chr(random.randint(97, 122))
choice_int = str(random.randint(0, 9))
choice_code = random.choice([choice_big, choice_small, choice_int])
imgdraw.text((num * 40 + 40, -3), choice_code, get_random_color(), imgfont) # 在图像上写字
code += choice_code
io_obj = BytesIO() # 生成字节内存管理对象
imgobj.save(io_obj, 'png') # 把图像保存到内存管理对象
request.session['captcha'] = code
request.session.set_expiry(60 * 5)
return HttpResponse(io_obj.getvalue())
标签:obj,name,项目,models,博客园,length,文章,BBS,verbose
From: https://www.cnblogs.com/clever-cat/p/17020741.html