day09 后台管理
后台管理
"""
当一个文件夹下文件比较多的时候 你还可以继续创建文件夹分类处理
templates文件夹
backend文件夹
应用1文件夹
应用2文件夹
"""
添加文章
有两个需要注意的问题
1.文章的简介
不能直接切去
应该先想办法获取到当前页面的文本内容之后截取150个文本字符
2.XSS攻击
针对支持用户直接编写html代码的网址
针对用户直接书写的script标签 我们需要处理
1.注视标签内部的内容
2.直接将script删除
如何解决?
我们自己的话
针对1 后端通过正则表达式筛选
针对2 首先需要确定及获取script标签
这两步都好烦 有木有人来帮我一下
beautifulsoup模块 bs4模块
专门用来帮你处理html页面内的
该模块主要用于爬虫程序
下载千万不要下错了
pip3 install beautifulsoup4
# 模块使用
soup = BeautifulSoup(content,'html.parser')
tags = soup.find_all()
# 获取所有的标签
for tag in tags:
# print(tag.name) # 获取页面所有的标签
# 针对script标签 直接删除
if tag.name == 'script':
# 删除标签
tag.decompose()
# 文章简介
# 1 先简单暴力的直接切去content 150个字符
# desc = content[0:150]
# 2 截取文本150个
desc = soup.text[0:150]
"""
当你发现一个数据处理起来不是很方便的时候
可以考虑百度搜搜有没有现成的模块帮你完成相应的功能
"""
kindeditor富文本编辑器
编辑器的种类有很多,你可以自己去网上搜索
编辑器上传图片
别人写好了接口 但是接口不是你自己的
你需要手动去修改
# 在使用别人的框架或者模块的时候 出现了问题不要慌 看看文档可能会有对应的处理方法
代码参考
目录结构
mypage.py
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=10, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
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)
url.py
# 后台管理
url(r'^backend/',views.backend),
# 添加文章
url(r'^add/article/',views.add_article),
# 编辑器上传图片接口
url(r'^upload_image/',views.upload_image),
view.py
from app01.utils.mypage import Pagination
@login_required
def backend(request):
# 获取当前用户对象所有的文章展示到页面
article_list = models.Article.objects.filter(user=request.user)
page_obj = Pagination(current_page=request.GET.get('page', 1), all_count=article_list.count())
page_queryset = article_list[page_obj.start:page_obj.end]
return render(request, 'backend/backend.html', locals())
from bs4 import BeautifulSoup
@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_id_list = request.POST.getlist('tag')
# 模块使用
soup = BeautifulSoup(content, 'html.parser')
tags = soup.find_all()
# 获取所有的标签
for tag in tags:
# print(tag.name) # 获取页面所有的标签
# 针对script标签 直接删除
if tag.name == 'script':
# 删除标签
tag.decompose()
# 文章简介
# 1 先简单暴力的直接切去content 150个字符
# desc = content[0:150]
# 2 截取文本150个
desc = soup.text[0:150]
article_obj = models.Article.objects.create(
title=title,
content=str(soup),
desc=desc,
category_id=category_id,
user=request.user
)
# 文章和标签的关系表 是我们自己创建的 没法使用add set remove clear方法
# 自己去操作关系表 一次性可能需要创建多条数据 批量插入bulk_create()
article_obj_list = []
for i in tag_id_list:
tag_article_obj = models.Article2Tag(article=article_obj, tag_id=i)
article_obj_list.append(tag_article_obj)
# 批量插入数据
models.Article2Tag.objects.bulk_create(article_obj_list)
# 跳转到后台管理文章展示页
return redirect('/backend/')
category_list = models.Category.objects.filter(blog=request.user.blog)
tag_list = models.Tag.objects.filter(blog=request.user.blog)
# print(request.user.blog)
# print(category_list,tag_list)
return render(request, 'backend/add_article.html', locals())
import os
from BBS import settings
def upload_image(request):
"""
//成功时
{
"error" : 0,
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
:param request:
:return:
"""
back_dic = {'error': 0, } # 先提前定义返回给编辑器的数据格式
# 用户写文章上传的图片 也算静态资源 也应该防盗media文件夹下
if request.method == "POST":
# 获取用户上传的图片对象
# print(request.FILES) # 打印看到了健固定叫imgFile
file_obj = request.FILES.get('imgFile')
# 手动拼接存储文件的路径
file_dir = os.path.join(settings.BASE_DIR, 'media', 'article_img')
# 优化操作 先判断当前文件夹是否存在 不存在 自动创建
if not os.path.isdir(file_dir):
os.mkdir(file_dir) # 创建一层目录结构 article_img
# 拼接图片的完整路径
file_path = os.path.join(file_dir, file_obj.name)
with open(file_path, 'wb') as f:
for line in file_obj:
f.write(line)
back_dic['url'] = '/media/article_img/%s' % file_obj.name
return JsonResponse(back_dic)
backend_base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
{% block css %}
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- 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="#">{{ request.user.blog.site_title }}</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">
<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="/set/avatar/">修改头像</a></li>
<li><a href="/backend/">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}">退出登陆</a></li>
</ul>
<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">
<h1 class="text-center">修改密码</h1>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<label for="">用户名</label>
<input type="text" disabled value="{{ request.user.username }}"
class="form-control">
</div>
<div class="form-group">
<label for="">原密码</label>
<input type="password" id="id_old_password" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="password" id="id_new_password" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="password" id="id_confirm_password" class="form-control">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消
</button>
<button class="btn btn-primary" id="id_edit">修改</button>
<span style="color: red" id="password_error"></span>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
</li>
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'login' %}">登陆</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</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">
<a href="/add/article/">添加文章</a>
</div>
</div>
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
aria-labelledby="headingOne">
<div class="panel-body">
<a href="">添加随笔</a>
</div>
</div>
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
aria-labelledby="headingOne">
<div class="panel-body">
<a href="">草稿箱</a>
</div>
</div>
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
aria-labelledby="headingOne">
<div class="panel-body">
<a href="">更多设置</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-10">
<div>
<!-- Nav tabs -->
<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>
<!-- Tab panes -->
<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 suibi %}
随笔界面
{% endblock %}
</div>
<div role="tabpanel" class="tab-pane" id="messages">
{% block file %}
文件界面
{% endblock %}
</div>
<div role="tabpanel" class="tab-pane" id="settings">
{% block set %}
设置界面
{% endblock %}
</div>
</div>
</div>
</div>
</div>
</div>
{% block js %}
{% endblock %}
</body>
</html>
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>
</tr>
</thead>
<tbody>
{% for article in page_queryset %}
<tr>
<td><a href="/{{ article.user.username }}/article/{{ article.pk }}/">{{ article.title }}</a></td>
<td>{{ article.up_count }}</td>
<td>{{ article.comment_count }}</td>
<td><a href="">编辑</a></td>
<td><a href="">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pull-right">
{{ page_obj.page_html|safe }}
</div>
{% endblock %}
add_article.html
{% extends 'backend/backend_base.html' %}
{% block article %}
<h3>添加文章</h3>
{# 直接利用form表单提交数据#}
<form action="" method="post">
{% csrf_token %}
<p>标题</p>
<div>
<input type="text" name="title" class="form-control">
</div>
<p>内容</p>
<div>
<textarea name="content" id="id_content" cols="30" rows="10"></textarea>
</div>
<p>分类</p>
<div>
{% for category in category_list %}
<input type="radio" value="{{ category.pk }}" name="category">{{ category.title }}
{% endfor %}
</div>
<p>标签</p>
<div>
{% for tag in tag_list %}
<input type="checkbox" value="{{ tag.pk }}" name="tag">{{ tag.title }}
{% endfor %}
</div>
<input type="submit" class="btn btn-danger">
</form>
{% 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: '600px',
resizeType:1,
uploadJson : '/upload_image/', // 上传图片的后端提交路径
extraFileUploadParams : {
'csrfmiddlewaretoken':'{{ csrf_token }}'
}
});
});
</script>
{% endblock %}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具