Merge pull request #535 from gasman/feature/menu-api

Refactor admin menu API to allow menu items to have bundled JS
pull/603/head
Karl Hobley 2014-09-05 11:28:43 +01:00
commit dbd0834624
17 zmienionych plików z 213 dodań i 100 usunięć

Wyświetl plik

@ -529,10 +529,19 @@ The available hooks are:
url(r'^how_did_you_almost_know_my_name/$', admin_view, name='frank' ),
]
.. _construct_main_menu:
.. _register_admin_menu_item:
``construct_main_menu``
Add, remove, or alter ``MenuItem`` objects from the Wagtail admin menu. The callable passed to this hook must take a ``request`` object and a list of ``menu_items``; it must return a list of menu items. New items can be constructed from the ``MenuItem`` class by passing in: a ``label`` which will be the text in the menu item, the URL of the admin page you want the menu item to link to (usually by calling ``reverse()`` on the admin view you've set up), CSS class ``name`` applied to the wrapping ``<li>`` of the menu item as ``"menu-{name}"``, CSS ``classnames`` which are used to give the link an icon, and an ``order`` integer which determine's the item's place in the menu.
``register_admin_menu_item``
.. versionadded:: 0.6
Add an item to the Wagtail admin menu. The callable passed to this hook must return an instance of ``wagtail.wagtailadmin.menu.MenuItem``. New items can be constructed from the ``MenuItem`` class by passing in a ``label`` which will be the text in the menu item, and the URL of the admin page you want the menu item to link to (usually by calling ``reverse()`` on the admin view you've set up). Additionally, the following keyword arguments are accepted:
:name: an internal name used to identify the menu item; defaults to the slugified form of the label. Also applied as a CSS class to the wrapping ``<li>``, as ``"menu-{name}"``.
:classnames: additional classnames applied to the link, used to give it an icon
:attrs: additional HTML attributes to apply to the link
:order: an integer which determines the item's position in the menu
``MenuItem`` can be subclassed to customise the HTML output, specify Javascript files required by the menu item, or conditionally show or hide the item for specific requests (for example, to apply permission checks); see the source code (``wagtail/wagtailadmin/menu.py``) for details.
.. code-block:: python
@ -541,11 +550,23 @@ The available hooks are:
from wagtail.wagtailcore import hooks
from wagtail.wagtailadmin.menu import MenuItem
@hooks.register('register_admin_menu_item')
def register_frank_menu_item():
return MenuItem('Frank', reverse('frank'), classnames='icon icon-folder-inverse', order=10000)
.. _construct_main_menu:
``construct_main_menu``
Called just before the Wagtail admin menu is output, to allow the list of menu items to be modified. The callable passed to this hook will receive a ``request`` object and a list of ``menu_items``, and should modify ``menu_items`` in-place as required. Adding menu items should generally be done through the ``register_admin_menu_item`` hook instead - items added through ``construct_main_menu`` will be missing any associated Javascript includes, and their ``is_shown`` check will not be applied.
.. code-block:: python
from wagtail.wagtailcore import hooks
@hooks.register('construct_main_menu')
def construct_main_menu(request, menu_items):
menu_items.append(
MenuItem( 'Frank', reverse('frank'), classnames='icon icon-folder-inverse', order=10000)
)
def hide_explorer_menu_item_from_frank(request, menu_items):
if request.user.username == 'frank':
menu_items[:] = [item for item in menu_items if item.name != 'explorer']
.. _insert_editor_js:

Wyświetl plik

@ -15,8 +15,6 @@ def register_admin_urls():
]
@hooks.register('construct_main_menu')
def construct_main_menu(request, menu_items):
menu_items.append(
MenuItem(_('Styleguide'), urlresolvers.reverse('wagtailstyleguide'), classnames='icon icon-image', order=1000)
)
@hooks.register('register_admin_menu_item')
def register_styleguide_menu_item():
return MenuItem(_('Styleguide'), urlresolvers.reverse('wagtailstyleguide'), classnames='icon icon-image', order=1000)

Wyświetl plik

@ -2,6 +2,7 @@ from django.http import HttpResponse
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.whitelist import attribute_rule, check_url, allow_without_attributes
from wagtail.wagtailadmin.menu import MenuItem
# Register one hook using decorators...
@ -28,3 +29,12 @@ def block_googlebot(page, request, serve_args, serve_kwargs):
if request.META.get('HTTP_USER_AGENT') == 'GoogleBot':
return HttpResponse("<h1>bad googlebot no cookie</h1>")
hooks.register('before_serve_page', block_googlebot)
class KittensMenuItem(MenuItem):
def is_shown(self, request):
return not request.GET.get('hide-kittens', False)
@hooks.register('register_admin_menu_item')
def register_kittens_menu_item():
return KittensMenuItem('Kittens!', 'http://www.tomroyal.com/teaandkittens/', classnames='icon icon-kitten', attrs={'data-fluffy': 'yes'}, order=10000)

Wyświetl plik

@ -1,20 +1,56 @@
from __future__ import unicode_literals
from six import text_type
from six import text_type, with_metaclass
try:
# renamed util -> utils in Django 1.7; try the new name first
from django.forms.utils import flatatt
except ImportError:
from django.forms.util import flatatt
from django.conf import settings
from django.forms import MediaDefiningClass
from django.utils.text import slugify
from django.utils.html import format_html
from django.utils.html import format_html, format_html_join
from wagtail.wagtailcore import hooks
class MenuItem(object):
def __init__(self, label, url, name=None, classnames='', order=1000):
class MenuItem(with_metaclass(MediaDefiningClass)):
def __init__(self, label, url, name=None, classnames='', attrs=None, order=1000):
self.label = label
self.url = url
self.classnames = classnames
self.name = (name or slugify(text_type(label)))
self.order = order
if attrs:
self.attr_string = flatatt(attrs)
else:
self.attr_string = ""
def is_shown(self, request):
"""
Whether this menu item should be shown for the given request; permission
checks etc should go here. By default, menu items are shown all the time
"""
return True
def render_html(self):
return format_html(
"""<li class="menu-{0}"><a href="{1}" class="{2}">{3}</a></li>""",
self.name, self.url, self.classnames, self.label)
"""<li class="menu-{0}"><a href="{1}" class="{2}"{3}>{4}</a></li>""",
self.name, self.url, self.classnames, self.attr_string, self.label)
_master_menu_item_list = None
def get_master_menu_item_list():
"""
Return the list of menu items registered with the 'register_admin_menu_item' hook.
This is the "master list" because the final admin menu may vary per request
according to the value of is_shown() and the construct_main_menu hook.
"""
global _master_menu_item_list
if _master_menu_item_list is None:
_master_menu_item_list = [fn() for fn in hooks.get_hooks('register_admin_menu_item')]
return _master_menu_item_list

Wyświetl plik

@ -21,32 +21,6 @@ $(function(){
}
});
// Dynamically load menu on request.
$(document).on('click', '.dl-trigger', function(){
var $this = $(this);
var $explorer = $('#explorer');
$this.addClass('icon-spinner');
if(!$explorer.children().length){
$explorer.load(window.explorer_menu_url, function() {
$this.removeClass('icon-spinner');
$explorer.addClass('dl-menuwrapper').dlmenu({
animationClasses : {
classin : 'dl-animate-in-2',
classout : 'dl-animate-out-2'
}
});
$explorer.dlmenu('openMenu');
});
}else{
$explorer.dlmenu('openMenu');
}
return false;
});
// Resize nav to fit height of window. This is an unimportant bell/whistle to make it look nice
var fitNav = function(){
$('.nav-wrapper').css('min-height',$(window).height());

Wyświetl plik

@ -0,0 +1,27 @@
$(function(){
// Dynamically load menu on request.
$(document).on('click', '.dl-trigger', function(){
var $this = $(this);
var $explorer = $('#explorer');
$this.addClass('icon-spinner');
if(!$explorer.children().length){
$explorer.load($this.data('explorer-menu-url'), function() {
$this.removeClass('icon-spinner');
$explorer.addClass('dl-menuwrapper').dlmenu({
animationClasses : {
classin : 'dl-animate-in-2',
classout : 'dl-animate-out-2'
}
});
$explorer.dlmenu('openMenu');
});
}else{
$explorer.dlmenu('openMenu');
}
return false;
});
});

Wyświetl plik

@ -1,5 +1,5 @@
{% extends "wagtailadmin/skeleton.html" %}
{% load compress %}
{% load compress wagtailadmin_tags %}
{% block css %}
{% compress css %}
@ -22,9 +22,7 @@
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/bootstrap-tab.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/jquery.dlmenu.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/core.js"></script>
<script>
window.explorer_menu_url = "{% url 'wagtailadmin_explorer_nav' %}";
</script>
{% main_nav_js %}
{% endcompress %}
{% block extra_js %}{% endblock %}

Wyświetl plik

@ -2,14 +2,12 @@ from __future__ import unicode_literals
from django.conf import settings
from django import template
from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailadmin.menu import MenuItem
from django.forms import Media
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import get_navigation_menu_items, UserPagePermissionsProxy, PageViewRestriction
from wagtail.wagtailcore.utils import camelcase_to_underscore
from wagtail.wagtailadmin.menu import get_master_menu_item_list
register = template.Library()
@ -31,12 +29,8 @@ def explorer_subnav(nodes):
@register.inclusion_tag('wagtailadmin/shared/main_nav.html', takes_context=True)
def main_nav(context):
menu_items = [
MenuItem(_('Explorer'), urlresolvers.reverse('wagtailadmin_explore_root'), classnames='icon icon-folder-open-inverse dl-trigger', order=100),
MenuItem(_('Search'), urlresolvers.reverse('wagtailadmin_pages_search'), classnames='icon icon-search', order=200),
]
request = context['request']
menu_items = [item for item in get_master_menu_item_list() if item.is_shown(request)]
for fn in hooks.get_hooks('construct_main_menu'):
fn(request, menu_items)
@ -46,6 +40,14 @@ def main_nav(context):
'request': request,
}
@register.simple_tag
def main_nav_js():
media = Media()
for item in get_master_menu_item_list():
media += item.media
return media['js']
@register.filter("ellipsistrim")
def ellipsistrim(value, max_length):

Wyświetl plik

@ -16,6 +16,18 @@ class TestHome(TestCase, WagtailTestUtils):
response = self.client.get(reverse('wagtailadmin_home'))
self.assertEqual(response.status_code, 200)
def test_admin_menu(self):
response = self.client.get(reverse('wagtailadmin_home'))
self.assertEqual(response.status_code, 200)
# check that media attached to menu items is correctly pulled in
self.assertContains(response, '<script type="text/javascript" src="/static/wagtailadmin/js/explorer-menu.js"></script>')
# check that custom menu items (including classname / attrs parameters) are pulled in
self.assertContains(response, '<a href="http://www.tomroyal.com/teaandkittens/" class="icon icon-kitten" data-fluffy="yes">Kittens!</a>')
# check that is_shown is respected on menu items
response = self.client.get(reverse('wagtailadmin_home') + '?hide-kittens=true')
self.assertNotContains(response, '<a href="http://www.tomroyal.com/teaandkittens/" class="icon icon-kitten" data-fluffy="yes">Kittens!</a>')
class TestEditorHooks(TestCase, WagtailTestUtils):
def setUp(self):

Wyświetl plik

@ -0,0 +1,24 @@
from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailcore import hooks
from wagtail.wagtailadmin.menu import MenuItem
class ExplorerMenuItem(MenuItem):
class Media:
js = ['wagtailadmin/js/explorer-menu.js']
@hooks.register('register_admin_menu_item')
def register_explorer_menu_item():
return ExplorerMenuItem(
_('Explorer'), urlresolvers.reverse('wagtailadmin_explore_root'),
classnames='icon icon-folder-open-inverse dl-trigger',
attrs={'data-explorer-menu-url': urlresolvers.reverse('wagtailadmin_explorer_nav')},
order=100)
@hooks.register('register_admin_menu_item')
def register_search_menu_item():
return MenuItem(
_('Search'), urlresolvers.reverse('wagtailadmin_pages_search'),
classnames='icon icon-search', order=200)

Wyświetl plik

@ -17,12 +17,13 @@ def register_admin_urls():
]
@hooks.register('construct_main_menu')
def construct_main_menu(request, menu_items):
if request.user.has_perm('wagtaildocs.add_document'):
menu_items.append(
MenuItem(_('Documents'), urlresolvers.reverse('wagtaildocs_index'), classnames='icon icon-doc-full-inverse', order=400)
)
class DocumentsMenuItem(MenuItem):
def is_shown(self, request):
return request.user.has_perm('wagtaildocs.add_document')
@hooks.register('register_admin_menu_item')
def register_documents_menu_item():
return DocumentsMenuItem(_('Documents'), urlresolvers.reverse('wagtaildocs_index'), classnames='icon icon-doc-full-inverse', order=400)
@hooks.register('insert_editor_js')

Wyświetl plik

@ -15,13 +15,15 @@ def register_admin_urls():
url(r'^forms/', include(urls)),
]
@hooks.register('construct_main_menu')
def construct_main_menu(request, menu_items):
# show this only if the user has permission to retrieve submissions for at least one form
if get_forms_for_user(request.user).exists():
menu_items.append(
MenuItem(_('Forms'), urlresolvers.reverse('wagtailforms_index'), classnames='icon icon-form', order=700)
)
class FormsMenuItem(MenuItem):
def is_shown(self, request):
# show this only if the user has permission to retrieve submissions for at least one form
return get_forms_for_user(request.user).exists()
@hooks.register('register_admin_menu_item')
def register_forms_menu_item():
return FormsMenuItem(_('Forms'), urlresolvers.reverse('wagtailforms_index'), classnames='icon icon-form', order=700)
@hooks.register('insert_editor_js')
def editor_js():

Wyświetl plik

@ -59,10 +59,14 @@ def construct_main_menu(request, menu_items):
if not OLD_STYLE_URLCONF_CHECK_PASSED:
check_old_style_urlconf()
if request.user.has_perm('wagtailimages.add_image'):
menu_items.append(
MenuItem(_('Images'), urlresolvers.reverse('wagtailimages_index'), classnames='icon icon-image', order=300)
)
class ImagesMenuItem(MenuItem):
def is_shown(self, request):
return request.user.has_perm('wagtailimages.add_image')
@hooks.register('register_admin_menu_item')
def register_images_menu_item():
return ImagesMenuItem(_('Images'), urlresolvers.reverse('wagtailimages_index'), classnames='icon icon-image', order=300)
@hooks.register('insert_editor_js')

Wyświetl plik

@ -15,10 +15,11 @@ def register_admin_urls():
]
@hooks.register('construct_main_menu')
def construct_main_menu(request, menu_items):
# TEMPORARY: Only show if the user is a superuser
if request.user.is_superuser:
menu_items.append(
MenuItem(_('Redirects'), urlresolvers.reverse('wagtailredirects_index'), classnames='icon icon-redirect', order=800)
)
class RedirectsMenuItem(MenuItem):
def is_shown(self, request):
# TEMPORARY: Only show if the user is a superuser
return request.user.is_superuser
@hooks.register('register_admin_menu_item')
def register_redirects_menu_item():
return RedirectsMenuItem(_('Redirects'), urlresolvers.reverse('wagtailredirects_index'), classnames='icon icon-redirect', order=800)

Wyświetl plik

@ -15,10 +15,11 @@ def register_admin_urls():
]
@hooks.register('construct_main_menu')
def construct_main_menu(request, menu_items):
# TEMPORARY: Only show if the user is a superuser
if request.user.is_superuser:
menu_items.append(
MenuItem(_('Editors picks'), urlresolvers.reverse('wagtailsearch_editorspicks_index'), classnames='icon icon-pick', order=900)
)
class EditorsPicksMenuItem(MenuItem):
def is_shown(self, request):
# TEMPORARY: Only show if the user is a superuser
return request.user.is_superuser
@hooks.register('register_admin_menu_item')
def register_editors_picks_menu_item():
return EditorsPicksMenuItem(_('Editors picks'), urlresolvers.reverse('wagtailsearch_editorspicks_index'), classnames='icon icon-pick', order=900)

Wyświetl plik

@ -18,12 +18,13 @@ def register_admin_urls():
]
@hooks.register('construct_main_menu')
def construct_main_menu(request, menu_items):
if user_can_edit_snippets(request.user):
menu_items.append(
MenuItem(_('Snippets'), urlresolvers.reverse('wagtailsnippets_index'), classnames='icon icon-snippet', order=500)
)
class SnippetsMenuItem(MenuItem):
def is_shown(self, request):
return user_can_edit_snippets(request.user)
@hooks.register('register_admin_menu_item')
def register_snippets_menu_item():
return SnippetsMenuItem(_('Snippets'), urlresolvers.reverse('wagtailsnippets_index'), classnames='icon icon-snippet', order=500)
@hooks.register('insert_editor_js')

Wyświetl plik

@ -15,9 +15,10 @@ def register_admin_urls():
]
@hooks.register('construct_main_menu')
def construct_main_menu(request, menu_items):
if request.user.has_module_perms('auth'):
menu_items.append(
MenuItem(_('Users'), urlresolvers.reverse('wagtailusers_index'), classnames='icon icon-user', order=600)
)
class UsersMenuItem(MenuItem):
def is_shown(self, request):
return request.user.has_module_perms('auth')
@hooks.register('register_admin_menu_item')
def register_users_menu_item():
return UsersMenuItem(_('Users'), urlresolvers.reverse('wagtailusers_index'), classnames='icon icon-user', order=600)