1 注册

# views.py
def register(request):
form_obj = MyRegForm()
# print(request.is_ajax()) # 判断当前请求是否是ajax请求
if request.method == 'POST':
# 定义一个与ajax回调函数交互的字典
back_dic = {"code":1000,'msg':""} # 校验数据 用户名 密码 确认密码
form_obj = MyRegForm(request.POST)
if form_obj.is_valid():
clean_data = form_obj.cleaned_data # 用变量接收正确的结果 clean_data = {'username' 'password' 'confirm_password' 'email'}
# 将确认密码键值对删除
clean_data.pop('confirm_password')
# 获取用户头像文件
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())
<!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 href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/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/img/头像1.jpg" alt="" width="200" style="margin-left: 10px" id="id_img">
</label>
<input type="file" name="myfile" id="id_avatar" style="display: none">
</div>
<input type="button" value="注册" class="btn btn-primary pull-right" 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())#}
// 循环myform里的每一个对象
$.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>

2 登陆

def login(request):
if request.method == 'POST':
back_dic = {"code": 1000, "msg": ''} username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
print(username, password, code)
if request.session.get('code').upper() == code.upper():
auth_obj = auth.authenticate(username=username, password=password)
if auth_obj:
auth.login(request, auth_obj)
back_dic['msg'] = '登陆成功'
back_dic['url'] = '/home/'
# return HttpResponse('登陆成功')
return JsonResponse(back_dic)
else:
back_dic['code'] = '2000'
back_dic['msg'] = "用户名或密码错误"
return JsonResponse(back_dic)
else:
back_dic['code'] = '3000'
back_dic['msg'] = '验证码错误'
return JsonResponse(back_dic)
return render(request, 'login.html')
<!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 href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h2 class="text-center">登陆</h2>
<div class="col-md-8 col-md-offset-2">
<form id="login_form">
{% csrf_token %}
<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>
<br>
<input type="button" value="登陆" class="btn btn-primary" id="id_btn">&nbsp <span style="color: red"></span>
</form>
</div>
</div> <script>
$('#id_img').click(function () {
var oldSrc = $(this).attr('src');
$(this).attr('src', oldSrc += '?')
}); $('#id_btn').click(function () {
var $btn = $(this);
$.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 if (data.code===2000){
$btn.next().text(data.msg)
}else {
$btn.next().text(data.msg)
}
}
})
})
</script>
</body>
</html>

3 图片验证码相关

url(r'^get_code/', views.get_code),

在登陆时,需要用到验证码

from PIL import Image, ImageDraw, ImageFont
"""
Image: 产生图片
ImageDraw: 在图片上写字
ImageFont: 控制图片上字体样式
"""
from io import BytesIO, StringIO
"""
BytesIO 能够临时帮你保存数据 获取的时候以二进制方式返回给你
StringIO 能够临时帮你保存数据 获取的时候以字符串方式返回给你
""" import random
def get_random():
return random.randint(0,255), random.randint(0, 255), random.randint(0, 255) def get_code(request):
# 在图片上写字
img_obj = Image.new('RGB', (350, 35), get_random())
# 产生针对该图片的画笔对象
img_draw = ImageDraw.Draw(img_obj)
# 产生一个字体样式对象
img_font = ImageFont.truetype(r'app01\static\font\新叶念体.otf', 35)
io_obj = BytesIO() 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')
# 将产生的随机验证码存储到session中 以便于后面的验证码校验
request.session['code'] = code
return HttpResponse(io_obj.getvalue())

4 首页相关,Django Admin后台录入数据

 url(r'^home/', views.home, name='_home'),

先创建一个超级管理员用户

createsuperuser
# 对密码有要求,不能太短

然后在 admin.py 文件下,导入模板,将所有表注册都管理员后台

# admin.py
from django.contrib import admin
from app01 import models
# Register your models here. admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Tag)
admin.site.register(models.Category)
admin.site.register(models.Article2Tag)
admin.site.register(models.Article)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)

登陆admin后台后,可以看到所有的表是英文的,还带了s

把表名变成中文操作:

在每个表添加一个Mate类

# models.py
class Meta:
# verbose_name = '用户表' # 带s
verbose_name_plural = '用户表' # 不带s

然后开始录入数据

录入的时候发现分类是对象,分不出来谁是谁

前端展示对象,就相当于打印对象

可以利用_str_ 方法

class Tag(models.Model):
name = models.CharField(max_length=32)
blog = models.ForeignKey(to='Blog',null=True)
class Meta:
verbose_name_plural = 'Tag标签表' def __str__(self):
return self.name

最后还要在用户表里给用户选择站点

选择后提交会报错,显示手机号不能为空

这个时候可以去UserInfo用户表中phone字段添加一个属性

blank=True

class UserInfo(AbstractUser):
phone = models.BigIntegerField(null=True,blank=True)
# blank告诉后台管理该字段可以为空

5 注销功能

url(r'^logout/', views.logout, name='_logout'),

auth组件

6 修改密码

 url(r'^set_pwd/', views.set_password, name='_set_pwd'),

修改密码可以用一个弹框

@login_required()
def set_password(request):
if request.is_ajax():
back_dic = {"code": 1000, 'msg': ''}
old_pwd = request.POST.get('old_pwd')
new_pwd = request.POST.get('new_pwd')
confirm_pwd = request.POST.get('confirm_pwd')
print(old_pwd, new_pwd, confirm_pwd)
if new_pwd == confirm_pwd:
is_right = request.user.check_password(old_pwd)
if is_right:
request.user.set_password(confirm_pwd)
request.user.save()
back_dic['msg'] = '修改成功'
back_dic['url'] = '/login/'
# back_dic['url'] = reverse('_login')
return JsonResponse(back_dic)
else:
back_dic['code'] = '2000'
back_dic['msg'] = '原密码错误'
return JsonResponse(back_dic)
else:
back_dic['code'] = '3000'
back_dic['msg'] = '两次密码不一致'
return JsonResponse(back_dic)

7 用户头像展示,media配置

网站所用的静态文件我们都默认放到了static文件夹下

而用户上传的文件也算静态资源,我们也应该找一个公共的地方专门存储用户上传的静态文件

media配置专门用来指定用户上传的静态文件存放路径

配置文件中只需要写下面一句配置即可

# settings.py
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
MEDIA_URL = '/media/'
# urls.py
from django.views.static import serve
from day60 import settings # 固定写法
url(r'^media/(?P<path>.*)',serve,{"document_root":settings.MEDIA_ROOT})

因为在models里指定了头像放在avatar下,settings里有指定了media下,

所以用户上传的头像都会保存到/media/avatar

8 个人站点,个人侧边栏

url(r'^(?P<username>\w+)/$', views.site, name='_site')

可以利用ORM的聚合分组来查询,然后定义在前端,

聚合分组查询需要先导入模块

from django.db.models import Count, Max, Min, Sum, Avg
# views
# 查看当前用户的分类及每个分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name') # 查询当前用户的标签及每个标签下的文章数
tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name')

年月日期分组,可以用官方提供的方法,注意导入模块

from django.db.models.functions import TruncMonth

-官方提供
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
# views
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values('c', 'month')
# site.html
{% for date in date_list %}
<p><a href="#">
{{ date.month|date:'Y年m月' }}({{ date.c }})
</a></p>
{% endfor %}

如果报错,在settings里调整市区,把 TIME_ZONE 改为 亚洲上海

# settings.py
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False

9 侧边栏筛选

url匹配优化,将三个url合并

# 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),
url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/', views.site),
 if kwargs:
print(kwargs) # {'condition': 'archive', 'param': '2022-03'}
condition = kwargs.get('condition') # category tag archive
param = kwargs.get('param') # 1 2 2019-11
if condition == 'category':
article_list = article_list.filter(category_id=param)
elif condition == 'tag':
article_list = article_list.filter(tags__pk=param)
else:
year, month = param.split('-')
article_list = article_list.filter(create_time__year=year, create_time__month=month)
# 点击跳转
href="/{{ username }}/category/{{ category.pk }}/"
href="/{{ username }}/tag/{{ tag.pk }}/"
href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/"

10 文章详情页

文章详情页和个人站点的侧边栏一样,可以用模板导入

利用模板导入site.html 时,发现用到一些逻辑渲染出来的,并不能继承

这时需要用到自定义标签 inclusion_tag

url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/',views.article_detail)

在应用名下新建一个名字必须为templatetags的文件夹

在templatetags新建任意.py文件, my_tags.py

# my_tags.py
from django.template import Library
register = Library() # 注意变量名必须为register,不可改变 @register.inclusion_tag('left_menu.html', name='my_left')
def index(username): # index函数里写 left_menu.html 里所需要的的数据
username_obj = models.UserInfo.objects.filter(username=username).first()
blog = username_obj.blog
article_list = models.Article.objects.filter(blog=blog)
# 查看当前用户的分类及每个分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name', 'pk')
# 查询当前用户的标签及每个标签下的文章数
tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name', 'pk')
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 text-center">文章分类</h3>
</div>
<div class="panel-body">
{% for category in category_list %}
<p><a href="/{{ username }}/category/{{ category.pk }}/">{{category.name}}
({{ category.c }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title text-center">文章标签</h3>
</div>
<div class="panel-body">
{% for tag in tag_list %}
<p> <a href="/{{ username }}/tag/{{ tag.pk }}/">{{ tag.name }}
({{ tag.c }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title text-center">日期归档</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>

重新建一个 base.html 文件当作基模板,把 site.html 里的代码复制进行,然后对需要用到的部分用模板导入

...
...
{% load my_tags %} #把刚才写的文件load进来
{% my_left username %} # 这个地方依然可以接受 urls 的有名分组关键字
...
...

而在article_detail文章详情页里,直接继承 base.html 模板,侧边栏不显示的问题就解决了

{% extends 'base.html' %}

{% block site %}

{% endblock %}

然后让文字在前端显示

11 点赞点踩

前端样式直接去复制。。。

怎么判断用户点了赞还是点了踩

给两个div添加一个相同的点击事件

然后用 hasClass('diggit')

如果点击的div有diggit属性就是ture, 没有就是false

从而可以判断,用户点的是赞还是踩

由于点赞点踩涉及业务逻辑比价多,所以新开了一个url

url(r'^up_or_down/', views.up_or_down, name='updown'),

由于前段判断的布尔值是 字符串 类型的,所以要用json转成python数据类型格式

# views.py
from django.db.models import F
import json
def up_or_down(request):
back_dic = {'code':1000, 'msg': ''}
if request.is_ajax():
article_id = request.POST.get('article_id')
is_up = request.POST.get('is_up')
is_up = json.loads(is_up)
"""
1.判断当前用户是否登录
2.当前文章是否是当前用户自己写的
3.当前用户是否已经给当前文章点过赞或踩了
4.操作数据库
操作两张表
数据库优化字段
"""
if request.user.is_authenticated():
article_obj = models.Article.objects.filter(pk=article_id).first()
if not article_obj.blog.userinfo == request.user:
is_click = models.UpAndDown.objects.filter(user=request.user, article=article_obj)
if not is_click:
if is_up:
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1)
back_dic['msg'] = '点赞成功'
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num')+1)
models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
else:
back_dic['code'] = 2000
back_dic['msg'] = '您已经支持过'
else:
back_dic['code'] = 3000
back_dic['msg'] = '不能给自己点赞'
else:
back_dic['code'] = 4000
back_dic['msg'] = '请先<a href="/login/">登陆</a>'
return JsonResponse(back_dic)
# article_detail.html  点赞点踩js代码
<script>
$('.action').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);
{## children() 返回被选元素旗下的所有直接子元素#}
{## next() 获取 当前元素紧邻其后的 同辈元素#}
}else{
$('#digg_tips').html(data.msg) # 这个地方用html() 可以识别html代码
}
}
})
})
</script>

12 文章评论

url(r'^comment/', views.comment, name='_comment'),

子评论,点击回复按钮之后

点击回复按钮发生了哪几件事

1.自动拼接处想要回复评论的那个人的人名 @人名\n

2.评论框自动聚焦

  • 子评论的内容需要做切割处理

  • 子评论的渲染

  • 提交完子评论之后页面不刷新 为何后续的评论都会变成子评论

// 评论样式代码
<div>
<p>评论列表</p>
<hr>
<ul class="list-group">
{% for comment in comment_list %}
<li class="list-group-item"> <span>#{{ forloop.counter }}楼&nbsp;&nbsp;{{ comment.comment_time|date:'Y-m-d' }} <a href="/{{ comment.user }}/">&nbsp;&nbsp;{{ comment.user }}</a></span>
<span class="pull-right"><a class="reply" UserName="{{ comment.user }}" CommentId="{{ comment.pk }}">回复</a></span>
<div>
{% if comment.parent_id %}
{# 拿子评论父评论的用户名 #}
<p>@{{ comment.parent.user.username }}</p> {% endif %}
{{ comment.content }}
</div>
</li>
<br>
{% endfor %} </ul> </div> {% if request.user.is_authenticated %}
<div> <p>发表评论</p>
<p>
昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}">
</p>
<p>评论内容</p>
<p>
<textarea name="content" id="id_content" cols="30" rows="10"></textarea>
</p>
<p>
<button class="btn btn-primary" id="id_comment">提交评论</button>&nbsp;&nbsp;<span></span>
</p>
</div>
{% else %}
<span ><a href="{% url '_login' %}">登录&nbsp;&nbsp;</a></span>
<span><a href=" {% url '_register' %}">注册</a></span>
{% endif %}
# 评论,子评论js代码
var ParentId = null;
$('#id_comment').click(function () {
var conTent = $('#id_content').val();
var $btn = $(this);
// 判断是否需要对conTent 进行处理
if (ParentId){
// 切割 获取第一个\n 对应的索引
var indexN = conTent.indexOf('\n') + 1; // 切片是顾头不顾尾的,所以索引需要加 1
conTent = conTent.slice(indexN) // 将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 Userinfo = '{{ request.user.username }}';
var Content = $('#id_content').val();
// 将内容临时渲染到ul标签内
var temp =`
<li class="list-group-item">
{#<span> <a href="/${Userinfo}/">${Userinfo}</a> </span>#}
<span><span class="glyphicon glyphicon-comment"></span><a href="/${Userinfo}/">${Userinfo}</a></span>
<div>
${Content}
</div>
</li>
`;
$('.list-group').append(temp);
$('#id_content').val(''); $btn.next().text(data.msg);
ParentId = null;
}
}
})
});
// 回复功能
$('.reply').click(function () {
var UserName = $(this).attr('UserName');
var Comment_Id = $(this).attr('CommentId');
var temp = '@' + UserName + '\n';
$('#id_content').val(temp).focus();
ParentId = Comment_Id;
});
</script>
def comment(request):
back_dic = {'code': 1000, 'msg': ''}
if request.is_ajax():
article_id = request.POST.get('article_id')
content = request.POST.get('content')
parent_id = request.POST.get('parent_id')
article_obj = models.Article.objects.filter(pk=article_id).first()
print(type(article_obj))
if request.user.is_authenticated():
models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num')+1)
# 如果是对象,直接用models字段存,不是就用数据库里的字段存
models.Comment.objects.create(content=content, article_id=article_id, user=request.user, parent_id=parent_id)
back_dic['msg'] = '评论成功'
return JsonResponse(back_dic)

13 后台管理

url(r'^backend/', views.backend, name='_backend'),

后台管理可以在template 文件夹下单独再建立一个backend 后台管理文件夹

@login_required
def backend(request):
article_list = models.Article.objects.filter(blog=request.user.blog).all()
# 分页
current_page = request.GET.get('page', 1)
all_count = article_list.count()
page_obj = Pagination(current_page=current_page, all_count=all_count, pager_count=9, per_page_num=3)
page_queryset = article_list[page_obj.start:page_obj.end]
return render(request, 'backend/backend.html', locals())

后台管理前端代码

# backend.html
{% extends 'backend/backend_base.html' %} {% block article %}
<table class="table table-hover table-striped">
<thead>
<tr>
<th>标题</th>
<th>发布日期</th>
<th>评论数</th>
<th>点赞数</th>
<th>操作</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for article in page_queryset %}
<tr>
<td><a href="/{{ request.user.username }}/article/{{ article.pk }}/">{{ article.title }}</a></td>
<td>{{ article.create_time }}</td>
<td>{{ article.comment_num }}</td>
<td>{{ article.up_num }}</td>
<td><a>编辑</a></td>
<td><a>删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pull-right">
{{ page_obj.page_html|safe }}
</div>
{% endblock %}

14 文章添加

KindEditor textarea 组件

文章添加问题

desc = soup.text[0:150]

如何截取150文字,XSS攻击

可以利用BeautifulSoup的 decompose() 方法删除script标签即可

url(r'^add_article/', views.add_article, name='_add_article'),

add_article.htnl 前端页面

{% extends 'backend/backend_base.html' %}

{% block article %}
<p>添加文章</p>
<form action="" method="post">
{% csrf_token %}
<p>标题</p>
<p><input type="text" name="title" class="form-control"></p>
<p>内容</p>
<p>
<textarea name="content" id="id_content" cols="30" rows="10"></textarea>
</p>
<div>
<p>分类</p>
{% for foo in categoty_list %}
{{ foo.name }}<input type="radio" name="category" value="{{ foo.pk }}">
{% endfor %} </div>
<div>
<p>标签</p>
{% for tag in tag_list %}
{{ tag.name }}<input type="checkbox" name="tag" value="{{ tag.pk }}">
{% endfor %} </div>
<input type="submit" class="btn btn-primary">
</form>
<script charset="utf-8" src="/static/kindeditor-4.1.11-zh-CN/kindeditor/kindeditor-all-min.js"></script>
<script>
KindEditor.ready(function(K) {
window.editor = K.create('#id_content',{
width: '100%',
height: '450px',
resizeType: 1
});
});
</script>
{% endblock %}

views.py

@login_required
def add_article(request):
if request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')
category_id = request.POST.get('category')
tag_list = request.POST.getlist('tag')
# 先生成一个该模块的对象
soup = BeautifulSoup(content, 'html.parser')
for tag in soup.find_all():
# 筛选出script标签直接删除
if tag.name == 'script':
tag.decompose() # 删除该标签
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)
print(article_obj)
# 手动操作文章与标签的第三张表
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'))
categoty_list = models.Category.objects.filter(blog=request.user.blog)
tag_list = models.Tag.objects.filter(blog=request.user.blog)

15 编辑器上传图片

url(r'^upload_image/', views.upload_image),
# views.py
import os
from day60 import settings
@login_required
def upload_image(request):
back_dic = {'error': 0}
if request.method == "POST":
file_obj = request.FILES.get('imgFile')
file_dir = os.path.join(settings.BASE_DIR, 'media', 'article_image')
if not os.path.isdir(file_dir):
os.mkdir(file_dir)
file_path = os.path.join(file_dir, file_obj.name)
with open(file_path, 'wb') as f:
for chunk in file_obj.chunks():
f.write(chunk)
# // 成功时
# {
# "error": 0,
# "url": "http://www.example.com/path/to/file.ext"
# }
# // 失败时
# {
# "error": 1,
# "message": "错误信息"
# }
back_dic['url'] = f'/media/article_image/{file_obj.name}'
return JsonResponse(back_dic)

需要在文章详情页添加以下代码

16 修改头像

url(r'^set_avatar/', views.set_avatar, name='_set_avatar'),

修改头像 和 注册的 上传头像 类似

// set_avatar.html
{% extends 'backend/backend_base.html' %} {% block article %} <p>原头像
<img src="/media/{{ request.user.avatar }}/" alt="">
</p> <form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label for="id_avatar">头像
<img src="/static/img/头像1.jpg" alt="" width="200" style="margin-left: 10px" id="id_img">
</label>
<input type="file" name="myfile" id="id_avatar" style="display: none">
</div>
<input type="submit" value="提交" class="btn btn-primary pull-right" id="id_submit"> </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 %}
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)
request.user.avatar = avatar_obj
request.user.save() # 更换头像可以直接用 request.user.save()方法
return render(request, 'set_avatar.html', locals())

最新文章

  1. μC/OS-Ⅲ系统的中断管理
  2. STL容器
  3. MySQL数据库在WINDOWS系统CMD下的编码问题
  4. poj2632 模拟
  5. Net use命令
  6. 《OD学hadoop》第四周0716
  7. Google搜索技巧-从入门到精通(从此学习进步、工作顺心)
  8. linux服务器初步印象,远程连接mysql数据库,传输文件,启动/关闭tomcat命令
  9. php开学之环境搭建
  10. C#调用R语言输出图片
  11. Android 多线程:使用Thread和Handler (从网络上获取图片)
  12. Jquery 解决 H5 placeholder元素问题
  13. [SVN]两个分支合并
  14. Orchard
  15. &nbsp;&nbsp;&nbsp;&nbsp;My GitHub
  16. [python]Python代码安全分析工具(Bandit)
  17. Hdoj 1058.Humble Numbers 题解
  18. js 正则表达式验证网站域名
  19. Codeforces Round #244 (Div. 2) C. Checkposts (tarjan 强连通分量)
  20. ELK实时日志分析平台环境部署--完整记录

热门文章

  1. Windows 10 20H1版名称被定为Windows 10 Version 2004版以示区分
  2. 帆软FineReport报表使用小技巧
  3. JDK各个版本的新特性jdk1.5-jdk8(转)
  4. kubernetes 1.5.2 部署kube-dns 踩过的坑
  5. 一文解读XaaS (转)
  6. 对于在MYSQL_WorkBench中创建新表时对PK NN UQ B UN ZF AI的理解
  7. python中对闭包的理解
  8. AngularJS1.X指令
  9. springboot#父项目
  10. Element 表单校验不消失问题