[django项目] 后台菜单管理功能
后臺(tái)菜單管理功能
菜單的管理功能其實(shí)就是, 對(duì)菜單的增刪改查
I. 業(yè)務(wù)功能分析
1>業(yè)務(wù)需求分析
后臺(tái)首頁(yè)菜單根據(jù)用戶權(quán)限動(dòng)態(tài)生成,不同菜單對(duì)應(yīng)不同的功能視圖。
菜單的增刪改查。
2>功能分析
- 菜單列表
- 添加菜單
- 修改菜單
- 刪除菜單
3>模型設(shè)計(jì)
3.1>字段分析
- name, 菜單名
- url, 菜單的路由
- parent, 父菜單的id
- order, 排序
- permission, 訪問(wèn)該菜單的權(quán)限名
- icon, 菜單顯示的icon
- codename, 菜單的權(quán)限碼
- is_visible, 是否可見(jiàn)
3.2>模型定義
# 在myadmin/models.py中定義如下模型 from django.db import models from django.contrib.auth.models import Permissionfrom utils.models import BaseModel # Create your models here.class Menu(BaseModel):name = models.CharField('菜單名', max_length=48, help_text='菜單名')url = models.CharField('url', max_length=256, null=True, blank=True, help_text='url')parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')order = models.SmallIntegerField('排序', default=0)permission = models.OneToOneField(Permission, on_delete=models.SET_NULL, null=True)icon = models.CharField('圖標(biāo)', max_length=48, default='fa-link')codename = models.CharField('權(quán)限碼', max_length=48, help_text='權(quán)限碼', unique=True)is_visible = models.BooleanField('是否可見(jiàn)', default=False)class Meta:ordering = ['-order']db_table = 'tb_menu'verbose_name = '菜單'verbose_name_plural = verbose_namedef __str__(self):return self.name模型創(chuàng)建后記得遷移, 項(xiàng)目到目前的階段, 數(shù)據(jù)庫(kù)的內(nèi)容開(kāi)始變得比較復(fù)雜了, 所以該遷移的時(shí)候一定不要落下
II. 菜單列表
1>業(yè)務(wù)流程分析
2>接口設(shè)計(jì)
| 請(qǐng)求方法 | GET |
| url定義 | /admin/menus/ |
| 參數(shù)格式 | 無(wú)參數(shù) |
返回結(jié)果
html
3>后端代碼
3.1>視圖
# myadmin/views.py下定義如下視圖: class MenuListView(View):"""菜單列表視圖url:/admin/menu_list/"""def get(self, request):# 為了便于后續(xù)的修改, 需要展示被邏輯刪除的菜單,# 因此filter的is_delete屬性就不許要加了menus = models.Menu.objects.only('name', 'url', 'icon', 'is_visible', 'order', 'codename').filter(parent=None)# parent=None表示沒(méi)有父菜單, 即一級(jí)菜單return render(request, 'myadmin/menu/menu_list.html', context={'menus': menus}) # myadmin/views.py中修改菜單管理對(duì)應(yīng)的路由: class IndexView(View):"""后臺(tái)首頁(yè)視圖"""def get(self, request):menus = [{...},{...},{...},{...},{...},{"name": "系統(tǒng)設(shè)置","icon": "fa-cogs","children": [{...},{...},{"name": "菜單管理","url": "myadmin:menu_list"},{...}]}]return render(request, 'myadmin/index.html', context={'menus': menus})3.2>路由
# admin/urls.py中添加如下路由 path('menu_list/', views.MenuListView.as_view(), name='menu_list'),4>前端代碼
4.1>html
咱們先簡(jiǎn)單的寫(xiě)一下前端, 然后去看一下頁(yè)面的情況
<!-- 創(chuàng)建templates/myadmin/menu/menu_list.html--> {% extends 'myadmin/base/content_base.html' %} {% load static %} {% block page_header %}系統(tǒng)設(shè)置{% endblock %} {% block page_option %}菜單管理{% endblock %}創(chuàng)建好后記得重啟一下django的服務(wù)哦
頁(yè)面效果:
可以打開(kāi)這個(gè)界面, 就說(shuō)明路由和視圖配置好了
接下來(lái)將列表填充到這個(gè)頁(yè)面, 我們可以使用AdminLTE為用戶提供的表格模板:
這個(gè)頁(yè)面上有很多類(lèi)型的表格, 修改前端代碼
{% extends 'myadmin/base/content_base.html' %} {% load static %} {% block page_header %}系統(tǒng)設(shè)置{% endblock %} {% block page_option %}菜單管理{% endblock %}{% block content %}<div class="box"><div class="box-header"><h3 class="box-title">菜單列表</h3><div class="box-tools"><button type="button" class="btn btn-primary btn-sm">添加菜單</button></div></div><!-- /.box-header --><div class="box-body"><table class="table table-bordered "><tbody><tr role="row"><!-- 列表字段 --><th>菜單</th><th>子菜單</th><th>路由地址</th><th>圖標(biāo)</th><th>權(quán)限碼</th><th>順序</th><th>是否可見(jiàn)</th><th>邏輯刪除</th><th>操作</th></tr>{% for menu in menus %}<!-- 循環(huán)遍歷列表內(nèi)容 --><tr><!-- 遍歷父菜單屬性 --><td>{{ menu.name }}</td><td><!-- 子菜單為空 --></td><td>{{ menu.url|default:'' }}</td><td>{{ menu.icon }}</td><td>{{ menu.codename }}</td><td>{{ menu.order }}</td><td>{% if menu.is_visible %}是{% else %}否{% endif %}</td><td style="width: 100px" data-id="{{ menu.id }}" data-name="{{ menu.name }}">{% if menu.children.all %}<button type="button" class="btn btn-info btn-xs edit">編輯</button>{% else %}<button type="button" class="btn btn-info btn-xs edit">編輯</button><button type="button" class="btn btn-danger btn-xs delete">刪除</button>{% endif %}</td></tr>{% if menu.children.all %}{% for child in menu.children.all %}<!-- 遍歷子菜單屬性 --><tr><td><!-- 父菜單為空 --></td><td>{{ child.name }}</td><td>{{ child.url }}</td><td>{{ child.icon }}</td><td>{{ child.codename }}</td><td>{{ child.order }}</td><td style="width: 80px">{% if child.is_visible %}是{% else %}否{% endif %}</td><td style="width: 100px" data-id="{{ child.id }}" data-name="{{ child.name }}"><button type="button" class="btn btn-info btn-xs edit">編輯</button><button type="button" class="btn btn-danger btn-xs delete">刪除</button></td></tr>{% endfor %}{% endif %}{% endfor %}</tbody></table></div><!-- /.box-body --></div> {% endblock %}注意父菜單和子菜單循環(huán)體中的那兩個(gè)<td></td>標(biāo)簽, 這樣做得目的是為了更好的分辨父菜單和子菜單
III. 添加菜單頁(yè)面
功能概述: 點(diǎn)擊添加菜單按鈕, 彈出新增菜單窗口, 輸入信息提交, 即可添加到菜單列表中
1>接口設(shè)計(jì)
| 請(qǐng)求方法 | GET |
| url定義 | /admin/menu/ |
| 參數(shù)格式 | 無(wú)參數(shù) |
返回?cái)?shù)據(jù)
html
2>后端代碼
2.1>視圖
# 在myadmin/views.py中添加如下視圖 class MenuAddView(View):"""添加菜單視圖url:/admin/menu/"""def get(self, request):form = MenuModelForm()return render(request, 'myadmin/menu/add_menu.html', context={'form': form})2.2>路由
# 在myadmin/urls.py中添加如下路由 path('menu/', views.MenuAddView.as_view(), name='add_menu')2.3>表單
# 在myadmin/forms.py中定義如下表單 from django import formsfrom .models import Menuclass MenuModelForm(forms.ModelForm):parent = forms.ModelChoiceField(queryset=None, required=False, help_text='父菜單')def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)self.fields['parent'].queryset = Menu.objects.filter(is_delete=False, is_visible=True, parent=None)# https://docs.djangoproject.com/en/2.2/ref/forms/fields/#fields-which-handle-relationshipsclass Meta:model = Menufields = ['name', 'url', 'order', 'parent', 'icon', 'codename', 'is_visible']2.4>自定義標(biāo)簽
為了在渲染表單是能加入自定義css樣式,在應(yīng)用admin中定義自定義標(biāo)簽,在admin下創(chuàng)建templatetags包,在其中創(chuàng)建admin_customer_tags.py模塊
# myadmin/tamplatetags/admin_customer_tags.py from django.template import Libraryregister = Library()@register.simple_tag() def add_class(field, class_str):return field.as_widget(attrs={'class': class_str})3>前端代碼
3.1>html
<!-- 修改 templates/myadmin/menu/menu_list.html --> {% extends 'myadmin/base/content_base.html' %} {% load static %} {% block page_header %}系統(tǒng)設(shè)置{% endblock %} {% block page_option %}菜單管理{% endblock %} {% block content %}<div class="box"><div class="box-header with-border">...</div><!-- /.box-header --><div class="box-body">...</div></div><!-- add modle --><div class="modal fade" id="modal-add" role="dialog" ><div class="modal-dialog"><div class="modal-content"></div><!-- /.modal-content --></div><!-- /.modal-dialog --></div><!-- /.modal -->{% endblock %}彈出窗口我們選用表單的形式來(lái)完成, 因此這里單獨(dú)創(chuàng)建一個(gè)模型, 方便渲染
這里的模型來(lái)自Bootstarp
<!-- 新建 templates/myadmin/menu/add_menu.html -->{% load admin_customer_tags %}{% load static %}<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button><h4 class="modal-title">添加菜單</h4></div><div class="modal-body"><form class="form-horizontal" id="add-menu">{% csrf_token %}<div class="box-body">{% for field in form %}{% if field.name == 'is_visible' %}<div class="form-group"><div class="col-sm-offset-2 col-sm-10"><div class="checkbox"><label for="{{ field.id_for_label }}">{{ field }}{{ field.label }}</label></div></div></div>{% else %}<div class="form-group {% if field.errors %}has-error{% endif %}"><label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label><div class="col-sm-10">{% for error in field.errors %}<label class="control-label" for="{{ field.id_for_label }}">{{ error }}</label>{% endfor %}{% add_class field 'form-control' %}</div></div>{% endif %}{% endfor %}</div></form></div><div class="modal-footer"><button type="button" class="btn btn-default pull-left" data-dismiss="modal">取消</button><button type="button" class="btn btn-primary add">添加</button></div>根據(jù)官方文檔我們還需要加一個(gè)href=#foo(路由地址)屬性到添加菜單按鈕標(biāo)簽中
<div class="box-tools"><button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#modal-add" href="/admin/add_menu/">添加菜單</button> </div>頁(yè)面效果:
IIII. 添加菜單
1>業(yè)務(wù)流程分析
- 接收表單參數(shù)
- 校驗(yàn)表單參數(shù)
- 校驗(yàn)成功保存菜單數(shù)據(jù),創(chuàng)建菜單一對(duì)一關(guān)聯(lián)權(quán)限對(duì)象,返回創(chuàng)建成功的json數(shù)據(jù)
- 校驗(yàn)失敗,返回渲染了錯(cuò)誤信息的表單
2>接口設(shè)計(jì)
2.1>接口說(shuō)明:
| 請(qǐng)求方法 | POST |
| url定義 | /admin/menu/ |
| 參數(shù)格式 | 表單參數(shù) |
2.2>參數(shù)說(shuō)明:
| name | 字符串 | 是 | 菜單名 |
| url | 字符串 | 否 | 當(dāng)前文章頁(yè)數(shù) |
| order | 整數(shù) | 是 | 排序 |
| parent | 整數(shù) | 否 | 父菜單id |
| icon | 字符串 | 是 | 渲染圖標(biāo)類(lèi)名 |
| codename | 字符串 | 是 | 權(quán)限碼 |
| is_visible | 整數(shù) | 是 | 是否可見(jiàn) |
2.3>返回?cái)?shù)據(jù)
# 添加正常返回json數(shù)據(jù) { "errno": "0", "errmsg": "菜單添加成功!" }如果有錯(cuò)誤,返回html表單
3>后端代碼
3.1>視圖
# 在myadmin/views.py中的MenuAddView視圖中添加post方法 class MenuAddView(View):"""添加菜單視圖url:/admin/menu/"""def get(self, request):form = MenuModelForm()return render(request, 'myadmin/menu/add_menu.html', context={'form': form})def post(self, request):form = MenuModelForm(request.POST)if form.is_valid():new_menu = form.save()content_type = ContentType.objects.filter(app_label='myadmin', model='menu').first()permission = Permission.objects.create(name=new_menu.name, content_type=content_type, codename=new_menu.codename)new_menu.permission = permissionnew_menu.save(update_fields=['permission'])return json_response(errmsg='菜單添加成功!')else:return render(request, 'myadmin/menu/add_menu.html', context={'form': form})4>前端代碼
4.1>js
// 創(chuàng)建static/js/myadmin/menu/add_menu.js $(() => {let $addBtn = $('button.add'); // 模態(tài)框中的添加按鈕let $form = $('#add-menu'); // 模態(tài)礦中的表單let data = {};$addBtn.click(function () {$.ajax({url: '/admin/menu/',type: 'POST',data: $form.serialize(),// dataType: "json"}).done((res) => {if (res.errno === '0') {// 添加成功,關(guān)閉模態(tài)框,并刷新菜單列表$('#modal-add').modal('hide').on('hidden.bs.modal', function (e) {$('#content').load($('.sidebar-menu li.active a').data('url'),(response, status, xhr) => {if (status !== 'success') {message.showError('服務(wù)器超時(shí),請(qǐng)重試!')}});});message.showSuccess(res.errmsg);} else {message.showError('添加菜單失敗!');// 更新模特框中的表單信息$('#modal-add .modal-content').html(res)}}).fail(() => {message.showError('服務(wù)器超時(shí),請(qǐng)重試');});});});4.2>html
<!-- 在 templates/myadmin/menu/add_menu.html 中引入js --> ... <script src="{% static 'js/myadmin/menu/add_menu.js' %}"></script>V. 刪除菜單
1>接口設(shè)計(jì)
1.1>接口說(shuō)明:
| 請(qǐng)求方法 | DELETE |
| url定義 | /admin/menu/<int:menu_id>/ |
| 參數(shù)格式 | 路徑參數(shù) |
1.2>參數(shù)說(shuō)明
| menu_id | 整數(shù) | 是 | 菜單id |
1.3>返回值
{ "errno": "0", "errmsg": "刪除菜單成功!" }后臺(tái)展示的菜單不重要,所以我們不用邏輯刪除菜單,而是采用真刪除
2>后端代碼
2.1>視圖
# 在admin/views.py中創(chuàng)建一個(gè)MenuUpdateView視圖 class MenuUpdateView(View):"""菜單管理視圖,delete刪除菜單url:/admin/menu/<int:menu_id>/"""def delete(self, request, menu_id):# 獲取到需要?jiǎng)h除的菜單menu = models.Menu.objects.only('name').filter(id=menu_id)if menu:menu = menu[0]# 判斷是是否為父菜單if menu.children.filter(is_delete=False).exists():return json_response(errno=Code.DATAERR, errmsg='父菜單不能刪除!')# 將menu模型中的permission設(shè)置為CASCADE級(jí)聯(lián)刪除, 就可以使其在被刪除的時(shí)候同時(shí)刪除當(dāng)條菜單menu.permission.delete()# menu.delete()return json_response(errmsg='刪除菜單:%s成功' % menu.name)else:return json_response(errno=Code.NODATA, errmsg='菜單不存在!')2.2>路由
# 在admin/urls.py中添加如下路由 path('menu/<int:menu_id>/', views.MenuUpdateView.as_view(), name='menu_manage'),3>前端代碼
3.1>html
<!-- 修改 templates/admin/menu/menu_list.html 在content中,添加刪除模態(tài)框 然后引入menu.js --> {% block content %} ... ...<div class="modal modal-danger fade" id="modal-delete"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button><h4 class="modal-title">警告</h4></div><div class="modal-body"><p>One fine body…</p></div><div class="modal-footer"><button type="button" class="btn btn-outline pull-left" data-dismiss="modal">取消</button><button type="button" class="btn btn-outline delete-confirm">刪除</button></div></div><!-- /.modal-content --></div><!-- /.modal-dialog --></div><!-- /.modal --> {% endblock %} {% block script %}<script src="{% static 'js/admin/menu/menu_list.js' %}"></script> {% endblock %}記得再去content_base里挖一個(gè)script的block
3.2>js
// 創(chuàng)建 static/js/admin/menu/menu_list.js $(() => {let $deleteBtns = $('button.delete'); // 刪除按鈕menuId = 0; // 被點(diǎn)擊菜單idlet $currentMenu = null; // 當(dāng)前被點(diǎn)擊菜單對(duì)象, 也就是對(duì)應(yīng)的tr標(biāo)簽(表格中的一行)$deleteBtns.click(function () {let $this = $(this);$currentMenu = $this.parent().parent();menuId = $this.parent().data('id'); // 菜單idlet menuName = $this.parent().data('name'); // 菜單名// 改變模態(tài)框的顯示內(nèi)容$('#modal-delete .modal-body p').html('確定刪除菜單: " + menuName + " ?');// 顯示 模態(tài)框$('#modal-delete').modal('show');});$('#modal-delete button.delete-confirm').click(() => {deleteMenu()});// 刪除菜單的函數(shù)function deleteMenu() {$.ajax({url: '/admin/menu/' + menuId + '/',type: 'DELETE',dataType: 'json'}).done((res) => {if (res.errno === '0') { // errno為0代表成功// 關(guān)閉模態(tài)框$('#modal-delete').modal('hide');// 刪除菜單元素$currentMenu.remove();message.showSuccess('刪除成功!');} else { // 否則就表示出現(xiàn)了問(wèn)題message.showError(res.errmsg)}}).fail(() => {message.showError('服務(wù)器超時(shí)請(qǐng)重試!')})} });因?yàn)閐elete方法會(huì)改變數(shù)據(jù)庫(kù),所以需要csrftoken,在js/myadmin/menu.js中添加如下代碼
function getCookie(name) {var cookieValue = null;if (document.cookie && document.cookie !== '') {var cookies = document.cookie.split(';');for (var i = 0; i < cookies.length; i++) {var cookie = cookies[i].trim();// 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 protectionreturn (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));}$.ajaxSetup({beforeSend: function (xhr, settings) {if (!csrfSafeMethod(settings.type) && !this.crossDomain) {xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));}}});記得前端要加上{% csrf_token %}, 關(guān)于cookie還有一點(diǎn)就是
如果你先執(zhí)行添加菜單再執(zhí)行刪除的話, 即使不加{% csrf_token %}也會(huì)執(zhí)行刪除
這是因?yàn)閳?zhí)行添加的話就會(huì)生成cookie, 已經(jīng)有了當(dāng)前頁(yè)面的cookie, 其他需要cookie的請(qǐng)求(post,put,delete)都可以執(zhí)行
VI. 編輯菜單頁(yè)面
1>接口設(shè)計(jì)
| 請(qǐng)求方法 | GET |
| url定義 | /admin/menu/<int:menu_id>/ |
| 參數(shù)格式 | 路徑參數(shù) |
| menu_id | 整數(shù) | 是 | 菜單id |
返回?cái)?shù)據(jù)
html
2>后端代碼
# 在admin/views.py中的MenuUpdateView視圖中添加一個(gè)get方法 class MenuUpdateView(View):"""菜單管理視圖url:/admin/menu/<int:menu_id>/"""def get(self, request, menu_id):# 找到需要編輯的菜單menu = models.Menu.objects.filter(id=menu_id).first()# 使用之前定義的表單form = MenuModelForm(instance=menu)return render(request, 'myadmin/menu/update_menu.html', context={'form': form})3>前端代碼
3.1>html
<!-- 修改 templates/admin/menu/menu_list.html 在content中,添加修改模態(tài)框 --> {% block content %}...<!-- update modle --><div class="modal fade" id="modal-update" role="dialog" aria-labelledby="myLargeModalLabel"><div class="modal-dialog"><div class="modal-content"></div><!-- /.modal-content --></div><!-- /.modal-dialog --></div><!-- /.modal --> {% endblock %}編輯的模態(tài)框其實(shí)和添加模態(tài)框很相似, 是可以進(jìn)行代碼復(fù)用的, 這個(gè)我們后面再說(shuō), 先完成功能
<!-- 新建 templates/admin/menu/update_menu.html -->{% load admin_customer_tags %}{% load static %}<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button><h4 class="modal-title">修改菜單</h4></div><div class="modal-body"><form class="form-horizontal" id="update-menu">{% csrf_token %}<div class="box-body">{% for field in form %}{% if field.name == 'is_visible' %}<div class="form-group"><div class="col-sm-offset-2 col-sm-10"><div class="checkbox"><label for="{{ field.id_for_label }}">{{ field }}{{ field.label }}</label></div></div></div>{% else %}<div class="form-group {% if field.errors %}has-error{% endif %}"><label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label><div class="col-sm-10">{% for error in field.errors %}<label class="control-label" for="{{ field.id_for_label }}">{{ error }}</label>{% endfor %}{% add_class field 'form-control' %}</div></div>{% endif %}{% endfor %}</div></form></div><div class="modal-footer"><button type="button" class="btn btn-default pull-left" data-dismiss="modal">取消</button><button type="button" class="btn btn-primary update">修改</button></div>3.2>js
// 修改 static/js/admin/menu/menu_list.js $(() => {let $editBtns = $('button.edit'); // 編輯按鈕let $deleteBtns = $('button.delete'); // 刪除按鈕menuId = 0; // 被點(diǎn)擊菜單id,要設(shè)置為全局變量, 不加let就行let $currentMenu = null; // 當(dāng)前被點(diǎn)擊菜單對(duì)象$deleteBtns.click(function () {...});$('#modal-delete button.delete-confirm').click(() => {...});// 刪除菜單的函數(shù)function deleteMenu() {...}// 編輯菜單$editBtns.click(function () {let $this = $(this);$currentMenu = $this.parent().parent();menuId = $this.parent().data('id');$.ajax({url: '/admin/menu/' + menuId + '/',type: 'GET'}).done((res)=>{ // res是ajax請(qǐng)求回來(lái)的數(shù)據(jù)// 改變模態(tài)框的html$('#modal-update .modal-content').html(res);// 顯示模態(tài)框$('#modal-update').modal('show')}).fail(()=>{message.showError('服務(wù)器超時(shí),請(qǐng)重試!')})}) });VII. 編輯菜單
完成了頁(yè)面, 接下來(lái)就是點(diǎn)擊修改, 再發(fā)送一個(gè)ajax回去, 我們需要再寫(xiě)一個(gè)后臺(tái)接收編輯數(shù)據(jù)
1>業(yè)務(wù)流程分析
- 接收表單參數(shù)
- 校驗(yàn)表單參數(shù)
- 校驗(yàn)成功, 保存菜單,判斷改動(dòng)字段是否影響了權(quán)限,如果有影響,修改權(quán)限,返回json信息
- 校驗(yàn)失敗,返回包含錯(cuò)誤信息的html
2>接口設(shè)計(jì)
| 請(qǐng)求方法 | PUT |
| url定義 | /admin/menu/<int:menu_id> |
| 參數(shù)格式 | 路徑參數(shù)+表單參數(shù) |
| menu_id | 整數(shù) | 是 | 菜單id |
| name | 字符串 | 是 | 菜單名 |
| url | 字符串 | 否 | 當(dāng)前文章頁(yè)數(shù) |
| order | 整數(shù) | 是 | 排序 |
| parent | 整數(shù) | 否 | 父菜單id |
| icon | 字符串 | 是 | 渲染圖標(biāo)類(lèi)名 |
| codename | 字符串 | 是 | 權(quán)限碼 |
| is_visible | 整數(shù) | 是 | 是否可見(jiàn) |
返回?cái)?shù)據(jù)
# 添加正常返回json數(shù)據(jù) { "errno": "0", "errmsg": "菜單修改成功!" }如果有錯(cuò)誤,返回html表單
3>后端代碼
3.1>視圖
# 在admin/views.py中的MenuUpdateView視圖中添加一個(gè)put方法 class MenuUpdateView(View):"""菜單管理視圖url:/admin/menu/<int:menu_id>/""" ...def put(self, request, menu_id):# 獲取到需要修改的菜單menu = models.Menu.objects.filter(id=menu_id).first()if not menu:return json_response(errno=Code.NODATA, errmsg='菜單不存在!')# 獲取put請(qǐng)求的數(shù)據(jù), request.body就是前端表單中所有的輸入put_data = QueryDict(request.body)# QueryDict這個(gè)方法會(huì)將body轉(zhuǎn)換成一個(gè)類(lèi)字典對(duì)象form = MenuModelForm(put_data, instance=menu)if form.is_valid():obj = form.save() # 拿到一個(gè)新的menu對(duì)象,命名為obj# 檢查修改了的字段是否和權(quán)限有關(guān)flag = Falseif 'name' in form.changed_data:# changed_data會(huì)返回一個(gè)列表,包含當(dāng)前修改了的字段# 將已有的name更新為最新的obj.permission.name = obj.nameflag = Trueif 'codename' in form.changed_data:obj.permission.codename = obj.nameflag = Trueif flag:obj.permission.save()return json_response(errmsg='菜單修改成功!')else:return render(request, 'admin/menu/update_menu.html', context={'form': form}) ...4>前端代碼
4.1>html
<!-- 在 templates/myadmin/menu/update_menu.html 中引入update_menu.js --> ... <script src="{% static 'js/myadmin/menu/update_menu.js' %}"></script>4.2>js
// 這里的代碼跟添加的js寫(xiě)法很相似,又是一個(gè)代碼復(fù)用點(diǎn) $(()=>{let $updateBtn = $('#modal-update button.update');let $form = $('#update-menu');$updateBtn.click(function () {$.ajax({url: '/admin/menu/' + menuId + '/',type: 'PUT',data: $form.serialize(),// dataType: "json"}).done((res) => {if (res.errno === '0') {// 關(guān)閉模態(tài)框$('#modal-update').modal('hide').on('hidden.bs.modal', function (e) {$('#content').load($('.sidebar-menu li.active a').data('url'),(response, status, xhr) => {if (status !== 'success') {message.showError('服務(wù)器超時(shí),請(qǐng)重試!')}});});message.showSuccess(res.errmsg);} else {message.showError('修改菜單失敗!');// 修改模態(tài)框的內(nèi)容為, 后端返回的帶有錯(cuò)誤信息的表單$('#modal-update .modal-content').html(res)}}).fail(() => {message.showError('服務(wù)器超時(shí),請(qǐng)重試');});}); });VIII. 整合后臺(tái)首頁(yè)面菜單加載
我們來(lái)填之前菜單列表的坑, 之前我們使用list寫(xiě)的硬編碼, 我們來(lái)到數(shù)據(jù)庫(kù)中拿可用的菜單
1>后端代碼
1.1>視圖
class IndexView(LoginRequiredMixin, View):"""后臺(tái)首頁(yè)視圖"""def get(self, request):objs = models.Menu.objects.only('name', 'url', 'icon', 'permission__codename','permission__content_type__app_label').select_related('permission__content_type').filter(is_delete=False, is_visible=True, parent=None)has_permissions = request.user.get_all_permissions()menus = []for menu in objs:if '%s.%s' % (menu.permission.content_type.app_label, menu.permission.codename) in has_permissions:temp = {'name': menu.name,'icon': menu.icon}children = menu.children.filter(is_delete=False, is_visible=True)if children:temp['children'] = []for child in children:if '%s.%s' % (child.permission.content_type.app_label, child.permission.codename) in has_permissions:temp['children'].append({'name': child.name,'url': child.url})else:if not menu.url:continuetemp['url'] = menu.urlmenus.append(temp)print(menus)return render(request, 'admin/index.html', context={'menus': menus})再運(yùn)行之前, 一定要先添加一個(gè)菜單管理, 這樣才可以看到我們的菜單列表效果
但是有一個(gè)bug, 當(dāng)我們添加了一個(gè)新菜單之后, 雖然可以展示出來(lái), 但是點(diǎn)擊訪問(wèn)時(shí)就會(huì)提示我們沒(méi)有定義路由,
這個(gè)可以設(shè)計(jì)一個(gè)自定義標(biāo)簽, 使用異常處理的方式, 若使用的使錯(cuò)誤的路由, 就展示wait界面, 代碼如下:
# 在myadmin/templatetags/admin_customer_tags.py中添加如下過(guò)濾器 @register.simple_tag() def my_url(pattern, *args):try:url = reverse(pattern, *args)except Exception as e:url = reverse('myadmin:wait')return url然后在前端代碼中引用:
<!-- 在templates/myadmin/index.html中修改如下代碼 --><!-- Sidebar Menu --><ul class="sidebar-menu" data-widget="tree">{% for menu in menus %}{% if 'children' in menu %}<li class="treeview"><a href="#"><i class="fa {{ menu.icon }}"></i> <span>{{ menu.name }}</span><span class="pull-right-container"><i class="fa fa-angle-left pull-right"></i></span></a><ul class="treeview-menu">{% for child in menu.children %}<li><a href="#" data-url="{% my_url child.url %}"><!-- 使用my_url過(guò)濾器 -->{{ child.name }}</a></li>{% endfor %}</ul></li>{% else %}<li><a href="#" data-url="{% my_url menu.url %}"><!-- 使用my_url過(guò)濾器 --><i class="fa {{ menu.icon }}"></i><span>{{ menu.name }}</span></a></li>{% endif %}{% endfor %}</ul>總結(jié)
以上是生活随笔為你收集整理的[django项目] 后台菜单管理功能的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 超融合架构hci之路坦力nutanix之
- 下一篇: stm32f103can总线过滤器配置