353 lines
12 KiB
Python
353 lines
12 KiB
Python
from django.core.urlresolvers import reverse
|
|
from django.utils.safestring import mark_safe
|
|
from django.utils.text import capfirst
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from admin_tools.utils import AppListElementMixin
|
|
|
|
|
|
class MenuItem(object):
|
|
"""
|
|
This is the base class for custom menu items.
|
|
A menu item can have the following properties:
|
|
|
|
``title``
|
|
String that contains the menu item title, make sure you use the
|
|
django gettext functions if your application is multilingual.
|
|
Default value: 'Untitled menu item'.
|
|
|
|
``url``
|
|
String that contains the menu item URL.
|
|
Default value: '#'.
|
|
|
|
``css_classes``
|
|
A list of css classes to be added to the menu item ``li`` class
|
|
attribute. Default value: [].
|
|
|
|
``accesskey``
|
|
The menu item accesskey. Default value: None.
|
|
|
|
``description``
|
|
An optional string that will be used as the ``title`` attribute of
|
|
the menu-item ``a`` tag. Default value: None.
|
|
|
|
``enabled``
|
|
Boolean that determines whether the menu item is enabled or not.
|
|
Disabled items are displayed but are not clickable.
|
|
Default value: True.
|
|
|
|
``template``
|
|
The template to use to render the menu item.
|
|
Default value: 'admin_tools/menu/item.html'.
|
|
|
|
``children``
|
|
A list of children menu items. All children items must be instances of
|
|
the ``MenuItem`` class.
|
|
"""
|
|
|
|
title = 'Untitled menu item'
|
|
url = '#'
|
|
css_classes = None
|
|
accesskey = None
|
|
description = None
|
|
enabled = True
|
|
template = 'admin_tools/menu/item.html'
|
|
children = None
|
|
|
|
def __init__(self, title=None, url=None, **kwargs):
|
|
|
|
if title is not None:
|
|
self.title = title
|
|
|
|
if url is not None:
|
|
self.url = url
|
|
|
|
for key in kwargs:
|
|
if hasattr(self.__class__, key):
|
|
setattr(self, key, kwargs[key])
|
|
self.children = self.children or []
|
|
self.css_classes = self.css_classes or []
|
|
|
|
def init_with_context(self, context):
|
|
"""
|
|
Like for menus, menu items have a ``init_with_context`` method that is
|
|
called with a ``django.template.RequestContext`` instance as unique
|
|
argument.
|
|
This gives you enough flexibility to build complex items, for example,
|
|
let's build a "history" menu item, that will list the last ten visited
|
|
pages::
|
|
|
|
from admin_tools.menu.items import MenuItem
|
|
|
|
class HistoryMenuItem(MenuItem):
|
|
title = 'History'
|
|
|
|
def init_with_context(self, context):
|
|
request = context['request']
|
|
# we use sessions to store the visited pages stack
|
|
history = request.session.get('history', [])
|
|
for item in history:
|
|
self.children.append(MenuItem(
|
|
title=item['title'],
|
|
url=item['url']
|
|
))
|
|
# add the current page to the history
|
|
history.insert(0, {
|
|
'title': context['title'],
|
|
'url': request.META['PATH_INFO']
|
|
})
|
|
if len(history) > 10:
|
|
history = history[:10]
|
|
request.session['history'] = history
|
|
|
|
Here's a screenshot of our history item:
|
|
|
|
.. image:: images/history_menu_item.png
|
|
"""
|
|
pass
|
|
|
|
def is_selected(self, request):
|
|
"""
|
|
Helper method that returns ``True`` if the menu item is active.
|
|
A menu item is considered as active if it's URL or one of its
|
|
descendants URL is equals to the current URL.
|
|
"""
|
|
current_url = request.get_full_path()
|
|
return self.url == current_url or \
|
|
len([c for c in self.children if c.is_selected(request)]) > 0
|
|
|
|
def is_empty(self):
|
|
"""
|
|
Helper method that returns ``True`` if the menu item is empty.
|
|
This method always returns ``False`` for basic items, but can return
|
|
``True`` if the item is an AppList.
|
|
"""
|
|
return False
|
|
|
|
|
|
class AppList(MenuItem, AppListElementMixin):
|
|
"""
|
|
A menu item that lists installed apps an their models.
|
|
In addition to the parent :class:`~admin_tools.menu.items.MenuItem`
|
|
properties, the ``AppList`` has two extra properties:
|
|
|
|
``models``
|
|
A list of models to include, only models whose name (e.g.
|
|
"blog.comments.Comment") match one of the strings (e.g. "blog.*")
|
|
in the models list will appear in the menu item.
|
|
|
|
``exclude``
|
|
A list of models to exclude, if a model name (e.g.
|
|
"blog.comments.Comment") match an element of this list (e.g.
|
|
"blog.comments.*") it won't appear in the menu item.
|
|
|
|
|
|
If no models/exclude list is provided, **all apps** are shown.
|
|
|
|
Here's a small example of building an app list menu item::
|
|
|
|
from admin_tools.menu import items, Menu
|
|
|
|
class MyMenu(Menu):
|
|
def __init__(self, **kwargs):
|
|
super(MyMenu, self).__init__(**kwargs)
|
|
self.children.append(items.AppList(
|
|
title='Applications',
|
|
exclude_list=('django.contrib',)
|
|
)
|
|
|
|
The screenshot of what this code produces:
|
|
|
|
.. image:: images/applist_menu_item.png
|
|
|
|
.. note::
|
|
|
|
Note that this menu takes into account user permissions, as a
|
|
consequence, if a user has no rights to change or add a ``Group`` for
|
|
example, the ``django.contrib.auth.Group model`` child item won't be
|
|
displayed in the menu.
|
|
"""
|
|
|
|
def __init__(self, title=None, **kwargs):
|
|
"""
|
|
``AppListMenuItem`` constructor.
|
|
"""
|
|
self.models = list(kwargs.pop('models', []))
|
|
self.exclude = list(kwargs.pop('exclude', []))
|
|
self.include_list = kwargs.pop('include_list', []) # deprecated
|
|
self.exclude_list = kwargs.pop('exclude_list', []) # deprecated
|
|
super(AppList, self).__init__(title, **kwargs)
|
|
|
|
|
|
def init_with_context(self, context):
|
|
"""
|
|
Please refer to the :meth:`~admin_tools.menu.items.MenuItem.init_with_context`
|
|
documentation from :class:`~admin_tools.menu.items.MenuItem` class.
|
|
"""
|
|
items = self._visible_models(context['request'])
|
|
apps = {}
|
|
for model, perms in items:
|
|
if not perms['change']:
|
|
continue
|
|
app_label = model._meta.app_label
|
|
if app_label not in apps:
|
|
apps[app_label] = {
|
|
'title': capfirst(app_label.title()),
|
|
'url': self._get_admin_app_list_url(model, context),
|
|
'models': []
|
|
}
|
|
apps[app_label]['models'].append({
|
|
'title': capfirst(model._meta.verbose_name_plural),
|
|
'url': self._get_admin_change_url(model, context)
|
|
})
|
|
|
|
for app in sorted(apps.keys()):
|
|
app_dict = apps[app]
|
|
item = MenuItem(title=app_dict['title'], url=app_dict['url'])
|
|
# sort model list alphabetically
|
|
apps[app]['models'].sort(key=lambda x: x['title'])
|
|
for model_dict in apps[app]['models']:
|
|
item.children.append(MenuItem(**model_dict))
|
|
self.children.append(item)
|
|
|
|
def is_empty(self):
|
|
"""
|
|
Helper method that returns ``True`` if the applist menu item has no
|
|
children.
|
|
|
|
>>> from admin_tools.menu.items import MenuItem, AppList
|
|
>>> item = AppList(title='My menu item')
|
|
>>> item.is_empty()
|
|
True
|
|
>>> item.children.append(MenuItem(title='foo'))
|
|
>>> item.is_empty()
|
|
False
|
|
>>> item.children = []
|
|
>>> item.is_empty()
|
|
True
|
|
"""
|
|
return len(self.children) == 0
|
|
|
|
|
|
class ModelList(MenuItem, AppListElementMixin):
|
|
"""
|
|
A menu item that lists a set of models.
|
|
In addition to the parent :class:`~admin_tools.menu.items.MenuItem`
|
|
properties, the ``ModelList`` has two extra properties:
|
|
|
|
``models``
|
|
A list of models to include, only models whose name (e.g.
|
|
"blog.comments.Comment") match one of the strings (e.g. "blog.*")
|
|
in the include list will appear in the dashboard module.
|
|
|
|
``exclude``
|
|
A list of models to exclude, if a model name (e.g.
|
|
"blog.comments.Comment" match an element of this list (e.g.
|
|
"blog.comments.*") it won't appear in the dashboard module.
|
|
|
|
Here's a small example of building a model list menu item::
|
|
|
|
from admin_tools.menu import items, Menu
|
|
|
|
class MyMenu(Menu):
|
|
def __init__(self, **kwargs):
|
|
super(MyMenu, self).__init__(**kwargs)
|
|
self.children += [
|
|
items.ModelList('Authentication', ['django.contrib.auth.*',])
|
|
]
|
|
|
|
.. note::
|
|
|
|
Note that this menu takes into account user permissions, as a
|
|
consequence, if a user has no rights to change or add a ``Group`` for
|
|
example, the ``django.contrib.auth.Group model`` item won't be
|
|
displayed in the menu.
|
|
"""
|
|
|
|
def __init__(self, title=None, models=None, exclude=None, **kwargs):
|
|
"""
|
|
``ModelList`` constructor.
|
|
"""
|
|
self.models = list(models or [])
|
|
self.exclude = list(exclude or [])
|
|
self.include_list = kwargs.pop('include_list', []) # deprecated
|
|
self.exclude_list = kwargs.pop('exclude_list', []) # deprecated
|
|
|
|
super(ModelList, self).__init__(title, **kwargs)
|
|
|
|
def init_with_context(self, context):
|
|
"""
|
|
Please refer to the :meth:`~admin_tools.menu.items.MenuItem.init_with_context`
|
|
documentation from :class:`~admin_tools.menu.items.MenuItem` class.
|
|
"""
|
|
items = self._visible_models(context['request'])
|
|
for model, perms in items:
|
|
if not perms['change']:
|
|
continue
|
|
title = capfirst(model._meta.verbose_name_plural)
|
|
url = self._get_admin_change_url(model, context)
|
|
item = MenuItem(title=title, url=url)
|
|
self.children.append(item)
|
|
|
|
def is_empty(self):
|
|
"""
|
|
Helper method that returns ``True`` if the modellist menu item has no
|
|
children.
|
|
|
|
>>> from admin_tools.menu.items import MenuItem, ModelList
|
|
>>> item = ModelList(title='My menu item')
|
|
>>> item.is_empty()
|
|
True
|
|
>>> item.children.append(MenuItem(title='foo'))
|
|
>>> item.is_empty()
|
|
False
|
|
>>> item.children = []
|
|
>>> item.is_empty()
|
|
True
|
|
"""
|
|
return len(self.children) == 0
|
|
|
|
|
|
class Bookmarks(MenuItem, AppListElementMixin):
|
|
"""
|
|
A menu item that lists pages bookmarked by the user. This menu item also
|
|
adds an extra button to the menu that allows the user to bookmark or
|
|
un-bookmark the current page.
|
|
|
|
Here's a small example of adding a bookmark menu item::
|
|
|
|
from admin_tools.menu import items, Menu
|
|
|
|
class MyMenu(Menu):
|
|
def __init__(self, **kwargs):
|
|
super(MyMenu, self).__init__(**kwargs)
|
|
self.children.append(items.Bookmarks('My bookmarks'))
|
|
|
|
"""
|
|
title = _('Bookmarks')
|
|
|
|
def __init__(self, title=None, **kwargs):
|
|
super(Bookmarks, self).__init__(title, **kwargs)
|
|
if 'bookmark' not in self.css_classes:
|
|
self.css_classes.append('bookmark')
|
|
|
|
def init_with_context(self, context):
|
|
"""
|
|
Please refer to the :meth:`~admin_tools.menu.items.MenuItem.init_with_context`
|
|
documentation from :class:`~admin_tools.menu.items.MenuItem` class.
|
|
"""
|
|
from admin_tools.menu.models import Bookmark
|
|
|
|
for b in Bookmark.objects.filter(user=context['request'].user):
|
|
self.children.append(MenuItem(mark_safe(b.title), b.url))
|
|
|
|
if not len(self.children):
|
|
self.enabled = False
|
|
|
|
def is_selected(self, request):
|
|
"""
|
|
A bookmark menu item is never considered as active, the real item is.
|
|
"""
|
|
return False
|
|
|
|
|