博客平台
程序员文章站
2022-04-15 13:33:53
...
一、 数据库设计
用户表:
class UserInfo(models.Model):
"""
用户表
"""
nid = models.BigAutoField(primary_key=True)
username = models.CharField(verbose_name='用户名', max_length=32, unique=True)
password = models.CharField(verbose_name='密码', max_length=64)
nickname = models.CharField(verbose_name='昵称', max_length=32)
email = models.EmailField(verbose_name='邮箱', unique=True)
avatar = models.ImageField(verbose_name='头像', upload_to='static/imgs') # 指定图片上传路径
create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
fans = models.ManyToManyField(verbose_name='粉丝们', # 从用户表自动生成粉丝表,自关联、
to='UserInfo',
through='UserFans',
related_name='f',
through_fields=('user', 'follower'))
互粉关系表:
class UserFans(models.Model):
"""
互粉关系表
"""
user = models.ForeignKey(verbose_name='博主', to='UserInfo', to_field='nid', related_name='users')
follower = models.ForeignKey(verbose_name='粉丝', to='UserInfo', to_field='nid', related_name='followers')
class Meta:
unique_together = [ # 联合唯一约束
('user', 'follower'),
]
博客信息表:
class Blog(models.Model):
"""
博客信息
"""
nid = models.BigAutoField(primary_key=True)
title = models.CharField(verbose_name='个人博客标题', max_length=64)
site = models.CharField(verbose_name='个人博客前缀', max_length=32, unique=True)
theme = models.CharField(verbose_name='博客主题', max_length=32)
user = models.OneToOneField(to='UserInfo', to_field='nid') # 等价于一对多外键关联 + 唯一性约束,一个博客对应一个用户
文章分类表:
class Category(models.Model):
"""
博主个人文章分类表
"""
nid = models.AutoField(primary_key=True)
title = models.CharField(verbose_name='分类标题', max_length=32)
blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid') # 一对多,一个博客有多个分类
标签表:
class Tag(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField(verbose_name='标签名称', max_length=32)
blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid') # 一对多,一个博客有多个标签
文章创建表:
class Article(models.Model):
"""
文章创建表
"""
nid = models.BigAutoField(primary_key=True)
title = models.CharField(verbose_name='文章标题', max_length=128)
summary = models.CharField(verbose_name='文章简介', max_length=255)
read_count = models.IntegerField(default=0) # 阅读次数
comment_count = models.IntegerField(default=0) # 评论数
up_count = models.IntegerField(default=0) # 点赞数
down_count = models.IntegerField(default=0) # 点踩数
create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid')
category = models.ForeignKey(verbose_name='文章类型', to='Category', to_field='nid', null=True)
type_choices = [
(1, "Python"),
(2, "Linux"),
(3, "OpenStack"),
(4, "GoLang"),
]
article_type_id = models.IntegerField(choices=type_choices, default=None) # 主菜单归属
tags = models.ManyToManyField( # 多对多,一篇文章对应多个标签,一个标签可应用于多篇文章
to="Tag",
through='Article2Tag',
through_fields=('article', 'tag'),
)
文章标签关系表:
class Article2Tag(models.Model):
article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid')
tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid')
class Meta:
unique_together = [ # 联合唯一约束
('article', 'tag'),
]
文章详细表:
class ArticleDetail(models.Model):
"""
文章详细表
"""
content = models.TextField(verbose_name='文章内容')
article = models.OneToOneField(verbose_name='所属文章', to='Article', to_field='nid') # 一对一
文章点赞表:
class UpDown(models.Model):
"""
文章顶或踩
"""
article = models.ForeignKey(verbose_name='文章', to='Article', to_field='nid')
user = models.ForeignKey(verbose_name='赞或踩用户', to='UserInfo', to_field='nid')
up = models.BooleanField(verbose_name='是否赞') # 是否已经点过赞
class Meta:
unique_together = [
('article', 'user'),
]
评论表:
class Comment(models.Model):
"""
评论表
"""
nid = models.BigAutoField(primary_key=True)
article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid')
user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid')
create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
content = models.CharField(verbose_name='评论内容', max_length=255)
reply = models.ForeignKey(verbose_name='回复评论', to='self', related_name='back', null=True)
二、首页布局(BootStrap应用)
urls:
url(r'^all/(?P<type_id>\d+)/', views.index), # 正则接收分类菜单值
url(r'',views.index),
views:
def index(request, *args, **kwargs):
condition={}
type_id = int(kwargs.get('type_id')) if kwargs.get('type_id') else None # 获得请求中的id数据
if type_id:
condition['article_type_id'] = type_id
type_choices = models.Article.type_choices # 获得菜单分类列表信息
article_list = models.Article.objects.filter(**condition) # 传入字典,获得分类对应的所有文章
return render(request, 'index.html',{
'type_choices_list':type_choices,
'article_list': article_list,
'type_id':type_id,
})
index.html (采用Bootstrap进行布局)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css">
<link rel="stylesheet" href="/static/css/commons.css">
</head>
<body>
{#导航条部分#}
<nav class="navbar navbar-default no-radius">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">老子的技术论坛</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
{# 选择项的背景块显示判断#}
{% if type_id %}
<li><a href="/">全部 <span class="sr-only">(current)</span></a></li>
{% else %}
<li class="active"><a href="/">全部 <span class="sr-only">(current)</span></a></li>
{% endif %}
{% for item in type_choices_list %}
{% if item.0 == type_id %}
<li class="active"><a href="/all/{{ item.0 }}/">{{ item.1 }}</a></li>
{% else %}
<li><a href="/all/{{ item.0 }}/">{{ item.1 }}</a></li>
{% endif %}
{% endfor %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">登录</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{#采用12个栅格进行内容布局部分#}
<div class="container">
<div class="col-md-8">
<div class="article-list">
{# 静态布局内容样版#}
<div class="article-item">
<h3 class="art-head"><a>英杰大战黄鳝</a></h3>
{# bootpstrap提供clearfix,相当于在该标签后面添加<div style="clear: both"></div>#}
<div class="art-body clearfix">
<a class="left"><img src="/static/imgs/yj.png"></a>
近期一个项目需要用到低功耗蓝牙的开发,由于之前没有蓝牙开发的经验,发现网上关于蓝牙开发的资料不多,不是随便描述一下就是已经过时的,在此整理一篇低功耗蓝牙的入门资料,能够完成使用蓝牙的接受和发送数据。
低功耗蓝牙 (BLE,
</div>
<div class="art-footer">
<a>
<span class="glyphicon glyphicon-user"></span>
张英杰
</a>
</div>
</div>
{# 从数据库中动态显示文章信息#}
{% for article in article_list %}
<div class="article-item">
<h3 class="art-head"><a>{{ article.title }}</a></h3>
<div class="art-body clearfix">
<a class="left"><img src="{{ article.blog.user.avatar }}"></a>
{# 文章标题#}
{{ article.summary }}
</div>
<div class="art-footer">
<a>
<span class="glyphicon glyphicon-user"></span>
张英杰
</a>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">Panel heading without title</div>
<div class="panel-body">
<p>text</p>
<p>text</p>
<p>text</p>
<p>text</p>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Panel heading without title</div>
<div class="panel-body">
<p>text</p>
<p>text</p>
<p>text</p>
<p>text</p>
</div>
</div>
</div>
</div>
</body>
</html>
三、 登陆验证码(图片验证码生成)
url:
url(r'^login/', views.login),
url(r'^check/', view.chieck_code),
views:
# 登陆页面
def loging(request):
if request.method == 'GET':
return render(request,'login.html')
else:
# 获得用户表单信息
# 验证用户输入的验证码
return redirct('index.html') # 登陆成功,跳转到首页
# 生成图片验证码
def check_code(request):
# # # 以硬盘作为存储媒介的方式处理图片
# # # 创建图片
# # from PIL import Image
# # f = open('code.png', 'wb')
# # img = Image.new(mode='RGB', size=(120,30), color=(234,234,234))
# # img.save(f, 'png') # 将图片字节数据写入文件句柄中,文件格式为png到硬盘中存储起来
# # f.close()
# #
# # # 从硬盘中读取图片成字节,将字节格式的内容发送给前端
# # f = open('code.png','rb')
# # data = f.read()
#
# # 以内存作为存储媒介的方式处理图片(推荐,不占用硬盘容量)
# from PIL import Image,ImageDraw, ImageFont
# from io import BytesIO
# f = BytesIO() # 开辟内存空间
# img = Image.new(mode='RGB', size=(120,30), color=(184,146,141))
# draw = ImageDraw.Draw(img, mode='RGB') # 创建画笔
# draw.point([10,10], fill='red') # 画圆点的位置及颜色
# draw.point([20,20], fill=(120,250,51)) # RGB数值可以设置成随机颜色
# draw.line([50,100,10,15], fill='red') # 画线条
# draw.arc([0,0,30,30], 0, 360, fill='red') # 画圆
# # font = ImageFont.truetype('kumo.ttf', 29) # 设置字体及大小
# # draw.text([0,0], 'python', 'black', font=font) # 写字
#
# # 生成随机字符串
# import random
# # char_list = []
# # for i in range(5):
# # char = chr(random.randint(65, 90)) # 随机生成整数
# # char_list.append(char)
# # v = ''.join(char_list)
# # 生成随机字符串的简写方式
# # v = ''.join([chr(random.randint65, 90) for i in range(5)])
#
# # 写入随机字符
# char_list = []
# for i in range(5):
# char = chr(random.randint(65, 90)) # 随机生成整数
# char_list.append(char)
# font = ImageFont.truetype('kumo.ttf', 29) # 设置字体及大小
# draw.text([i*24, 0], char, (random.randint(0,255), random.randint(0,255), random.randint(0,255)), font=font) # 写字
# img.save(f, 'png') # 将图片保存到内在中
# data = f.getvalue() # 从内存中读取出图片字节格式数据后发送给用户
#
# code = ''.join(char_list) # 生成的随机字符串列表
# request.session['code'] = code # 保存到session中对用户输入的验证码进行匹配
# 使用自定义验证码组件
from io import BytesIO
from utils.random_check_code import check_code
img , code = check_code() # 调用自定义的组件获得img对象及字符串内容
stream = BytesIO()
img.save(stream, 'png') # 将生成的图片写入内存中
request.session['code']= code # 将生成的验证码写入session中对用户进行验证
return HttpResponse(stream.getvalue()) # 从内存中取出图片数据发送给前端
# return HttpResponse(data)
自定义生成图片验证码的组件:
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
def check_code(width=120, height=30, char_length=5, font_file='kumo.ttf', font_size=28):
code = []
img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
def rndChar():
"""
生成随机字母
:return:
"""
return chr(random.randint(65, 90))
def rndColor():
"""
生成随机颜色
:return:
"""
return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
# 写文字
font = ImageFont.truetype(font_file, font_size)
for i in range(char_length):
char = rndChar()
code.append(char)
h = random.randint(0, 4)
draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
# 写干扰点
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
# 写干扰圆圈
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
# 画干扰线
for i in range(5):
x1 = random.randint(0, width)
y1 = random.randint(0, height)
x2 = random.randint(0, width)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill=rndColor())
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,强化图片效果
return img, ''.join(code)
html:
<link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css"/>
<style>
.login {
width: 600px;
margin: 0 auto;
padding: 20px;
margin-top: 80px;
}
</style>
</head>
<body>
<div class="login">
<form class="form-horizontal">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">用户名</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail3" placeholder="用户名">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="密码">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">验证码</label>
<div class="col-sm-3">
<input type="password" class="form-control" id="inputPassword3" placeholder="验证码">
</div>
<div class="col-sm-5">
<img src="/check_code/" style="width: 120px; height: 30px;" onclick="changeCode(this);" title="点击更新" >
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> Remember me
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">登录</button>
</div>
</div>
</form>
</div>
js内容:
<script src="/static/jquery-1.12.4.js"></script>
<script>
# 点击刷新验证码生成
function changeCode(ths) {
{# 重新添加新的值后,会自动再发送一次请求#}
ths.src = ths.src + '?';
}
四、注册中的图像上传
urls:
url(r'^register/', views.register),
views:
def register(request):
return render(request, 'register.html',)
html:
<link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css"/>
<style>
.register {
width: 600px;
margin: 0 auto;
padding: 20px;
margin-top: 80px;
}
.size{
width: 80px;
height: 80px;
}
</style>
</head>
<body>
<div class="register">
<div style="position: relative;height: 80px;width: 80px">
<img class="size" id='previewImg' src="/static/imgs/default.png">
<input type="file" id = 'imgSelect' style="position: absolute;width: 80px;height: 80px;top: 0;left: 0;opacity: 0">
</div>
<form class="form-horizontal" action="/register/" method="post" >
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">用户名</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail3" placeholder="用户名">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="密码">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">确认密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="确认密码">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">验证码</label>
<div class="col-sm-3">
<input type="password" class="form-control" id="inputPassword3" placeholder="验证码">
</div>
<div class="col-sm-5">
<img src="/check_code/" style="width: 120px; height: 30px;">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> Remember me
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">注册</button>
</div>
</div>
</form>
</div>
js:
<script src="/static/jquery-1.12.4.js"></script>
<script>
$(function () { # 页面加载时,加载该函数中的内容
bindAvatar();
})
/* 完成各种图片预览的兼容模式*/
function bindAvatar() {
if(window.URL.createObjectURL){
bindAvatar2();
}else if(window.FileReader){
bindAvatar3()
}else {
bindAvatar1()
}
}
/* Ajax直接上传到后台,不会有预览 */
function bindAvatar1(){
$('#imgSelect').change(function(){
var obj = $(this)[0].files[0];
// Ajax发送后台,并获取路径
// img.src = 获取路径
})
}
/* 本地上传预览方式一 */
function bindAvatar2() {
$('#imgSelect').change(function () {
var obj = $(this)[0].files[0]; /*将jquery对象转换为dom对象进行取值*/
var v = window.URL.createObjectURL(obj); /* 通过浏览器获得图片资源位置的方法(低版本浏览器不支持该方法)*/
$('#previewImg').attr('src', v );
$('#previewImg').load(function () { /*图片加载完成时会自动触发该函数*/
window.URL.revokeObjectURL(v); /* 手动清理内存中的图片数据*/
})
})
}
/* 本地上传预览方式二 */
function bindAvatar3() {
$('#imgSelect').change(function () {
var obj = $(this)[0].files[0]
var reader = new FileReader();
reader.readAsDataURL(obj); /* 将图片读取到内存当中*/
reader.onload = function () { /* 图片在内存中加载完成触发事件,并自动清理内存*/
$('#previewImg').attr('src', this.result);
}
})
}
</script>
五、Form组件实现注册
urls:
url(r'^register/', views.register),
自定义Form组件:
from django.forms import fields
from django.forms import widgets
from django.forms import Form
from django.forms import ValidationError
class RegisterForm(Form):
username = fields.CharField(
widget=widgets.TextInput(attrs={'class': 'form-control'})
)
password = fields.CharField(
widget=widgets.PasswordInput(attrs={'class': 'form-control'})
)
password2 = fields.CharField(
widget=widgets.PasswordInput(attrs={'class': 'form-control'})
)
avatar = fields.FileField(widget=widgets.FileInput(attrs={'id': "imgSelect", 'class': "f1"}) # 文件上传组件
)
code = fields.CharField(
widget=widgets.TextInput(attrs={'class': 'form-control'})
)
def __init__(self, request,*args, **kwargs): # 通过构造方法将request传入
super(RegisterForm, self).__init__(*args, **kwargs) # 调用父类Form中的构造方法来重新加载父类
self.request = request
def clean_code(self): # 通过内置的钩子对字段进行扩展定制,对验证码进行匹配
input_code = self.cleaned_data['code'] # 从提交表单字典中获得该字段的值
session_code = self.request.session.get('code') # 获得session中的验证码,采用get('code'),若无数据返回None,而不会报错
print(input_code, session_code)
if input_code == session_code:
return input_code # 重新将字段的值返回给cleaned_data
raise ValidationError('验证码错误') # 匹配失败,将错误信息传入验证码字段对应的错误列表中
def clean(self): # 通过内置的钩子对表单中的多个字段进行扩展,对两次输入的密码进行验证
p1 = self.cleaned_data.get('password')
p2 = self.cleaned_data.get('password2')
if p1 == p2:
return None # 返回值,不进行任何操作
# self.add_error(None,ValidationError('密码不一致')) # key可以为空,会将错误信息传入公共错误列表中,而非字段对应的错误列表中
self.add_error('password2', ValidationError('密码不一致')) # 将错误信息传入给password2字段对应的错误列表中
views:
from app01.forms import RegisterForm
from django.core.exceptions import NON_FIELD_ERRORS
def register(request):
if request.method == 'GET':
obj = RegisterForm(request) # 生成前端表单组件
return render(request, 'register.html',{'obj':obj})
else:
# 验证码操作
obj = RegisterForm(request,request.POST,request.FILES)
if obj.is_valid():
# 添加数据到数据库
pass
else:
"""
{
__all__: [错误1,错误2]
user: [错误1,错误2]
password: [错误1,错误2]
}
"""
# print(obj.errors['__all__'])
# print(obj.errors[NON_FIELD_ERRORS]) # '__all__'
return render(request, 'register.html', {'obj': obj})
html:
<div class="register">
<form class="form-horizontal" action="/register/" method="post" novalidate>
{# /* 从公共错误信息列表中获取两次密码输入不匹配的错误提示信息*/#}
<h3>{{ obj.non_field_errors }}</h3>
<div style="position: relative;height:80px;width: 80px;">
<img id="previewImg" style="height:80px;width: 80px;" src="/static/imgs/default.png">
{{ obj.avatar }}
</div>
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">用户名</label>
<div class="col-sm-10">
{{ obj.username }}
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">密码</label>
<div class="col-sm-10">
{{ obj.password }}
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">确认密码</label>
<div class="col-sm-10">
{{ obj.password2 }}{{ obj.errors.password2.0 }}
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">验证码</label>
<div class="col-sm-3">
{{ obj.code}}{{ obj.errors.code.0 }}
</div>
<div class="col-sm-5">
<img src="/check_code/" style="width: 120px; height: 30px;" onclick="changeCode(this);" title="点击更新" >
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> Remember me
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">注册</button>
</div>
</div>
</form>
</div>
六、博客主页(包含一对一、一对多、多对多查询)
urls:
url(r'(\w+)/', views.home), # 接收博客后缀名参数
views:
def home(request, site):
"""
访问个人博客主页 http://127.0.0.1:8000/fangshaowei/
:param request: 请求相关信息
:return: 个人博客后缀,如: www.xxx.com/xxxxx/
"""
blog = models.Blog.objects.filter(site=site).first() # 通过后缀名获得对应的博客
if not blog:
return redirect('/')
----------
# 获得当前博客用户信息(一对一操作)
obj = models.Blog.objects.filter(site=site).first() # 以QuerySet对象格式为结果查询:一对一正向操作,通过博客后缀获取对应的用户名
print(obj.user.username) # username = linhai
obj = models.UserInfo.objects.filter(username='linhai').first() # 以QuerySet对象格式为结果查询:一对一反向操作,通过用户名获得博客后缀
print(obj.blog.site) # site = LH
user_info = models.Blog.objects.filter(site=site).values('site', 'user__nickname') # 以字典列表格式为结果查询:一对一正向操作,通过博客表跨表到用户信息表中获取该表中的用户昵称和博客后缀名。
v = models.UserInfo.objects.filter(blog__site=site).values('blog__site', 'nickname') # 以字典列表格式为结果查询:一对一反向操作,通过用户信息表,跨表到博客表(通过表名小写__字段名,blog__site反向跨表)作为条件查询。
print(user_info) # 两者结果一样 [{'blog__site': 'LH', 'nickname': '小清新'}]
print(v) # 两者结果一样 [{'blog__site': 'LH', 'nickname': '小清新'}]
----------
# 获得当前博客所有分类及分类总数(一对多操作)
# 第一种实现方式:反向连表查询
cate_list = models.Category.objects.filter(blog=blog) # 获得博客所有目录
for item in cate_list:
category_title = item.title # 每个目录的名称
c = item.article_set.all().count() # 反向操作获得目录对应的所有文章总数
# 第二种实现方式:通过分组
from django.db.models import Count
models.Article.objects.filter(blog=blog).values('category_id', 'category__title').annotate(c=Count('nid')) # 分组后的统计数字段名为c, 获得3个字段,分别是category_id, category__title, c
----------
# 获取当前博客所有的标签(多对多操作)
# 获取当前博客对应的标签分类(标签名+当前标签下所有文章的数量):
# 一:手动生成第3张关联表时的分组查询方式:(tag__blog=blog表示从关联表中跨表到tag表中的blog字段
models.Article2Tag.objects.filter(tag__blog=blog).values('tag_id','tag__title').annotate(c = Count('id')) # 获取tag_id字段无需跨表,因为Article2Tag表中含有该字段
# 二: 通过Article类中的m2m字段自动生成第3张关联表时的分组查询方式:
models.Article.objects.filter(blog=blog).values('tag__id', 'tag__title').annotate(c = Count('nid')) # 获取tag__id字段需要跨表,因为Article表中无该字段
----------
# 获取当前博客所有的时间分类对应的文章(extra 添加额外)
# 操作MySQL数据库时的写法:
data_list= models.Article.objects.filter(blog=blog).extra(select={'c':"date_format(create_time,'%%Y-%%m')"}).values('c').annotate(ct=Count('c'))
# select={'c':"date_format(create_time,'%%Y-%%m')" 等价于为查出的数据表添加一个函数处理后的新字段
"""
nid xx create_time c ct
1 x 2018-01-01 11:11 2018-01 统计数
"""
# 操作SQLlite数据库时的写法:
date_list = models.Article.objects.filter(blog=blog).extra(select={'c': "strftime('%%Y-%%m',create_time)"}).values(
'c').annotate(ct=Count('c'))
return render(request,'blog.html',省略……)
七、多级评论
后台实现原理解析:(利用列表中的内存地址共享)
# # 列表的特性:引用数据类型,共用内存地址,
# data = [
# [11,33,44],
# [77,88,99]
# ]
# data[0].append(data[1]) # 将data[1]的内存地址添加到data[0]中,data[0]中的data[1]与原来的data[1]共用一份内存地址
# print(data) # [[11, 33, 44, [77, 88, 99]], [77, 88, 99]]
# data[1].append(00) # 往data[1]中添加数据00时,data[0]中的data[1]的数据也会同步更新
# print(data) # [[11, 33, 44, [77, 88, 99, 0]], [77, 88, 99, 0]]
########################################################
msg_list = [
{'id':1,'content':'xxx','parent_id':None},
{'id':2,'content':'xxx','parent_id':None},
{'id':3,'content':'xxx','parent_id':None},
{'id':4,'content':'xxx','parent_id':1},
{'id':5,'content':'xxx','parent_id':4},
{'id':6,'content':'xxx','parent_id':2},
{'id':7,'content':'xxx','parent_id':5},
{'id':8,'content':'xxx','parent_id':3},
]
# 一、将字典列表格式重新组装成字典格式,key为字典中的id号,两者中的值对应同一内存地址;
msg_list_dic={
}
for item in msg_list:
item ['child'] = [] # 先给msg_list列表添加child空的k,v值:child:[],为后续根据parent_id添加到对应id字典中做准备
# print(msg_list) # [{'id': 1, 'content': 'xxx', 'parent_id': None, 'child': []},……],
# 也可写成列表生成式:v=[ row.setdefault('child',[]) for row in msg_list]
msg_list_dic[item['id']] = item # 将列表转换为字典,key为msg_list列表中的id号,value为msg_list列表中的字典,msg_list_dic中的value值与msg_list中的字典两者共用同一内存地址
# print(msg_list_dic) # {1: {'id': 1, 'content': 'xxx', 'parent_id': None, 'child': []}, 2:{}……},
# 补充:字典查询速度比列表快,字典有hash索引,通过算法快速定位内存地址进行查询
# 二、 根据msg_list中parent_id的值找到对应的字典中的key,将该条数据添加至有索引的字典中,完成最后组装
result =[] # 用来保存parent_id为Nnoe时的前3条数据:
for item in msg_list:
pid = item['parent_id'] # 获得每条字典中parent_id的值
if pid:
msg_list_dic[pid]['child'].append(item) # 根据parent_id的索引号,从msg_list列表中取出对应的数据向msg_list_dic字典中的child中添加
else:
result.append(item) # 该item中的数据中,parent_id为Nnoe,result就是最后结果
print(result)
"""
[
{'id': 1, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 4, 'content': 'xxx', 'parent_id': 1, 'child': [{'id': 5, 'content': 'xxx', 'parent_id': 4, 'child': [{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []}]}]}]},
{'id': 2, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 6, 'content': 'xxx', 'parent_id': 2, 'child': []}]},
{'id': 3, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 8, 'content': 'xxx', 'parent_id': 3, 'child': []}]}
]
"""
print(msg_list)
"""
[
{'id': 1, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 4, 'content': 'xxx', 'parent_id': 1, 'child': [{'id': 5, 'content': 'xxx', 'parent_id': 4, 'child': [{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []}]}]}]},
{'id': 2, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 6, 'content': 'xxx', 'parent_id': 2, 'child': []}]},
{'id': 3, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 8, 'content': 'xxx', 'parent_id': 3, 'child': []}]},
{'id': 4, 'content': 'xxx', 'parent_id': 1, 'child': [{'id': 5, 'content': 'xxx', 'parent_id': 4, 'child': [{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []}]}]},
{'id': 5, 'content': 'xxx', 'parent_id': 4, 'child': [{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []}]},
{'id': 6, 'content': 'xxx', 'parent_id': 2, 'child': []},
{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []},
{'id': 8, 'content': 'xxx', 'parent_id': 3, 'child': []}
]
"""
print(msg_list_dic)
"""
{
1: {'id': 1, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 4, 'content': 'xxx', 'parent_id': 1, 'child': [{'id': 5, 'content': 'xxx', 'parent_id': 4, 'child': [{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []}]}]}]},
2: {'id': 2, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 6, 'content': 'xxx', 'parent_id': 2, 'child': []}]},
3: {'id': 3, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 8, 'content': 'xxx', 'parent_id': 3, 'child': []}]},
4: {'id': 4, 'content': 'xxx', 'parent_id': 1, 'child': [{'id': 5, 'content': 'xxx', 'parent_id': 4, 'child': [{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []}]}]},
5: {'id': 5, 'content': 'xxx', 'parent_id': 4, 'child': [{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []}]},
6: {'id': 6, 'content': 'xxx', 'parent_id': 2, 'child': []},
7: {'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []},
8: {'id': 8, 'content': 'xxx', 'parent_id': 3, 'child': []}
}
"""
通过后台来实现多级评论应用:
urls:
url(r'^(?P<site>\w+)/(?P<nid>\d+).html$', views.atricle), # nid表示文章id
views:
from utils.comment import comment_tree,comment
# msg_list 假设从数据库中获得的所有数据
msg_list = [
{'id': 1, 'content': '写的太好了', 'parent_id': None},
{'id': 2, 'content': '你说得对', 'parent_id': None},
{'id': 3, 'content': '顶楼上', 'parent_id': None},
{'id': 4, 'content': '你眼瞎吗', 'parent_id': 1},
{'id': 5, 'content': '我看是', 'parent_id': 4},
{'id': 6, 'content': '鸡毛', 'parent_id': 2},
{'id': 7, 'content': '你是没呀', 'parent_id': 5},
{'id': 8, 'content': '惺惺惜惺惺想寻', 'parent_id': 3},
]
comment_list = comment(msg_list) # 将数据进行重新组装成
comment_str = comment_tree(comment_list) # 组装成html格式数据
自定义comment组件来组装数据格式:
"""
多级评论生成组件
"""
def comment(msg_list):
"""
:param comment_list: 传入的参数格式为字典列表
:return:
"""
# 数据格式重新组装
msg_list_dic = { }
for item in msg_list:
item['child'] = [] # 先给msg_list列表添加child空的k,v值:child:[],为后续根据parent_id添加到对应id字典中做准备
msg_list_dic[item['id']] = item # 将列表转换为字典,key为msg_list列表中的id号,value为msg_list列表中的字典,msg_list_dic中的value值与msg_list中保存的字典列表两者共用同一内存地址
# print(msg_list_dic) # {1: {'id': 1, 'content': 'xxx', 'parent_id': None, 'child': []}, 2:{}……},
result = [] # 用来保存parent_id为Nnoe时的前3条数据:
for item in msg_list:
pid = item['parent_id'] # 获得每条字典中parent_id的值
if pid:
msg_list_dic[pid]['child'].append(item) # 根据parent_id的索引号,从msg_list列表中取出对应的数据向msg_list_dic字典中的child中添加
else:
result.append(item) # 该item中的数据中,parent_id为Nnoe,result就是最后结果
return result
"""
result列表中的结果:
[
{'id': 1, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 4, 'content': 'xxx', 'parent_id': 1, 'child': [{'id': 5, 'content': 'xxx', 'parent_id': 4, 'child': [{'id': 7, 'content': 'xxx', 'parent_id': 5, 'child': []}]}]}]},
{'id': 2, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 6, 'content': 'xxx', 'parent_id': 2, 'child': []}]},
{'id': 3, 'content': 'xxx', 'parent_id': None, 'child': [{'id': 8, 'content': 'xxx', 'parent_id': 3, 'child': []}]}
]
"""
# 组装多级评论表数据
def comment_tree(comment_list):
commnt_str = '<div class=comment>' # 组装头部
for row in comment_list:
tp1='<div class="content">%s</div>'%(row['content']) # 组装第一级的评论内容
commnt_str += tp1
if row['child']: # 如果有子评论
child_str = comment_tree(row['child']) # 递归调用,组装出所有子评论中的内容
commnt_str += child_str
commnt_str += '</div>' # 组装尾部
return commnt_str
html:
……
<h3>评论</h3>
{{ comment_str|safe}}
通过前端来实现多级评论:
urls:
url(r'^comment-(\d+).html$', views.comment), # (\d+)表示匹配至少一个数字,此处表示文章的id
views:
def commnet(request, nid ):
response = {'status':True, 'msg':None, 'data':None}
try:
msg_list = [ # 模拟从数据库中取出的值
{'id': 1, 'content': '写的太好了', 'parent_id': None},
{'id': 2, 'content': '你说得对', 'parent_id': None},
{'id': 3, 'content': '顶楼上', 'parent_id': None},
{'id': 4, 'content': '你眼瞎吗', 'parent_id': 1},
{'id': 5, 'content': '我看是', 'parent_id': 4},
{'id': 6, 'content': '鸡毛', 'parent_id': 2},
{'id': 7, 'content': '你是没呀', 'parent_id': 5},
{'id': 8, 'content': '惺惺惜惺惺想寻', 'parent_id': 3},
]
msg_list_dic={}
for item in msg_list:
item['child']=[] # 给每一条数据添加child的key
msg_list_dic[item['id']]=item # 组装成新的字典格式数据结构,key为id
result=[]
for item in msg_list:
pid = item['parent_id'] # 判断是否有父标签,如有则添加到新字典中对应父标签的Id
if pid:
msg_list_dic[pid]['child'].append(item) # 根据parent_id,添加到新字典数据格式中的child中
else:
result.append(item)
response['data'] = result
except Exception as e :
response['status'] = False
response['msg'] = str(e)
import json
return HttpResponse(json.dumps(response))
html:
<h3>评论</h3>
<div id="commentArea">
</div>
js:
<script src="/static/jquery-1.12.4.js"></script>
<script>
String.prototype.Format=function (arg) { /*对String原型进行扩展,自定义Format函数进行字符串格式化*/
/*
this 代表当前字符串: "<div class='content'>{content}</div>"
* replace 替换函数
* /\{(\w+)\}/g 匹配 <div class='content'>{content}</div>中的{content}的几个字母内容。 g代表全局,匹配所有带{ }的字母内容
* k 表示获得{content}, kk表示传入的字典数据中的key,此时Key为content
* arg[kk] 表示从传入参数的字典数据中获得key为content的值进行返回
* 最终完成对this代表的字符串中的{content}进行替换
*/
this.replace(/\{(\w+)\}/g, function (k,kk) {
return arg[kk];
})
}
$(function () { /*页面加载时执行的函数*/
// 发送Ajax请求,获取所有评论信息
// 列表
// js生成结构
$.ajax({
url:'/comment-{{ obj.nid }}.html',
type:'GET',
dataType:'JSON',
success:function (arg) {
if(arg.status){
var comment = commentTree(arg.data)
$('#commentArea'). append(comment)
}else {
alert(arg.msg)
}
}
})
})
function commentTree(commentList) {
var comment_str = "<div class='comment'>"; /* 组装第一条div*/
$.each(commentList, function (k,row) { /* 对列表进行循环*/
var temp = "<div class='content'>{content}</div>".Format({content:row.content}) /*传入字典,key为content, value为row.content,也表示数据库中的每条评论的具体内容*/
comment_str += temp;
if(row.child.length > 0){ /*如果child有值*/
conment_str += commentTree(row.child) /*对child进行递归调用*/
}
});
comment_str += '</div>';
return comment_str;
}
八、个人博客主页展示
九、博客筛选功能
url:
url(r'^lizhi-(?P<article_type_id>\d+)-(?P<category_id>\d+)-(?P<tags__nid>\d+).html$', views.lizhi),
views:
def lizhi(request,**kwargs):
condition = {}
for k,v in kwargs.items():
kwargs[k] = int(v)
if v != '0':
condition[k] = v
print(condition)
# 大分类
type_list = models.Article.type_choices
# 个人分类
category_list = models.Category.objects.filter(blog_id=1)
# 个人标签
tag_list = models.Tag.objects.filter(blog_id=1)
# 进行筛选
condition['blog_id'] = 1
article_list = models.Article.objects.filter(**condition)
return render(request,'lizhi.html',{
'type_list':type_list,
'category_list':category_list,
'tag_list':tag_list,
'article_list':article_list,
'kwargs':kwargs
})
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.condition a{
display: inline-block;
padding: 5px;
}
.condition a.active{
background-color: #204d74;
color: white;
}
</style>
</head>
<body>
<h3>筛选</h3>
<div class="condition">
大大分类:
{% if kwargs.article_type_id == 0 %}
<a class="active" href="/lizhi-0-{{ kwargs.category_id }}-{{ kwargs.tags__nid }}.html">全部</a>
{% else %}
<a href="/lizhi-0-{{ kwargs.category_id }}-{{ kwargs.tags__nid }}.html">全部</a>
{% endif %}
{% for row in type_list %}
{% if row.0 == kwargs.article_type_id %}
<a class="active" href="/lizhi-{{ row.0 }}-{{ kwargs.category_id }}-{{ kwargs.tags__nid }}.html">{{ row.1 }}</a>
{% else %}
<a href="/lizhi-{{ row.0 }}-{{ kwargs.category_id }}-{{ kwargs.tags__nid }}.html">{{ row.1 }}</a>
{% endif %}
{% endfor %}
</div>
<div class="condition">
个人分类:
<a href="#">全部</a>
{% for row in category_list %}
<a href="{{ row.nid }}">{{ row.title }}</a>
{% endfor %}
</div>
<div class="condition">
个人标签:
<a href="#">全部</a>
{% for row in tag_list %}
<a href="{{ row.nid }}">{{ row.title }}</a>
{% endfor %}
</div>
<h3>结果</h3>
{% for row in article_list %}
<div>
<h4><a href="#">{{ row.title }}</a></h4>
<div>{{ row.summary }}</div>
</div>
{% endfor %}
</body>
</html>
十、组合筛选
url:
url(r'^(?P<site>\w+)/(?P<nid>\d+).html$', views.article),
views:
def article(request,site,nid):
blog = models.Blog.objects.filter(site=site).first()
if not blog:
return redirect('/')
# 按照:分类,标签,时间
# 分类
category_list = models.Article.objects.filter(blog=blog).values('category_id','category__title').annotate(ct=Count('nid'))
# 标签
tag_list = models.Article2Tag.objects.filter(article__blog=blog).values('tag_id','tag__title').annotate(ct=Count('id'))
# 时间
date_list = models.Article.objects.filter(blog=blog).extra(select={'ctime':"strftime('%%Y-%%m',create_time)"}).values('ctime').annotate(ct=Count('nid'))
# select xxx as x
obj = models.Article.objects.filter(blog=blog,nid=nid).first()
# ####################### 评论 #############################
msg_list = [
{'id':1,'content':'写的太好了','parent_id':None},
{'id':2,'content':'你说得对','parent_id':None},
{'id':3,'content':'顶楼上','parent_id':None},
{'id':4,'content':'你眼瞎吗','parent_id':1},
{'id':5,'content':'我看是','parent_id':4},
{'id':6,'content':'鸡毛','parent_id':2},
{'id':7,'content':'你是没呀','parent_id':5},
{'id':8,'content':'惺惺惜惺惺想寻','parent_id':3},
]
msg_list_dict = {}
for item in msg_list:
item['child'] = []
msg_list_dict[item['id']] = item
# #### msg_list_dict用于查找,msg_list
result = []
for item in msg_list:
pid = item['parent_id']
if pid:
msg_list_dict[pid]['child'].append(item)
else:
result.append(item)
# ########################### 打印 ###################
from utils.comment import comment_tree
comment_str = comment_tree(result)
# 递归组装数据
def comment_tree(comment_list):
"""
:param result: [ {id,:child:[xxx]},{}]
:return:
"""
comment_str = "<div class='comment'>"
for row in comment_list:
tpl = "<div class='content'>%s</div>" %(row['content'])
comment_str += tpl
if row['child']:
#
child_str = comment_tree(row['child'])
comment_str += child_str
comment_str += "</div>"
return comment_str
return render(
request,
'article.html',
{
'blog':blog,
'category_list':category_list,
'tag_list':tag_list,
'date_list':date_list,
'obj':obj,
'comment_str':comment_str
}
)
十一、文章点赞功能
url
url(r'^up.html$', views.up),
views:
def up(request):
# 是谁?文章?赞或踩 1赞,0踩
# 是谁?当前登录用户,session中获取
# 文章?
response = {'status':1006,'msg':None}
try:
user_id = request.session.get('user_id')
article_id = request.POST.get('nid')
val = int(request.POST.get('val'))
obj = models.UpDown.objects.filter(user_id=user_id,article_id=article_id).first()
if obj:
# 已经赞或踩过
pass
else:
from django.db import transaction
with transaction.atomic(): # 事务管理
if val:
models.UpDown.objects.create(user_id=user_id,article_id=article_id,up=True)
models.Article.objects.filter(nid=article_id).update(up_count=F('up_count')+1)
else:
models.UpDown.objects.create(user_id=user_id,article_id=article_id,up=False)
models.Article.objects.filter(nid=article_id).update(down_count=F('down_count')+1)
except Exception as e:
response['status'] = False
response['msg'] = str(e)
return HttpResponse(json.dumps(response))
html:
<a onclick="updown(this,{{ obj.nid }},1);">
<span>赞</span>
<i>{{ obj.up_count }}</i>
</a>
<a onclick="updown(this,{{ obj.nid }},0);">
<span>踩</span>
<i>{{ obj.down_count }}</i>
</a>
<script>
function updown(ths,nid,val){
$.ajax({
url: '/up.html',
data:{'val':val,'nid':nid,'csrfmiddlewaretoken':'{{ csrf_token }}'},
type: "POST",
dataType:'JSON',
success:function(arg){
if(arg.status){
// 显示赞个数+1
}else{
// 显示错误信息
}
}
})
}
function up(ths,nid){
$.ajax({
url: '/up.html',
data:{'val':1,'nid':nid,'csrfmiddlewaretoken':'{{ csrf_token }}'},
type: "POST",
dataType:'JSON',
success:function(arg){
if(arg.status){
// 显示赞个数+1
}else{
// 显示错误信息
}
}
})
}
<script>
十二、显示文章最终页
url:
url(r'^(?P<site>\w+)/(?P<nid>\d+).html$', views.article),
views:
def article(request,site,nid):
blog = models.Blog.objects.filter(site=site).first()
if not blog:
return redirect('/')
# 按照:分类,标签,时间
# 分类
category_list = models.Article.objects.filter(blog=blog).values('category_id','category__title').annotate(ct=Count('nid'))
# 标签
tag_list = models.Article2Tag.objects.filter(article__blog=blog).values('tag_id','tag__title').annotate(ct=Count('id'))
# 时间
date_list = models.Article.objects.filter(blog=blog).extra(select={'ctime':"strftime('%%Y-%%m',create_time)"}).values('ctime').annotate(ct=Count('nid'))
# select xxx as x
obj = models.Article.objects.filter(blog=blog,nid=nid).first()
# ####################### 评论 #############################
msg_list = [
{'id':1,'content':'写的太好了','parent_id':None},
{'id':2,'content':'你说得对','parent_id':None},
{'id':3,'content':'顶楼上','parent_id':None},
{'id':4,'content':'你眼瞎吗','parent_id':1},
{'id':5,'content':'我看是','parent_id':4},
{'id':6,'content':'鸡毛','parent_id':2},
{'id':7,'content':'你是没呀','parent_id':5},
{'id':8,'content':'惺惺惜惺惺想寻','parent_id':3},
]
msg_list_dict = {}
for item in msg_list:
item['child'] = []
msg_list_dict[item['id']] = item
# #### msg_list_dict用于查找,msg_list
result = []
for item in msg_list:
pid = item['parent_id']
if pid:
msg_list_dict[pid]['child'].append(item)
else:
result.append(item)
# ########################### 打印 ###################
from utils.comment import comment_tree
comment_str = comment_tree(result)
return render(
request,
'article.html',
{
'blog':blog,
'category_list':category_list,
'tag_list':tag_list,
'date_list':date_list,
'obj':obj,
'comment_str':comment_str
}
)
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.comment{
margin-left: 30px;
}
</style>
</head>
<body>
<div class="c1">
<div class="c11">{{ blog.user.nickname }}</div>
<div class="c12">{{ blog.title }}</div>
</div>
<div class="c2">
<h3>分类</h3>
<ul>
{% for item in category_list %}
<li>
<a href="/{{ blog.site }}/category/{{ item.category_id }}/">{{ item.category__title }}({{ item.ct }})</a>
</li>
{% endfor %}
</ul>
</div>
<div class="c3">
<h3>标签</h3>
<ul>
{% for item in tag_list %}
<li>
<a href="/{{ blog.site }}/tag/{{ item.tag_id }}/">{{ item.tag__title }}({{ item.ct }})</a>
</li>
{% endfor %}
</ul>
</div>
<div>
<h3>时间</h3>
<ul>
{% for item in date_list %}
<li>
<a href="/{{ blog.site }}/date/{{ item.ctime }}/">{{ item.ctime }}({{ item.ct }})</a>
</li>
{% endfor %}
</ul>
</div>
<div>
<h3><a>{{ obj.title }}</a></h3>
{{ obj.articledetail.content|safe }}
</div>
<a onclick="updown(this,{{ obj.nid }},1);">
<span>赞</span>
<i>{{ obj.up_count }}</i>
</a>
<a onclick="updown(this,{{ obj.nid }},0);">
<span>踩</span>
<i>{{ obj.down_count }}</i>
</a>
<h3>评论</h3>
<div id="commentArea">
</div>
{# {{ comment_str|safe }}#}
<script src="/static/jquery-1.12.4.js"></script>
<script>
/*
1. 调用对象方法时,通过调用类的prototype中的方法,可以扩展
2. 正则表达式 /\w+/g
3. 字符串replace
''.replace('alex','sb');
''.replace(/\w+/,'sb');
''.replace(/\w+/g,'sb');
''.replace(/(\w+)/g,function(k,kk){return 11;});
*/
String.prototype.Format = function(arg){
/*
this,当前字符串 "i am {name1}, age is {age9}"
arg,Format方法传入的参数 {name:'alex',age:18}
return,格式化之后获取的新内容 i am alex, age is 18
*/
var temp = this.replace(/\{(\w+)\}/g,function(k,kk){
return arg[kk];
});
return temp;
};
$(function(){
// 发送Ajax请求,获取所有评论信息
// 列表
// js生成结构
$.ajax({
url: '/comments-{{ obj.nid }}.html',
type:'GET',
dataType:"JSON",
success:function(arg){
if(arg.status){
/*
* [ {},{},{}]
*
* */
var comment = commentTree(arg.data);
$('#commentArea').append(comment);
}else{
alert(arg.msg);
}
}
})
});
function commentTree(commentList){
var comment_str = "<div class='comment'>";
$.each(commentList,function(k,row){
// var temp = "<div class='content'>"+ row.content +"</div>";
var temp = "<div class='content'>{content}</div>".Format({content:row.content});
comment_str += temp;
if(row.child.length>0){
comment_str += commentTree(row.child);
}
});
comment_str += '</div>';
return comment_str;
}
function updown(ths,nid,val){
$.ajax({
url: '/up.html',
data:{'val':val,'nid':nid,'csrfmiddlewaretoken':'{{ csrf_token }}'},
type: "POST",
dataType:'JSON',
success:function(arg){
if(arg.status){
// 显示赞个数+1
}else{
// 显示错误信息
}
}
})
}
function down(ths,nid){
$.ajax({
url: '/up.html',
data:{'val':0,'nid':nid,'csrfmiddlewaretoken':'{{ csrf_token }}'},
type: "POST",
dataType:'JSON',
success:function(arg){
if(arg.status){
// 显示踩个数+1
}else{
// 显示错误信息
}
}
})
}
</script>
</body>
</html>
十三、kindeditor组件使用
url:
url(r'^wangzhe.html$', views.wangzhe),
views:
CONTENT = ""
from app01.forms import ArticleForm
def wangzhe(request):
if request.method == "GET":
obj = ArticleForm()
return render(request,'wangzhe.html',{'obj':obj})
else:
obj = ArticleForm(request.POST)
if obj.is_valid():
content = obj.cleaned_data['content']
global CONTENT
CONTENT = content
print(content)
return HttpResponse('...')
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form method="POST" action="/wangzhe.html" novalidate>
{% csrf_token %}
<p>
文章标题
{{ obj.title }}
</p>
<div>
<div>文章内容</div>
<div>
{{ obj.content }}
</div>
</div>
<input type="submit" value="提交" />
</form>
<script src="/static/kindeditor-4.1.10/kindeditor-all.js"></script>
<script>
KindEditor.create("#i1",{
width: "700px",
height: "300px",
resizeType:1,
uploadJson: '/upload_img.html', # 上传图片
extraFileUploadParams:{
"csrfmiddlewaretoken":"{{ csrf_token }}"
}
})
</script>
</body>
</html>
十四、kindeditor上传图片
url:
url(r'^upload_img.html$', views.upload_img),
views:
def upload_img(request):
import os
upload_type = request.GET.get('dir')
file_obj = request.FILES.get('imgFile')
file_path = os.path.join('static/imgs',file_obj.name)
with open(file_path,'wb') as f:
for chunk in file_obj.chunks():
f.write(chunk)
dic = {
'error': 0,
'url': '/' + file_path,
'message': '错误了...'
}
import json
return HttpResponse(json.dumps(dic))
十五、BeautifulSoup实现kindeditor数据过滤
url:
url(r'^wangzhe.html$', views.wangzhe),
views:
CONTENT = ""
from app01.forms import ArticleForm
def wangzhe(request):
if request.method == "GET":
obj = ArticleForm()
return render(request,'wangzhe.html',{'obj':obj})
else:
obj = ArticleForm(request.POST)
if obj.is_valid():
content = obj.cleaned_data['content']
global CONTENT
CONTENT = content
print(content)
return HttpResponse('...')
form组件:
class ArticleForm(Form):
title = fields.CharField(max_length=64)
content = fields.CharField(
widget=widgets.Textarea(attrs={'id':'i1'})
)
def clean_content(self):
old = self.cleaned_data['content']
from utils.xss import xss
return xss(old)
防xss脚本攻击(对js代码过滤):
from bs4 import BeautifulSoup
def xss(old):
valid_tag = {
'p': ['class','id'],
'img':['src'],
'div': ['class']
}
soup = BeautifulSoup(old,'html.parser')
tags = soup.find_all()
for tag in tags:
if tag.name not in valid_tag:
tag.decompose()
if tag.attrs:
for k in list(tag.attrs.keys()): # {id:'i1',a=123,b=999}
if k not in valid_tag[tag.name]:
del tag.attrs[k]
content_str = soup.decode()
return content_str
十六、权限管理数据库设计
from django.db import models
class User(models.Model):
"""
用户表
"""
username = models.CharField(verbose_name='用户名', max_length=32)
password = models.CharField(verbose_name='密码', max_length=64)
email = models.EmailField(verbose_name='邮箱')
def __str__(self):
return self.username
class Role(models.Model):
"""
角色表
"""
caption = models.CharField(verbose_name='角色', max_length=32)
def __str__(self):
return self.caption
class User2Role(models.Model):
"""
用户角色关系表
"""
user = models.ForeignKey(User, verbose_name='用户', related_name='roles',on_delete='')
role = models.ForeignKey(Role, verbose_name='角色', related_name='users',on_delete='')
def __str__(self):
return '%s-%s' % (self.user.username, self.role.caption,)
class Menu(models.Model):
"""
菜单表
"""
caption = models.CharField(verbose_name='菜单名称', max_length=32)
parent = models.ForeignKey('self', verbose_name='父菜单', related_name='p', null=True, blank=True,on_delete='')
def __str__(self):
prev = ""
parent = self.parent
while True:
if parent:
prev = prev + '-' + str(parent.caption)
parent = parent.parent
else:
break
return '%s-%s' % (prev, self.caption,)
class Permission(models.Model):
"""
权限
"""
caption = models.CharField(verbose_name='权限', max_length=32)
url = models.CharField(verbose_name='URL正则', max_length=128)
menu = models.ForeignKey(Menu, verbose_name='所属菜单', related_name='permissions',null=True,blank=True,on_delete='')
def __str__(self):
return "%s-%s" % (self.caption, self.url,)
class Action(models.Model):
"""
操作:增删改查
"""
caption = models.CharField(verbose_name='操作标题', max_length=32)
code = models.CharField(verbose_name='方法', max_length=32)
def __str__(self):
return self.caption
class Permission2Action2Role(models.Model):
"""
角色权限操作关系表
"""
permission = models.ForeignKey(Permission, verbose_name='权限URL', related_name='actions',on_delete='')
action = models.ForeignKey(Action, verbose_name='操作', related_name='permissions',on_delete='')
role = models.ForeignKey(Role, verbose_name='角色', related_name='p2as',on_delete='')
class Meta:
unique_together = (
('permission', 'action', 'role'),
)
def __str__(self):
return "%s-%s-%s" % (self.permission, self.action, self.role,)
标签搜索过滤
url:
url(r'^(?P<site>\w+)/(?P<key>((tag)|(date)|(category)))/(?P<val>\w+-*\w*)/', views.filter),
views:
def filter(request,site,key,val):
blog = models.Blog.objects.filter(site=site).first()
if not blog:
return redirect('/')
# 按照:分类,标签,时间
# 分类
category_list = models.Article.objects.filter(blog=blog).values('category_id','category__title').annotate(ct=Count('nid'))
# 标签
tag_list = models.Article2Tag.objects.filter(article__blog=blog).values('tag_id','tag__title').annotate(ct=Count('id'))
# 时间
date_list = models.Article.objects.filter(blog=blog).extra(select={'ctime':"strftime('%%Y-%%m',create_time)"}).values('ctime').annotate(ct=Count('nid'))
# select xxx as x
if key == 'category':
article_list = models.Article.objects.filter(blog=blog,category_id=val)
elif key == 'tag':
# v= models.Article.objects.filter(blog=blog,article2tag__tag__title=val)
# print(v.query)
# 自定义第三张表,
# 自己反向关联
# v= models.Article.objects.filter(blog=blog,article2tag__tag=val)
# 通过M2M字段
# v= models.Article.objects.filter(blog=blog,tags__nid=val)
article_list = models.Article.objects.filter(blog=blog,tags__nid=val)
else:
# val = 2017-09
# 2018- 01-12 11:11:111
article_list = models.Article.objects.filter(blog=blog).extra(where=["strftime('%%Y-%%m',create_time)=%s"],params=[val,]) # 以时间作为查询条件
return render(
request,
'filter.html',
{
'blog':blog,
'category_list':category_list,
'tag_list':tag_list,
'date_list':date_list,
'article_list':article_list,
}
)
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="/static/css/theme/{{ blog.theme }}.css" />
</head>
<body>
<div class="c1">
<div class="c11">{{ blog.user.nickname }}</div>
<div class="c12">{{ blog.title }}</div>
</div>
<div class="c2">
<h3>分类</h3>
<ul>
{% for item in category_list %}
<li>
<a href="/{{ blog.site }}/category/{{ item.category_id }}/">{{ item.category__title }}({{ item.ct }})</a>
</li>
{% endfor %}
</ul>
</div>
<div class="c3">
<h3>标签</h3>
<ul>
{% for item in tag_list %}
<li>
<a href="/{{ blog.site }}/tag/{{ item.tag_id }}/">{{ item.tag__title }}({{ item.ct }})</a>
</li>
{% endfor %}
</ul>
</div>
<div>
<h3>时间</h3>
<ul>
{% for item in date_list %}
<li>
<a href="/{{ blog.site }}/date/{{ item.ctime }}/">{{ item.ctime }}({{ item.ct }})</a>
</li>
{% endfor %}
</ul>
</div>
<div class="c4">
{% for row in article_list %}
<div>
<a href="/{{ blog.site }}/{{ row.nid }}.html">{{ row.title }}</a>
</div>
{% endfor %}
</div>
</body>
</html>
十七、中间件权限控制
urls:
# 用户权限
url(r'^auth-login.html$', views2.login), # 登录获取用户权限的请求路由
url(r'^auth-index(-\d*)*.html$', views2.index), # 正则匹配多种数据请求格式
views:
from django.shortcuts import render
from django.shortcuts import HttpResponse
# Create your views here.
def login(request):
if request.method == 'GET':
return render(request, 'login2.html')
else:
user_permission_dict = { # 模拟从数据库当中取出数据后重新组装的数据结构
'/auth-index.html': ["GET", "POST", "DEL", "Edit"],
'/order.html': ["GET", "POST", "DEL", "Edit"],
'/auth-index(-\d+)*.html': ["GET", "POST", "DEL", "Edit"],
}
request.session['user_permission_dict'] = user_permission_dict # 将用户登录成功后获得的权限字典写入session当中
return HttpResponse('登陆成功!')
def index(request,id):
print('input',id)
print(request.path_info)
return HttpResponse('登陆成功,并拥有了该URL的访问权限')
自定义权限验证中间件M1(使用时需手动注册进setting配置文件中):
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
import re
class M1(MiddlewareMixin): # 自定义权限控制的中间件
print('input')
def process_request(self, request, *args, **kwargs):
valid = ['/auth-login.html', '/index.html'] # 设置白名单,免权限验证
if request.path_info not in valid:
print(request.path_info) # /auth-index
action = request.GET.get('md') # 获得get请求中?md后面的操作数据, http://127.0.0.1:8001/auth-index?md=GET
user_perssion_dict = request.session.get('user_permission_dict') # 获得用户session中保存的权限数据
print(user_perssion_dict)
if not user_perssion_dict:
return HttpResponse('无权限')
flag = False
if request.path_info != '/favicon.ico':
for k, v in user_perssion_dict.items():
if re.match(k, request.path_info): # 进行正则匹配,用户的请求url是否在权限session中
if action in v: # 用户请求中的操作是否在权限session中
flag = True
break
if not flag:
return HttpResponse('无权限')
html:
<form action="/auth-login.html" method="post">
{% csrf_token %}
<input type="text" name="user">
<input type="submit" value="提交">
</form>
十八、权限挂靠菜单及前后端的实现
urls:
# 权限管理菜单和权限挂靠的实现
url(r'^auth-menu.html$', views2.menu),
views:
def menu(request):
user = models.User.objects.filter(username='tom').first() # 通过用户名获得用户对象
# role_list = models.Role.objects.filter(user2role__user=user) # 获得用户对应的所有角色(写法一)
role_list = models.Role.objects.filter(users__user=user) # 获得用户对应的所有角色(写法二)
# 获得用户的所有权限
permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission_id',
'permission__url',
'permission__menu_id',
'permission__caption').distinct()
"""
获得结果格式如下:
[
{permission_id:1, 'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },
{permission_id:2, 'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 2 },
{permission_id:3, 'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 3 },
{permission_id:4, 'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 4 },
……
]
"""
all_menu_list = models.Menu.objects.all().values('id', 'caption', 'parent_id') # 获得所有菜单:用于处理成权限关联后的菜单
"""
[
{'id':1, 'caption':'菜单1', parent_id:None},
{'id':2, 'caption':'菜单2', parent_id:None},
{'id':3, 'caption':'菜单3', parent_id:None},
{'id':4, 'caption':'菜单1-1', parent_id:1},
……
]
"""
# 重新组装菜单,将列表转换成字典格式
all_menu_dict = {}
for row in all_menu_list:
row['child'] = [] # 添加子代
row['status'] = [] # 是否显示菜单
row['opened'] = [] # 是否默认打开状态
all_menu_dict[row['id']] = row
"""
{
1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[]},
2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
4:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
}
"""
##### 将权限挂靠到菜单上 ########
for per in permission_list:
if not per['permission__menu_id']: # 权限中是否含有菜单Id,没有则表示该权限未与菜单有关联
continue
# 将权限转换成字典格式
item = {
'id': per['permission_id'],
'caption': per['permission__caption'],
'menu_id': per['permission__menu_id'],
'url': per['permission__url'],
'status': True, # 需要显示该菜单下的子菜单
'opened': False,
}
# if re.match(per['permission__url'],"/tom.html"): # 正则匹配
if re.match(per['permission__url'], request.path_info): # 判断访问的url与菜单栏中对应的url是否匹配
item['opened'] = True # 菜单栏为打开状态
menu_id = item['menu_id'] #
all_menu_dict[menu_id]['child'].append(item) # 通过权限表中的菜单id,将权限挂靠到对应的菜单上
# 将当前权限的父级的显示状态status改为True
temp = menu_id # 等价于父级ID
while not all_menu_dict[temp]['status']: # 菜单显示状态为False时表示可修改,减少重复修改次数
all_menu_dict[temp]['status'] = True
temp = all_menu_dict[temp]['parent_id'] # 获得当前菜单中parent_id中的值,可再上升一个层级进行显示状态的修改
if not temp:
break
# 将当前权限父级opened的打开状态改为True
if item['opened']: # 如果当前权限处于打开状态
temp1 = menu_id # 父级ID
while not all_menu_dict[temp1]['opened']: # 菜单打开状态为False时表示可修改,减少重复修改次数
all_menu_dict[temp1]['opened'] = True
temp1 = all_menu_dict[temp1]['parent_id'] # 将父级id再上升一个层级进行r打开状态的修改
if not temp1:
break
############# 处理菜单和菜单之间的等级关系 ############
"""
all_menu_dict = {
1:{'id':1, 'caption':'菜单1', parent_id:None,status:True,opened:True,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },]},
2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
4:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
}
all_menu_list= [
{'id':1, 'caption':'菜单1', parent_id:None,status:True,opened:True,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 }, {'id':4, 'caption':'菜单1-1', parent_id:1,status:True,opened:True,child:[]},]},
{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
]
"""
result = []
for row in all_menu_list: # 此处就循环列表
pid = row['parent_id']
if pid:
all_menu_dict[pid]['child'].append(row)
else:
result.append(row) # 获得的是pid为None的数据,也表示得到最顶层的菜单
##################### 结构化处理结果 #####################
for row in result:
print(row)
##################### 通过结构化处理结果,生成菜单开始 #####################
"""
result = [
{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},2:{'id':2, 'caption':'菜单2', parent_id:1,status:False,opened:False,child:[]},]}
{'id':2, 'caption':'菜单2', parent_id:None,status:True,opened:False,child:[]},
{'id':3, 'caption':'菜单3', parent_id:None,status:true,opened:False,child:[url...]},
]
status = False, 不生产该菜单/权限
opened = True, 等于true时不加属性。 等于flase时加hide属性
# 组装后的结构如下:
<div class='menu-item'>
<div class='menu-header'>菜单1</div>
<div class='menu-body %s'>
<a>权限1</a>
<a>权限2</a>
<div class='menu-item'>
<div class='menu-header'>菜单11</div>
<div class='menu-body hide'>
<a>权限11</a>
<a>权限12</a>
</div>
</div>
</div>
</div>
<div class='menu-item'>
<div class='menu-header'>菜单2</div>
<div class='menu-body hide'>
<a>权限1</a>
<a>权限2</a>
</div>
</div>
"""
def menu_tree(menu_list):
# 菜单模版
tpl1 = """
<div class='menu-item'>
<div class='menu-header'>{0}</div>
<div class='menu-body {2}'>{1}</div>
</div>
"""
# 权限模版
tpl2 = """
<a href='{0}' class='{1}'>{2}</a>
"""
menu_str = ''
for menu in menu_list:
if not menu['status']:
continue
# 字符串格式化权限URL
if menu.get('url'):
menu_str += tpl2.format(menu['url'], 'active' if menu['opened'] else '', menu['caption'])
else:
# 字符串格式化菜单
if menu['child']:
child_html = menu_tree(menu['child']) # 递归调用
else:
child_html = ''
menu_str += tpl1.format(menu['caption'], child_html, '' if menu['opened'] else 'hide')
return menu_str
menu_html = menu_tree(result)
return render(request, 'menu.html', {'menu_html': menu_html})
##################### 通过结构化处理结果,生成菜单结束 #####################
# return HttpResponse('...')
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.hide {
display: none;
}
.menu-body {
margin-left: 20px;
}
.menu-body a {
display: block;
}
.menu-body a.active {
color: red;
}
</style>
</head>
<body>
{{ menu_html|safe }} /*菜单栏以html格式显示*/
<script src="/static/jquery-1.12.4.js"></script>
<script>
$(function () {
$('.menu-header').click(function () {
$(this).next().removeClass('hide').parent().siblings().find('.menu-body').addClass('hide');
})
})
</script>
</body>
</html>
十九、将权限做成组件
组件解析: https://blog.csdn.net/qq_33591055/article/details/79406265
github上的代码:https://github.com/linhai1111/Assembly.git