后端功能实现
apps/admin/views.py
import json
import logging
from django.db.models import Count
from django.shortcuts import render
from django.views import View
# Create your views here.
from apps.news import models
from apps.admin import constants
from utils.json_fun import to_json_data
from utils.res_code import Code, error_map
logger = logging.getLogger('django')
class IndexView(View):
"""
/admin/
"""
def get(self,request):
"""
:param request:
:return:
"""
return render(request,'admin/index/index.html')
class TagManagerView(View):
"""
/admin/tag/
"""
def get(self,request):
"""
要返回给前端: tag_name news_name
:param request:
:return:
"""
#分组查询 annotate 配合 values 使用 values 指定输出的内容(id: ,name: ),是字典格式
#按标签名(tag_name)和 id(tag_id) 分组,统计每个标签(Tag)中 news(外键关联,
# 通过 news的主键 id 统计) 的数量,并且按降序排列
tags = models.Tag.objects.select_related('news').values('id','name').annotate(num_news = \
Count('news')).filter(is_delete=False).order_by('-num_news','update_time')
return render(request,'admin/news/tag_manage.html',locals())
def post(self,request):
"""
get_or_create(defaults=None, **kwargs)
一个通过给出的kwargs 来查询对象的便捷方法(如果你的模型中的所有字段都有默认值,可以为空),需要的话创建一个对象。
返回一个由(object, created)组成的元组,元组中的object 是一个查询到的或者是被创建的对象,
created 是一个表示是否创建了新的对象的布尔值。
:param request:
:return:
"""
# 1. 获取参数
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8'))
#从前端传进来的要修改的标签名
tag_name = dict_data.get('name')
if tag_name:
# tag_name = tag_name.strip()
# a = tag_name
#get_or_create 查名为 tag_name 的标签,无则增,有则查 得到的是元组类型 请确保只在 post 请求中使用它
tag_tuple = models.Tag.objects.filter(is_delete=False).get_or_create(name=tag_name)
#对得到的元组进行拆包
tag_instance,tag_boolean = tag_tuple
#如果 tag_boolean 为 True,即创建了名字为 tag_name 的标签
# 否则就是查到了名字为 tag_name 的标签,即标签已存在,不创建
if tag_boolean:
news_tag_dict = {
'id':'tag_instance.id',
'name':'tag_instance.name',
}
return to_json_data(errmsg='标签创建成功!',data=news_tag_dict)
else:
return to_json_data(errno=Code.DATAEXIST,errmsg='标签已存在!')
else:
return to_json_data(errno=Code.PARAMERR,errmsg='标签不能为空!')
#传参:
class TagEditView(View):
"""
/admin/tag/<tag_id>/
"""
def delete(self,request,tag_id):
"""
:param request:
:param tag_id:
:return:
"""
tag = models.Tag.objects.only('id').filter(id=tag_id).first()
if tag:
tag.is_delete = True
#只改 is_delete 字段,用于优化
tag.save(update_fields=['is_delete'])
#删除后局部刷新,用 ajax,返回给前端 json 格式
return to_json_data(errmsg='标签删除成功!')
else:
return to_json_data(errno=Code.PARAMERR,errmsg='标签不存在!')
def put(self,request,tag_id):
"""
:param request:
:param tag_id:
:return:
"""
#修改之后的 name 通过 ajax 获取 只有一个参数,用 ajax 即可
# 1. 获取参数
json_data = request.body #byte str
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8'))
# 从前端传进 要修改的 tag
tag_name = dict_data.get('name')
# 从数据库中拿到 tag
tag = models.Tag.objects.only('name').filter(id=tag_id).first()
if tag:
#去掉两边的空格
tag_name = tag_name.strip()
#如果不加 is_delete 删除的标签再创建或者修改无法实现
if tag.name == tag_name:
return to_json_data(errno=Code.PARAMERR,errmsg='标签未变化!')
if not models.Tag.objects.only('id').filter(name=tag_name, is_delete=False).exists():
#如果标签没变化,提示报错
# if tag.name == tag_name:
# return to_json_data(errno=Code.PARAMERR,errmsg='标签未变化!')
tag.name = tag_name
tag.save(update_fields=['name', 'update_time'])
return to_json_data(errmsg='标签更新成功!')
else:
return to_json_data(errno=Code.PARAMERR,errmsg='标签已经存在!')
else:
#有异常的一定会用到 errno
return to_json_data(errno=Code.PARAMERR,errmsg='标签不存在!')
# 热门文章管理页面
#news.title,tag.name,hotnews.priority
class HotNewsManageView(View):
"""
/admin/hotnews/
"""
def get(self,request):
hot_news = models.HotNews.objects.select_related('news__tag').only('news__title','news__tag__name','news__id').\
filter(is_delete=False).order_by('priority','-news__clicks')[:constants.SHOW_HOTNEWS_COUNT]
return render(request,'admin/news/news_hot.html',locals())
class HotNewsEditView(View):
"""
/admin/hotnews/<int:tag_id>
"""
def delete(self,request,hotnews_id):
hotnews = models.HotNews.objects.only('id').filter(id=hotnews_id).first()
if hotnews:
hotnews.is_delete = True
hotnews.save(update_fields = ['is_delete','update_time'])
return to_json_data(errmsg='热门文章删除成功!')
else:
return to_json_data(errno=Code.PARAMERR,errmsg='需要删除的热门文章不存在!')
def put(self,request,hotnews_id):
"""
更新热门新闻
#1. 获取参数
:param request:
:param hotnews_id:
:return:
"""
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8'))
try:
priority = int(dict_data.get('priority'))
#列表生成式 对于不需要的可以用 _ 代替
priority_list = [i for i,_ in models.HotNews.PRI_CHOICES]
if priority not in priority_list:
return to_json_data(errno=Code.PARAMERR,errmsg='热门文章优先级设置错误!')
except Exception as e:
logger.info('热门文章优先级异常:\n{}'.format(e))
return to_json_data(errno=Code.PARAMERR,errmsg='热门文章优先级设置错误!')
hotnews = models.HotNews.objects.only('id').filter(id=hotnews_id).first()
if not hotnews:
return to_json_data(errno=Code.PARAMERR,errmsg='需要更新的热门新闻不存在!')
if hotnews.priority == priority:
return to_json_data(errno=Code.PARAMERR,errmsg='热门新闻优先级未改变!')
hotnews.priority = priority
hotnews.save(update_fields=['priority','update_time'])
return to_json_data(errmsg='热门新闻优先级更新成功!')
class HotNewsAddView(View):
"""
添加热门新闻,先选标签,才能选标签下的文章,选择优先级不受他们影响
/addmin/hotnews/add/
"""
def get(self,request):
#选择标签和优先级
#有排序
tags = models.Tag.objects.values('id','name').annotate(num_news = Count('news')).filter(is_delete=False).\
order_by('-num_news','update_time')
#将列表嵌套个元组转换成字典
priority_dict = dict(models.HotNews.PRI_CHOICES)
return render(request,'admin/news/news_hot_add.html',locals())
def post(self,request):
#添加热门新闻
#1.获取参数
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8'))
#news_id priority
try:
news_id = int(dict_data.get('news_id'))
except Exception as e:
logger.info('热门文章:\n{}'.format(e))
return to_json_data(errno=Code.PARAMERR,errmsg='参数错误')
if not models.News.objects.filter(id=news_id).exists():
return to_json_data(errno=Code.PARAMERR,errmsg='文章不存在!')
try:
priority = int(dict_data.get('priority'))
priority_list = [i for i,_ in models.HotNews.PRI_CHOICES]
if priority not in priority_list:
return to_json_data(errno=Code.PARAMERR,errmsg='热门文章优先级设置错误!')
except Exception as e:
logger.info('热门文章优先级异常:\n{}'.format(e))
return to_json_data(errno=Code.PARAMERR,errmsg='热门文章优先级设置错误')
hotnews_tuple = models.HotNews.objects.get_or_create(news_id=news_id)
hotnews,is_created = hotnews_tuple
hotnews.priority = priority
hotnews.save(update_fields=['priority','update_time'])
return to_json_data(errmsg='热门文章创建成功!')
class NewsByTagIdView(View):
"""
选完标签后进行这步,选标签下的文章
不用展示 tag_id
/admin/tags/<int:tag_id>/news/
"""
def get(self,request,tag_id):
news = models.News.objects.filter(is_delete=False,tag_id=tag_id).values('id','title')
#将字典嵌套进列表
news_list = [i for i in news]
return to_json_data(data={
'news':news_list
})
apps/admin/urls.py
from apps.admin import views
from django.urls import path
app_name = 'admin'
urlpatterns = [
path('',views.IndexView.as_view(),name='index'),
path('tags/',views.TagManagerView.as_view(),name='tags'),
path('tags/<int:tag_id>/',views.TagEditView.as_view(),name='tag_edit'),
path('hotnews/',views.HotNewsManageView.as_view(),name='hotnews'),
path('hotnews/<int:hotnews_id>/',views.HotNewsEditView.as_view(),name='hotnews_edit'),
path('hotnews/add/',views.HotNewsAddView.as_view(),name='hotnews_add'),
path('tags/<int:tag_id>/news/',views.NewsByTagIdView.as_view(),name='hotnews_by_tagid'),
]
前端功能实现
templates/admin/news/news_hot.html
{% extends 'admin/base/base.html' %}
{% load static %}
{% 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">
<a href="{% url 'admin:hotnews_add' %}" class="btn btn-primary pull-right"
{# <a href="#" class="btn btn-primary pull-right"#}
{# <a href="#" class="btn btn-primary pull-right"#}
id="btn-add-news-recommend">添加热门文章</a>
</div>
<div class="box-body">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>文章名称</th>
<th>文章分类</th>
<th>优先级</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for hot_new in hot_news %}
<tr data-id="{{ hot_new.id }}" data-name="{{ hot_new.news.title }}">
<td>
<a href="{% url 'news:news_detail' hot_new.news_id %}" data-news-id="{{ hot_new.news_id }}">
{{ hot_new.news.title }}
</a>
</td>
<td>{{ hot_new.news.tag.name }}</td>
{# <td>{{ hot_new.priority }}</td>#}
<td>{{ hot_new.get_priority_display }}</td>
<td>
<button class="btn btn-xs btn-warning btn-edit"
data-priority="{{ hot_new.priority }}">编辑</button>
<button class="btn btn-xs btn-danger btn-del">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="box-footer"></div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="/static/js/admin/news/news_hot.js"></script>
{% endblock %}
static/js/admin/news/news_hot.js
// 创建static/js/admin/news/news_hot.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 === "0") {
fAlert.alertSuccessToast(inputVal + " 标签添加成功");
setTimeout(function () {
window.location.reload();
}, 1000)
} else {
swal.showInputError(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
});
});
// 编辑热门文章
let $HotNewsEdit = $(".btn-edit"); // 1. 获取编辑按钮
$HotNewsEdit.click(function () { // 2. 点击触发事件
let _this = this;
let sHotNewsId = $(this).parents('tr').data('id');
// let sHotNewsTitle = $(this).parents('tr').data('name');
let sPriority = $(this).data('priority');
fAlert.alertOneInput({
title: "编辑热门文章优先级",
text: "你正在编辑热门文章的优先级",
placeholder: "请输入文章优先级",
value: sPriority,
confirmCallback: function confirmCallback(inputVal) {
if (!inputVal.trim()) {
swal.showInputError('输入框不能为空!');
return false;
} else if (inputVal == sPriority) {
swal.showInputError('优先级未修改');
return false;
} else if (!jQuery.inArray(inputVal, ['1', '2', '3'])) {
swal.showInputError('优先级只能取1,2,3中的一个');
return false;
}
let sDataParams = {
"priority": inputVal
};
$.ajax({
// 请求地址
url: "/admin/hotnews/" + sHotNewsId + "/", // url尾部需要添加/
// 请求方式
type: "PUT",
data: JSON.stringify(sDataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
if (res.errno === "0") {
swal.close();
message.showSuccess("标签修改成功");
// $(_this).parents('tr').find('td:nth-child(3)').text(inputVal);
setTimeout(function () {
// window.location.href = '../../../../apps/admin/hotnews/';
window.location.href = '../../../../admin/hotnews/';
}, 800)
} else {
swal.showInputError(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
});
});
// 删除热门文章
let $HotNewsDel = $(".btn-del"); // 1. 获取删除按钮
$HotNewsDel.click(function () { // 2. 点击触发事件
let _this = this;
let sHotNewsId = $(this).parents('tr').data('id');
fAlert.alertConfirm({
title: "确定删除热门文章吗?",
type: "error",
confirmText: "确认删除",
cancelText: "取消删除",
confirmCallback: function confirmCallback() {
$.ajax({
url: "/admin/hotnews/" + sHotNewsId + "/", // url尾部需要添加/
type: "DELETE",
dataType: "json",
})
.done(function (res) {
if (res.errno === "0") {
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'));
}
}
});
});
templates/admin/news/news_hot_add.html
{% extends 'admin/base/base.html' %}
{% load static %}
{% block title %}
添加热门文章
{% endblock %}
{% block content_header %}
添加热门文章
{% endblock %}
{% block header_option_desc %}
创建热门文章
{% endblock %}
{% block content %}
<div class="box box-primary">
<div class="box-body">
<div class="form-horizontal">
<div class="form-group">
<label for="category-select" class="col-md-2 col-sm-2 control-label">选择文章</label>
<div class="col-md-2 col-sm-3">
<select name="category" id="category-select" class="form-control input-md">
<option value="0">--请选择文章分类--</option>
{% for one_tag in tags %}
<option value="{{ one_tag.id }}">{{ one_tag.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-8 col-sm-7">
<label for="news-select" style="display: none;"></label>
<select name="news" class="form-control input-md" id="news-select">
<option value="0">--请选择文章--</option>
</select>
</div>
</div>
<div class="form-group">
<label for="priority" class="col-md-2 col-sm-2 control-label">选择优先级</label>
<div class="col-md-2 col-sm-3">
<select name="priority" id="priority" class="form-control input-md">
<option value="0">--请选择优先级--</option>
{% for id, value in priority_dict.items %}
<option value="{{ id }}">{{ value }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="box-footer">
<a href="javascript:void(0);" class="btn btn-primary pull-right" id="save-btn">保存</a>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="/static/js/admin/news/news_hot_add.js"></script>
{% endblock %}
static/js/admin/news/news_hot_add.js
// 创建static/js/admin/news/news_hot_add.js文件
$(function () {
let $tagSelect = $("#category-select"); // 获取选择分类标签元素
let $newsSelect = $("#news-select"); // 获取选择文章标签元素
let $saveBtn = $('#save-btn'); // 获取保存按钮元素
// 选择文章不同类别,获取相应的文章
$tagSelect.change(function () {
// 获取当前选中的下拉框的value
let sTagId = $(this).val();
if (sTagId === '0') {
$newsSelect.children('option').remove();
$newsSelect.append(`<option value="0">--请选择文章--</option>`);
return
}
// 根据文章分类id向后端发起get请求
$.ajax({
url: "/admin/tags/" + sTagId + "/news/", // url尾部需要添加/
type: "GET",
dataType: "json",
})
.done(function (res) {
if (res.errno === "0") {
$newsSelect.children('option').remove();
$newsSelect.append(`<option value="0">--请选择文章--</option>`);
res.data.news.forEach(function (one_news) {
let content = `<option value="${one_news.id}">${one_news.title}</option>`;
$newsSelect.append(content)
});
} else {
// swal.showInputError(res.errmsg);
fAlert.alertErrorToast(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
});
// 点击保存按钮执行的事件
$saveBtn.click(function () {
// 获取优先级
let priority = $("#priority").val();
// 获取下拉框中选中的文章标签id 和 文章id
let sTagId = $tagSelect.val();
let sNewsId = $newsSelect.val();
// 打印值
console.log(`
priority(优先级): ${priority}
tagId(文章标签id): ${sTagId}
newsId(文章id): ${sNewsId}
`);
// 判断是否为 0, 表示在第一个 未选择
if (sTagId !== '0' && sNewsId !== '0' && priority !== '0') {
let sDataParams = {
"priority": priority,
"news_id": sNewsId
};
$.ajax({
// 请求地址
url: "/admin/hotnews/add/", // url尾部需要添加/
// 请求方式
type: "POST",
data: JSON.stringify(sDataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
if (res.errno === "0") {
message.showSuccess("热门文章创建成功");
setTimeout(function () {
// window.location.href = '../../../../apps/admin/hotnews/';
window.location.href = '../../../../admin/hotnews/';
}, 800)
} else {
// swal.showInputError(res.errmsg);
message.showError(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
} else {
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'));
}
}
});
});