Merge pull request #107 from skirsdeda/feature/cmsattachmenu
CMS Attach menu and language switcher related fixes
This commit is contained in:
commit
58b69ced3b
6 changed files with 201 additions and 2 deletions
|
@ -1,12 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from cms.app_base import CMSApp
|
from cms.app_base import CMSApp
|
||||||
from cms.apphook_pool import apphook_pool
|
from cms.apphook_pool import apphook_pool
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _, get_language
|
||||||
|
from .menu import BlogCategoryMenu
|
||||||
|
|
||||||
|
|
||||||
class BlogApp(CMSApp):
|
class BlogApp(CMSApp):
|
||||||
name = _('Blog')
|
name = _('Blog')
|
||||||
urls = ['djangocms_blog.urls']
|
urls = ['djangocms_blog.urls']
|
||||||
app_name = 'djangocms_blog'
|
app_name = 'djangocms_blog'
|
||||||
|
menus = [BlogCategoryMenu]
|
||||||
|
|
||||||
apphook_pool.register(BlogApp)
|
apphook_pool.register(BlogApp)
|
||||||
|
|
64
djangocms_blog/menu.py
Normal file
64
djangocms_blog/menu.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from cms.menu_bases import CMSAttachMenu
|
||||||
|
from menus.base import Modifier, NavigationNode
|
||||||
|
from menus.menu_pool import menu_pool
|
||||||
|
from django.db.models.signals import post_save, post_delete
|
||||||
|
from django.utils.translation import ugettext_lazy as _, get_language
|
||||||
|
from .models import BlogCategory
|
||||||
|
|
||||||
|
|
||||||
|
class BlogCategoryMenu(CMSAttachMenu):
|
||||||
|
name = _('Blog Category menu')
|
||||||
|
|
||||||
|
def get_nodes(self, request):
|
||||||
|
nodes = []
|
||||||
|
qs = BlogCategory.objects.translated(get_language())
|
||||||
|
qs = qs.order_by('parent__id', 'translations__name')
|
||||||
|
for category in qs:
|
||||||
|
node = NavigationNode(
|
||||||
|
category.name,
|
||||||
|
category.get_absolute_url(),
|
||||||
|
category.pk,
|
||||||
|
category.parent_id
|
||||||
|
)
|
||||||
|
nodes.append(node)
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
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):
|
||||||
|
if post_cut: return nodes
|
||||||
|
if not hasattr(request, 'toolbar'):
|
||||||
|
return nodes
|
||||||
|
models = ('djangocms_blog.post', 'djangocms_blog.blogcategory')
|
||||||
|
model = request.toolbar.get_object_model()
|
||||||
|
if model not in models:
|
||||||
|
return nodes
|
||||||
|
if model == 'djangocms_blog.blogcategory':
|
||||||
|
cat = request.toolbar.obj
|
||||||
|
else:
|
||||||
|
cat = request.toolbar.obj.categories.first()
|
||||||
|
if not cat: return nodes
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
if (node.namespace.startswith(BlogCategoryMenu.__name__) and
|
||||||
|
cat.pk == node.id):
|
||||||
|
node.selected = True
|
||||||
|
# no break here because django-cms maintains two menu structures
|
||||||
|
# for every apphook (attached to published page and draft page)
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
menu_pool.register_modifier(BlogNavModifier)
|
||||||
|
|
||||||
|
def clear_menu_cache(**kwargs):
|
||||||
|
menu_pool.clear(all=True)
|
||||||
|
|
||||||
|
post_save.connect(clear_menu_cache, sender=BlogCategory)
|
||||||
|
post_delete.connect(clear_menu_cache, sender=BlogCategory)
|
|
@ -49,6 +49,15 @@ class BlogCategory(TranslatableModel):
|
||||||
def count(self):
|
def count(self):
|
||||||
return self.blog_posts.filter(publish=True).count()
|
return self.blog_posts.filter(publish=True).count()
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
lang = get_language()
|
||||||
|
if self.has_translation(lang):
|
||||||
|
slug = self.safe_translation_getter('slug', language_code=lang)
|
||||||
|
return reverse('djangocms_blog:posts-category', kwargs={'category': slug})
|
||||||
|
# in case category doesn't exist in this language, gracefully fallback
|
||||||
|
# to posts-latest
|
||||||
|
return reverse('djangocms_blog:posts-latest')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.safe_translation_getter('name')
|
return self.safe_translation_getter('name')
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,12 @@ class PostDetailView(TranslatableSlugMixin, BaseBlogView, DetailView):
|
||||||
slug_field = 'slug'
|
slug_field = 'slug'
|
||||||
view_url_name = 'djangocms_blog:post-detail'
|
view_url_name = 'djangocms_blog:post-detail'
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
# submit object to cms to get corrent language switcher and selected category behavior
|
||||||
|
if hasattr(self.request, 'toolbar'):
|
||||||
|
self.request.toolbar.set_object(self.get_object())
|
||||||
|
return super(PostDetailView, self).get(*args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(PostDetailView, self).get_context_data(**kwargs)
|
context = super(PostDetailView, self).get_context_data(**kwargs)
|
||||||
context['meta'] = self.get_object().as_meta()
|
context['meta'] = self.get_object().as_meta()
|
||||||
|
@ -136,6 +142,12 @@ class CategoryEntriesView(BaseBlogView, ListView):
|
||||||
self._category = BlogCategory.objects.active_translations(get_language(), slug=self.kwargs['category']).latest('pk')
|
self._category = BlogCategory.objects.active_translations(get_language(), slug=self.kwargs['category']).latest('pk')
|
||||||
return self._category
|
return self._category
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
# submit object to cms toolbar to get correct language switcher behavior
|
||||||
|
if hasattr(self.request, 'toolbar'):
|
||||||
|
self.request.toolbar.set_object(self.category)
|
||||||
|
return super(CategoryEntriesView, self).get(*args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super(CategoryEntriesView, self).get_queryset()
|
qs = super(CategoryEntriesView, self).get_queryset()
|
||||||
if 'category' in self.kwargs:
|
if 'category' in self.kwargs:
|
||||||
|
|
|
@ -22,6 +22,9 @@ from djangocms_blog.models import BlogCategory, Post
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
def _get_cat_pk(lang, name):
|
||||||
|
return lambda: BlogCategory.objects.translated(lang, name=name).get().pk
|
||||||
|
|
||||||
|
|
||||||
class BaseTest(TestCase):
|
class BaseTest(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -59,6 +62,22 @@ class BaseTest(TestCase):
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cat_data = {
|
||||||
|
'it': [
|
||||||
|
{'name': u'Fortissimo'},
|
||||||
|
{'name': u'Pianississimo'},
|
||||||
|
{'name': u'Mezzo'},
|
||||||
|
{'name': u'Forte', 'parent_id': _get_cat_pk('it', 'Mezzo')},
|
||||||
|
],
|
||||||
|
'en': [
|
||||||
|
{'name': u'Very loud'},
|
||||||
|
{'name': u'Very very silent'},
|
||||||
|
{'name': u'Almost'},
|
||||||
|
{'name': u'Loud', 'parent_id': _get_cat_pk('en', 'Almost')},
|
||||||
|
{'name': u'Silent', 'parent_id': _get_cat_pk('en', 'Almost')},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.request_factory = RequestFactory()
|
cls.request_factory = RequestFactory()
|
||||||
|
@ -91,6 +110,19 @@ class BaseTest(TestCase):
|
||||||
original_filename=self.image_name,
|
original_filename=self.image_name,
|
||||||
file=file_obj)
|
file=file_obj)
|
||||||
|
|
||||||
|
def _get_category(self, data, category=None, lang='en'):
|
||||||
|
for k, v in data.items():
|
||||||
|
if hasattr(v, '__call__'):
|
||||||
|
data[k] = v()
|
||||||
|
if not category:
|
||||||
|
category = BlogCategory.objects.create(**data)
|
||||||
|
else:
|
||||||
|
category.set_current_language(lang)
|
||||||
|
for attr, val in data.items():
|
||||||
|
setattr(category, attr, val)
|
||||||
|
category.save()
|
||||||
|
return category
|
||||||
|
|
||||||
def _get_post(self, data, post=None, lang='en', sites=None):
|
def _get_post(self, data, post=None, lang='en', sites=None):
|
||||||
if not post:
|
if not post:
|
||||||
post_data = {
|
post_data = {
|
||||||
|
@ -117,7 +149,7 @@ class BaseTest(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for post in Post.objects.all():
|
for post in Post.objects.all():
|
||||||
post.delete()
|
post.delete()
|
||||||
|
|
80
tests/test_menu.py
Normal file
80
tests/test_menu.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import copy
|
||||||
|
from django.utils.translation import activate
|
||||||
|
from menus.menu_pool import menu_pool
|
||||||
|
from parler.utils.context import switch_language
|
||||||
|
from djangocms_blog.views import PostDetailView, CategoryEntriesView
|
||||||
|
from . import BaseTest
|
||||||
|
|
||||||
|
|
||||||
|
class MenuTest(BaseTest):
|
||||||
|
def setUp(self):
|
||||||
|
super(MenuTest, self).setUp()
|
||||||
|
self.cats = [self.category_1]
|
||||||
|
for i, cat_data in enumerate(self.cat_data['en']):
|
||||||
|
cat = self._get_category(cat_data)
|
||||||
|
if i < len(self.cat_data['it']):
|
||||||
|
cat = self._get_category(self.cat_data['it'][i], cat, 'it')
|
||||||
|
self.cats.append(cat)
|
||||||
|
|
||||||
|
activate('en')
|
||||||
|
menu_pool.discover_menus()
|
||||||
|
# All cms menu modifiers should be removed from menu_pool.modifiers
|
||||||
|
# so that they do not interfere with our menu nodes
|
||||||
|
menu_pool.modifiers = [m for m in menu_pool.modifiers if m.__module__.startswith('djangocms_blog')]
|
||||||
|
|
||||||
|
def test_menu_nodes(self):
|
||||||
|
"""
|
||||||
|
Tests if all categories are present in the menu
|
||||||
|
"""
|
||||||
|
for lang in ('en', 'it'):
|
||||||
|
request = self.get_page_request(None, self.user,
|
||||||
|
r'/%s/blog/' % lang, edit=False)
|
||||||
|
activate(lang)
|
||||||
|
nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu')
|
||||||
|
nodes_copy = copy.deepcopy(nodes)
|
||||||
|
for cat in self.cats:
|
||||||
|
if not cat.has_translation(lang):
|
||||||
|
continue
|
||||||
|
with switch_language(cat, lang):
|
||||||
|
# find in node list
|
||||||
|
found = None
|
||||||
|
for node in nodes_copy:
|
||||||
|
if node.url == cat.get_absolute_url():
|
||||||
|
found = node
|
||||||
|
break
|
||||||
|
self.assertIsNotNone(found)
|
||||||
|
nodes_copy.remove(found)
|
||||||
|
self.assertEqual(node.id, cat.id)
|
||||||
|
self.assertEqual(node.title, cat.name)
|
||||||
|
# check that all categories were found in menu
|
||||||
|
self.assertEqual(len(nodes_copy), 0)
|
||||||
|
|
||||||
|
def test_modifier(self):
|
||||||
|
"""
|
||||||
|
Tests if correct category is selected in the menu
|
||||||
|
according to context (view object)
|
||||||
|
"""
|
||||||
|
post1, post2 = self.get_posts()
|
||||||
|
tests = (
|
||||||
|
# view class, view kwarg, view object, category
|
||||||
|
(PostDetailView, 'slug', post1, post1.categories.first()),
|
||||||
|
(CategoryEntriesView, 'category', self.cats[2], self.cats[2])
|
||||||
|
)
|
||||||
|
for view_cls, kwarg, obj, cat in tests:
|
||||||
|
request = self.get_page_request(None, self.user, r'/en/blog/', edit=False)
|
||||||
|
activate('en')
|
||||||
|
with switch_language(obj, 'en'):
|
||||||
|
view_obj = view_cls()
|
||||||
|
view_obj.request = request
|
||||||
|
view_obj.kwargs = {kwarg: obj.slug}
|
||||||
|
view_obj.get(request)
|
||||||
|
# check if selected menu node points to cat
|
||||||
|
nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu')
|
||||||
|
found = False
|
||||||
|
for node in nodes:
|
||||||
|
if node.selected:
|
||||||
|
self.assertEqual(node.url, cat.get_absolute_url())
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
self.assertTrue(found)
|
Loading…
Reference in a new issue