文章目录
获取静态站点模板
- 可以使用 git clone 到本地
git clone https://github.com/almasaeed2010/AdminLTE.git
- 也可以在 github 中将其下载到本地,链接https://github.com/almasaeed2010/AdminLTE/releases
使用需要的组件
- 源文件非常大,按需索取即可
- 创建 templates/admin/base 文件夹,将下载的文件夹中 starter.html 页面复制粘贴,放到 base 文件夹中,修改名字为 base.html
- 将不需要的组件删除
- 创建 static/js/admin/base 文件夹,static/css/admin/base 文件夹和 static/css/admin/fonts 文件夹,将需要的 js,css,front 文件从下载的源文件夹中分别复制粘贴,放到对应的项目静态文件夹内
- 创建 static/images/admin/base 文件夹,将用户图像文件放置其中
模板抽取
templates/admin/base/base.html
{% load static %}
<!DOCTYPE html>
<!--
This is a starter template page. Use this page to start your new project from
scratch. This page gets rid of all links and provides the needed markup only.
-->
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>
{% block titile %}
{% endblock %}
</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
<link rel="stylesheet" href="{% static 'css/admin/base/bootstrap.min.css' %}">
<!-- Font Awesome -->
<link rel="stylesheet" href="{% static 'css/admin/base/font-awesome.min.css' %}">
<!-- Ionicons -->
<link rel="stylesheet" href="{% static 'css/admin/base/ionicons.min.css' %}">
<!-- Theme style -->
<link rel="stylesheet" href="{% static 'css/admin/base/AdminLTE.min.css' %}">
<!-- AdminLTE Skins. We have chosen the skin-blue for this starter
page. However, you can choose any other skin. Make sure you
apply the skin class to the body tag so the changes take effect. -->
<link rel="stylesheet" href="{% static 'css/admin/base/skin-blue.min.css' %}">
<link rel="stylesheet" href="{% static 'css/admin/base/sweetalert.css' %}">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- Google Font -->
{# <link rel="stylesheet"#}
{# href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">#}
{% block link %}
{% endblock %}
</head>
<!--
BODY TAG OPTIONS:
=================
Apply one or more of the following classes to get the
desired effect
|---------------------------------------------------------|
| SKINS | skin-blue |
| | skin-black |
| | skin-purple |
| | skin-yellow |
| | skin-red |
| | skin-green |
|---------------------------------------------------------|
|LAYOUT OPTIONS | fixed |
| | layout-boxed |
| | layout-top-nav |
| | sidebar-collapse |
| | sidebar-mini |
|---------------------------------------------------------|
-->
<body class="hold-transition skin-blue sidebar-mini">
<div class="wrapper">
<!-- Main Header -->
<header class="main-header">
<!-- Logo -->
<a href="{% url 'news:index' %}" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"><b>P</b>y</span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg"><b>Admin</b></span>
</a>
<!-- Header Navbar -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
<span class="sr-only">Toggle navigation</span>
</a>
<!-- Navbar Right Menu -->
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- User Account Menu -->
<li class="dropdown user user-menu">
<!-- Menu Toggle Button -->
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<!-- The user image in the navbar-->
<img src="{% static 'images/taka.jpg' %}" class="user-image" alt="User Image">
<!-- hidden-xs hides the username on small devices so only the image appears. -->
<span class="hidden-xs">{{ request.user }}</span>
</a>
<ul class="dropdown-menu">
<!-- The user image in the menu -->
<li class="user-header">
<img src="{% static 'images/taka.jpg' %}" class="img-circle" alt="User Image">
<p>
学习使我快乐!!!
<small>2019.5.5</small>
</p>
</li>
<!-- Menu Footer-->
<li class="user-footer">
<div class="pull-left">
<a href="#" class="btn btn-default btn-flat">个人详情</a>
</div>
<div class="pull-right">
<a href="#" class="btn btn-default btn-flat">登出</a>
</div>
</li>
</ul>
</li>
</ul>
</div>
</nav>
</header>
<!-- Left side column. contains the logo and sidebar -->
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<!-- Sidebar user panel (optional) -->
<div class="user-panel">
<div class="pull-left image">
<img src="{% static 'images/taka.jpg' %}" class="img-circle" alt="User Image">
</div>
<div class="pull-left info">
<p>Taka</p>
<!-- Status -->
<a href="#"><i class="fa fa-circle text-success"></i> happy</a>
</div>
</div>
<!-- Sidebar Menu -->
<ul class="sidebar-menu" data-widget="tree">
<li class="header">头部</li>
<!-- Optionally, you can add icons to the links -->
<li class="active"><a href="#"><i class="fa fa-user"></i> <span>个人信息</span></a></li>
<li class="header">文章相关</li>
<li class="treeview">
<a href="#"><i class="fa fa-book"></i> <span>文章</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<!-- 文章标签分类 start -->
<li>
<a href="{% url 'admin:tags' %}">
{# <a href="#">#}
<i class="fa fa-tags"></i>
<span>文章标签分类</span>
</a>
</li>
<!-- /.文章标签分类 end -->
<!-- 文章发布 start -->
<li>
<a href="{% url 'admin:news_pub' %}">
<i class="fa fa-newspaper-o"></i>
<span>文章发布</span>
</a>
</li>
<!-- /.文章发布 end -->
<!-- 文章管理 start -->
<li>
<a href="{% url 'admin:news_manage' %}">
<i class="fa fa-cogs"></i>
<span>文章管理</span>
</a>
</li>
<!-- /.文章管理 end -->
<!-- Hot文章管理 start -->
<li>
<a href="{% url 'admin:hotnews' %}">
<i class="fa fa-rocket"></i>
<span>Hot文章管理</span>
</a>
</li>
<!-- /.Hot文章管理 end -->
<!-- 轮播图管理 start -->
<li>
<a href="{% url 'admin:banners_manage' %}">
<i class="fa fa-file-picture-o"></i>
<span>轮播图管理</span>
</a>
</li>
<!-- /.轮播图管理 end -->
</ul>
</li>>
<li class="header">文档相关</li>
<li class="active"><a href="{% url 'admin:docs_manage' %}"><i class="fa fa-book"></i> <span>文档管理</span></a></li>
<li class="active"><a href="{% url 'admin:docs_pub' %}"><i class="fa fa-cog"></i> <span>文档发布</span></a></li>
<li class="header">在线课堂</li>
<li class="active"><a href="{% url 'admin:courses_manage' %}"><i class="fa fa-file-movie-o"></i> <span>课程管理</span></a></li>
<li class="active"><a href="{% url 'admin:courses_pub' %}"><i class="fa fa-film"></i> <span>课程发布</span></a></li>
<li class="header">权限管理</li>
<li class="active"><a href="{% url 'admin:groups_manage' %}"><i class="fa fa-group "></i> <span>组管理</span></a></li>
<li class="active"><a href="{% url 'admin:groups_add' %}"><i class="fa fa-user-plus"></i> <span>组创建</span></a></li>
<li class="active"><a href="{% url 'admin:users_manage' %}"><i class="fa fa-male"></i> <span>用户管理</span></a></li>
<!-- /.sidebar-menu -->
</ul>>
</section>
<!-- /.sidebar -->
</aside>
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
{% block content_header %}
{% endblock %}
<small>
{% block header_option_desc %}
{% endblock %}
</small>
</h1>
</section>
<!-- Main content -->
<section class="content container-fluid">
<!--------------------------
| Your Page Content Here |
-------------------------->
{% block content %}
{% endblock %}
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<!-- Main Footer -->
<footer class="main-footer">
<!-- To the right -->
<div class="pull-right hidden-xs">
人生苦短,我用python!
</div>
<!-- Default to the left -->
<strong>Copyright © 2019 <a href="#">Company</a>.</strong> All rights reserved.
</footer>
</div>
<!-- ./wrapper -->
<!-- REQUIRED JS SCRIPTS -->
<!-- jQuery 3 -->
<script src="{% static 'js/admin/base/jquery.min.js' %}"></script>
<!-- Bootstrap 3.3.7 -->
<script src="{% static 'js/admin/base/bootstrap.min.js' %}"></script>
<!-- AdminLTE App -->
<script src="{% static 'js/admin/base/adminlte.min.js' %}"></script>
<!-- 自定义的导入 -->
<script src="{% static 'js/admin/base/message.js' %}"></script>
<script src="{% static 'js/admin/base/fsweetalert.js' %}"></script>
<script src="{% static 'js/admin/base/sweetalert.min.js' %}"></script>
{% block script %}
{% endblock %}
<!-- Optionally, you can add Slimscroll and FastClick plugins.
Both of these plugins are recommended to enhance the
user experience. -->
</body>
</html>
给网站添加 favicon.ico 头像
- 将 favicon.ico 图片复制粘贴到项目 static/images 文件夹中
- 在前台和后台站点中的 base 模板页上放加入如下代码
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
admin app
- 创建 admin 应用,用于实现后台管理功能
新建 app
python manage.py startapp admin
settings.py
- INSTALLED_APPS 中添加 admin app,将模板自带的 admin 注释掉
INSTALLED_APPS = [
# 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.course',
'apps.users',
'apps.news',
'apps.doc',
'apps.verifications',
'haystack',
'apps.admin'
]
web_prv/urls.py
- 添加 apps.admin.urls,将自带的 admin 路径注释掉
# from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
#404就是路径问题
urlpatterns = [
# path('admin/', admin.site.urls),
path('',include('apps.news.urls')),
path('users/',include('apps.users.urls')),
path('doc/',include('apps.doc.urls')),
path('course/',include('apps.course.urls')),
path('',include('apps.verifications.urls')),
path('admin/',include('apps.admin.urls')),
]+static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
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')
]
apps/admin/views.py
import json
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 utils.json_fun import to_json_data
from utils.res_code import Code, error_map
class IndexView(View):
"""
"""
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='标签不存在!')
前端功能实现
templates/admin/index/index.html
{% extends 'admin/base/base.html' %}
{% load static %}
{% block titile %}
首页
{% endblock %}
{% block content_header %}
欢迎来到后台管理系统
{% endblock %}
{% block header_option_desc %}
学习使我快乐
{% endblock %}
templates/admin/news/tags_manage.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">
<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 id="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 %}
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 === "0") {
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 === "0") {
// 更新标签成功
$(_this).parents('tr').find('td:nth-child(1)').text(inputVal);
swal.close();
message.showSuccess("标签修改成功");
setTimeout(function () {
window.location.reload();
},1000)
// window.location.reload();
} 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 === "0") {
// 更新标签成功
message.showSuccess("标签删除成功");
$(_this).parents('tr').remove();
} 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'));
}
}
});
});