DjangoBBS项目功能拆分
1、随机验证码
url(r'^get_code/', views.get_code, name='get_code'),
# 获取随机3个0-255数
def get_random():
"""
:return: 返回0-255三个随机数,元组
"""
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
# 获取验证码
def get_code(request):
# 1.产生一张随机颜色的图片
img_obj = Image.new('RGB', (350, 35), get_random())
# 2.产生一只在图片上的画笔
img_draw = ImageDraw.Draw(img_obj)
# 3.产生字体样式
img_font = ImageFont.truetype(r'static\font\font.ttf', 35)
io_obj = BytesIO()
# 产生5个随机验证码
code = ''
for i in range(5):
upper_str = chr(random.randint(65, 90)) # 大写字母
lower_str = chr(random.randint(97, 122)) # 小写字母
random_int = str(random.randint(0, 9)) # 数字
# 随机取一个
temp_str = random.choice([upper_str, lower_str, random_int])
# 写在图片上,位置,内容,颜色,字体
img_draw.text((45 + i * 60, -2), temp_str, get_random(), font=img_font)
# 储存
code += temp_str
print(code)
img_obj.save(io_obj, 'png')
request.session['code'] = code
return HttpResponse(io_obj.getvalue())
前端代码:
<img src="/get_code/" alt="图片验证码" id="id_img">
js代码:
原理:src改变,立马刷新。点击一次图片,给url添加一个?号
$('#id_img').click(function () {
var oldSrc = $(this).attr('src');
$(this).attr('src', oldSrc += '?')
});
2、注册功能
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.bootcss.com/sweetalert/2.0.0/sweetalert.min.js"></script>
<link rel="https://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.css">
{% load static %}
<link rel="stylesheet" href="{% static '/bootstrap-3.3.7-dist/css/bootstrap.min.css'%}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
<div class="row">
<h2 class="text-center">注册页面</h2>
<div class="col-md-8 col-md-offset-2">
<form id="myform">
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group">
<label for="{{ form.id_for_label }}">{{ form.label }}</label>
{{ form }}
<span style="color: red" class="pull-right"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="id_avatar">头像
<img src="/static/images/default.jpg" alt="" width="100" style="margin-left: 10px" id="id_img">
</label>
<input type="file" name="myfile" id="id_avatar">
</div>
<a href="/login/"><input type="button" value="登录" class="btn btn-success pull-left"></a>
<input type="button" value="注册" class="btn btn-danger" id="id_submit">
</form>
</div>
</div>
</div>
<script>
$('#id_avatar').change(function () {
// 1 先获取用户上传的头像文件
var avatarFile = $(this)[0].files[0];
// 2 利用文件阅读器对象
var myFileReader = new FileReader();
// 3 将文件交由阅读器对象读取
myFileReader.readAsDataURL(avatarFile);
// 4 修改img标签的src属性 等待文件阅读器对象读取文件之后再操作img标签
myFileReader.onload = function(){
$('#id_img').attr('src',myFileReader.result)
}
});
// 点击按钮触发ajax提交动作
$('#id_submit').on('click',function () {
// 1 先生成一个内置对象 FormData
var myFormData = new FormData();
// 2 添加普通键值对
{#console.log($('#myform').serializeArray())#}
$.each($('#myform').serializeArray(),function (index,obj) {
myFormData.append(obj.name,obj.value)
});
// 3 添加文件数据
myFormData.append('avatar',$('#id_avatar')[0].files[0]);
// 4 发送数据
$.ajax({
url:'',
type:'post',
data:myFormData,
// 两个关键性参数
contentType:false,
processData:false,
success:function (data) {
if (data.code===1000){
// 注册成功之后 应该跳转到后端返回过来的url
location.href = data.url
}else{
$.each(data.msg,function(index,obj){
// 1 先手动拼接字段名所对应的input框的id值
var targetId = '#id_' + index; // #id_username
// 2 利用id选择器查找标签 并且将div标签添加报错类
$(targetId).next().text(obj[0]).parent().addClass('has-error')
})
}
}
})
});
$('input').focus(function () {
// 移除span标签内部的文本 还需要移除div标签的class中has-error属性
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
后端urls.py:
# 注册
url(r'^register/', views.register, name='register'),
views.py:
# 注册
def register(request):
form_obj = MyRegForm()
if request.method == 'POST':
back_dic = {'code': 1000, 'msg': ""}
# 校验用户名、密码
form_obj = MyRegForm(request.POST)
if form_obj.is_valid():
# 用变量接收正确的结果 clean_data = {'username' 'password' 're_password' 'email'}
clean_data = form_obj.cleaned_data
# 将确认密码键值对删除,表中没有re_password
clean_data.pop('re_password')
# 把签名、用户CSS名字存进Blog表中
sign = clean_data.get('sign')
username = clean_data.get('username')
site_theme = username + '.css'
models.Blog.objects.create(site_name=username, site_title=sign, site_theme=site_theme)
# 添加字段
clean_data['blog'] = models.Blog.objects.filter(site_name=username).first()
clean_data.pop('sign')
# 额外做的事情:给每个新的注册用户添加3个默认的分类和3个默认的标签
create_list = []
blog = models.Blog.objects.filter(site_name=username).first()
for i in ['一', '二', '三']:
category_name = username+'的分类'+i
create_list.append(models.Category(name=category_name, blog=blog))
models.Category.objects.bulk_create(create_list)
# 添加3个默认标签
create_list = []
for i in ['一', '二', '三']:
tag_name = username + '的标签' + i
create_list.append(models.Tag(name=tag_name, blog=blog))
models.Tag.objects.bulk_create(create_list)
# 获取用户头像文件
avatar_obj = request.FILES.get('avatar')
# 判断用户头像文件是否为空,用户没有上传
if avatar_obj:
# 用户上传了,添加到clean_data中
clean_data['avatar'] = avatar_obj # clean_data = {'username' 'password' 'email' 'avatar'}
models.UserInfo.objects.create_user(**clean_data) # 打散传入 ??=??的形式
back_dic['msg'] = '注册成功'
back_dic['url'] = '/login/'
else:
back_dic['code'] = 2000
back_dic['msg'] = form_obj.errors
return JsonResponse(back_dic)
return render(request, 'register.html', locals())
myforms.py:
from django import forms
from app01 import models
class MyRegForm(forms.Form):
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={
"min_length":'用户名最短3位',
"max_length":'用户名最长8位',
"required":'用户名不能为空',
},widget=forms.widgets.TextInput(attrs={'class':'form-control'})
)
password = forms.CharField(min_length=3, max_length=8, label='密码',
error_messages={
"min_length": '密码最短3位',
"max_length": '密码最长8位',
"required": '密码不能为空',
}, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
re_password = forms.CharField(min_length=3, max_length=8, label='确认密码',
error_messages={
"min_length": '确认密码最短3位',
"max_length": '确认密码最长8位',
"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'})
)
sign = forms.CharField(min_length=5, max_length=15, label='学习宣言',
error_messages={
"min_length": '学习宣言最短5位',
"max_length": '学习宣言最长15位',
"required": '学习宣言不能为空',}
, widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
)
# 钩子函数
# 局部钩子校验用户名是否已存在
def clean_username(self):
username = self.cleaned_data.get('username')
is_alive = models.UserInfo.objects.filter(username=username)
if is_alive:
self.add_error('username','用户名已存在')
return username
# 全局钩子校验密码与确认密码是否一致
def clean(self):
password = self.cleaned_data.get('password')
re_password = self.cleaned_data.get('re_password')
if not password == re_password:
self.add_error('re_password','两次密码不一致')
return self.cleaned_data
3、登录功能
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.bootcss.com/sweetalert/2.0.0/sweetalert.min.js"></script>
<link rel="https://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.css">
{% load static %}
<link rel="stylesheet" href="{% static '/bootstrap-3.3.7-dist/css/bootstrap.min.css'%}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
<h2 class="text-center">登录页面</h2>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<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" name="code" class="form-control" id="id_code">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="图片验证码" id="id_img">
</div>
</div>
</div>
<input type="button" value="登录" class="btn btn-success" id="id_submit">
<a href="/register/"><input type="button" value="注册" class="btn btn-danger"></a>
<span style="color: red" id="error"></span>
</div>
</div>
</div>
<script>
$('#id_img').click(function () {
var oldSrc = $(this).attr('src');
$(this).attr('src', oldSrc += '?')
});
$('#id_submit').click(function () {
$.ajax({
url:'',
type:'post',
data:{
'username': $('#id_username').val(),
'password': $('#id_password').val(),
'csrfmiddlewaretoken':'{{ csrf_token }}',
'code':$('#id_code').val()
},
success:function (data) {
if (data.code === 1000){
// 登录成功,跳转页面
location.href = data.url
}else {
// 点击此按钮,添加文本信息
$('#error').text(data.msg)
}
}
})
})
</script>
</body>
</html>
后端代码:
urls.py:
# 登录
url(r'^login/', views.login, name='login'),
views.py:
# 登录
def login(request):
back_dic = {'code': None, 'msg': None}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code') # 从前端拿过来的验证码
# 先对比验证码
if request.session.get('code').lower() == code.lower():
# 校验用户名和密码
user_obj = auth.authenticate(username=username, password=password)
if user_obj:
# 记录登录状态
auth.login(request, user_obj)
back_dic['code'] = 1000
back_dic['msg'] = '登录成功'
back_dic['url'] = '/home/'
# back_dic['url'] = '/%s/' % username
else:
back_dic['code'] = 2000
back_dic['msg'] = '用户名或密码错误'
else:
back_dic['code'] = 3000
back_dic['msg'] = '验证码错误'
return JsonResponse(back_dic)
return render(request, 'login.html')
4、登录认证装饰器配置
settings.py:
LOGIN_URL = '/login/'
5、修改密码模态框方法
前端代码:
<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
{#修改密码模态框#}
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<h2 class="text-center">修改密码</h2>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<label for="">用户名</label>
<input type="text" name="username" value={{ request.user.username }} class="form-control"
disabled>
</div>
<div class="form-group">
<label for="id_old_password">原密码</label>
<input type="password" name="old_password" class="form-control" id="id_old_password">
</div>
<div class="form-group">
<label for="id_new_password">新密码</label>
<input type="password" name="new_password" class="form-control" id="id_new_password">
</div>
<div class="form-group">
<label for="id_confirm_password">确认密码</label>
<input type="password" name="confirm_password" class="form-control" id="id_confirm_password">
</div>
<button class="btn btn-primary" id="id_set">修改</button>
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<span style="color: red"></span>
</div>
</div>
<br>
</div>
</div>
</div>
js代码:
<script>
{#修改密码#}
$('#id_set').click(function () {
var $btn = $(this);
$.ajax({
url: '{% url 'set_password' %}',
type: 'post',
data: {
old_password: $('#id_old_password').val(),
new_password: $('#id_new_password').val(),
confirm_password: $('#id_confirm_password').val(),
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function (data) {
if (data.code === 1000) {
location.href = data.url
} else {
$btn.next().next().text(data.msg)
}
}
})
});
</script>
后端代码:
urls.py:
# 修改密码
url(r'^set_password', views.set_password, name='set_password'),
views.py:
# 修改密码
@login_required
def set_password(request):
if request.is_ajax():
back_dic = {'code': 1000, 'msg': ''}
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
confirm_password = request.POST.get('confirm_password')
if new_password == confirm_password:
is_right = request.user.check_password(old_password)
if is_right:
request.user.set_password(new_password)
request.user.save()
back_dic['msg'] = '修改成功'
back_dic['url'] = reverse('login')
else:
back_dic['code'] = 2000
back_dic['msg'] = '原密码错误'
else:
back_dic['code'] = 3000
back_dic['msg'] = '两次密码不一致'
return JsonResponse(back_dic)
6、修改头像
前端代码:
<li><a href="/set_avatar/">修改头像</a></li>
stt_avatar.html:
{% extends 'base.html' %}
{% block content %}
<form action="" method="post" enctype="multipart/form-data">
<p><a href="/home/">返回</a></p>
<input type="file" name="myfile" id="id_avatar"><br>
{% csrf_token %}
<div class="form-group">
<label for="id_avatar">
<img src="/static/images/default.jpg" alt="" width="100" id="id_img"> <span>新头像</span>
</label>
</div>
<label for="id_avatar">
<img src="/media/{{ request.user.avatar }}/" width="200" alt="原头像"> <span>原头像</span>
</label>
<p><input type="submit" class="btn btn-primary"></p>
</form>
<script>
$('#id_avatar').change(function () {
// 1 先获取用户上传的头像文件
var avatarFile = $(this)[0].files[0];
// 2 利用文件阅读器对象
var myFileReader = new FileReader();
// 3 将文件交由阅读器对象读取
myFileReader.readAsDataURL(avatarFile);
// 4 修改img标签的src属性 等待文件阅读器对象读取文件之后再操作img标签
myFileReader.onload = function () {
$('#id_img').attr('src', myFileReader.result)
}
});
</script>
{% endblock %}
后端代码:
urls.py:
# 修改用户头像
url(r'^set_avatar/', views.set_avatar, name='set_avatar'),
views.py:
# 修改头像
@ login_required
def set_avatar(request):
if request.method == 'POST':
avatar_obj = request.FILES.get('myfile')
# models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=avatar_obj) # 不会帮你自动添加前缀
# 用自己的save方法,自动帮你添加前缀
request.user.avatar = avatar_obj
request.user.save()
return render(request, 'set_avatar.html')
7、修改签名模态框方法
前端代码:
<li><a href="#" data-toggle="modal" data-target=".set_sign">编辑签名</a></li>
{#编辑签名模态框#}
<div class="modal fade bs-example-modal-lg set_sign" id="set_sign" tabindex="-1" role="dialog"
aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<h2 class="text-center">编辑签名</h2>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<label for="">用户名</label>
<input type="text" name="username" value={{ request.user.username }} class="form-control"
disabled>
</div>
<div class="form-group">
<label for="id_old_sign">原签名</label><br>
<textarea name="old_sign" id="id_old_sign" cols="81" rows="5"
disabled>{{ request.user.blog.site_title }}</textarea>
</div>
<div class="form-group">
<label for="id_new_sign">新签名</label>
<span style="color: red" id="error_sign"></span>
<textarea name="new_sign" id="id_new_sign" cols="81" rows="5"></textarea>
</div>
<button class="btn btn-primary" id="set_sign">修改</button>
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
</div>
</div>
<br>
</div>
</div>
</div>
js代码:
{#修改签名#}
$("#set_sign").click(function () {
$.ajax({
url: '{% url 'set_sign' %}',
type: 'post',
data: {
new_sign: $('#id_new_sign').val(),
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function (data) {
if (data.code === 1000) {
location.href = data.url;
$('#error_sign').text(data.msg)
} else {
$('#error_sign').text(data.msg)
}
}
})
});
后端代码:
urls.py:
# 编辑签名
url(r'^set_sign', views.set_sign, name='set_sign'),
views.py:
# 编辑签名
@login_required
def set_sign(request):
if request.is_ajax():
back_dic = {'code': 1000, 'msg': ''}
site_name = request.user.username
site_title = request.POST.get('new_sign')
if len(site_title) < 5:
back_dic['code'] = 2000
back_dic['msg'] = '(你的学习宣言必须大于5位)'
elif len(site_title) > 15:
back_dic['code'] = 3000
back_dic['msg'] = '(你的学习宣言必须小于于15位)'
else:
back_dic['code'] = 1000
back_dic['url'] = reverse('home')
back_dic['msg'] = '修改成功'
models.Blog.objects.filter(site_name=site_name).update(site_title=site_title)
return JsonResponse(back_dic)
8、注销功能模态框
前端代码:
<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-sm">注销</a></li>
{# 退出确认模态框#}
<div class="modal fade bs-example-modal-sm" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title">你忍心离开我吗</h4>
</div>
<div class="modal-body">
<p>确定退出?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="exit">确定</button>
</div>
</div><!-- /.modal-content -->
</div>
</div>
js代码:
{#注销#}
$('#exit').click(function () {
location.href = "/logout/"
});
后端代码:
urls.py:
# 注销
url(r'^logout', views.logout, name='logout'),
views.py:
# 注销
@login_required
def logout(request):
auth.logout(request) # 原理删除了对应的session值
return redirect(reverse('home'))
9、用户上传静态文件配置
setting.py文件配置:
配置好之后,文件夹自动创建
# media配置
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 用户上传的文件全部保存该文件下
10、图片防盗链
请求头里面有一个referer请求头,用来标识你上一次是从哪一个网址过来的
判断上一次这个网址是否有权限
自己的项目:把图片所在的文件夹暴露,那么只能访问图片。
别人的图片怎么解决防盗链?:
1.用爬虫将所有的图片资源下载到本地 这是爬虫的价值所在
2.修改请求头参数 百度搜吧
referer属性:
11、暴露任意文件的配置
urls.py:
注意:千万不要暴露重要文件资源,否则拍屁股走人
MEDIA_ROOT,一定不要暴露关键文件
from django.views.static import serve
# 暴露任意后端资源配置
url(r'^media/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),
图片文件地址:
<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}"
style="width: 60px" alt="这是你的头像" width="60px;">
12、分页器的使用
分页器:新建py文件,把代码复制过来
代码:
class Pagination(object):
def __init__(self,current_page,all_count,per_page_num=2,pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
用法:
queryset = model.objects.all()
page_obj = Pagination(current_page,all_count)
page_data = queryset[page_obj.start:page_obj.end]
获取数据用page_data而不再使用原始的queryset
获取前端分页样式用page_obj.page_html
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page <1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
使用方法:
后端代码:
from app01.utils.mypagenation import Pagination # 分页器导
# 首页
def home(request):
# 将网站的所有文章展示到前端
article_list = models.Article.objects.all()
# 分页处理
page_obj = Pagination(current_page=request.GET.get('page',1),all_count=article_list.count())
article_list = article_list[page_obj.start:page_obj.end]
return render(request, 'home.html', locals())
前端代码:
{#分页器 #}
<div class="text-center">{{ page_obj.page_html|safe }}</div>
13、每个用户拥有自己的css
1.在注册的时候把用户的css文件的名字固定写好,写进数据库
2.在用户编辑CSS的时候,再通过文件操作,创建用户固定的CSS文件
3.再应用导入自己的CSS文件
{#引用自己的css#}
<link rel="stylesheet" href="/media/css/{{ user_obj.blog.site_theme }}">
前端代码:
<a href="{% url 'blog_css' %}">个人站点CSS设置</a>
urls.py:
# 个人站点CSS设置
url(r'^blog_css/', views.blog_css, name='blog_css'),
views.py:
# 个人站点CSS设置
@login_required
def blog_css(request):
username = request.user.username
site_theme = models.Blog.objects.filter(site_name=username).first().site_theme
css_dir = f'media/css/{site_theme}'
if request.method == 'POST':
new_css = request.POST.get('new_css')
with open(css_dir, 'w', encoding='utf-8')as f:
for line in new_css:
res = line.replace('\n', '')
f.write(res)
f.close()
return redirect('/blog_css/')
if request.method == 'GET':
# 先判断用户css文件是否存在,不存在就创建。存在就读取
isfile = os.path.exists(css_dir)
# 不存在创建空的css文件
if not isfile:
with open(css_dir, 'w')as f:
f.close()
# 存在就读取
with open(css_dir, 'r')as f:
old_css = f.read()
return render(request, 'backend/blog_css.html', locals())
backend/blog_css.html:
{% extends 'backend/backend_base.html' %}
{% block article %}
<form action="" method="post">
{% csrf_token %}
<h2>页面定制 CSS 代码</h2>
<textarea name="new_css" id="" cols="100" rows="30">{{ old_css }}</textarea>
<p>推荐客户端: <a href="">Open Live Writer</a></p>
<p>MetaWeblog访问地址: <a href="http://127.0.0.1:8000/home">http://127.0.0.1:8000/home</a>/</p>
<input type="submit" class="btn btn-primary">
</form>
{% endblock %}
14、分组,按年月等。官方推荐
-官方提供
from django.db.models.functions import TruncMonth
Article.objects
.annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list
.values('month') # Group By month
.annotate(c=Count('id')) # Select the count of the grouping
.values('month', 'c') # (might be redundant, haven't tested) select month and count
# 3.按照文章的年月分组
date_list = models.Article.objects.filter(blog=blog).\
annotate(month=TruncMonth('create_time')).values(
'month').annotate(c=Count('pk')).values('c', 'month')
{% for date in date_list %}
<p>
<a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/">
{{ date.month|date:'Y年m月' }}({{ date.c }})</a>
</p>
{% endfor %}
15、侧边栏筛选(自定义过滤器方法)
新建文件夹和py文件:
mytag.py代码:
from django.template import Library
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
register = Library()
# 侧边栏渲染,自定义过滤器方法
@register.inclusion_tag('left_menu.html', name='my_left')
def index(username):
# 提供left_menu所需要的所有数据
user_obj = models.UserInfo.objects.filter(username=username).first()
blog = user_obj.blog
# 1.查询当前用户的分类及每个分类下的文章数
category_list = models.Category.objects.all().filter(blog=blog).annotate(article_sum=Count('article__pk')).values(
'article_sum', 'name', 'pk')
# 2.查询当前用户的标签,及每个标签下的文章数
tag_list = models.Tag.objects.all().filter(blog=blog).annotate(tag_sum=Count('article__pk')).values('tag_sum',
'name', 'pk')
# 3.按照文章的年月分组
date_list = models.Article.objects.filter(blog=blog).\
annotate(month=TruncMonth('create_time')).values(
'month').annotate(c=Count('pk')).values('c', 'month')
return locals()
left_menu.html:
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">文章分类</h3>
</div>
<div class="panel-body">
{% for category in category_list %}
<p><a href="/{{ username }}/category/{{ category.pk }}">{{ category.name }}({{ category.article_sum }})</a>
</p>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">文章标签</h3>
</div>
<div class="panel-body">
{% for tag in tag_list %}
<p><a href="/{{ username }}/tag/{{ tag.pk }}">{{ tag.name }}({{ tag.tag_sum }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">日期归档</h3>
</div>
<div class="panel-body">
{% for date in date_list %}
<p>
<a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/">
{{ date.month|date:'Y年m月' }}({{ date.c }})</a>
</p>
{% endfor %}
</div>
</div>
16、点赞点踩
前端样式:可以直接去别人网站拷贝html代码,改改自己用
{# 点赞点踩前端样式#}
<div class="clearfix">
<div id="div_digg">
<div class="diggit jeff">
<span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
</div>
<div class="buryit jeff">
<span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red">
</div>
</div>
</div>
js代码:
<script>
{#点赞点踩JS代码#}
$('.jeff').click(function () {
var $divEle = $(this);
$.ajax({
url: '{% url 'updown' %}',
type: 'post',
data: {
'article_id':{{ article_obj.pk }},
'is_up': $(this).hasClass('diggit'),
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (data) {
if (data.code === 1000) {
$('#digg_tips').text(data.msg);
$divEle.children().text(Number($divEle.children().text()) + 1)
} else {
$('#digg_tips').html(data.msg)
}
}
})
});
</script>
后端:
urls.py:
# 点赞点踩
url(r'^up_or_down/', views.up_or_down, name='updown'),
views.py:
# 点赞点踩
import json
from django.contrib import auth
from django.db.models import F
def up_or_down(request):
bank_dic = {'code': 1000, 'msg': ''}
if request.is_ajax():
article_id = request.POST.get('article_id')
# 注意:前端返回来的bool值是str形式。拿到是点赞还是点踩 赞True 踩false
is_up = request.POST.get('is_up')
is_up = json.loads(is_up) # 转成python形式的bool值
'''
1.必须是登录的用户才能点赞点踩,判断用户是否登录
2.判断当前文章是否是用户自己写的,自己不能给自己点赞点踩
3.当前用户是否已经给文章点过赞或踩了
4.操作数据库---操作两张表,优化表字段
'''
# 1.判断用户是否已登录
if request.user.is_authenticated():
# 2.拿到当前文章,从文章里拿到当前用户,和登录的用户比较。如果用户一样,则证明是自己写的文章,不能点赞踩
article_obj = models.Article.objects.filter(pk=article_id).first()
if not article_obj.blog.userinfo.pk == request.user.pk:
# 3.判断当前用户是否已经给当前文章点过赞或踩了。到点赞点踩表中查询是否有当前用户的记录,如果有,则证明当前用户已经点过了
is_click = models.UpAndDown.objects.filter(user=request.user.pk, article=article_id)
if not is_click:
# 用户没电点过,操作表数据.第一张表
# 点赞给点赞字段+1
if is_up:
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
bank_dic['msg'] = '点赞成功'
# 点踩给点踩字段+1
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
bank_dic['msg'] = '点踩成功'
# 操作表数据,第二张表
models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
else:
bank_dic['code'] = 2000
bank_dic['msg'] = '你已经点过了'
else:
bank_dic['code'] = 3000
bank_dic['msg'] = '不能给自己点'
else:
bank_dic['code'] = 4000
bank_dic['msg'] = '请先<a href="/login/">登录</a>'
return JsonResponse(bank_dic)
17、模板字符串
文章评论零时渲染:
//定义全局变量
var parentId = null;
// 文章评论js代码
$('#id_comment').click(function () {
var conTent = $('#id_content').val();
// 如果是根评论不处理,如果是子评论需要处理,将@jeff 切割
// @jeff 萨尔
if (parentId) {
//切割方式 获取第一个\n对应的索引
var indexN = conTent.indexOf('\n') + 1 //顾头不顾尾
// 按照获取的索引切割
conTent = conTent.slice(indexN) //将indexN之前的全部切除,中保留之后的
}
$.ajax({
url: '{% url "comment" %}',
type: 'post',
data: {
"article_id":{{ article_obj.pk }},
"content": conTent,
"csrfmiddlewaretoken": '{{ csrf_token }}',
"parent_id": parentId
},
success: function (data) {
if (data.code === 1000) {
// 临时渲染评论内容
var UserName = '{{ request.user.username }}';
var conTent = $('#id_content').val();
// 将内容临时渲染到ul标签内
var temp = `
<li class="list-group-item">
<span><span class="glyphicon glyphicon-comment"></span><a href="/${UserName}/">${UserName}</a></span>
<div>
${conTent}
</div>
</li>
`;
$('.list-group').append(temp);
// 将获取用户输入评论的内容框清空
$('#id_content').val('');
// 将全局的parentId清空,否则parentId后续一直有值,就一直是子评论
parentId = null
}
}
})
});
18、KindEditor编辑器使用
看官方文档
前端内容:
{% extends 'backend/backend_base.html' %}
{% block article %}
<h2>添加文章</h2>
<form action="" method="post">
{% csrf_token %}
<p>标题</p>
<p>
<input type="text" name="title" class="form-control">
</p>
<p>内容(使用kindeditor编辑器)</p>
<p>
<textarea name="content" id="id_content" cols="60" rows="20"></textarea>
</p>
<div>
<p>文章标签</p>
<p>
{% for tag in tag_list %}
{{ tag.name }} <input type="checkbox" name="tag" value="{{ tag.pk }}">
{% endfor %}
</p>
</div>
<div>
<p>文章分类</p>
<p>
{% for category in category_list %}
{{ category.name }} <input type="radio" name="category" value="{{ category.pk }}">
{% endfor %}
</p>
</div>
<input type="submit" class="btn btn-primary" 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:'500px',
resizeType:0,
uploadJson : '/upload_image/', //控制用户写文章上传文件的后端地址
extraFileUploadParams : {
'csrfmiddlewaretoken':'{{ csrf_token }}',
}
});
});
</script>
{% endblock %}
后端代码:
urls.py:
# 添加文章
url(r'^add_article/', views.add_article, name='add_article'),
views.py:
# 添加随笔
from bs4 import BeautifulSoup
@login_required
def add_article(request):
if request.method == 'POST':
# 获取从前端页面传来的文章数据
title = request.POST.get('title')
content = request.POST.get('content')
tag_list = request.POST.get('tag')
category_id = request.POST.get('category')
# 先生成一个该模块beautifulsoup4的对象
soup = BeautifulSoup(content, 'html.parser')
for tag in soup.find_all():
# 筛选除script标签直接删除,避免XSS攻击
if tag.name == 'script':
tag.decompose() # 删除该标签
# desc = content[0:150] # 截取文章简介,错误示范。会从html代码截取
desc = soup.text[0:150] # 通过模块处理,直接从内容截取
# 写入数据
article_obj = models.Article.objects.create(title=title, desc=desc, content=str(soup), category_id=category_id, blog=request.user.blog)
# 手动操作文章与标签的第三张表
# 用批量插入数据 bulk_create
b_list = []
for tag_id in tag_list:
b_list.append(models.Article2Tag(article=article_obj, tag_id=tag_id))
models.Article2Tag.objects.bulk_create(b_list)
return redirect(reverse('backend'))
# 获取文章分类、文章标签列表,让用户选择添加文章的分类与标签
category_list = models.Category.objects.filter(blog=request.user.blog)
tag_list = models.Tag.objects.filter(blog=request.user.blog)
return render(request, 'backend/add_article.html', locals())
19、Django时区及国际化设置
LANGUAGE_CODE = 'zh-hans' # 更改国际化翻译,中文
TIME_ZONE = 'Asia/Shanghai' # 更改东八区时间
USE_TZ = False # 表示数据库的同步时间,使用上面的东八区时间
20、models.py代码
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
# 用户表
class UserInfo(AbstractUser):
phone = models.BigIntegerField(null=True, blank=True) # blank=True 告诉后台管理该字段可以为空
# 存用户头像的地址
avatar = models.FileField(upload_to='avatar/', default='avatar/default.jpg')
create_time = models.DateField(auto_now_add=True) # 创建时间自动
# 一个用户只能有一个站点,一个站点给一个用户用。一对一
blog = models.OneToOneField(to='Blog', null=True)
# admin后台管理页面展示的表名
class Meta:
verbose_name_plural = '用户表'
# 给前端便于展示
def __str__(self):
return self.username
# 个人站点表
class Blog(models.Model):
site_name = models.CharField(max_length=32)
site_title = models.CharField(max_length=64)
site_theme = models.CharField(max_length=64) # 站点样式
class Meta:
verbose_name_plural = '个人站点表'
# 便于前端展示
def __str__(self):
return self.site_name
# 分类表
class Category(models.Model):
name = models.CharField(max_length=32)
# 一个站点有多个类,一个类中只有一个站点。一对多,外键在多的一张表中
blog = models.ForeignKey(to='Blog', null=True)
class Meta:
verbose_name_plural = '分类表'
def __str__(self):
return self.name
# 标签表
class Tag(models.Model):
name = models.CharField(max_length=32) # 标签名
blog = models.ForeignKey(to='Blog', null=True) # 一对多,外键在多的一张表中
class Meta:
verbose_name_plural = '标签表'
def __str__(self):
return self.name
# 文章表
class Article(models.Model):
title = models.CharField(max_length=64) # 标题
desc = models.CharField(max_length=254) # 摘要
content = models.TextField() # 文章内容
create_time = models.DateTimeField('Edit the date', auto_now_add=True) # 创建时间自动
# 这里考虑到复杂度,我们使用简单的版本。一篇文章只能分到一个类中。一对多的关系
# 一对多,分类字段
category = models.ForeignKey(to='Category', null=True)
# 多对多,标签字段
tags = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag'))
# 一对多,个人站点字段。一个站点拥有多篇文章,一个文章只有一个站点
blog = models.ForeignKey(to='Blog', null=True)
# 数据库优化设计,把这三个字段加到文章表中,时时更新就行了。
# 不用再到下面这几张表中查询,减少了查询的次数
comment_num = models.BigIntegerField(null=True, default=0) # 评论人数
up_num = models.BigIntegerField(null=True, default=0) # 点赞人数
down_num = models.BigIntegerField(null=True, default=0) # 点踩人数
class Meta:
verbose_name_plural = '文章表'
def __str__(self):
return self.title
# 文章和标签的第三张关系表
class Article2Tag(models.Model):
article = models.ForeignKey(to='Article')
tag = models.ForeignKey(to='Tag')
class Meta:
verbose_name_plural = '文章标签多对多关系表'
# 点赞点踩表
class UpAndDown(models.Model):
user = models.ForeignKey(to='UserInfo') # 放用户名
article = models.ForeignKey(to='Article') # 放文章
is_up = models.BooleanField() # 标识赞或踩,0,1
class Meta:
verbose_name_plural = '点赞点踩表'
# 评论表
class Comment(models.Model):
user = models.ForeignKey(to='UserInfo')
article = models.ForeignKey(to='Article') # 放文章
content = models.CharField(max_length=254)
comment_time = models.DateTimeField('Edit the date', auto_now_add=True) # 评论时间
# 该字段存的是父评论的主键值
# 如果有值 说明当前评论是子评论 如果没有值 说明当前评论是根评论
parent = models.ForeignKey(to='self', null=True)
class Meta:
verbose_name_plural = '评论表'
21、urls.py代码
"""BBS URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.contrib import admin
from app01 import views
from django.views.static import serve
from BBS import settings
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 注册
url(r'^register/', views.register, name='register'),
# 登录
url(r'^login/', views.login, name='login'),
# 获取图片验证码
url(r'^get_code/', views.get_code, name='get_code'),
# 首页
url(r'^home/', views.home, name='home'),
url(r'^$', views.home, name='home'),
# 修改密码
url(r'^set_password', views.set_password, name='set_password'),
# 编辑签名
url(r'^set_sign', views.set_sign, name='set_sign'),
# 修改用户头像
url(r'^set_avatar/', views.set_avatar, name='set_avatar'),
# 注销
url(r'^logout', views.logout, name='logout'),
# 暴露任意后端资源配置
url(r'^media/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),
# 点赞点踩
url(r'^up_or_down/', views.up_or_down, name='updown'),
# 文章评论
url(r'^comment/', views.comment, name='comment'),
# 后台管理
url(r'^backend/', views.backend, name='backend'),
# 添加文章
url(r'^add_article/', views.add_article, name='add_article'),
# 编辑分类
url(r'^edit_category/', views.edit_category, name='edit_category'),
# 编辑标签
url(r'^edit_tag/', views.edit_tag, name='edit_tag'),
# 添加分类
url(r'^create_category/', views.create_category, name='create_category'),
# 添加标签
url(r'^create_tag/', views.create_tag, name='create_tag'),
# 删除分类
url(r'^delete_category/(?P<del_id>\d+)/', views.delete_category, name='delete_category'),
# 删除标签
url(r'^delete_tag/(?P<del_id>\d+)/', views.delete_tag, name='delete_tag'),
# 编辑分类名
url(r'^edit_category_name/(?P<edit_id>\d+)/', views.edit_category_name, name='edit_category_name'),
# 编辑标签名
url(r'^edit_tag_name/(?P<edit_id>\d+)/', views.edit_tag_name, name='edit_tag_name'),
# 个人站点CSS设置
url(r'^blog_css/', views.blog_css, name='blog_css'),
# 编辑器上传图片
url(r'^upload_image/', views.upload_image, name='upload_image'),
# 个人站点
url(r'^(?P<username>\w+)/$', views.site, name='site'),
# 侧边栏筛选功能
# url(r'^(?P<username>\w+)/category/(\d+)', views.site),
# url(r'^(?P<username>\w+)/tag/(\d+)', views.site),
# url(r'^(?P<username>\w+)/archive/(.*)', views.site),
# 合并3条url
url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/', views.site),
# 文章详情页
url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/', views.article_detail)
]
22、mytag代码
from django.template import Library
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
register = Library()
# 侧边栏渲染,自定义过滤器方法
@register.inclusion_tag('left_menu.html', name='my_left')
def index(username):
# 提供left_menu所需要的所有数据
user_obj = models.UserInfo.objects.filter(username=username).first()
blog = user_obj.blog
# 1.查询当前用户的分类及每个分类下的文章数
category_list = models.Category.objects.all().filter(blog=blog).annotate(article_sum=Count('article__pk')).values(
'article_sum', 'name', 'pk')
# 2.查询当前用户的标签,及每个标签下的文章数
tag_list = models.Tag.objects.all().filter(blog=blog).annotate(tag_sum=Count('article__pk')).values('tag_sum',
'name', 'pk')
# 3.按照文章的年月分组
date_list = models.Article.objects.filter(blog=blog).\
annotate(month=TruncMonth('create_time')).values(
'month').annotate(c=Count('pk')).values('c', 'month')
return locals()