BBS博客项目
数据库表设计
用户表(uth_user表再扩展几个字段)
phone
avatar
create_time
blog 一对一个人站点表
个人站点表
site_name
site_title
site_theme
标签表
name
blog 一对多个人站点
分类表
name
blog 一对多个人站点
文章表
title
desc
content
create_time
blog 一对多个人站点
tag 多对多标签
category 一对多分类
# 数据库设计优化(******)
# 文章的评论数需要跨表查询到评论表筛选出user和article再count,数据库压力大
# 建comment_num普通字段,通过事务操作评论表时同时comment_num字段加1,再需要文章的评论数只需要拿comment_num字段,不再需要跨表查了
# 当有个字段需要频繁的跨表查才能查出来并且很麻烦的时候就可以写个普通字段来记录,只需要通过事务保证这个普通字段所对应的数字和需要跨的表所记录的数据一致就行
comment_num 普通字段
up_num 普通字段
down_num 普通字段
点赞点踩表
user 一对多用户表(点赞点踩表的一条记录能否对应user表的多条记录(不能),user表的一条记录能否对应点赞点踩表的多条记录(能))
article 一对多文章表
is_up 0/1
#表关系判断
user article is_up
1 1 1
1 2 1
2 1 1
评论表
user 一对多用户表
article 一对多文章表
content
create_time
parent 一对多评论表(自关联),父评论的id,如果有值说明是子评论,如果没有值说明是父评论,parent = models.ForeignKey(to='self', null=True) # 或者 to='Comment'
"""
<div>
{% if comment.parent %}
<p>@{{ comment.parent.user.username }}</p> # comment.parent到了当前评论的父评论
{% endif %}
{{ comment.content }}
</div>
"""
展示用户上传的头像
<body>
<div class="form-group">
<label for="myfile">头像
<img src="/static/img/default.webp" alt="" height="80" style="margin-left: 20px;" id="img">
</label>
<input type="file" name="avatar" id="myfile" style="display: none">
</div>
</body>
<script>
$('#myfile').change(function () {
// 获取用户上传的头像,替换到img标签中
// 1 获取用户上传的文件对象
var fileObj = $(this)[0].files[0];
// 2.利用内置对象FileReader文件阅读器
var fileReader = new FileReader();
// 3.将文件对象交由FileReader文件阅读器读取文件内容
fileReader.readAsDataURL(fileObj); // IO操作速度较慢
// 4.等待文件阅读器读取完文件数据之后,找到img标签,修改src属性
fileReader.onload = function(){$('#img').attr('src',fileReader.result)}
});
</script>
图片验证码
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2 class="text-center">登陆</h2>
<div class="form-group">
<label for="id_username">用户名</label>
<input type="text" name="username" class="form-control" id="id_username">
</div>
<div class="form-group">
<label for="id_password">密码</label>
<input type="password" name="password" class="form-control" id="id_password">
</div>
<div class="form-group">
<label for="id_code">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="id_code" name="code" class="form-control">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="" height="35" width="360" id="id_img">
</div>
</div>
</div>
<button class="btn btn-success" id="id_submit">登陆</button>
<span class="errors" style="color: red"></span>
</div>
</div>
</div>
<script>
$('#id_img').click(function () {
// 图片点击后在原src后加个?,即重新向后端要图片验证码
var oldSrc = $(this).attr('src');
$(this).attr('src',oldSrc + '?')
});
</script>
</body>
from PIL import Image,ImageDraw,ImageFont # pip3 install pillow
"""
Image 生成图片
ImageDraw 在图片上写字
ImageFont 控制字体样式
"""
import random
from io import BytesIO,StringIO
"""
io 内存管理器模块
BytesIO 存储数据 二进制格式
StringIO 存储数据 字符串格式
"""
def get_random():
return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):
# 推导步骤1: 直接将本地图片以二进制方式读取发送给前端
# with open(r'D:\python\BBS\avatar\222.jpg','rb') as f:
# data = f.read()
# return HttpResponse(data)
# 推导步骤2: pillow模块生成图片
# # img_obj = Image.new('RGB',(360,35),'green') # image.new(图片格式,(长,宽),颜色)
# img_obj = Image.new('RGB',(360,35),get_random())
# 先以文件的形式保存下来
# with open('xxx','wb') as f:
# img_obj.save(f,'png')
# 然后再打开这个文件发送
# with open('xxx','rb') as f:
# data = f.read()
# return HttpResponse(data)
# 推导步骤3: BytesIO临时存储文件,避免频繁文件读写操作
# img_obj = Image.new('RGB',(360,35),get_random())
# io_obj = BytesIO() # 实例化产生一个内存管理对象,可以把它当成文件句柄
# img_obj.save(io_obj,'png')
# return HttpResponse(io_obj.getvalue()) # 从内存对象中获取二进制的图片数据
# 推导步骤4: 在生成的图片上写验证码
img_obj = Image.new('RGB', (360,35), get_random())
img_draw = ImageDraw.Draw(img_obj) # 生成一个可以在图片上写字的画笔对象
img_font = ImageFont.truetype('static/font/222.ttf',30) # 字体样式及大小
io_obj = BytesIO()
# 五位验证码,包含数字或大写字母或小写字母
code = ''
for i in range(5):
upper_str = chr(random.randint(65,90))
low_str = chr(random.randint(97,122))
random_int = str(random.randint(0,9))
temp_code = random.choice([upper_str,low_str,random_int])
# 朝图片上写验证码
img_draw.text((70+i*45,0),temp_code,get_random(),font=img_font) # 画笔对象.text((x轴,y轴),写什么,字体颜色,字体样式)
code += temp_code
# 将产生的随机验证码存入session中,以便后续其他视图函数获取并校验验证码
request.session['code'] = code
img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())
前端判断用户是否登陆
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">更多操作 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">修改头像</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">注销</a></li>
</ul>
</li>
{% else %}
<li><a href="/login/">登陆</a></li>
<li><a href="/register/">注册</a></li>
{% endif %}
用户表绑定个人站点表报错
解决:
class UserInfo(AbstractUser):
phone = models.BigIntegerField(null=True,blank=True) # blank是用来告诉admin后台该字段可以不填
文章按年月归档
from django.db.models.functions import TruncMonth
def site(request, username):
date_menu = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month','c') # annotate(month=TruncMonth('create_time'))根据create_time字段按年月生成虚拟字段month
print(date_menu) # <QuerySet [(datetime.date(2023, 1, 1), 2), (datetime.date(2023, 6, 1), 1), (datetime.date(2023, 3, 1), 1)]> 虽然显示年月日但日都显示1号
return render(request, 'site.html', locals())
{% for date in date_menu %}
<p><a href="#">{{ date.0|date:'Y年m月' }}({{ date.1 }})</a></p>
{% endfor %}
KindEditor编辑器
下载:http://kindeditor.net/down.php
文档:http://kindeditor.net/doc.php
下载解压后复制到bbs项目的static文件夹下
使用:
add_article.html
{% extends 'backend/base.html' %}
{% block article %}
<h2>添加文章</h2>
<p>标题</p>
<form action="" method="post">
{% csrf_token %}
<p><input type="text" name="title" class="form-control" id="id_title"></p>
<p>内容(kindeditor编辑器,支持拖放/粘贴上传图片)</p>
<p>
<textarea name="content" id="id_content" cols="30" rows="10"></textarea>
</p>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">文章标签</h3>
</div>
<div class="panel-body">
{% for tag in tag_list %}
<input type="checkbox" name="tag" value="{{ tag.pk }}">{{ tag.name }}
{% endfor %}
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">文章分类</h3>
</div>
<div class="panel-body">
{% for category in category_list %}
<input type="radio" name="category" value="{{ category.pk }}">{{ category.name }}
{% endfor %}
</div>
</div>
<input type="submit" class="btn btn-danger" value="发布">
</form>
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#id_content', {
width: '100%',
height: '450px',
resizeType: 1,
uploadJson : '/upload_img/',
extraFileUploadParams : {
'csrfmiddlewaretoken':'{{ csrf_token }}'
}
});
});
</script>
{% endblock %}
urls.py
path('upload_img/', views.upload_img), # 文本编辑器上传图片功能
views.py
def upload_img(request):
# 接收用户写文章上传的图片资源
if request.method == 'POST':
file_obj = request.FILES.get('imgFile')
# 将文件存入media文件下一个专门用来存储文章图片的文件夹(article_img)
# 1 先拼接出图片所在的文件夹路径
base_path = os.path.join(settings.BASE_DIR,'media','article_img')
if not os.path.exists(base_path):
os.mkdir(base_path)
# 2 拼接图片的具体路径
file_path = os.path.join(base_path,file_obj.name)
# 3 文件操作
with open(file_path,'wb') as f:
for line in file_obj:
f.write(line)
"""
KindEditor返回格式(JSON):
//成功时
{
"error" : 0,
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
"""
back_dic = {
'error':0,
'url':'/media/article_img/%s'%file_obj.name
}
return JsonResponse(back_dic)
处理xss攻击和文章简介获取
-
处理xss攻击即防止用户写script脚本(beautifulsoup4模块简称BS4)
- 安装beautifulsoup4模块:pip3 install beautifulsoup4
- 方式一:获取用户输入的所有script标签直接删除
- 方式二:给script转义
-
文章简介的获取
- 截取150个中文字符
@login_required
def add_article(request):
if request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')
tags = request.POST.getlist('tag')
category_id = request.POST.get('category')
# 先生成一个BeautifulSoup对象
soup = BeautifulSoup(content, 'html.parser') # content:要处理的文档内容; 指定一个解析器thml.parser
for tag in soup.find_all():
# print(tag.name) # 获取当前html页面所有的标签
if tag.name == 'script':
tag.decompose() # 将script标签删除
# print(soup.text) # 获取当前html页面所有的文本
# desc = content[0:150] # html标签也截取了
desc = soup.text[0:150] # 文章简介应该是150个文本内容
article_obj = models.Article.objects.create(title=title, desc=desc, content=str(soup), category_id=category_id, blog=request.user.blog) # content=str(soup)
article_obj.tag.add(*tags)
return redirect('/backend/')
tag_list = models.Tag.objects.filter(blog=request.user.blog)
category_list = models.Category.objects.filter(blog=request.user.blog)
return render(request, 'backend/add_article.html', locals())
标签:12,obj,img,random,request,博客,user,article,BBS
From: https://www.cnblogs.com/jlllog/p/17115191.html