Commit ac8392b8 authored by Serge S. Koval's avatar Serge S. Koval

Fixed #416. More flexible menu system

parent 4466d365
...@@ -84,6 +84,10 @@ if __name__ == '__main__': ...@@ -84,6 +84,10 @@ if __name__ == '__main__':
admin.add_link(NotAuthenticatedMenuLink(name='Login', admin.add_link(NotAuthenticatedMenuLink(name='Login',
endpoint='login_view')) endpoint='login_view'))
# Add links with categories
admin.add_link(MenuLink(name='Google', category='Links', url='http://www.google.com/'))
admin.add_link(MenuLink(name='Mozilla', category='Links', url='http://mozilla.org/'))
# Add logout link by endpoint # Add logout link by endpoint
admin.add_link(AuthenticatedMenuLink(name='Logout', admin.add_link(AuthenticatedMenuLink(name='Logout',
endpoint='logout_view')) endpoint='logout_view'))
......
from functools import wraps from functools import wraps
from flask import Blueprint, render_template, url_for, abort, g from flask import Blueprint, render_template, abort, g
from flask.ext.admin import babel from flask.ext.admin import babel
from flask.ext.admin._compat import with_metaclass from flask.ext.admin._compat import with_metaclass
from flask.ext.admin import helpers as h from flask.ext.admin import helpers as h
# For compatibility reasons import MenuLink
from flask.ext.admin.menu import MenuCategory, MenuView, MenuLink
def expose(url='/', methods=('GET',)): def expose(url='/', methods=('GET',)):
""" """
...@@ -343,79 +346,6 @@ class AdminIndexView(BaseView): ...@@ -343,79 +346,6 @@ class AdminIndexView(BaseView):
return self.render(self._template) return self.render(self._template)
class MenuItem(object):
"""
Simple menu tree hierarchy.
"""
def __init__(self, name, view=None):
self.name = name
self._view = view
self._children = []
self._children_urls = set()
self._cached_url = None
self.url = None
if view is not None:
self.url = view.url
def add_child(self, view):
self._children.append(view)
self._children_urls.add(view.url)
def get_url(self):
if self._view is None:
return None
if self._cached_url:
return self._cached_url
self._cached_url = url_for('%s.%s' % (self._view.endpoint, self._view._default_view))
return self._cached_url
def is_active(self, view):
if view == self._view:
return True
return view.url in self._children_urls
def is_visible(self):
if self._view is None:
return False
return self._view.is_visible()
def is_accessible(self):
if self._view is None:
return False
return self._view.is_accessible()
def is_category(self):
return self._view is None
def get_children(self):
return [c for c in self._children if c.is_accessible() and c.is_visible()]
class MenuLink(object):
"""
Additional menu links.
"""
def __init__(self, name, url=None, endpoint=None):
self.name = name
self.url = url
self.endpoint = endpoint
def get_url(self):
return self.url or url_for(self.endpoint)
def is_visible(self):
return True
def is_accessible(self):
return True
class Admin(object): class Admin(object):
""" """
Collection of the admin views. Also manages menu structure. Collection of the admin views. Also manages menu structure.
...@@ -492,7 +422,8 @@ class Admin(object): ...@@ -492,7 +422,8 @@ class Admin(object):
# If app was provided in constructor, register view with Flask app # If app was provided in constructor, register view with Flask app
if self.app is not None: if self.app is not None:
self.app.register_blueprint(view.create_blueprint(self)) self.app.register_blueprint(view.create_blueprint(self))
self._add_view_to_menu(view)
self._add_view_to_menu(view)
def add_link(self, link): def add_link(self, link):
""" """
...@@ -501,26 +432,33 @@ class Admin(object): ...@@ -501,26 +432,33 @@ class Admin(object):
:param link: :param link:
Link to add. Link to add.
""" """
self._menu_links.append(link) if link.category:
self._add_menu_item(link, link.category)
else:
self._menu_links.append(link)
def _add_view_to_menu(self, view): def _add_menu_item(self, menu_item, target_category):
""" """
Add a view to the menu tree Add a view to the menu tree
:param view: :param view:
View to add View to add
""" """
if view.category: if target_category:
category = self._menu_categories.get(view.category) category = self._menu_categories.get(target_category)
if category is None: if category is None:
category = MenuItem(view.category) category = MenuCategory(target_category)
self._menu_categories[view.category] = category self._menu_categories[target_category] = category
self._menu.append(category) self._menu.append(category)
category.add_child(MenuItem(view.name, view)) category.add_child(menu_item)
else: else:
self._menu.append(MenuItem(view.name, view)) self._menu.append(menu_item)
def _add_view_to_menu(self, view):
self._add_menu_item(MenuView(view.name, view), view.category)
def init_app(self, app): def init_app(self, app):
""" """
...@@ -536,7 +474,6 @@ class Admin(object): ...@@ -536,7 +474,6 @@ class Admin(object):
# Register views # Register views
for view in self._views: for view in self._views:
app.register_blueprint(view.create_blueprint(self)) app.register_blueprint(view.create_blueprint(self))
self._add_view_to_menu(view)
def _init_extension(self): def _init_extension(self):
if not hasattr(self.app, 'extensions'): if not hasattr(self.app, 'extensions'):
......
from flask import url_for
class BaseMenu(object):
"""
Base menu item
"""
def __init__(self, name):
self.name = name
self._children = []
def add_child(self, menu):
self._children.append(menu)
def get_url(self):
raise NotImplemented()
def is_category(self):
return False
def is_active(self, view):
for c in self._children:
if c.is_active(view):
return True
return False
def is_visible(self):
return True
def is_accessible(self):
return True
def get_children(self):
return [c for c in self._children if c.is_accessible() and c.is_visible()]
class MenuCategory(BaseMenu):
"""
Menu category item.
"""
def get_url(self):
return None
def is_category(self):
return True
def is_visible(self):
for c in self._children:
if c.is_visible():
return True
return False
def is_accessible(self):
for c in self._children:
if c.is_accessible():
return True
return False
class MenuView(BaseMenu):
"""
Admin view menu item
"""
def __init__(self, name, view=None):
super(MenuView, self).__init__(name)
self._view = view
self._cached_url = None
def get_url(self):
if self._view is None:
return None
if self._cached_url:
return self._cached_url
self._cached_url = url_for('%s.%s' % (self._view.endpoint, self._view._default_view))
return self._cached_url
def is_active(self, view):
if view == self._view:
return True
return super(MenuView, self).is_active(view)
def is_visible(self):
if self._view is None:
return False
return self._view.is_visible()
def is_accessible(self):
if self._view is None:
return False
return self._view.is_accessible()
class MenuLink(BaseMenu):
"""
Link item
"""
def __init__(self, name, url=None, endpoint=None, category=None):
super(MenuLink, self).__init__(name)
self.category = category
self.url = url
self.endpoint = endpoint
def get_url(self):
return self.url or url_for(self.endpoint)
...@@ -262,11 +262,16 @@ def test_submenu(): ...@@ -262,11 +262,16 @@ def test_submenu():
eq_(admin._menu[1].name, 'Test') eq_(admin._menu[1].name, 'Test')
eq_(len(admin._menu[1]._children), 2) eq_(len(admin._menu[1]._children), 2)
# Categories don't have URLs and they're not accessible # Categories don't have URLs
eq_(admin._menu[1].get_url(), None) eq_(admin._menu[1].get_url(), None)
eq_(admin._menu[1].is_accessible(), False)
eq_(len(admin._menu[1].get_children()), 1) # Categories are only accessible if there is at least one accessible child
eq_(admin._menu[1].is_accessible(), True)
children = admin._menu[1].get_children()
eq_(len(children), 1)
ok_(children[0].is_accessible())
def test_delayed_init(): def test_delayed_init():
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment