文章标签页功能
1.分析
业务处理流程:
- 查询标签 -- get
- 新增标签 -- post
- 更新标签 -- put
- 删除标签 -- delete
请求方法:GET, POST, PUT, DELETE
url定义:
/admin/tags/
/admin/tags/<int:tag_id>/
请求参数:url路径传参, body传参
参数 | 类型 | 前端是否必须传 | 描述 |
---|---|---|---|
tag_id | 字符串 | 是(put, delete方法) | 需要更新或删除的标签id |
tag_name | 字符串 | 是(post, put, delete方法) | 需要新增,更新的标签名 |
注:post请求,在向后端发起请求时,需要附带csrf token
2.后端视图
- views.py
import json
from django.views import View
from django.db.models import Count
from django.shortcuts import render
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from news import models
from utils.json_translate.json_fun import to_json_data
from utils.res_code.rescode import Code, error_map
# 后台标签页管理
class TagsManageView(PermissionRequiredMixin, View):
"""
判断用户是否拥有权限,
渲染标签管理首页 -get url /admin/tags/
新增标签 -post url /admin/tags/
"""
permission_required = ('news.add_tag', 'news.view_tag')
# 会返回系统自带的403界面,当然也可以自定义
raise_exception = True
# 对异常进行控制,返回json数据
# 区别get/post请求
def handle_no_permission(self):
if self.request.method.lower() != 'get':
return to_json_data(errno=Code.ROLEERR, errmsg='没有操作权限')
else:
# 调用父类返回403界面
return super(TagsManageView, self).handle_no_permission()
def get(self, request):
"""
将标签名和对应新闻数量进行分组
:param request:
:return:
"""
tags = models.Tag.objects.values('id', 'name').annotate(num_news = Count('news')).\
filter(is_delete=False).order_by('-num_news')
return render(request, 'admin/news/tags_manage.html', locals())
def post(self, request):
"""
1 获取前端新增标签名参数 -- tag_name
2 判断是否与数据库重复
3 返回新建成功/失败
:param request:
:return:
"""
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
# 将json转化为dict
dict_data = json.loads(json_data.decode('utf8'))
tag_name = dict_data.get('name')
if tag_name and tag_name.strip():
tag_tuple = models.Tag.objects.get_or_create(name=tag_name)
# 若create 则tag_created_boolean为true
tag_instance, tag_created_boolean = tag_tuple
new_tag_dict = {
"id": tag_instance.id,
"name": tag_instance.name
}
return to_json_data(errmsg="标签创建成功", data=new_tag_dict) if tag_created_boolean else \
to_json_data(errno=Code.DATAEXIST, errmsg="标签名已存在")
else:
return to_json_data(errno=Code.PARAMERR, errmsg="标签名为空")
# 后台标签页编辑
class TagEditView(PermissionRequiredMixin, View):
"""
权限校验,
标签更新,-- put
标签删除,-- delete
"""
permission_required = ('news.change_tag', 'news.delete_tag')
raise_exception = True
def handle_no_permission(self):
# 返回相应errno,前端做界面
# return render(self.request, '403.html')
return to_json_data(errno=Code.ROLEERR, errmsg=error_map[Code.ROLEERR])
def put(self, request, tag_id):
"""
1.获取前端参数 tag_id, tag_name
2.判断tag_id,tag_name是否已存在
3.返回更新成功/失败
:param request:
:param tag_id:
:return:
"""
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
# 将json转化为dict
dict_data = json.loads(json_data.decode('utf8'))
tag_name = dict_data.get('name')
tag = models.Tag.objects.only('id').filter(id=tag_id).first()
if tag:
if tag_name:
tag_name = tag_name.strip()
if not models.Tag.objects.only('id').filter(name=tag_name).exists():
tag.name = tag_name
# 优化仅更新name字段
tag.save(update_fields=['name'])
return to_json_data(errmsg="标签更新成功")
else:
return to_json_data(errno=Code.DATAEXIST, errmsg="标签名已存在")
else:
return to_json_data(errno=Code.PARAMERR, errmsg="标签名为空")
else:
return to_json_data(errno=Code.PARAMERR, errmsg="需要更新的标签不存在")
def delete(self, request, tag_id):
"""
1.获取前端参数 tag_id
2.判断是否存在
3.返回逻辑删除成功/失败
:param request:
:param tag_id:
:return:
"""
tag = models.Tag.objects.only('id').filter(id=tag_id).first()
if tag:
# 真删
# tag.delete()
tag.is_delete = True
tag.save(update_fields=['is_delete'])
return to_json_data(errmsg="标签删除成功")
else:
return to_json_data(errno=Code.PARAMERR, errmsg="需要删除的标签不存在")
- urls.py
from django.urls import path
from . import views
app_name='admin'
urlpatterns = [
path('index/', views.AdminIndexView.as_view(), name='admin_index'),
path('tags/', views.TagsManageView.as_view(), name='admin_tags'),
path('tags/<int:tag_id>/', views.TagEditView.as_view(), name='tag_edit'),
]
3.前端代码
- tags_manage.html
<!-- 创建templates/admin/news/tags_manage.html文件 -->
{% extends 'admin/base/base.html' %}
{% block title %}
标签管理页
{% endblock %}
{% block content_header %}
标签管理
{% endblock %}
{% block header_option_desc %}
标签分类
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12 col-xs-12 col-sm-12">
<div class="box box-primary">
<div class="box-header">
<button class="btn btn-primary pull-right" id="btn-add-tag">添加标签</button>
</div>
<div class="box-body">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>标签名称</th>
<th>文章数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for one_tag in tags %}
<tr data-id="{{ one_tag.id }}" data-name="{{ one_tag.name }}">
<td>{{ one_tag.name }}</td>
<td>{{ one_tag.num_news }}</td>
<td>
<button class="btn btn-xs btn-warning btn-edit">编辑</button>
<button class="btn btn-xs btn-danger btn-del">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'js/admin/news/tags_manage.js' %}"></script>
{% endblock %}
- tags_manage.js
// 创建static/js/admin/news/tags_manage.js文件
$(function () {
// 添加标签
let $tagAdd = $("#btn-add-tag"); // 1. 获取添加按钮
$tagAdd.click(function () { // 2. 点击事件
fAlert.alertOneInput({
title: "请输入文章标签",
text: "长度限制在20字以内",
placeholder: "请输入文章标签",
confirmCallback: function confirmCallback(inputVal) {
console.log(inputVal);
if (inputVal === "") {
swal.showInputError('标签不能为空');
return false;
}
let sDataParams = {
"name": inputVal
};
$.ajax({
// 请求地址
url: "/admin/tags/", // url尾部需要添加/
// 请求方式
type: "POST",
data: JSON.stringify(sDataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
if (res.errno === "200") {
fAlert.alertSuccessToast(inputVal + " 标签添加成功");
setTimeout(function () {
window.location.reload();
}, 1000)
} else {
swal.showInputError(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
});
});
// 编辑标签
let $tagEdit = $(".btn-edit"); // 1. 获取编辑按钮
$tagEdit.click(function () { // 2. 点击触发事件
let _this = this;
let sTagId = $(this).parents('tr').data('id');
let sTagName = $(this).parents('tr').data('name');
fAlert.alertOneInput({
title: "编辑文章标签",
text: "你正在编辑 " + sTagName + " 标签",
placeholder: "请输入文章标签",
value: sTagName,
confirmCallback: function confirmCallback(inputVal) {
console.log(inputVal);
if (inputVal === sTagName) {
swal.showInputError('标签名未变化');
return false;
}
let sDataParams = {
"name": inputVal
};
$.ajax({
// 请求地址
url: "/admin/tags/" + sTagId + "/", // url尾部需要添加/
// 请求方式
type: "PUT",
data: JSON.stringify(sDataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
if (res.errno === "200") {
// 更新标签成功
$(_this).parents('tr').find('td:nth-child(1)').text(inputVal);
swal.close();
message.showSuccess("标签修改成功");
setTimeout(function () {
window.location.reload();
}, 1000)
} else {
swal.showInputError(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
});
});
// 删除标签
let $tagDel = $(".btn-del"); // 1. 获取删除按钮
$tagDel.click(function () { // 2. 点击触发事件
let _this = this;
let sTagId = $(this).parents('tr').data('id');
let sTagName = $(this).parents('tr').data('name');
fAlert.alertConfirm({
title: "确定删除 " + sTagName + " 标签吗?",
type: "error",
confirmText: "确认删除",
cancelText: "取消删除",
confirmCallback: function confirmCallback() {
$.ajax({
// 请求地址
url: "/admin/tags/" + sTagId + "/", // url尾部需要添加/
// 请求方式
type: "DELETE",
dataType: "json",
})
.done(function (res) {
if (res.errno === "200") {
// 更新标签成功
message.showSuccess("标签删除成功");
$(_this).parents('tr').remove();
setTimeout(function () {
window.location.reload();
}, 1000)
} else {
swal.showInputError(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
});
});
// get cookie using jQuery
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
// Setting the token on the AJAX request
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
});
- 补充
后台用户权限控制,如果用户没有登录,则会重定向到登录页面,用户登录url会携带?next=
// 在static/js/users/login.js登录逻辑中的.done方法加入如下代码:
.done(function (res) {
if (res.errno === "0") {
// 注册成功
message.showSuccess('恭喜你,登录成功!');
setTimeout(function () {
// 注册成功之后重定向到打开登录页面之前的页面
// window.location.href = document.referrer;
let sCurrentUrl = $(location).attr("href");
if (sCurrentUrl.indexOf('?next=') !== -1) {
let sDomain = window.location.origin;
window.location.href = sDomain + sCurrentUrl.split('=')[1];
} else {
// 注册成功之后重定向到打开登录页面之前的页面
window.location.href = document.referrer;
}
}, 800)
} else {
// 登录失败,打印错误信息
message.showError(res.errmsg);
}
})
在templates下创建403.html(格式固定),对其添加样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>403</title>
</head>
<body>
<p> 403 page</p>
</body>
</html>
但是对于ajax 发起的post,put等请求,由于权限不够,却返回一个403html界面给前端json接收,会报错,所以必须返回json数据,可以通过对异常进行控制,返回json数据/html界面
class TagsEditView(PermissionRequiredMixin, View):
permission_required = ('news.change_tag', 'news.delete_tag')
raise_exception = True
# 对异常进行控制
def handle_no_permission(self):
# 返回相应errno,前端做界面
return to_json_data(errno=Code.ROLEERR, errmsg=error_map[Code.ROLEERR])