一、后台功能的初始配置
1. urls.py路由分发
re_path('app02/', include('app02.urls')),
2.app02/urls.py
from django.urls import path, re_path, include
from app02 import views
urlpatterns = [
path('home/', views.home),
path('article_list/', views.article_list),
path('add_article/', views.add_article),
path('upload_image/', views.upload_image),
]
二、后台功能之首页
1.首页前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% load static %}
<script src="{% static 'js/jquery.min.js' %}"></script>
<link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
<script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
<script src="{% static 'layer/layer.js' %}"></script>
</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="#">BBS博客园后台系统</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">搜索</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.session.username %}
<li style="line-height: 50px;">
<!-- /media/{ article.blog.userinfo.avatar }} -->
{# <img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" style="width: 100px;" alt="..."> #}
<img src="/media/{{ cur_avatar }}" style="width: 50px; height: 36px;" class="onImg" alt="">
</li>
<li><a href="#">{{ request.session.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="/logout/">退出登录</a></li>
<li><a href="#">后台管理</a></li>
</ul>
</li>
{% else %}
<li><a href="/login/">登录</a></li>
<li><a href="/register/">注册</a></li>
{% endif %}
</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">
<div class="row">
<h1 class="text-center">修改密码</h1>
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
用户名:<input type="text" readonly value="{{ request.session.username }}"
class="form-control">
</div>
<div class="form-group">
原密码:<input type="password" id="old_password" class="form-control" msg="原密码必须输入">
</div>
<div class="form-group">
新密码:<input type="password" id="new_password" class="form-control" msg="原密码必须输入">
</div>
<div class="form-group">
确认密码:<input type="password" id="re_password" class="form-control" msg="原密码必须输入">
</div>
<div class="form-group">
<input type="button" value="修改密码" class="btn btn-primary btn-block btn_password">
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{# 导航条结束 #}
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="list-group">
<a href="#" class="list-group-item active">
首页
</a>
<a href="/app02/article_list/" class="list-group-item">文章列表</a>
<a href="#" class="list-group-item">分类类别</a>
<a href="#" class="list-group-item">标签列表</a>
<a href="#" class="list-group-item">更多</a>
</div>
</div>
<div class="col-md-9">
<div class="panel panel-info">
<div class="panel-heading">我自一口真气足</div>
<div class="panel-body">
{% block content %}
<div class="jumbotron">
<h1>最牛叉的博客平台</h1>
<p>无招胜有招</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">更过风景</a></p>
</div>
<div class="row">
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="https://img2.baidu.com/it/u=3323311628,2330835932&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1692464400&t=3cf590e7ea88465e48ef5170f7c70884"
alt="...">
<div class="caption">
<h3>Thumbnail label</h3>
<p>清风拂山岗</p>
<p><a href="#" class="btn btn-primary" role="button">Button</a> <a href="#"
class="btn btn-default"
role="button">如来神掌</a>
</p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="https://img2.baidu.com/it/u=3323311628,2330835932&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1692464400&t=3cf590e7ea88465e48ef5170f7c70884"
alt="...">
<div class="caption">
<h3>Thumbnail label</h3>
<p>清风拂山岗</p>
<p><a href="#" class="btn btn-primary" role="button">Button</a> <a href="#"
class="btn btn-default"
role="button">如来神掌</a>
</p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="https://img2.baidu.com/it/u=3323311628,2330835932&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1692464400&t=3cf590e7ea88465e48ef5170f7c70884"
alt="...">
<div class="caption">
<h3>Thumbnail label</h3>
<p>清风拂山岗</p>
<p><a href="#" class="btn btn-primary" role="button">Button</a> <a href="#"
class="btn btn-default"
role="button">如来神掌</a>
</p>
</div>
</div>
</div>
</div>
{% endblock %}
</div>
</div>
</div>
</div>
</div>
{% block js %}
{% endblock %}
</body>
</html>
2.首页后端
def home(request):
return render(request, 'backend/home.html', locals())
三、后台功能之文章列表展示
1.文章列表展示前端
{% extends 'backend/home.html' %}
{% block content %}
<h2 class="text-center">文章列表</h2>
<form action="">
<div class="form-group">文章标题
<input type="text" id="title" class="form-control">
</div>
<div class="form-group">文章分类
<select name="" id="cate" class="form-control">
{% for cate in cate_list %}
<option value="{{ cate.pk }}">{{ cate.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<p>文章标签</p>
{% for tags in tags_list %}
{{ tags.name }} <input type="checkbox" value="{{ tags.pk }}" name="tags">
{% endfor %}
</div>
<div class="form-group">文章内容
<textarea id="editor_id" name="content" style="width:100%;height:300px;"></textarea>
</div>
<div class="form-group">
<input type="button" id="title" value="提交" class="btn btn-success btn-block btn_article">
</div>
</form>
{% endblock %}
{% block js %}
<script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script>
<script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
height: '500px',
items: [
'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste',
'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright',
'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript',
'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/',
'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold',
'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image', 'multiimage',
'flash', 'media', 'insertfile', 'table', 'hr', 'emoticons', 'baidumap', 'pagebreak',
'anchor', 'link', 'unlink', '|', 'about'
],
colorTable: [
['#E53333', '#E56600', '#FF9900', '#64451D', '#DFC5A4', '#FFE500'],
['#009900', '#006600', '#99BB00', '#B8D100', '#60D978', '#00D5FF'],
['#337FE5', '#003399', '#4C33E5', '#9933E5', '#CC33E5', '#EE33EE'],
['#FFFFFF', '#CCCCCC', '#999999', '#666666', '#333333', '#000000']
],
resizeType: 0, // 0,1,2
uploadJson : '/app02/upload_image/',
extraFileUploadParams : {
csrfmiddlewaretoken: '{{ csrf_token }}'
},
});
});
$(".btn_article").click(function () {
// 接收参数
// 标题
let title = $("#title").val();
//分类
let cate_id = $("#cate").val();
// 标签:由于是复选框,所以有可能有多个值的情况,需用循环取值
// 定义一个数组,用于接收tags的值
tags_arr = []
var tags = $("input[name='tags']:checked");
$.each(tags, function (index, value) {
console.log(index, $(this).val());
// 将复选框的值放入数组中
tags_arr.push($(this).val());
});
console.log(tags_arr); // [3, 4]
// 注意:由于这里的值是数组,在后端获取不到,需要转为字符串才能传给后端获取
// 数组转字符串 join
var tags_str = tags_arr.join(',') // 3, 4
console.log(tags_str);
// 内容
// 同步数据后可以直接取得textarea的value
editor.sync();
content = $('#editor_id').val(); // jQuery
console.log(content)
// 发起Ajax请求
$.ajax({
url: '',
type: 'post',
data: {
title: title,
cate_id: cate_id,
tags: tags_str,
content: content,
csrfmiddlewaretoken: '{{ csrf_token }}',
},
success: function (res) {
if (res.code === 200) {
layer.msg(res.msg, {} ,function () {
location.href = '/app02/article_list/'
});
} else {
layer.msg(res.msg, {});
}
}
});
});
</script>
{% endblock %}
2.文章列表后端
def article_list(request):
# 查询出所有的文章列表
articles_list = models.Article.objects.all()
return render(request, 'backend/article_list.html', locals())
三、后台功能之添加文章
1.添加文章前端
{% extends 'backend/home.html' %}
{% block content %}
<h3 class="text-center">添加文章</h3>
<form action="">
<div class="form-group">文章标题:
<input type="text" id="title" class="form-control">
</div>
<div class="form-group">文章分类:
<select name="" id="cate" class="form-control">
{% for category in category_list %}
<option value="{{ category.pk }}">{{ category.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<p>文章标签:</p>
{% for tags in tags_list %}
{{ tags.name }}
<input type="checkbox" id="title" name="tags" value="{{ tags.pk }}" style="margin-right: 10px;">
{% endfor %}
</div>
<div class="form-group">文章内容:
<textarea id="editor_id" name="content" style="width:100%;height:400px;"></textarea>
</div>
<div class="form-group">
<!-- type="submit" 会自动提交表单 -->
<!-- <button></button> 会自动提交表单,注意:当写在外面的时候才不会自动提交form表单 -->
{# <input type="submit" value="提交" id="title" class="btn btn-success btn-block"> #}
<!-- type="button" 不会自动提交表单 -->
<input type="button" value="提交" id="title" class="btn btn-success btn-block btn_article">
</div>
</form>
{% endblock %}
2.添加文章后端
def add_article(request):
# 文本编辑器官网 http://www.kindsoft.net/down.php
user_obj = models.UserInfo.objects.filter(pk=request.session.get('id')).first()
if not user_obj:
return redirect('/login/')
blog = user_obj.blog
# 查询所有的分类
category_list = models.Category.objects.all()
# 查询所有的标签列表
tags_list = models.Tag.objects.all()
back_dict = {'code': 200, 'msg': '添加成功', 'data': []}
# 1.获取前段传递过来的数据
if request.method == 'POST':
title = request.POST.get('title')
cate_id = request.POST.get('cate_id')
content = request.POST.get('content')
tags = request.POST.get('tags') # 1, 2
# tags原本是一个列表,但是前端传值时进行了转换,有列表转为了字符串
# 在此,要先转位列表去使用
tags_list = tags.split(',') # [1, 2]
# 2.参数验证
if not title:
back_dict['code'] = 1500
back_dict['msg'] = '标题必须有'
return JsonResponse(back_dict)
if not cate_id:
back_dict['code'] = 1501
back_dict['msg'] = '分类必须选择'
return JsonResponse(back_dict)
if not tags:
back_dict['code'] = 1502
back_dict['msg'] = '标签必须选择'
return JsonResponse(back_dict)
if not content:
back_dict['code'] = 1503
back_dict['msg'] = '内容不能为空'
return JsonResponse(back_dict)
'''
1.摘要截取的问题
2.xss攻击的问题----->原理:有了script标签------>把提交过来的内容过滤出script标签,然后做删除
解决方法:
1.使用正则匹配 script,匹配到之后,做删除,这个方法很麻烦,不推荐
2.利用第三方模块来处理:bs4模块
pip install bs4
BeautifulSoup它是用在爬虫里面,它能够筛选数据,清晰html数据
BeautifulSoup('', 'html.parser')
使用lxml的话需要安装 pip install lxml
BeautifulSoup('', 'lxml')
'''
# 解决xss攻击的问题
from bs4 import BeautifulSoup
soup = BeautifulSoup(content, 'html.parser')
# print(soup.find_all('a')) # 写的a那么久就筛选a
# print(soup.find_all('script'))
# print(soup.text) # 打印文本内容
for tag in soup.find_all():
# print(tag.name)
if tag.name == 'script':
# 删除script
tag.decompose()
# 文章摘要直接从内容中截取 100字
desc = soup.text[:100]
# 3.数据入库,需要操作文章表,文章、标签的第三张表
article_obj = models.Article.objects.create(title=title, content=str(soup), desc=desc, category_id=cate_id,
blog=blog)
# 操作标签的第三张表,数据是多对多,选择批量插入数据
article_tag_list = []
for i in tags_list:
article_tag_obj = models.Article2Tag(article_id=article_obj.pk, tag_id=i)
article_tag_list.append(article_tag_obj)
models.Article2Tag.objects.bulk_create(article_tag_list)
return JsonResponse(back_dict)
return render(request, 'backend/add_article.html', locals())
四、后台功能之上传文件
1.添加文章前端
{% extends 'backend/home.html' %}
{% block content %}
<h3 class="text-center">添加文章</h3>
<form action="">
<div class="form-group">文章标题:
<input type="text" id="title" class="form-control">
</div>
<div class="form-group">文章分类:
<select name="" id="cate" class="form-control">
{% for category in category_list %}
<option value="{{ category.pk }}">{{ category.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<p>文章标签:</p>
{% for tags in tags_list %}
{{ tags.name }}
<input type="checkbox" id="title" name="tags" value="{{ tags.pk }}" style="margin-right: 10px;">
{% endfor %}
</div>
<div class="form-group">文章内容:
<textarea id="editor_id" name="content" style="width:100%;height:400px;"></textarea>
</div>
<div class="form-group">
<!-- type="submit" 会自动提交表单 -->
<!-- <button></button> 会自动提交表单,注意:当写在外面的时候才不会自动提交form表单 -->
{# <input type="submit" value="提交" id="title" class="btn btn-success btn-block"> #}
<!-- type="button" 不会自动提交表单 -->
<input type="button" value="提交" id="title" class="btn btn-success btn-block btn_article">
</div>
</form>
{% endblock %}
{% block js %}
{% load static %}
<script charset="utf-8" src="{% static 'kindeditor/kindeditor-all.js' %}"></script>
<script charset="utf-8" src="{% static 'kindeditor/lang/zh-CN.js' %}"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
height: '300px',
items: [
'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste',
'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright',
'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript',
'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/',
'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold',
'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image', 'multiimage',
'flash', 'media', 'insertfile', 'table', 'hr', 'emoticons', 'baidumap', 'pagebreak',
'anchor', 'link', 'unlink', '|', 'about'
],
resizeType: 0,
colorTable: [
['#E53333', '#E56600', '#FF9900', '#64451D', '#DFC5A4', '#FFE500'],
['#009900', '#006600', '#99BB00', '#B8D100', '#60D978', '#00D5FF'],
['#337FE5', '#003399', '#4C33E5', '#9933E5', '#CC33E5', '#EE33EE'],
['#FFFFFF', '#CCCCCC', '#999999', '#666666', '#333333', '#000000'],
],
uploadJson: '/upload_image/',
extraFileUploadParams: {
csrfmiddlewaretoken: '{{ csrf_token }}'
},
});
});
// 绑定点击事件
$(".btn_article").click(function () {
// 同步数据后可以直接取得textarea的value
editor.sync();
// 获取数据
// 标题
let title = $("#title").val();
// 分类
let cate = $("#cate").val();
// 文章标签是复选框,所以通过属性选择器获取值,
// 由于是复选框的缘故,可能会有多个值的情况出现,所以应该循环取值
// 注意:获取复选框时,不要加上 .val(),循环取值的再加上
let tags = $("input[name='tags']:checkbox");
let tags_arr = []
$.each(tags, function (index, value) {
tags_arr.push($(this).val());
});
console.log(tags_arr); // [1,2,3,4]
// 由于是数组,所以传递到后台的数据不是字符串,要先转为字符串
// 在下面的Ajax的 data 中传值也要传递字符串
var tags_str = tags_arr.join(',');
console.log(tags_str);
// 内容
{#html = document.getElementById('editor_id').value; // 原生API#}
{#html = K('#editor_id').val(); // KindEditor Node API#}
let content = $("#editor_id").val(); // jQuery
console.log(content);
// 发起Ajax请求
$.ajax({
url: '',
type: 'post',
data: {
title: title,
cate: cate,
tags: tags_str,
content: content,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function (res) {
if (code === 200) {
layer.msg(res.msg, {}, function () {
location.href = '/app02/article_list/';
});
} else {
layer.msg(res.msg, {});
}
}
});
});
</script>
{% endblock %}
2.添加文章后端
# 上传图片
def upload_image(request):
'''
返回格式:josn
//成功时
{
"error" : 0,
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
'''
# 方式1:
if request.method == 'POST':
file_obj = request.FILES.get('imgFile')
# print(file_obj) # <MultiValueDict: {'imgFile': [<InMemoryUploadedFile: 7.jpg (image/jpeg)>]}>
# 拼接上传文件的路径
import os
from django.conf import settings
# /media/article_img/xx.123.png
BASE_DIE = os.path.join(settings.BASE_DIR, 'media', 'article_img')
file_name = os.path.join(BASE_DIE, file_obj.name)
with open(file_name, 'wb') as f:
for line in file_obj:
f.write(line)
# return HttpResponse('OK')
return JsonResponse({
"error": 0,
"url": "/media/article_img/%s" % file_obj.name
})
# 方式2:将文件名用uuid随机生成重命名
# 生成新的文件名
import uuid
new_str = str(uuid.uuid4())
new_uuid = new_str.replace('-', '')
new_file_name = new_uuid + '.' + file_obj.name.rsplit('.')[-1]
new_file = os.path.join(BASE_DIE, new_file_name)
with open(new_file, 'wb') as f:
for line in file_obj:
f.write(line)
return JsonResponse({
"error": 0,
"url": "/media/article_img/%s" % new_file_name
})
文章来源地址https://www.toymoban.com/news/detail-661262.html
文章来源:https://www.toymoban.com/news/detail-661262.html
到了这里,关于BBS项目day05 后台系统功能(首页、文章列表展示、添加文章、上传文件)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!