Skip to content

Commit 6b0a8a0

Browse files
Merge pull request #2696 from samialfattani/feature/menu-divider
Ability to add menu divider
2 parents 67c5702 + 0eb8920 commit 6b0a8a0

File tree

5 files changed

+101
-35
lines changed

5 files changed

+101
-35
lines changed

doc/introduction.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,10 +331,14 @@ To nest related views within these drop-downs, use the `add_sub_category` method
331331

332332
admin.add_sub_category(name="Links", parent_name="Team")
333333

334-
And to add arbitrary hyperlinks to the menu::
334+
To add arbitrary hyperlinks to the menu::
335335

336336
admin.add_link(MenuLink(name='Home Page', url='/', category='Links'))
337337

338+
And to add a menu divider to separate menu items in the menu::
339+
340+
admin.add_menu_item(MenuDivider(), target_category='Links')
341+
338342

339343
Adding Your Own Views
340344
=====================

examples/bootstrap4/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from flask import Flask
55
from flask_admin import Admin
66
from flask_admin.contrib.sqla import ModelView
7+
from flask_admin.menu import MenuDivider
78
from flask_admin.menu import MenuLink
89
from flask_admin.theme import Bootstrap4Theme
910
from flask_sqlalchemy import SQLAlchemy
@@ -208,6 +209,7 @@ def build_sample_db():
208209
menu_class_name="text-warning",
209210
)
210211
)
212+
admin.add_menu_item(MenuDivider(), target_category="Menu")
211213
admin.add_view(CustomView(Page, db.session, category="Menu"))
212214
admin.add_view(
213215
CustomView(
@@ -254,6 +256,7 @@ def build_sample_db():
254256
class_name="text-success",
255257
)
256258
)
259+
admin.add_menu_item(MenuDivider(), target_category="Links")
257260
admin.add_link(
258261
MenuLink(name="External link", url="http://www.example.com/", category="Links")
259262
)

flask_admin/menu.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,23 @@ class SubMenuCategory(MenuCategory):
181181
def __init__(self, *args: str, **kwargs: t.Any) -> None:
182182
super().__init__(*args, **kwargs)
183183
self.class_name += " dropdown-submenu dropright"
184+
185+
186+
class MenuDivider(MenuLink):
187+
"""
188+
Bootstrap Menu divider item
189+
Usage:
190+
admin = Admin(app, ...)
191+
admin.add_menu_item(MenuDivider(), target_category='Category1')
192+
"""
193+
194+
def __init__(self, class_name=""):
195+
class_name = "dropdown-divider" + (" " + class_name if class_name else "")
196+
super().__init__("divider", class_name=class_name)
197+
198+
def get_url(self):
199+
return None
200+
201+
def is_visible(self):
202+
# Return True/False depending on your use-case
203+
return True

flask_admin/templates/bootstrap4/admin/layout.html

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,42 +22,45 @@
2222
{% set children = item.get_children() %}
2323
{%- if children %}
2424
{% set class_name = item.get_class_name() or '' %}
25-
{%- if item.is_active(admin_view) %}
26-
<li class="active dropdown">
27-
{% else -%}
28-
<li class="dropdown">
29-
{%- endif %}
30-
<a class="dropdown-toggle {% if is_main_nav %}nav-link{% else %}dropdown-item{% endif %}" data-toggle="dropdown" href="javascript:void(0)">
31-
<!-- show icon -->
32-
{% if item.class_name %}<span class="{{ item.class_name }}"></span> {% endif %}
33-
{{ menu_icon(item) }}{{ item.name }}
34-
{%- if 'dropdown-submenu' in class_name -%}
35-
<i class="glyphicon glyphicon-chevron-right small"></i>
36-
{%- else -%}
37-
<i class="glyphicon glyphicon-chevron-down small"></i>
38-
{%- endif -%}
39-
</a>
40-
<ul class="dropdown-menu">
41-
{%- for child in children -%}
42-
{%- if child.is_category() -%}
43-
{{ menu(menu_root=[child]) }}
44-
{% else %}
45-
{% set class_name = child.get_class_name() %}
46-
<li>
47-
{%- if child.is_active(admin_view) %}
48-
<a class="dropdown-item active {{ class_name }} " href="{{ child.get_url() }}"{% if child.target %}
49-
target="{{ child.target }}"{% endif %}>
50-
{{ menu_icon(child) }}{{ child.name }}</a>
25+
26+
<!-- just enhance the readability -->
27+
<li class="{% if item.is_active(admin_view) %}active {% endif %}dropdown">
28+
29+
<a class="dropdown-toggle {% if is_main_nav %}nav-link{% else %}dropdown-item{% endif %}" data-toggle="dropdown" href="javascript:void(0)">
30+
<!-- show icon -->
31+
{% if item.class_name %}<span class="{{ item.class_name }}"></span> {% endif %}
32+
{{ menu_icon(item) }}{{ item.name }}
33+
{%- if 'dropdown-submenu' in class_name -%}
34+
<i class="glyphicon glyphicon-chevron-right small"></i>
35+
{%- else -%}
36+
<i class="glyphicon glyphicon-chevron-down small"></i>
37+
{%- endif -%}
38+
</a>
39+
<ul class="dropdown-menu">
40+
{%- for child in children -%}
41+
{%- if child.is_category() -%}
42+
{{ menu(menu_root=[child]) }}
5143
{% else %}
52-
<a class="dropdown-item {{ class_name }}" href="{{ child.get_url() }}"{% if child.target %}
53-
target="{{ child.target }}"{% endif %}>
54-
{{ menu_icon(child) }}{{ child.name }}</a>
44+
{% set class_name = child.get_class_name() %}
45+
{% if 'dropdown-divider' in class_name %}
46+
<li class="{{ class_name }}"></li>
47+
{% else %}
48+
<li>
49+
{%- if child.is_active(admin_view) %}
50+
<a class="dropdown-item active {{ class_name }} " href="{{ child.get_url() }}"{% if child.target %}
51+
target="{{ child.target }}"{% endif %}>
52+
{{ menu_icon(child) }}{{ child.name }}</a>
53+
{% else %}
54+
<a class="dropdown-item {{ class_name }}" href="{{ child.get_url() }}"{% if child.target %}
55+
target="{{ child.target }}"{% endif %}>
56+
{{ menu_icon(child) }}{{ child.name }}</a>
57+
{%- endif %}
58+
</li>
59+
{%- endif %}
5560
{%- endif %}
56-
</li>
57-
{%- endif %}
58-
{%- endfor %}
59-
</ul>
60-
</li>
61+
{%- endfor %}
62+
</ul>
63+
</li>
6164
{% endif %}
6265
{%- else %}
6366
{%- if item.is_accessible() and item.is_visible() -%}

flask_admin/tests/test_base.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from flask_admin import base
1313
from flask_admin import BaseView
1414
from flask_admin import expose
15+
from flask_admin.menu import MenuDivider
16+
from flask_admin.menu import MenuLink
1517

1618

1719
@pytest.fixture
@@ -355,6 +357,40 @@ def test_submenu(admin):
355357
assert children[0].is_accessible()
356358

357359

360+
def test_menu_divider(app, admin):
361+
# admin.add_view(MockView(name="Test 1", category="Test", endpoint="test1"))
362+
# admin.add_view(MockView(name="Test 2", category="Test", endpoint="test2"))
363+
admin.add_link(
364+
MenuLink(name="link1", url="http://www.example.com/", category="Links")
365+
)
366+
admin.add_link(
367+
MenuLink(name="link2", url="http://www.example.com/", category="Links")
368+
)
369+
admin.add_menu_item(MenuDivider(), target_category="Links")
370+
admin.add_link(
371+
MenuLink(name="link3", url="http://www.example.com/", category="Links")
372+
)
373+
374+
assert admin.menu()[1].name == "Links"
375+
assert len(admin._menu) == 2
376+
assert admin._menu[1].name == "Links"
377+
assert len(admin._menu[1]._children) == 4
378+
379+
client = app.test_client()
380+
381+
rv = client.get("/admin/")
382+
assert rv.status_code == 200
383+
384+
data = rv.data.decode("utf-8")
385+
pos1 = data.find("link1")
386+
pos2 = data.find("link2")
387+
pos3 = data.find('<li class="dropdown-divider"></li>')
388+
pos4 = data.find("link3")
389+
assert pos2 > pos1
390+
assert pos3 > pos2
391+
assert pos4 > pos3
392+
393+
358394
def test_delayed_init(app, admin):
359395
admin.add_view(MockView())
360396

0 commit comments

Comments
 (0)