Django打造大型企业官网-项目实战(三)
一、CRM 后台管理系统
前面我们使用的是 xadmin 后台管理系统,在使用中发现,在权限限制中,我们能实现不同等级的用户/管理(超级管理员/管理员/用户)登录后台时拥有不同数据的操作权限(查看、修改等),但实际上在更细方面的地方我们并没有找到有效的方法实现精准的权限限制,比如当普通用户想新增新闻时,在作者字段一栏会直接列出目前所有的用户名(作者),当前用户可以任意选择作者作为当前文章的发布者,这样明显是很不安全的,而如果我们需要改动这些细节就必须得从xadmin源码上寻求解决方法。为了更好的控制后台权限及数据展示,我们重新部署自己的后台管理系统。
重新部署后台管理系统,需要使用到新的第三方库:adminLTE ,
文档及下载:http://adminlte.la998.com/documentation/
GitHub 下载:https://github.com/ColorlibHQ/AdminLTE
操作演示:http://adminlte.la998.com/index2.html
界面:
一般使用,可以将所需要的页面的代码拷贝到自己项目系统中对应的位置,再把相关联的样式文件等拷贝进去。如果感兴趣,可以参考上述文档链接或查询官方文档。
crm 后台管理系统
1、后台系统登录相关功能
新建app 并注册到settings.py下的INSTALED_APPS 中 ,命名 crm :python manage.py startapp crm
登录界面:
登录功能相关代码:
1)HTML前端代码:

<!-- crm/login.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>小饭桌 | 后台登录</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"> <!-- Bootstrap 3.3.7 --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/bootstrap/dist/css/bootstrap.min.css' %}"> <!-- Font Awesome --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/font-awesome/css/font-awesome.min.css' %}"> <!-- Ionicons --> <link rel="stylesheet" href="{% static 'adminlte/bower_components/Ionicons/css/ionicons.min.css' %}"> <!-- Theme style --> <link rel="stylesheet" href="{% static 'adminlte/dist/css/AdminLTE.min.css' %}"> <!-- iCheck --> <link rel="stylesheet" href="{% static 'adminlte/plugins/iCheck/square/blue.css' %}"> <link rel="stylesheet" href="{% static 'adminlte/login error.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"> </head> <body class="hold-transition login-page"> <div class="login-box"> <div class="login-logo"> <a href="#">小饭桌管理系统</a> </div> <!-- /.login-logo --> <div class="login-box-body"> <p class="login-box-msg">请登录</p> <form action="{% url 'crm_login' %}" method="post"> {% csrf_token %} <div class="form-group has-feedback {% if login_form.errors.mobile.0 %}errorput{% endif %}"> <input type="text" class="form-control" name="mobile" {% if login_form.errors.mobile.0 %} placeholder="{{ login_form.errors.mobile.0 }}" {% elif login_form.mobile.value %} value="{{ login_form.mobile.value }}" {% else %} placeholder="请输入您的手机号" {% endif %}> <span class="glyphicon glyphicon-envelope form-control-feedback"></span> </div> <div class="form-group has-feedback {% if login_form.errors.password.0 %} errorput {% endif %}"> <input type="password" class="form-control" name="password" {% if login_form.errors.password.0 %} placeholder="{{ login_form.errors.password.0 }}" {% else %} placeholder="请输入密码" {% endif %}> <span class="glyphicon glyphicon-lock form-control-feedback"></span> </div> <div class="form-group has-feedback error"> {{ msg }} </div> <div class="row"> <div class="col-xs-8"> <div class="checkbox icheck"> <label> <input type="checkbox" name="remember" value="1"> 记住我 </label> </div> </div> <!-- /.col --> <div class="col-xs-4"> <button type="submit" class="btn btn-primary btn-block btn-flat">登录</button> </div> <!-- /.col --> </div> </form> </div> <!-- /.login-box-body --> </div> <!-- /.login-box --> <!-- jQuery 3 --> <script src="{% static 'adminlte/bower_components/jquery/dist/jquery.min.js' %}"></script> <!-- Bootstrap 3.3.7 --> <script src="{% static 'adminlte/bower_components/bootstrap/dist/js/bootstrap.min.js' %}"></script> <!-- iCheck --> <script src="{% static 'adminlte/plugins/iCheck/icheck.min.js' %}"></script> <script> $(function () { $('input').iCheck({ checkboxClass: 'icheckbox_square-blue', radioClass: 'iradio_square-blue', increaseArea: '20%' /* optional */ }); }); </script> </body> </html>
2)views.py 后端代码:

from django.shortcuts import render, redirect from django.contrib.auth import authenticate, login, from xfz.forms import LoginForm def crm_login(request): """crm 登录""" if request.method == 'GET': # 如果在前端已经登录,可以直接进入后台管理系统 try: if request.user.is_authenticated: return redirect('/crm/index/') except: pass login_form = LoginForm() return render(request, 'crm/login.html') else: login_form = LoginForm(request.POST) if login_form.is_valid(): mobile = login_form.cleaned_data.get('mobile') password = login_form.cleaned_data.get("password") remember = login_form.cleaned_data.get('remember') user = authenticate(request, username=mobile, password=password) if user: if user.is_active and user.is_staff: login(request, user) if remember: request.session.set_expiry(None) else: request.session.set_expiry(0) return redirect('/crm/index/') else: return render(request, 'crm/login.html', {'msg': '用户无权登录后台系统', 'login_form': login_form}) else: return render(request, 'crm/login.html', {'msg': '用户名或密码错误', 'login_form': login_form}) else: return render(request, 'crm/login.html', {'msg': '用户名或密码格式错误,请重新输入!', 'login_form': login_form})
3)urls.py 路由:

# xfz/urls.py:path("crm/", include("apps.crm.urls")), # crm 管理后台 from django.urls import path from .views import crm_login, index urlpatterns = [ path("login/", crm_login, name='crm_login'), # crm 管理后台 path("index/", index, name='crm_index'), # crm 管理后台 ]
2、后台登录权限限制:
限制只有后台权限的用户才能登录(django自带员工识别装饰器,识别只有is_staff 的用户才能登录后台系统),如后台首页登录限制,代码如下:
# crm/views.py from django.contrib.admin.views.decorators import staff_member_required # 是否为后台员工识别装饰器 @staff_member_required(login_url='index') # 不是后台员工,无法登录后台,重定向到前端首页 def index(request): """小饭桌管理后台首页""" return render(request, 'crm/index.html')
这样,无论是在网址栏上直接输入网址还是前端跳转,只要不是员工都无法访问到后台
3、cms后台管理系统-首页
界面:
功能实现代码:
1)HTML前端代码:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>{% block title %}{% endblock %} | 小饭桌管理系统</title> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <link rel="stylesheet" href="{% static 'adminlte/bower_components/bootstrap/dist/css/bootstrap.min.css' %}"> <link rel="stylesheet" href="{% static 'adminlte/bower_components/font-awesome/css/font-awesome.min.css' %}"> <link rel="stylesheet" href="{% static 'adminlte/dist/css/AdminLTE.min.css' %}"> <link rel="stylesheet" href="{% static 'adminlte/dist/css/skins/_all-skins.min.css' %}"> <link rel="stylesheet" href="{% static 'adminlte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css' %}"> <link rel="stylesheet" href="{% static 'sweetalert/sweetalert.css' %}"> <script src="{% static 'adminlte/bower_components/jquery/dist/jquery.min.js' %}"></script> <script src="{% static 'adminlte/bower_components/bootstrap/dist/js/bootstrap.min.js' %}"></script> <script src="{% static 'adminlte/dist/js/adminlte.min.js' %}"></script> <script src="{% static 'sweetalert/sweetalert.min.js' %}"></script> {# <script src="{% static 'js/xfzajax.min.js' %}"></script>#} {# <script src="{% static 'js/xfzalert.min.js' %}"></script>#} {# <script src="{% static 'js/message.min.js' %}"></script>#} {% block head %}{% endblock %} </head> <body class="hold-transition skin-blue sidebar-mini"> <div class="wrapper"> <header class="main-header"> <!-- Logo --> <a href="#" class="logo"> <!-- mini logo for sidebar mini 50x50 pixels --> <span class="logo-mini"><b>CMS</b></span> <!-- logo for regular state and mobile devices --> <span class="logo-lg">小饭桌后台管理系统</span> </a> <!-- Header Navbar: style can be found in header.less --> <nav class="navbar navbar-static-top"> <!-- Sidebar toggle button--> <a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button"> <span class="sr-only">Toggle navigation</span> </a> <div class="navbar-custom-menu"> <ul class="nav navbar-nav"> <li class="dropdown user user-menu"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <img src="{% static 'images/auth/aobama.png' %}" class="user-image" alt="User Image"> <span class="hidden-xs">{{ request.user.username }}</span> </a> <ul class="dropdown-menu"> <!-- User image --> <li class="user-header"> <img src="{% static 'images/auth/aobama.png' %}" class="img-circle" alt="User Image"> <p> {{ request.user.username }} <small>{{ request.user.employee }}</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> <aside class="main-sidebar"> <!-- sidebar: style can be found in sidebar.less --> <section class="sidebar"> <form action="/" method="get" class="sidebar-form"> <div class="input-group"> <input type="text" name="q" class="form-control" placeholder="首页"> <span class="input-group-btn"> <button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i> </button> </span> </div> </form> <!-- /.search form --> <!-- sidebar menu: : style can be found in sidebar.less --> <ul class="sidebar-menu" data-widget="tree"> <li class="active"> <a href="{% url 'crm_index' %}"> <i class="fa fa-home"></i> <span>首页</span> </a> </li> <li class="header">新闻管理</li> <li> <a href="#"> <i class="fa fa-list"></i> <span>新闻列表</span> </a> </li> <li> <a href="#"> <i class="fa fa-edit"></i> <span>发布新闻</span> </a> </li> <li> <a href="#"> <i class="fa fa-tag"></i> <span>新闻分类</span> </a> </li> <li> <a href="#"> <i class="fa fa-window-restore"></i> <span>轮播图</span> </a> </li> <li class="header">课程管理</li> <li> <a href="#"> <i class="fa fa-tv"></i> <span>发布课程</span> </a> </li> </ul> </section> </aside> <div class="content-wrapper"> <section class="content-header"> {% block content-header %}{% endblock %} </section> <section class="content"> {% block content %}{% endblock %} </section> </div> <footer class="main-footer"> <strong>小饭桌</strong>后台管理系统 </footer> </div> </body> </html>

{% extends 'crm/base.html' %} {% block title %}首页{% endblock %} {% block content-header %} <h1>欢迎来到小饭桌CMS管理系统</h1> {% endblock %}
2)views.py 后端代码(有待优化):

from django.contrib.admin.views.decorators import staff_member_required # 是否为后台员工识别装饰器 @staff_member_required(login_url='index') # 不是后台员工,无法登录后台,重定向到前端首页 def index(request): """小饭桌管理后台首页""" return render(request, 'crm/index.html')
3)urls.py:

from django.urls import path from .views import index urlpatterns = [ path("index/", index, name='crm_index'), # crm 管理后台首页 ]
4、新闻分类 - 功能:获取所有新闻分类 、 添加新闻分类 、 修改新闻分类 、 删除新闻分类
需注意的是,在获取新闻分类时,需要设定权限限制,只有拥有后台管理权限的用户才能对新闻分类进行一系列的操作
1)获取所有新闻分类
界面:
HTML前端代码:继承于crm/index.html

{% extends 'crm/base.html' %} {% load crm_tags %} {% block title %} 新闻分类 {% endblock %} {% block head %} <link rel="stylesheet" href="{% static 'css/news_category/category.min.css'%}"> {# <script src="{% static 'js/news_category.min.js' %}"></script>#} {% endblock %} {% block content-header %} <h1>新闻分类</h1> {% endblock %} {% block content %} <div class="row"> <div class="col-md-12"> <div class="box box-primary"> <div class="box-header"> <button id="add-btn" class="btn btn-primary pull-right" onclick="addAction()">添加分类</button> </div> <div class="box-body"> <table class="table table-bordered"> <thead> <tr> <th>分类名称</th> <th>新闻数量</th> <th>操作</th> </tr> </thead> <tbody> {% for category in categories %} <tr data-pk="{{ category.id}}" data-name="{{ category.name }}"> <td>{{ category.name }}</td> <td>{{ category.news_nums }}</td> <td> <button class="btn btn-warning btn-xs edit-btn">编辑</button> <button class="btn btn-danger btn-xs delete-btn">删除</button> </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </div> <div class="pagination"> <!-- 分页 --> {% render_paginator categories %} </div>
views.py:包含分页功能

from django.shortcuts import render
from django.contrib.admin.views.decorators import staff_member_required # 是否为后台员工识别装饰器
from django.views.decorators.http import require_POST, require_GET
from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage
from news.models import NewsCategory
@staff_member_required(login_url='index')
@require_GET
def new_category(request):
"""新闻分类详情"""
categories = NewsCategory.objects.all()
# 分页(分页功能)
paginator = Paginator(categories, 2) # 每页显示2行数据
page = request.GET.get('_page')
try:
categories = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
categories = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
categories = paginator.page(paginator.num_pages) # paginator.num_pages:总页数,即返回最后一页
return render(request, 'crm/news_category.html', locals())
urls.py:

from django.urls import path
from crm.views import new_category
urlpatterns = [
path("category/", new_category, name='news_category'), # crm 管理后台 新闻分类
]
crm/models.py/news_cetagory

class NewsCategory(models.Model):
"""新闻分类"""
name = models.CharField("新闻分类", max_length=40)
add_time = models.DateTimeField("添加时间", default=datetime.now)
class Meta:
verbose_name = "新闻分类"
verbose_name_plural = verbose_name
def news_nums(self):
"""该分类下新闻数量"""
return self.news_set.all().count()
def __str__(self):
return self.name
获取所有新闻数据后,我们需要对所有新闻数据进行分页操作,使用 templatetags 自定义标签的方式实现:

"""
templatetags包,用于创建自定义标签,再用于前端显示
自定义标签步骤: 1、在APP文件中创建 templatetags包(不是文件夹)
2、在templatetags包中创建 kingadmin.py文件
3、导入:from django.template import Library
4、实例化:register = Library() 。注:命名必须‘register’,不能改
"""
在crm 中新建 templatetags 包,在 templatetags 包中新建crm_tags.py文件,分页代码如下:

from django.template import Library from django.utils.safestring import mark_safe register = Library() @register.simple_tag def render_paginator(querysets): """ 分页功能 从views中拿到querysets paginator = Paginator(querysets, 2) :一页显示2行 querysets = paginator.page(page):当前页码 """ ele = ''' <nav aria-label="Page navigation"> <ul class="pagination"> <li> <a href="?_page=1" aria-label="shouye"> <span aria-hidden="true">首页</span> </a> </li> ''' if querysets.has_previous(): p_ele = ''' <li><a href="?_page=%s" aria-label="Previous">«上一页</a></li> ''' % (querysets.previous_page_number()) ele += p_ele # querysets.paginator=paginator,page_range:页数范围 for i in querysets.paginator.page_range: # querysets.number:当前页码 if abs(querysets.number - i) < 4:# 只显示相邻页码,最多3页 active = '' # 当前页 if querysets.number ==i: active = 'active' p_ele = ''' <li class="%s"><a href="?_page=%s">%s</a></li> '''% (active, i, i) ele += p_ele # 是否有下一页 if querysets.has_next(): p_ele = ''' <li><a href="?_page=%s" aria-label="Next">下一页»</a></li> ''' % (querysets.next_page_number()) ele += p_ele # querysets.paginator.num_pages:总页数 p_ele = ''' <li> <a href="?_page=%s" aria-label="weiye"> <span aria-hidden="true">尾页</span> </a> </li> '''%(querysets.paginator.num_pages) ele += p_ele ele += "</ul></nav>" return mark_safe(ele)
HTML 模板中运用:

{% load crm_tags %} <div class="pagination"> <!-- 分页 --> {% render_paginator categories %} </div>
views.py代码实现:

from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage
from news.models import NewsCategory
categories = NewsCategory.objects.all()
# 分页(分页功能)
paginator = Paginator(categories, 2) # 每页显示2行数据
page = request.GET.get('_page')
try:
categories = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
categories = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
categories = paginator.page(paginator.num_pages) # paginator.num_pages:总页数,即返回最后一页
2)新增新闻分类功能
在新闻分类页面中,使用模态对话框的形式实现 (修改新闻分类、删除新闻分类也使用此方法)
功能实现:
HTML前端代码(结合获取新闻前端代码,及js、ajax异步post数据):

{% extends 'crm/base.html' %} {% load crm_tags %} {% block title %} 新闻分类 {% endblock %} {% block head %} <link rel="stylesheet" href="{% static 'css/news_category/category.min.css'%}"> {# <script src="{% static 'js/news_category.min.js' %}"></script>#} {% endblock %} {% block content-header %} <h1>新闻分类</h1> {% endblock %} {% block content %} <div class="row"> <div class="col-md-12"> <div class="box box-primary"> <div class="box-header"> <button id="add-btn" class="btn btn-primary pull-right" onclick="addAction()">添加分类</button> </div> <div class="box-body"> <table class="table table-bordered"> <thead> <tr> <th>分类名称</th> <th>新闻数量</th> <th>操作</th> </tr> </thead> <tbody> {% for category in categories %} <tr data-pk="{{ category.id}}" data-name="{{ category.name }}"> <td>{{ category.name }}</td> <td>{{ category.news_nums }}</td> <td> <button class="btn btn-warning btn-xs edit-btn">编辑</button> <button class="btn btn-danger btn-xs delete-btn">删除</button> </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </div> <div class="pagination"> <!-- 分页 --> {% render_paginator categories %} </div> <!-- add cetagory --> <div class="shade hide" id="shade-hide"></div> <div class="login-box-body add-action hide" id="add-form"> <p class="login-box-msg">添加新闻分类</p> <span class="glyphicon glyphicon-remove form-control-feedback" id="add-close" onclick="addClose()"></span> <form action="javascript:void(0)" method="post" id="add-form"> {% csrf_token %} <div class="form-group has-feedback {% if not msg.status %}errorput{% endif %}"> <input type="text" class="form-control" name="name" placeholder="请输入新闻分类"> </div> <div> <div class="col-xs-7 add-btn"> <button type="submit" class="btn btn-primary btn-block btn-flat" onclick="addCetagory()">确定</button> </div> </div> </form> </div> {% endblock %} {% block front-js %} <script> // add category function addAction() { $("#shade-hide").removeClass("hide"); $("#add-form").removeClass("hide"); } function addClose() { $("#shade-hide").addClass("hide"); $("#add-form").addClass("hide"); } function addCetagory() { var nameInput = $("input[name='name']").val(); if(nameInput == ""){ alert("请输入新闻分类!") } else { $.ajax({ cache:false, type:'post', async:true, headers:{"X-CSRFToken":"{{ csrf_token }}"}, // 获取csrf token dataType:'json', url:'{% url 'add_category' %}', data:{'name':nameInput}, 'success':function (result) { console.log(result); alert(result['message']); if(result['status'] == true){ window.location.href = '{% url "news_category" %}' } }, 'fail':function (error) { console.log(error); } }) } } </script> {% endblock %}
views.py:

@require_POST
def add_new_category(request):
"""新增-新闻分类"""
name = request.POST.get('name')
print("name:", name)
exists = NewsCategory.objects.filter(name=name).exists()
if exists:
return JsonResponse({"status": False, "message": "该分类已经存在"})
else:
NewsCategory.objects.create(name=name)
return JsonResponse({"status": True, "message": "分类添加成功"})
urls.py:

from django.urls import path
from crm.views import add_new_category
urlpatterns = [
path("add_category/", add_new_category, name='add_category'), # crm 管理后台 新闻分类
]
3)修改新闻分类 、删除新闻分类
修改新闻分类、删除新闻分类,在本质来说实现方式是差不多的,当 修改/删除 某条新闻分类时,我们获取当条分类的分类名及id号,在生成的模态对话框中(修改/删除页面),将当前选择的当条分类的分类名及分类id填到对应的输入框中,其中id input框选择隐藏属性,目的只是将当条分类的id传回给后端,方便结合分类名对当条新闻分类进行相关修改或删除操作,在实现删除功能能,分类名input框最好改成p标签等,起到用户不能修改数据的作用。
关于修改新闻分类、删除新闻分类功能实现,结合新增新闻分类相关功能代码,具体实现如下(新闻分类 新增/修改/删除 全部前后端代码),关于项目所用的js、css及其他相关文件均会在项目博文最后提供下载地址
修改操作界面:
删除操作界面:
HTML前端代码:

{% extends 'crm/base.html' %} {% load crm_tags %} {% block title %} 新闻分类 {% endblock %} {% block head %} <link rel="stylesheet" href="{% static 'css/news_category/category.min.css'%}"> {# <script src="{% static 'js/news_category.min.js' %}"></script>#} {% endblock %} {% block content-header %} <h1>新闻分类</h1> {% endblock %} {% block content %} <div class="row"> <div class="col-md-12"> <div class="box box-primary"> <div class="box-header"> <button id="add-btn" class="btn btn-primary pull-right" onclick="addAction()">添加分类</button> </div> <div class="box-body"> <table class="table table-bordered"> <thead> <tr> <th>分类名称</th> <th>新闻数量</th> <th>操作</th> </tr> </thead> <tbody> {% for category in categories %} <tr data-pk="{{ category.id}}" data-name="{{ category.name }}"> <td>{{ category.name }}</td> <td>{{ category.news_nums }}</td> <td> <button class="btn btn-warning btn-xs edit-btn" onclick="editAction(this)">编辑</button> <button class="btn btn-danger btn-xs delete-btn" onclick="deleteAction(this)">删除</button> </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </div> <div class="pagination"> <!-- 分页 --> {% render_paginator categories %} </div> <!-- add category --> <div class="shade hide" id="add-category"></div> <div class="login-box-body add-action hide" id="add-form"> <p class="login-box-msg">添加新闻分类</p> <span class="glyphicon glyphicon-remove form-control-feedback close-icon" id="add-close" onclick="addClose()"></span> <form action="javascript:void(0)" method="post" id="add-form"> {% csrf_token %} <div class="form-group has-feedback"> <input type="text" class="form-control" name="name" placeholder="请输入新闻分类" id="add-input"> </div> <div> <div class="col-xs-7 add-btn"> <button type="submit" class="btn btn-primary btn-block btn-flat" id="add-submit">确定</button> </div> </div> </form> </div> <!-- edit category --> <div class="shade hide" id="edit-category"></div> <div class="login-box-body add-action hide" id="edit-form"> <p class="login-box-msg">修改新闻分类</p> <span class="glyphicon glyphicon-remove form-control-feedback close-icon" id="edit-close" onclick="editClose()"></span> <form action="javascript:void(0)" method="post"> {% csrf_token %} <div class="form-group has-feedback"> <input type="text" class="form-control" name="name" placeholder="请输入新闻分类" id="edit-input"> <input type="hidden" class="form-control" name="uid" placeholder="编号" id="edit-uid"> </div> <div> <div class="col-xs-7 add-btn"> <button type="submit" class="btn btn-primary btn-block btn-flat" id="edit-submit">确定</button> </div> </div> </form> </div> <!-- delete category --> <div class="shade hide" id="delete-category"></div> <div class="login-box-body add-action hide" id="delete-form"> <p class="login-box-msg">您确定删除此条分类吗?</p> <span class="glyphicon glyphicon-remove form-control-feedback close-icon" id="delete-close" onclick="deleteClose()"></span> <form action="javascript:void(0)" method="post"> {% csrf_token %} <div class="form-group has-feedback"> <p class="delete-category form-control"></p> <input type="hidden" class="form-control" name="uid" placeholder="编号" id="delete-uid"> </div> <div> <span class="btn btn-info pull-right" onclick="backAction()">返回</span> <input type="submit" class="btn btn-danger pull-right margin-r-5" value="确认删除" onclick="deleteSubmit()"> </div> </form> </div> {% endblock %} {% block front-js %} <script> // add category function addAction() { $("#add-category").removeClass("hide"); $("#add-form").removeClass("hide"); $(".pagination").addClass("hide"); } function addClose() { $("#add-category").addClass("hide"); $("#add-form").addClass("hide"); $(".pagination").removeClass("hide"); } // add-category submit $("#add-submit").click(function () { var nameInput = $("#add-input").val(); if(nameInput == ""){ alert("请输入新闻分类!") } else { $.ajax({ cache:false, type:'post', async:true, headers:{"X-CSRFToken":"{{ csrf_token }}"}, // 获取csrf token dataType:'json', url:"{% url 'add_category' %}", data:{'name':nameInput}, 'success':function (result) { alert(result['message']); if(result['status'] == true){ window.location.href = '{% url "news_category" %}' } }, 'fail':function (error) { console.log(error); } }) } }); // edit category function editAction(self) { var tr = $(self).parent().parent(); var uid = tr.attr('data-pk'); // id var name = tr.attr('data-name'); // name $("#edit-input").val(name); //将name 填充到修改新闻分类的name栏上, 不用这种方式 // $("input[name='name']").attr('placeholder',name); //将name 填充到修改新闻分类的name栏上,placeholder形式 $("#edit-uid").val(uid); //将 id 号填充到修改分类的隐藏栏上,目的是为了给后台提供当前修改的某条数据 $("#edit-category").removeClass("hide"); $("#edit-form").removeClass("hide"); $(".pagination").addClass("hide"); } function editClose() { $("#edit-category").addClass("hide"); $("#edit-form").addClass("hide"); $(".pagination").removeClass("hide"); } // edit-category submit $("#edit-submit").click(function () { var nameInput = $("#edit-input").val(); var uidInput = $("#edit-uid").val(); if(nameInput == ""){ alert("请输入新闻分类!") } else { $.ajax({ cache:false, type:'post', async:true, headers:{"X-CSRFToken":"{{ csrf_token }}"}, // 获取csrf token dataType:'json', url:"{% url 'edit_category' %}", data:{'name':nameInput, 'uid':uidInput}, 'success':function (result) { alert(result['message']); if(result['status'] == true){ window.location.href = '{% url "news_category" %}' } }, 'fail':function (error) { console.log(error); } }) } }); // delete category function deleteAction(self) { var tr = $(self).parent().parent(); var uid = tr.attr('data-pk'); // id var name = tr.attr('data-name'); // name // $("#delete-input").val(name); //将name 填充到隐藏 name栏上, 用于提交数据到后台 $(".delete-category").text(name); $("#delete-uid").val(uid); //将 id 号填充到删除分类的隐藏栏上,目的是为了给后台提供当前删除的某条数据 $("#delete-category").removeClass("hide"); $("#delete-form").removeClass("hide"); $(".pagination").addClass("hide"); } function deleteClose() { $("#delete-category").addClass("hide"); $("#delete-form").addClass("hide"); $(".pagination").removeClass("hide"); } function backAction() { $("#delete-category").addClass("hide"); $("#delete-form").addClass("hide"); $(".pagination").removeClass("hide"); } // delete-category submit function deleteSubmit() { var nameInput = $(".delete-category").text(); var uidInput = $("#delete-uid").val(); // 结合uid、name双重判断,这样就算前端修改了name数值,id不通过也无法删除数据,保证安全 $.ajax({ cache:false, type:'post', async:true, headers:{"X-CSRFToken":"{{ csrf_token }}"}, // 获取csrf token dataType:'json', url:"{% url 'delete_category' %}", data:{'name':nameInput, 'uid':uidInput}, 'success':function (result) { alert(result['message']); if(result['status'] == true){ window.location.href = '{% url "news_category" %}' } }, 'fail':function (error) { console.log(error); } }) } </script> {% endblock %}
views.py:

from django.shortcuts import render, redirect from django.contrib.admin.views.decorators import staff_member_required # 是否为后台员工识别装饰器 from django.views.decorators.http import require_POST, require_GET from django.http import JsonResponse from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage @staff_member_required(login_url='index') @require_GET def new_category(request): """新闻分类详情""" # if request.user.is_superuser: # categories = NewsCategory.objects.all() # 返回所有分类 # else: # categories = NewsCategory.objects.filter(news__author=request.user) # 返回当前用户的分类 # return render(request, 'crm/news_category.html', locals()) categories = NewsCategory.objects.all().order_by("-id") # 分页(分页功能) paginator = Paginator(categories, 2) # 每页显示2行数据 page = request.GET.get('_page') try: categories = paginator.page(page) except PageNotAnInteger: # If page is not an integer, deliver first page. categories = paginator.page(1) except EmptyPage: # If page is out of range (e.g. 9999), deliver last page of results. categories = paginator.page(paginator.num_pages) # paginator.num_pages:总页数,即返回最后一页 return render(request, 'crm/news_category.html', locals()) @require_POST def add_new_category(request): """新增-新闻分类""" name = request.POST.get('name') exists = NewsCategory.objects.filter(name=name).exists() if exists: return JsonResponse({"status": False, "message": "该分类已经存在"}) else: NewsCategory.objects.create(name=name) return JsonResponse({"status": True, "message": "分类添加成功"}) @require_POST def edit_new_category(request): """修改-新闻分类""" uid = request.POST.get('uid') name = request.POST.get('name') exists = NewsCategory.objects.filter(name=name).exists() if exists: return JsonResponse({"status": False, "message": "该分类已经存在"}) else: NewsCategory.objects.filter(id=uid).update(name=name) return JsonResponse({"status": True, "message": "分类编辑完成"}) @require_POST def delete_new_category(request): """删除-新闻分类""" name = request.POST.get('name') id = request.POST.get('uid') try: exists = NewsCategory.objects.filter(name=name, id=id)[0] except Exception as e: exists = None if exists: exists.delete() return JsonResponse({"status": True, "message": "该分类已被删除"}) else: return JsonResponse({"status": False, "message": "该分类不存在!"})
urls.py:

from django.urls import path from crm.views import new_category, add_new_category, edit_new_category, delete_new_category urlpatterns = [ path("category/", new_category, name='news_category'), # crm 管理后台 新闻分类 path("add_category/", add_new_category, name='add_category'), # crm 管理后台 添加新闻分类 path("edit_category/", edit_new_category, name='edit_category'), # crm 管理后台 修改新闻分类 path("delete_category/", delete_new_category, name='delete_category'), # crm 管理后台 删除新闻分类 ]
models.py:

class NewsCategory(models.Model): """新闻分类""" name = models.CharField("新闻分类", max_length=40) add_time = models.DateTimeField("添加时间", default=datetime.now) class Meta: verbose_name = "新闻分类" verbose_name_plural = verbose_name def news_nums(self): """该分类下新闻数量""" return self.news_set.all().count() def __str__(self): return self.name
crm_tag.py 自定义分页标签:

from django.template import Library from django.utils.safestring import mark_safe register = Library() @register.simple_tag def render_paginator(querysets): """ 分页功能 从views中拿到querysets paginator = Paginator(querysets, 2) :一页显示2行 querysets = paginator.page(page):当前页码 """ ele = ''' <nav aria-label="Page navigation"> <ul class="pagination"> <li> <a href="?_page=1" aria-label="shouye"> <span aria-hidden="true">首页</span> </a> </li> ''' if querysets.has_previous(): p_ele = ''' <li><a href="?_page=%s" aria-label="Previous">«上一页</a></li> ''' % (querysets.previous_page_number()) ele += p_ele # querysets.paginator=paginator,page_range:页数范围 for i in querysets.paginator.page_range: # querysets.number:当前页码 if abs(querysets.number - i) < 4:# 只显示相邻页码,最多3页 active = '' # 当前页 if querysets.number ==i: active = 'active' p_ele = ''' <li class="%s"><a href="?_page=%s">%s</a></li> '''% (active, i, i) ele += p_ele # 是否有下一页 if querysets.has_next(): p_ele = ''' <li><a href="?_page=%s" aria-label="Next">下一页»</a></li> ''' % (querysets.next_page_number()) ele += p_ele # querysets.paginator.num_pages:总页数 p_ele = ''' <li> <a href="?_page=%s" aria-label="weiye"> <span aria-hidden="true">尾页</span> </a> </li> '''%(querysets.paginator.num_pages) ele += p_ele ele += "</ul></nav>" return mark_safe(ele)