BBS项目后台管理部分总结
一、开设的路由
# 后台管理接口
path('backend/', views.backend_func),
# 后台管理之添加文章接口
path('add_article/', views.add_article_func),
# 后台管理之文章上传图片
path('upload_img/', views.upload_img_func),
# 后台管理之文章编辑
path('edit_article/<int:article_pk>/', views.edit_article_func),
# 后台管理之文章删除
path('delete_article/', views.delete_article_func),
# 用户头像修改
path('set_avatar/', views.set_avatar_func),
二、视图函数部分
@login_required
def backend_func(request):
site_obj = models.Site.objects.filter(site_name=request.user.username).first()
# 获取当前站点下所有的文章
article_queryset = models.Article.objects.filter(site=site_obj)
page_obj = mypage.Pagination(current_page=request.GET.get('page'), all_count=article_queryset.count())
page_queryset = article_queryset[page_obj.start:page_obj.end]
return render(request, 'backend/backend.html', locals())
from bs4 import BeautifulSoup
@login_required
def add_article_func(request):
site_obj = models.Site.objects.filter(site_name=request.user.username).first()
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')
# 将文章内容交给bs4模块处理
soup = BeautifulSoup(content, 'lxml') # 第二个参数是解析器 不同的解析器功能不一样 最好用的是lxml 需要提前下载
tags = soup.find_all()
for tag in tags:
if tag.name == 'script':
tag.decompose() # 删除script标签
article_obj = models.Article.objects.create(
title=title,
desc=soup.text[0:150], # 获取文本然后切割150个字符
content=str(soup), # 使用处理之后的内容(不包含script标签)
site=site_obj,
category_id=category_id
)
# 自己去操作文章和标签的第三张表 无法使用add set remove clear
# for tag_id in tag_list: # 当tag_list较多的时候 频繁执行create速度会很慢 >>>:大批量数据插入可以使用orm提供的批量插入 bulk_create
# models.Article2Tag.objects.create(article=article_obj, tag_id=tag_id)
tag_obj_list = []
for tag_id in tag_list:
article2tag_obj = models.Article2Tag(article=article_obj, tag_id=tag_id)
tag_obj_list.append(article2tag_obj)
models.Article2Tag.objects.bulk_create(tag_obj_list) # 这里还可以使用列表生成式再次缩减代码
return redirect('/backend/')
category_list = models.Category.objects.filter(site=site_obj)
tag_list = models.Tag.objects.filter(site=site_obj)
return render(request, 'backend/addArticlePage.html', locals())
from django.conf import settings
import os
@login_required
def upload_img_func(request):
# 返回的数据格式也是有要求的
"""
//成功时
{
"error" : 0,
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
"""
back_dict = {
"error": 0,
}
# 获取编辑器上传的图片数据
img_obj = request.FILES.get('imgFile')
# 拼接文章图片所存放的路径
img_dir_path = os.path.join(settings.BASE_DIR, 'media', 'article')
if not os.path.exists(img_dir_path):
os.mkdir(img_dir_path)
img_file_path = os.path.join(img_dir_path, f'{img_obj.name}') # 针对文件名 为了防止冲突可以添加唯一标识 比如: 用户名+当前时间+文件名
with open(img_file_path, 'wb') as f:
for line in img_obj:
f.write(line)
back_dict['url'] = '/media/article/%s' % img_obj.name # 不能返回后端绝对路径 而应该是暴露的接口路径
return JsonResponse(back_dict)
@login_required
def edit_article_func(request, article_pk):
site_obj = models.Site.objects.filter(site_name=request.user.username).first()
article_obj = models.Article.objects.filter(pk=article_pk).first()
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')
# 将文章内容交给bs4模块处理
soup = BeautifulSoup(content, 'lxml') # 第二个参数是解析器 不同的解析器功能不一样 最好用的是lxml 需要提前下载
tags = soup.find_all()
for tag in tags:
if tag.name == 'script':
tag.decompose() # 删除script标签
models.Article.objects.filter(pk=article_pk).update(
title=title,
desc=soup.text[0:150], # 获取文本然后切割150个字符
content=str(soup), # 使用处理之后的内容(不包含script标签)
site=site_obj,
category_id=category_id
)
models.Article2Tag.objects.filter(article_id=article_pk).delete()
tag_obj_list = []
for tag_id in tag_list:
article2tag_obj = models.Article2Tag(article=article_obj, tag_id=tag_id)
tag_obj_list.append(article2tag_obj)
models.Article2Tag.objects.bulk_create(tag_obj_list) # 这里还可以使用列表生成式再次缩减代码
return redirect('/backend/')
category_list = models.Category.objects.filter(site=site_obj)
tag_list = models.Tag.objects.filter(site=site_obj)
return render(request, 'backend/editArticlePage.html', locals())
@login_required
def delete_article_func(request):
back_dict = {'code': 10000, 'msg': ''}
if request.method == "POST":
article_pk = request.POST.get('article_pk')
models.Article.objects.filter(pk=article_pk).delete()
models.Article2Tag.objects.filter(article_id=article_pk).delete()
back_dict['msg'] = '删除成功 准备跑路!'
return JsonResponse(back_dict)
@login_required
def set_avatar_func(request):
new_avatar = request.FILES.get('new_avatar')
# models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=new_avatar) # update不会再自动加avatar前缀
user_obj = models.UserInfo.objects.filter(pk=request.user.pk).first()
user_obj.avatar = new_avatar
user_obj.save() # 保存数据
return redirect('/backend/')
三、前端页面部分
1.backendBasePage
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/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="#">
{% block title %}
BBS
{% endblock %}
</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>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="搜索">
</div>
<button type="submit" class="btn btn-default">搜索</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li style="width: 50px;height: 50px;border-radius: 50%;overflow: hidden;display: block"><img src="/media/{{ request.user.avatar }}/" alt="" style="max-width: 100%"></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="#myModal">修改密码</a></li>
<li><a href="#" data-toggle="modal" data-target="#myavatar">修改头像</a></li>
<li><a href="/backend/">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout/">注销登录</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'register_view' %}">注册</a></li>
<li><a href="{% url 'login_view' %}">登录</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<!--导航条结束-->
<!--模态框开始-->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" 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 text-center" id="myModalLabel">修改密码</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="">用户名</label>
<input type="text" value="{{ request.user.username }}" disabled class="form-control">
</div>
<div class="form-group">
<label for="">原密码</label>
<input type="text" id="old_pwd" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="text" id="new_pwd" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="text" id="confirm_pwd" class="form-control">
</div>
</div>
<div class="modal-footer">
<span id="error" style="color: red"></span>
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-warning" id="setBtn">修改</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="myavatar" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" 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 text-center" id="myModalLabel">修改头像</h4>
</div>
<div class="modal-body">
<form action="/set_avatar/" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label for="">用户名</label>
<input type="text" value="{{ request.user.username }}" disabled class="form-control">
</div>
<div class="form-group">
<label for="">原头像</label>
<img src="media/{{ request.user.avatar }}/" alt="" width="120">
</div>
<div class="form-group">
<label for="myfile">新头像
<img src="/static/img/default.jpg" alt="" width="120" id="myimg">
</label>
<input type="file" id="myfile" style="display: none" name="new_avatar">
</div>
<input type="submit" class="btn btn-warning btn-block">
</form>
</div>
</div>
</div>
</div>
<!--模态框结束-->
<!--内容区域开始-->
<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="/add_article/">添加文章</a></p>
<p><a href="#">添加分类</a></p>
<p><a href="#">添加标签</a></p>
<p><a href="#">更多操作</a></p>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingTwo">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion"
href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
私密后台
</a>
</h4>
</div>
<div id="collapseTwo" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingTwo">
<div class="panel-body">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad
squid.
3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt
laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin
coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes
anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings
occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't
heard
of them accusamus labore sustainable VHS.
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingThree">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion"
href="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
友情链接
</a>
</h4>
</div>
<div id="collapseThree" class="panel-collapse collapse" role="tabpanel"
aria-labelledby="headingThree">
<div class="panel-body">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad
squid.
3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt
laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin
coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes
anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings
occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't
heard
of them accusamus labore sustainable VHS.
</div>
</div>
</div>
</div>
</div>
{% block content %}
<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>
<li role="presentation"><a href="#others" 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 articleConetent %}
{% endblock %}
</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 role="tabpanel" class="tab-pane" id="others">其他列表</div>
</div>
</div>
</div>
{% endblock %}
</div>
</div>
<!--内容区域结束-->
<script>
$('#setBtn').click(function () {
$.ajax({
url: '/set_pwd/',
type: 'post',
data: {
'old_pwd': $('#old_pwd').val(),
'new_pwd': $('#new_pwd').val(),
'confirm_pwd': $('#confirm_pwd').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}',
},
success: function (args) {
if (args.code === 10000) {
window.location.href = args.url
} else {
$('#error').text(args.msg)
}
}
})
})
// 1.用户头像的实时展示
$('#myfile').change(function () {
// 1.产生一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 2.获取用户上传的头像文件
let fileObj = this.files[0];
// 3.将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj);
// 等待文件阅读器对象加载完毕之后再修改src
myFileReaderObj.onload = function () {
// 4.修改img标签的src属性展示图片
$('#myimg').attr('src', myFileReaderObj.result)
}
})
</script>
{% block js %}
{% endblock %}
</body>
</html>
2.backnd
{% extends 'backend/backendBasePage.html' %}
{% block css %}
<link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}/">
{% endblock %}
{% block title %}
{{ site_obj.site_title }}
{% endblock %}
{% block articleConetent %}
<table class="table table-hover table-striped">
<thead>
<tr>
<th>标题</th>
<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="/{{ site_obj.site_name }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></td>
<td>{{ article_obj.create_time|date:'Y-m-d H:i:s' }}</td>
<td>{{ article_obj.comment_num }}</td>
<td>{{ article_obj.up_num }}</td>
<td>{{ article_obj.down_num }}</td>
<td><a href="/edit_article/{{ article_obj.pk }}/">编辑</a></td>
<td><a href="#" class="delBtn" article_pk="{{ article_obj.pk }}">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pull-right">{{ page_obj.page_html|safe }}</div>
{% endblock %}
{% block js %}
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
<script>
$('.delBtn').click(function () {
let currentBtn = $(this);
swal({
title: "你确认要删除吗?",
text: "一旦删除了 后果自负哦 大概率需要提桶跑路!!!",
icon: "warning",
buttons: true,
dangerMode: true,
})
.then((willDelete) => {
if (willDelete) {
// 用户点击OK按钮 发送ajax请求
$.ajax({
url:'/delete_article/',
type:'post',
data:{'csrfmiddlewaretoken':'{{ csrf_token }}','article_pk':$(this).attr('article_pk')},
success:function (args) {
if(args.code===10000){
swal(args.msg, {
icon: "success",
});
// 使用DOM操作 临时刷新页面
currentBtn.parent().parent().remove()
} else{
swal(args.msg);
}
}
})
} else {
swal("你个怂货 数据都不敢删!");
}
});
})
</script>
{% endblock %}
3.addArticlePage
{% extends 'backend/backendBasePage.html' %}
{% block css %}
<link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}/">
{% endblock %}
{% block title %}
{{ site_obj.site_title }}
{% endblock %}
{% block articleConetent %}
<h2 class="text-center">添加文章</h2>
<form action="" method="post">
{% csrf_token %}
<p>文章标题</p>
<input type="text" name="title" class="form-control">
<p>文章内容</p>
<textarea name="content" id="mycontent" cols="30" rows="10" class="form-control"></textarea>
<p>文章分类</p>
<p>
{% for category_obj in category_list %}
<input type="radio" name="category" value="{{ category_obj.pk }}">{{ category_obj.name }}
{% endfor %}
</p>
<p>文章标签</p>
<p>
{% for tag_obj in tag_list %}
<input type="checkbox" name="tag" value="{{ tag_obj.pk }}">{{ tag_obj.name }}
{% endfor %}
</p>
<input type="submit" class="form-control btn btn-success btn-block" value="发布">
</form>
{% endblock %}
{% block js %}
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
<script>
KindEditor.ready(function(K) {
window.editor = K.create('#mycontent',
{
width : '100%',
height: '600px',
resizeType:1,
uploadJson : '/upload_img/',
extraFileUploadParams : {
csrfmiddlewaretoken : '{{ csrf_token }}',
}
}
);
});
</script>
{% endblock %}
4.editArticlePage
{% extends 'backend/backendBasePage.html' %}
{% block css %}
<link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}/">
{% endblock %}
{% block title %}
{{ site_obj.site_title }}
{% endblock %}
{% block articleConetent %}
<h2 class="text-center">添加文章</h2>
<form action="" method="post">
{% csrf_token %}
<p>文章标题</p>
<input type="text" name="title" class="form-control" value="{{ article_obj.title }}">
<p>文章内容</p>
<textarea name="content" id="mycontent" cols="30" rows="10" class="form-control" >{{ article_obj.content }}</textarea>
<p>文章分类</p>
<p>
{% for category_obj in category_list %}
{% if article_obj.category == category_obj %}
<input type="radio" name="category" value="{{ category_obj.pk }}" checked>{{ category_obj.name }}
{% else %}
<input type="radio" name="category" value="{{ category_obj.pk }}">{{ category_obj.name }}
{% endif %}
{% endfor %}
</p>
<p>文章标签</p>
<p>
{% for tag_obj in tag_list %}
{% if tag_obj in article_obj.tags.all %}
<input type="checkbox" name="tag" value="{{ tag_obj.pk }}" checked>{{ tag_obj.name }}
{% else %}
<input type="checkbox" name="tag" value="{{ tag_obj.pk }}">{{ tag_obj.name }}
{% endif %}
{% endfor %}
</p>
<input type="submit" class="form-control btn btn-danger btn-block" value="发布">
</form>
{% endblock %}
{% block js %}
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
<script>
KindEditor.ready(function(K) {
window.editor = K.create('#mycontent',
{
width : '100%',
height: '600px',
resizeType:1,
uploadJson : '/upload_img/',
extraFileUploadParams : {
csrfmiddlewaretoken : '{{ csrf_token }}',
}
}
);
});
</script>
{% endblock %}
标签:总结,obj,models,request,site,tag,后台,article,BBS
From: https://www.cnblogs.com/almira998/p/17052429.html