Merge pull request #206 from nephila/feature/menu_modifier

Fix selecting current menu item according to menu layout
This commit is contained in:
Iacopo Spalletti 2016-02-16 07:52:21 +01:00
commit 8c35763150
4 changed files with 113 additions and 15 deletions

View file

@ -11,6 +11,7 @@ History
* Use all_languages_column to admin * Use all_languages_column to admin
* Add publish button * Add publish button
* Fix issues in migrations. Thanks @skirsdeda * Fix issues in migrations. Thanks @skirsdeda
* Fix selecting current menu item according to menu layout
0.6.3 (2015-12-22) 0.6.3 (2015-12-22)
++++++++++++++++++ ++++++++++++++++++

View file

@ -1,21 +1,34 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals from __future__ import absolute_import, print_function, unicode_literals
from cms.apphook_pool import apphook_pool
from cms.menu_bases import CMSAttachMenu from cms.menu_bases import CMSAttachMenu
from django.core.urlresolvers import resolve
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from django.utils.translation import get_language_from_request, ugettext_lazy as _ from django.utils.translation import get_language_from_request, ugettext_lazy as _
from menus.base import NavigationNode from menus.base import Modifier, NavigationNode
from menus.menu_pool import menu_pool from menus.menu_pool import menu_pool
from .cms_appconfig import BlogConfig from .cms_appconfig import BlogConfig
from .models import BlogCategory, Post from .models import BlogCategory, Post
from .settings import MENU_TYPE_CATEGORIES, MENU_TYPE_COMPLETE, MENU_TYPE_POSTS from .settings import MENU_TYPE_CATEGORIES, MENU_TYPE_COMPLETE, MENU_TYPE_POSTS, get_setting
class BlogCategoryMenu(CMSAttachMenu): class BlogCategoryMenu(CMSAttachMenu):
"""
Main menu class
Handles all types of blog menu
"""
name = _('Blog menu') name = _('Blog menu')
def get_nodes(self, request): def get_nodes(self, request):
"""
Generates the nodelist
:param request:
:return: list of nodes
"""
nodes = [] nodes = []
language = get_language_from_request(request, check_path=True) language = get_language_from_request(request, check_path=True)
@ -40,9 +53,12 @@ class BlogCategoryMenu(CMSAttachMenu):
node = NavigationNode( node = NavigationNode(
category.name, category.name,
category.get_absolute_url(), category.get_absolute_url(),
'%s-%s' % (category.__class__.__name__, category.pk), '{0}-{1}'.format(category.__class__.__name__, category.pk),
('%s-%s' % (category.__class__.__name__, category.parent.id) if category.parent (
else None) '{0}-{1}'.format(
category.__class__.__name__, category.parent.id
) if category.parent else None
)
) )
nodes.append(node) nodes.append(node)
@ -57,10 +73,10 @@ class BlogCategoryMenu(CMSAttachMenu):
if categories_menu: if categories_menu:
category = post.categories.first() category = post.categories.first()
if category: if category:
parent = '%s-%s' % (category.__class__.__name__, category.pk) parent = '{0}-{1}'.format(category.__class__.__name__, category.pk)
post_id = '%s-%s' % (post.__class__.__name__, post.pk), post_id = '{0}-{1}'.format(post.__class__.__name__, post.pk),
else: else:
post_id = '%s-%s' % (post.__class__.__name__, post.pk), post_id = '{0}-{1}'.format(post.__class__.__name__, post.pk),
if post_id: if post_id:
node = NavigationNode( node = NavigationNode(
post.get_title(), post.get_title(),
@ -75,7 +91,54 @@ class BlogCategoryMenu(CMSAttachMenu):
menu_pool.register_menu(BlogCategoryMenu) menu_pool.register_menu(BlogCategoryMenu)
class BlogNavModifier(Modifier):
"""
This navigation modifier makes sure that when
a particular blog post is viewed,
a corresponding category is selected in menu
"""
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
"""
Actual modifier function
:param request: request
:param nodes: complete list of nodes
:param namespace: Menu namespace
:param root_id: eventual root_id
:param post_cut: flag for modifier stage
:param breadcrumb: flag for modifier stage
:return: nodeslist
"""
app = None
config = None
if getattr(request, 'current_page', None) and request.current_page.application_urls:
app = apphook_pool.get_apphook(request.current_page.application_urls)
if app and app.app_config:
namespace = resolve(request.path).namespace
config = app.get_config(namespace)
if config and config.menu_structure != MENU_TYPE_CATEGORIES:
return nodes
if post_cut:
return nodes
current_post = getattr(request, get_setting('CURRENT_POST_IDENTIFIER'), None)
category = None
if current_post and current_post.__class__ == Post:
category = current_post.categories.first()
if not category:
return nodes
for node in nodes:
if '{0}-{1}'.format(category.__class__.__name__, category.pk) == node.id:
node.selected = True
return nodes
menu_pool.register_modifier(BlogNavModifier)
def clear_menu_cache(**kwargs): def clear_menu_cache(**kwargs):
"""
Empty menu cache when saving categories
"""
menu_pool.clear(all=True) menu_pool.clear(all=True)
post_save.connect(clear_menu_cache, sender=BlogCategory) post_save.connect(clear_menu_cache, sender=BlogCategory)

View file

@ -29,6 +29,7 @@ class MenuTest(BaseTest):
self.cats.append(cat) self.cats.append(cat)
activate('en') activate('en')
menu_pool.clear(all=True)
menu_pool.discover_menus() menu_pool.discover_menus()
# All cms menu modifiers should be removed from menu_pool.modifiers # All cms menu modifiers should be removed from menu_pool.modifiers
# so that they do not interfere with our menu nodes # so that they do not interfere with our menu nodes
@ -139,22 +140,55 @@ class MenuTest(BaseTest):
(PostDetailView, 'slug', posts[0], posts[0].categories.first()), (PostDetailView, 'slug', posts[0], posts[0].categories.first()),
(CategoryEntriesView, 'category', self.cats[2], self.cats[2]) (CategoryEntriesView, 'category', self.cats[2], self.cats[2])
) )
self.app_config_1.app_data.config.menu_structure = MENU_TYPE_COMPLETE
self.app_config_1.save()
for view_cls, kwarg, obj, cat in tests: for view_cls, kwarg, obj, cat in tests:
request = self.get_page_request(pages[1], self.user, path=obj.get_absolute_url())
with smart_override('en'): with smart_override('en'):
with switch_language(obj, 'en'): with switch_language(obj, 'en'):
request = self.get_page_request(
pages[1], self.user, path=obj.get_absolute_url()
)
cache.clear()
menu_pool.clear(all=True)
view_obj = view_cls() view_obj = view_cls()
view_obj.request = request view_obj.request = request
view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.namespace, view_obj.config = get_app_instance(request)
view_obj.app_config = self.app_config_1 view_obj.app_config = self.app_config_1
view_obj.kwargs = {kwarg: obj.slug} view_obj.kwargs = {kwarg: obj.slug}
view_obj.get(request) view_obj.get(request)
view_obj.get_context_data()
# check if selected menu node points to cat # check if selected menu node points to cat
nodes = menu_pool.get_nodes(request) nodes = menu_pool.get_nodes(request)
found = False found = []
for node in nodes: for node in nodes:
if node.selected: if node.selected:
self.assertEqual(node.url, obj.get_absolute_url()) found.append(node.get_absolute_url())
found = True self.assertTrue(obj.get_absolute_url() in found)
break
self.assertTrue(found) self.app_config_1.app_data.config.menu_structure = MENU_TYPE_CATEGORIES
self.app_config_1.save()
for view_cls, kwarg, obj, cat in tests:
with smart_override('en'):
with switch_language(obj, 'en'):
request = self.get_page_request(
pages[1], self.user, path=obj.get_absolute_url()
)
cache.clear()
menu_pool.clear(all=True)
view_obj = view_cls()
view_obj.request = request
view_obj.namespace, view_obj.config = get_app_instance(request)
view_obj.app_config = self.app_config_1
view_obj.kwargs = {kwarg: obj.slug}
view_obj.get(request)
view_obj.get_context_data()
# check if selected menu node points to cat
nodes = menu_pool.get_nodes(request)
found = []
for node in nodes:
if node.selected:
found.append(node.get_absolute_url())
self.assertTrue(cat.get_absolute_url() in found)
self.app_config_1.app_data.config.menu_structure = MENU_TYPE_COMPLETE
self.app_config_1.save()

View file

@ -91,7 +91,7 @@ class AdminTest(BaseTest):
for fieldname in BlogConfigForm.base_fields: for fieldname in BlogConfigForm.base_fields:
self.assertContains(response, 'id="id_config-%s"' % fieldname) self.assertContains(response, 'id="id_config-%s"' % fieldname)
self.assertContains(response, '<input id="id_config-og_app_id" maxlength="200" name="config-og_app_id" type="text" />') self.assertContains(response, '<input id="id_config-og_app_id" maxlength="200" name="config-og_app_id" type="text" />')
self.assertContains(response, '<input class="vTextField" id="id_namespace" maxlength="100" name="namespace" type="text" value="sample_app" />') self.assertContains(response, 'sample_app')
def test_admin_category_views(self): def test_admin_category_views(self):
post_admin = admin.site._registry[BlogCategory] post_admin = admin.site._registry[BlogCategory]