Instantly share code, notes, and snippets.
Last active
August 15, 2017 20:53
-
Save rodfersou/ccf56c4ef4057c6cfc72261d53bcd73c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" bin/instance -O Plone run reorder_menu.py """ | |
from collections import OrderedDict | |
from plone import api | |
from plone.dexterity.interfaces import IDexterityContent | |
import transaction | |
class EasyMenu(OrderedDict): | |
""" Using ideas from Beyond PEP8 talk from Raymond Hettinger to easily reorder Plone Menu """ | |
def __init__(self, obj=None, parent=None, depth=1, max_depth=2): | |
super(EasyMenu, self).__init__() | |
if obj is None: | |
obj = api.portal.get() | |
self.obj = obj | |
self.parent = parent | |
self.depth = depth | |
self.max_depth = max_depth | |
if depth > max_depth: | |
return | |
# Create internal structure | |
for o in obj.listFolderContents(): | |
if self._exclude_from_nav(o): | |
continue | |
if api.content.get_state(o, '') != 'published': | |
continue | |
if 'folder' in o.portal_type.lower(): | |
self[o.id] = MenuFolder(obj=o, parent=self, depth=depth+1, max_depth=max_depth) | |
else: | |
self[o.id] = MenuItem(obj=o, parent=self, depth=depth+1, max_depth=max_depth) | |
def _exclude_from_nav(self, obj): | |
"""Check DX and AT way if is a menu item.""" | |
if IDexterityContent.providedBy(obj): | |
try: | |
# Dexterity | |
exclude_from_nav = obj.exclude_from_nav | |
if callable(exclude_from_nav): | |
return True | |
return exclude_from_nav | |
except AttributeError: | |
pass | |
else: | |
try: | |
# Archetypes | |
return obj.getExcludeFromNav() | |
except AttributeError: | |
pass | |
return True # For some content type that can't be a Menu | |
def sort(self, key=None, folder_first=False, walk_tree=True, child=False): | |
"""Sort menu""" | |
if key is None: | |
# by default we sort by Title if exists or by ID | |
key = lambda x: x.title if x.title != '' else x.id.decode('utf-8') | |
# map tuple to value object | |
if not child: | |
the_key = key | |
key = lambda x: the_key(x[1].obj) | |
if not folder_first: | |
menu = OrderedDict(sorted(self.iteritems(), key=key)) | |
self.clear() | |
for k, v in menu.iteritems(): | |
self[k] = v | |
if walk_tree: | |
for folder in self.values(): | |
if isinstance(folder, MenuItem): | |
continue | |
folder.sort( | |
key=key, folder_first=folder_first, walk_tree=walk_tree, child=True) | |
return | |
# Keep folders first | |
folders = {k: v for k, v in self.iteritems() if isinstance(v, MenuFolder)} | |
items = {k: v for k, v in self.iteritems() if isinstance(v, MenuItem)} | |
folders = OrderedDict(sorted(folders.iteritems(), key=key)) | |
items = OrderedDict(sorted(items.iteritems(), key=key)) | |
if walk_tree: | |
for folder in folders.values(): | |
folder.sort(key=key, folder_first=folder_first, walk_tree=walk_tree, child=True) | |
self.clear() | |
for k, v in folders.iteritems(): | |
self[k] = v | |
for k, v in items.iteritems(): | |
self[k] = v | |
def __repr__(self): | |
return 'EasyMenu(\n' + '\n'.join(' {}: {},'.format(k, v._repr()) for k, v in self.iteritems()) + '\n)' | |
def __enter__(self): | |
return self | |
def __exit__(self, exctype, excinst, exctb): | |
"""Map dict reordering into Plone folder.""" | |
if exctype is not None: | |
return | |
def walk_tree(item): | |
keys = item.keys() | |
if len(keys) == 0: | |
return | |
if len(keys) > 1: | |
item.obj.moveObjectsToTop(keys) | |
for subitem in item.itervalues(): | |
if not isinstance(subitem, MenuFolder): | |
continue | |
walk_tree(subitem) | |
walk_tree(self) | |
class MenuFolder(EasyMenu): | |
def __init__(self, obj, parent, depth, max_depth): | |
super(MenuFolder, self).__init__(obj, parent, depth, max_depth) | |
def _repr(self): | |
return 'MenuFolder({})'.format(self.obj.id) | |
class MenuItem(object): | |
def __init__(self, obj, parent, depth, max_depth): | |
self.obj = obj | |
self.parent = parent | |
self.depth = depth | |
self.max_depth = max_depth | |
def _repr(self): | |
return 'MenuItem({})'.format(self.obj.id) | |
def __repr__(self): | |
return self._repr() | |
# Sort by Title if possible or by ID | |
with EasyMenu() as em: | |
em.sort() | |
## Sort by ID | |
#with EasyMenu() as em: | |
# em.sort(key=lambda x: x.id) | |
## Sort with folders first | |
#with EasyMenu() as em: | |
# em.sort(folders_first=True) | |
## Sort specific part of menu | |
#obj = api.content.get('/my/folder/path') | |
#with EasyMenu(obj=obj) as em: | |
# em.sort(walk_tree=False) | |
## Uncomment to save changes | |
#transaction.commit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment