22. 后台管理页面搭建
所有的导航条的后台管理标签都绑定路由.
<li><a href="/backstage/">后台管理</a></li>
22.1 路由层
url(r'^backstage/', views.backstage),
22.2 视图层
@login_required
def backstage(request):
return render(request, 'backstage/backend.html')
22.3 模板层
后台管理中的页面很多, 可以在templates目录下新建一个目录存放, 用于划分管理.
1. 模板
后台管理的页面导航条, 侧边栏, 管理选择是固定的, 需要改变的是对应区域的内容, 将固定区域制作成模板.
左侧菜单栏使用一个 Accordion example 可折叠栏
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
<div class="panel-body">
</div>
</div>
</div>
</div>
右侧使用展示一个标签页
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">Home</a></li>
<li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
<li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">Messages</a></li>
<li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Settings</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="home">...</div>
<div role="tabpanel" class="tab-pane" id="profile">...</div>
<div role="tabpanel" class="tab-pane" id="messages">...</div>
<div role="tabpanel" class="tab-pane" id="settings">...</div>
</div>
</div>
tempates/backstage/back_base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>后台管理</title>
{% load static %}
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<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>
<link rel="stylesheet" href="{% static 'bootstrap-sweetalert-master/dist/sweetalert.css' %}">
<script src="{% static 'bootstrap-sweetalert-master/dist/sweetalert.min.js' %}"></script>
<link rel="stylesheet" href="/media/css/{{ request.user.blog.site_theme }}/">
{% block css %}
{% endblock %}
</head>
<body>
<div>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<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="#">{{ request.user.blog.site_name }}</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
<li><a href="#">文章</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">更多 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout/">退出登入</a></li>
</ul>
</li>
{% else %}
<li><a href="/register/">注册</a></li>
<li><a href="/login/">登入</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
aria-expanded="true" aria-controls="collapseOne">
更多操作
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
aria-labelledby="headingOne">
<div class="panel-body">
<p><a href="">添加文章</a></p>
<br>
<p><a href="">添加随笔</a></p>
<br>
<p><a href="">草稿箱</a></p>
<br>
<p><a href="">其他</a></p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-10">
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
data-toggle="tab">文章</a></li>
<li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">随笔</a>
</li>
<li role="presentation"><a href="#messages" aria-controls="messages" role="tab"
data-toggle="tab">草稿</a></li>
<li role="presentation"><a href="#settings" aria-controls="settings" role="tab"
data-toggle="tab">设置</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="home">
{% block article %}
{% endblock %}
</div>
<div role="tabpanel" class="tab-pane" id="profile">
{% block essay %}
{% endblock %}
</div>
<div role="tabpanel" class="tab-pane" id="messages">
{% block draft %}
{% endblock %}
</div>
<div role="tabpanel" class="tab-pane" id="settings">
{% block setup %}
{% endblock %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% block js %}
{% endblock %}
</body>
</html>
2. 文章管理页面
新建一个文章管理页面
article_page.html
{% extends 'backstage/back_base.html' %}
{% block article %}
{% endblock %}
使用分页器展示所有的文章
@login_required
def backstage(request):
article_list = models.Article.objects.filter(blog=request.user.blog)
print(article_list)
from app01.utils.pages import Pagination
all_queryset_sum = article_list.count()
page_num = request.GET.get('page', 1)
page_obj = Pagination(
current_page=page_num,
all_count=all_queryset_sum,
per_page_num=2,
pager_count=10,
)
"""
current_page: 当前页 必须的 自动转换类型
all_count: 数据库中的数据总条数 必须的
per_page_num: 每页显示的数据条数 默认10
pager_count: 最多显示的页码个数 默认10
"""
page_queryset = article_list[page_obj.start: page_obj.end]
return render(request, 'backstage/article_page.html', locals())
{% extends 'backstage/back_base.html' %}
{% block article %}
<div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>文章</th>
<th>点赞</th>
<th>点踩</th>
<th>评论数</th>
<th>操作</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for article_obj in page_queryset %}
<tr>
<td><a href="/{{ request.user.username}}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></td>
<td>{{ article_obj.up_num }}</td>
<td>{{ article_obj.down_num }}</td>
<td>{{ article_obj.comment_num }}</td>
<td><a href="">编辑</a></td>
<td><a href="">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="pull-right">
{{ page_obj.page_html|safe }}
</div>
{% endblock %}
文章的标签使用a标签包裹. 在点击标题的时候跳转到文章的详细展示页面
/{{ request.user.username}}/article/{{ article_obj.pk }}/
用户/article/文章的主键
为所有导航条的设置头像a标签设置路由
<li><a href="/backstage/">后台管理</a></li>
23. 添加文章
<p><a href="/add/article/">添加文章</a></p>
23.1 路由层
url(r'^add/article/', views.add_article),
23.2 视图层
def add_article(request):
sort_list = models.Sort.objects.filter(blog=request.user.blog)
print(sort_list)
tag_list = models.Tag.objects.filter(blog=request.user.blog)
print(tag_list)
return render(request, 'backstage/add_article.html', locals())
23.3 模板层
{% extends 'backstage/back_base.html' %}
{% block article %}
<div>
<form action="" method="post">
{% csrf_token %}
<h3 class="bg-info">添加文章</h3>
<div class="form-group">
<p>标题</p>
<input type="text" name="title" class="form-control">
</div>
<div class="form-group">
<p>内容</p>
<textarea name="content" id="id_content" cols="30" rows="10"></textarea>
</div>
<div class="form-group">
<p>分类</p>
{% for sort_obj in sort_list %}
<label class="radio-inline">
<input type="radio" name="sort"
value="{{ sort_obj.pk }}">{{ sort_obj.name }}
</label>
{% endfor %}
</div>
<div class="form-group">
<p>标签</p>
{% for tag_obj in tag_list %}
<label class="checkbox-inline">
<input type="checkbox" name="tag" value="{{ tag_obj.pk }}">{{ tag_obj.name }}
</label>
{% endfor %}
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary btn-block">
</div>
</form>
</div>
{% endblock %}
<p><a href="/add/articlr/">添加文章</a></p>
23.4 Kind编辑器
KindEditor 是一套开源的在线HTML编辑器,主要用于让用户在网站上获得所见即所得编辑效果,开发人员可以用 KindEditor 把传统的多行文本输入框(textarea)替换为可视化的富文本输入框。 KindEditor 使用 JavaScript 编写,可以无缝地与 Java、.NET、PHP、ASP 等程序集成,比较适合在 CMS、商城、论坛、博客、Wiki、电子邮件等互联网应用上使用。
下载地址: http://kindeditor.net/down.php
使用方法:
1.在需要显示编辑器的位置添加textarea输入框。
<textarea id="editor_id"> </textarea></textarea>
id在当前页面必须是唯一的值。
2.在该HTML页面添加以下脚本。
<script charset="utf-8" src="/editor/kindeditor.js"></script>
<script charset="utf-8" src="/editor/lang/zh-CN.js"></script>
<script>
KindEditor.ready(function(K) {
window.editor = K.create('#editor_id');
});
</script>
3.获取HTML数据
html = editor.html();
editor.sync();
html = document.getElementById('editor_id').value;
html = K('#editor_id').val();
html = $('#editor_id').val();
editor.html('HTML内容');
基本参数:
width: 编辑器的宽度,可以设置px或%,比textarea输入框样式表宽度优先度高。
数据类型: String
默认值: textarea输入框的宽度
height: 编辑器的高度,只能设置px,比textarea输入框样式表高度优先度高。
数据类型: String
默认值: textarea输入框的高度
minHeight: 指定编辑器最小高度,单位为px。
数据类型: Int
默认值: 100
resizeType:
数据类型: Int
0: 不能拖动文本框大小
1: 只能改变高度
2: 可以随意改动
items: 配置编辑器的工具栏,其中”/”表示换行,”|”表示分隔符。
数据类型: Array
默认不写展示全部的工具.
更多查看: http://kindeditor.net/docs/option.html
23.5 Kind编辑使用
下载后解压将文件复制到静态文件static目录中
{% block js %}
{% load static %}
<script charset="utf-8" src="{% static 'kindeditor/kindeditor-all-min.js' %}"></script> <!--需要的js文件-->
<script>
KindEditor.ready(function (K) {
window.editor = K.create(
'#id_content',
{
width: '100%',
height: '500px',
resizeType: 1,
});
});
</script>
{% endblock %}
23.6 将文章添加进数据库
后端获取form表单提交的数据, 提交方式为post, 提交成功后返回到管理页面.
后端需要操作两张表:
1. 文章表
字段有 标题title 简介desc 内容content 分类 个人站点(一定不要忘记这个) (点赞点踩评论数默认值)
2. 文章与标签的第三张表
标签
def add_article(request):
if request.method == 'POST':
post_obj = request.POST
title = post_obj.get('title')
content = post_obj.get('content')
sort_id = post_obj.get('sort_id')
tag_id_list = post_obj.getlist('tag_id_list')
desc = content[0:128]
print(sort_id, tag_id_list)
article_obj = models.Article.objects.create(
title=title,
desc=desc,
content=content,
sort_id=sort_id,
blog=request.user.blog)
article_obj_list = []
for tag_id in tag_id_list:
article_to_tag_obj = models.ArticleToTag(article=article_obj, tag_id=tag_id)
article_obj_list.append(article_to_tag_obj)
models.ArticleToTag.objects.bulk_create(article_obj_list)
return redirect('/backstage/')
sort_list = models.Sort.objects.filter(blog=request.user.blog)
tag_list = models.Tag.objects.filter(blog=request.user.blog)
return render(request, 'backstage/add_article.html', locals())
23.7 添加文章问题
文章添加成功之后还存在两个问题.
1. 文章的简介是截取html格式的文章, 看起来杂乱.
解决方法: 将文本信息提起出来之后(剔除标签)再截取文本的字符作为文章简介.
2. 文章使用html书写, 用户可以在html中利用script标签写JavaScript代码, js可以写死循环,
不停的向后端发送请求, 如果多写些文章, 不停的向服务器发送请求, 造成XSS脚本攻击.
简介方法:
将script标签的内容注释
删除script标签
写一个while简单测试.
<script>
while (1){
alert(123)
}
</script>
Kind编辑器点击html按钮, 之后写html代码, 不点html按钮写的就是段落, 被p标签包裹.
打开文章会卡死, 开发者工具都出卡死.
23.8 beautifulsoup4模块
beautiful soup使用Python得到一个库只要的功能可以从HTML或XML文件中提取数据, 能将上面两个问题解决.
使用方式:
安装 pip install beautifulsoup4
导入模块 from bs4 import BeautifulSoup
方法:
find_all() 获取所有标签
标签.decompose() 删除标签
text 获取所有文本--> str类型
soup = BeautifulSoup(content, 'html.parser')
tags = soup.find_all()
for tag in tags:
if tag.name == 'script':
tag.decompose()
desc = soup.text[0:150]
再次测试
文章的简介正常.
文章展示区域的script被删除.
24. 编辑器上传文件
24.1 上传接口
KindEditor默认提供ASP、ASP.NET、PHP、JSP上传程序,这些程序是演示程序,建议不要直接在实际项目中使用。
详细: http://kindeditor.net/docs/upload.html
Kind编辑器框架, 写好了图片上传接口, 但这个接口是开发者的, 需要替换成自己的接口, 不然就会失效.
KindEditor.ready(function(K) {
K.create('#textarea_id', {
uploadJson : '../jsp/upload_json.jsp',
fileManagerJson : '../jsp/file_manager_json.jsp',
allowFileManager : true
});
});
使用form表单提交, 提交方式为POST.
POST参数:
imgFile: 文件form名称
dir: 上传类型,分别为image、flash、media、file
返回格式(JSON)
//成功时
{
"error" : 0,
// 图片的路径
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
采用表单就需要进行csrf检验.
编辑器的 extraFileUploadParams:
上传图片、Flash、视音频、文件时,支持添加别的参数一并传到服务器。
{% block js %}
{% load static %}
<script charset="utf-8" src="{% static 'kindeditor/kindeditor-all-min.js' %}"></script> <!--需要的js文件-->
<script>
KindEditor.ready(function (K) {
window.editor = K.create(
'#id_content',
{
width: '100%',
height: '500px',
resizeType: 1,
uploadJson: '/upload_img/',
extraFileUploadParams: {
'csrfmiddlewaretoken': "{{csrf_token }}",
}
});
});
</script>
{% endblock %}
24.2 路由层
url(r'upload_img/', views.upload_img),
24.3 视图层
def upload_img(request):
if request.method == 'POST':
print(request.FILES)
return HttpResponse('OK')
import uuid
randon_str = str(uuid.uuid4())
name, suffix = file_obj.name.split('.')
file_name = name + '_' + randon_str + '.' + suffix
print(file_name)
上传文件之后有一个固定分返回值格式:
//成功时
{
"error" : 0,
// 文件的路径
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
文件的返回:
http://127.0.0.1:8000/
media/article_img/%E5%A4%B4%E5%83%8F1_bc32ac91-06d2-4014-bc22-b7587d39bfc8.png
def upload_img(request):
if request.method == 'POST':
try:
print(request.FILES)
file_obj = request.FILES.get('imgFile')
import os
from BBS.settings import BASE_DIR
file_dir = os.path.join(BASE_DIR, 'media', 'article_img')
if not os.path.isdir(file_dir):
os.mkdir(file_dir)
import uuid
randon_str = str(uuid.uuid4())
name, suffix = file_obj.name.split('.')
file_name = name + '_' + randon_str + '.' + suffix
print(file_name)
file_path = os.path.join(file_dir, file_name)
print(file_path)
with open(file_path, mode='wb') as wf:
for line in file_obj:
wf.write(line)
back_dict = {'error': 0,
'url': '/media/article_img/file_name/'
}
except Exception as e:
back_dict = {'error': 0,
'message': e
}
return JsonResponse(back_dict)
else:
return HttpResponse('非法访问!')
24. 修改头像
为所有导航条的设置头像a标签设置路由
<li><a href="/set/avatar/">修改头像</a></li>
24.1 路由层
url(r'^set/avatar/', views.set_avatar),
24.2 视图层
def set_avatar(request):
return render(request, 'set_avatar.html')
24.3 模板层
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% load static %}
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<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-fluid">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row">
<div class="col-md-4 col-md-offset-2">
<h3>原头像</h3>
<img src="/media/{{ request.user.avatar }}" alt="..." width="300px">
</div>
<div class="col-md-4 col-md-offset-2">
<label for="my_avatar"><h3>新头像(点击图片)</h3>
{% load static %}
<img src="{% static 'img/default.png' %}" alt="" id='my_img' width="300px">
<input type="file" name="avatar" id="my_avatar" style="display: none">
</label>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4"><input type="submit" class="btn-primary btn-block"></div>
</div>
</div>
</form>
</div>
<script>
$('#my_avatar').change(function () {
let FileReaderObj = new FileReader()
let UpFileObj = $(this)[0].files[0];
FileReaderObj.readAsDataURL(UpFileObj)
FileReaderObj.onload = function () {
$('#my_img').attr('src', FileReaderObj.result)
}
})
</script>
</body>
</html>
24.4 业务逻辑
def set_avatar(request):
if request.method == 'POST':
file_obj = request.FILES.get('avatar')
models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=file_obj)
"""
models.UserInfo.objects.update(avatar=file_obj)
直接更新, 头像的路径就变成了文件名, 少了 media目录开头.
解决方法:
1. 手动拼接 media
2. 先创建对象在使用save保存.
"""
user_obj = request.user
user_obj.avatar = file_obj
user_obj.save()
return redirect('/home/')
return render(request, 'set_avatar.html')
models.UserInfo.objects.update(avatar=file_obj)
先创建数据对象在修改值, 再保存则会以media开头.
user_obj = request.user
user_obj.avatar = file_obj
user_obj.save()
25. 编辑文章
管理页面编辑标签绑定编辑文章的路由.
25.1 路由层
后端需要文字的数据(包含当前文章的类di), 站点的所有分类, 站点的所有标签, 标签与文章第三张表的数据.
url(r'^edit_article/', views.edit_article),
25.2 视图层
def edit_article(request):
article_id = request.GET.get('article_id')
print(article_id)
if article_id:
article_obj = models.Article.objects.filter(pk=article_id).first()
if article_obj:
sort_list = models.Sort.objects.filter(blog=request.user.blog)
tag_list = models.Tag.objects.filter(blog=request.user.blog)
article_tag = models.ArticleToTag.objects.filter(article_id=article_id)
all_tag = []
for obj in article_tag:
print(obj.tag)
all_tag.append(obj.tag)
return render(request, 'backstage/edit_article.html', locals())
return render(request, 'error.html')
默认选中
blog_obj = models.Blog.objects.filter(site_name='kid')
tag_obj = models.Tag.objects.filter(blog=blog_obj)
print(tag_obj)
for obj in tag_obj:
print(obj)
"""
kid站点第一个标签
kid站点第二个标签
"""
ArticleToTag_obj = models.ArticleToTag.objects.filter(article_id=1)
print(ArticleToTag_obj)
ArticleToTag_list = []
for obj in ArticleToTag_obj:
print(obj.tag)
ArticleToTag_list.append(obj.tag)
for obj in tag_obj:
if obj in ArticleToTag_list:
print(f'默认选中{obj}')
"""
默认选中kid站点第一个标签
默认选中kid站点第二个标签
"""
"""
# 7.4 打印query_set self.外键 子查询, 查到的一个数据对象, 打印的触发该类的__str__
def __str__(self):
return f'{self.article_id} -- {self.tag_id}'
如果__str__方法中返回的值id值那么在获取数据的时候拿的就是 获取到的就是 id值
models.ArticleToTag.objects.filter(article_id=1)
# <QuerySet [<ArticleToTag: 1 -- 1>, <ArticleToTag: 1 -- 2>]>
return f'{self.article} -- {self.tag}'
如果__str__方法中返回的值数据对象值那么在获取数据的时候拿的就是 获取到的就是 数据对象
# <QuerySet [<ArticleToTag: kid发布的第一篇文章 Python介绍 -- kid站点第一个标签>
在前端需要默认选中标签的时候, 就会出现
for i in [1,2]
i in [[1,2], [2,4]] 这样的格式
把需要的值先取出来拿一个新的列表存储 [[1,2], [2,4]] ==> [2, 4]
i in [2, 4] 这样就正常了
"""
25.3 模板层
类别默认选中 {% if sort_obj.pk == article_obj.sort_id %}
标签默认选中 {% if tag_obj in all_tag %}
{% extends 'backstage/back_base.html' %}
{% block article %}
<div>
<form action="" method="post">
{% csrf_token %}
<h3 class="bg-info">添加文章</h3>
<div class="form-group">
<p>标题</p>
<input type="text" name="title" class="form-control" value="{{ article_obj.title }}">
</div>
<div class="form-group">
<p>内容</p>
<textarea name="content" id="id_content" cols="30" rows="10">
{{ article_obj.content }}
</textarea>
</div>
<div class="form-group">
<p>分类</p>
{% for sort_obj in sort_list %}
{% if sort_obj.pk == article_obj.sort_id %}
<label class="radio-inline">
<input type="radio" name="sort_id"
value="{{ sort_obj.pk }}" checked="checked">{{ sort_obj.name }}
</label>
{% else %}
<label class="radio-inline">
<input type="radio" name="sort_id"
value="{{ sort_obj.pk }}">{{ sort_obj.name }}
</label>
{% endif %}
{% endfor %}
</div>
<div class="form-group">
<p>标签</p>
{% for tag_obj in tag_list %}
{% if tag_obj in all_tag %}
<label class="checkbox-inline">
<input type="checkbox" name="tag_id_list" value="{{ tag_obj.pk }}"
checked="checked">{{ tag_obj.name }}
</label>
{% else %}
<label class="checkbox-inline">
<input type="checkbox" name="tag_id_list" value="{{ tag_obj.pk }}">{{ tag_obj.name }}
</label>
{% endif %}
{% endfor %}
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary btn-block">
</div>
</form>
</div>
{% endblock %}
{% block js %}
{% load static %}
<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: 1,
uploadJson: '/upload_img/',
extraFileUploadParams: {
'csrfmiddlewaretoken': "{{ csrf_token }}",
}
});
});
</script>
{% endblock %}
25.4 保存文件
第三张表是手动创建的, 无法使用ORM的add set, remove, clear方法
需要将原来的数全部删除再重新写数据.
@login_required
def edit_article(request):
article_id = request.GET.get('article_id')
if request.method == 'POST':
post_obj = request.POST
title = post_obj.get('title')
content = post_obj.get('content')
sort_id = post_obj.get('sort_id')
tag_id_list = post_obj.getlist('tag_id_list')
soup = BeautifulSoup(content, 'html.parser')
tags = soup.find_all()
for tag in tags:
if tag.name == 'script':
tag.decompose()
desc = soup.text[0:150]
article_obj = models.Article.objects.filter(pk=article_id).update(
title=title,
desc=desc,
content=str(soup),
sort_id=sort_id,
blog=request.user.blog)
models.ArticleToTag.objects.filter(article_id=article_id).delete()
article_obj_list = []
for tag_id in tag_id_list:
article_to_tag_obj = models.ArticleToTag(article_id=article_id, tag_id=tag_id)
article_obj_list.append(article_to_tag_obj)
models.ArticleToTag.objects.bulk_create(article_obj_list)
return redirect('/backstage/')
if article_id:
article_obj = models.Article.objects.filter(pk=article_id).first()
if article_obj:
sort_list = models.Sort.objects.filter(blog=request.user.blog)
tag_list = models.Tag.objects.filter(blog=request.user.blog)
article_tag = models.ArticleToTag.objects.filter(article_id=article_id)
all_tag = []
for obj in article_tag:
print(obj.tag)
all_tag.append(obj.tag)
return render(request, 'backstage/edit_article.html', locals())
return render(request, 'error.html')
26. 删除文件
删除一篇文章, 需要先删除文章表与标签表第三张表的数据, 再删除文章.
26.1 路由层
url(r'^del_article/', views.del_article),
26.2 数图层
@login_required
def del_article(request):
if request.is_ajax():
article_id = request.POST.get('article_id')
print(article_id)
try:
models.ArticleToTag.objects.filter(article_id=article_id).delete()
models.Article.objects.filter(pk=article_id).delete()
back_dict = {'code': 200}
except Exception as e:
print(e)
back_dict = {'code': 400}
return JsonResponse(back_dict)
else:
return render(request, 'error.html')
26.3模板层
{% block js %}
<script>
$('.del_article').on('click', function () {
let article_id = $(this).attr('article_id')
console.log(article_id)
swal({
title: '你确定删除吗?',
text: '删除之后清除所有数据!',
type: 'warning',
showCancelButton: true,
confirmButtonText: '确定删除!',
cancelButtonText: '取消操作!',
closeOnConfirm: false,
closeOnCancel: false
},
function (isConfirm) {
if (isConfirm) {
$.ajax({
url: '/del_article/',
type: 'post',
data: {'article_id': article_id , 'csrfmiddlewaretoken': '{{ csrf_token }}'},
success: function (args) {
if (args.code === 200){
swal("删除成功", "想要找回请在3小时内联系管理员!", "success");
window.location.reload()
}
}
})
} else {
swal('取消成功!', "已经撤销你的操作", 'error');
}
}
)
})
</script>
{% endblock %}
|