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)