Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some improvemens #161

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
10 changes: 5 additions & 5 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ recursive-include docs .rst
recursive-include docs .py

recursive-include sitetree/locale *
recursive-include sitetree/migrations .py
recursive-include sitetree/south_migrations .py
recursive-include sitetree/templates .html
recursive-include sitetree/templatetags .py
recursive-include sitetree/management .py
recursive-include sitetree/migrations *.py
recursive-include sitetree/south_migrations *.py
recursive-include sitetree/templates *.html
recursive-include sitetree/templatetags *.py
recursive-include sitetree/management *.py
4 changes: 2 additions & 2 deletions sitetree/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ class TreeItemAdmin(admin.ModelAdmin):
exclude = ('tree', 'sort_order')
fieldsets = (
(_('Basic settings'), {
'fields': ('parent', 'title', 'url',)
'fields': ('parent', 'title', 'url', 'softroot_for')
}),
(_('Access settings'), {
'classes': ('collapse',),
'fields': ('access_loggedin', 'access_guest', 'access_restricted', 'access_permissions', 'access_perm_type')
}),
(_('Display settings'), {
'classes': ('collapse',),
'fields': ('hidden', 'inmenu', 'inbreadcrumbs', 'insitetree')
'fields': ('hidden', 'inmenu', 'inbreadcrumbs', 'insitetree', 'hide_from',)
}),
(_('Additional settings'), {
'classes': ('collapse',),
Expand Down
10 changes: 5 additions & 5 deletions sitetree/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,18 @@ class Migration(migrations.Migration):
'verbose_name': 'Site Tree',
'verbose_name_plural': 'Site Trees',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='TreeItem',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(help_text='Site tree item title. Can contain template variables E.g.: {{ mytitle }}.', max_length=100, verbose_name='Title')),
('hint', models.CharField(default='', help_text='Some additional information about this item that is used as a hint.', max_length=200, verbose_name='Hint', blank=True)),
('hint', models.CharField(default=b'', help_text='Some additional information about this item that is used as a hint.', max_length=200, verbose_name='Hint', blank=True)),
('url', models.CharField(help_text='Exact URL or URL pattern (see "Additional settings") for this item.', max_length=200, verbose_name='URL', db_index=True)),
('urlaspattern', models.BooleanField(default=False, help_text='Whether the given URL should be treated as a pattern.<br /><b>Note:</b> Refer to Django "URL dispatcher" documentation (e.g. "Naming URL patterns" part).', db_index=True, verbose_name='URL as Pattern')),
('hidden', models.BooleanField(default=False, help_text='Whether to show this item in navigation.', db_index=True, verbose_name='Hidden')),
('alias', sitetree.models.CharFieldNullable(max_length=80, blank=True, help_text='Short name to address site tree item from a template.<br /><b>Reserved aliases:</b> "trunk", "this-children", "this-siblings", "this-ancestor-children", "this-parent-siblings".', null=True, verbose_name='Alias', db_index=True)),
('description', models.TextField(default='', help_text='Additional comments on this item.', verbose_name='Description', blank=True)),
('alias', sitetree.models.CharFieldNullable(max_length=80, blank=True, help_text='Short name to address site tree item from a template.<br /><b>Reserved aliases:</b> "trunk", "this-children", "this-siblings", "this-ancestor-children", "this-parent-siblings", "this-softroot".', null=True, verbose_name='Alias', db_index=True)),
('description', models.TextField(default=b'', help_text='Additional comments on this item.', verbose_name='Description', blank=True)),
('inmenu', models.BooleanField(default=True, help_text='Whether to show this item in a menu.', db_index=True, verbose_name='Show in menu')),
('inbreadcrumbs', models.BooleanField(default=True, help_text='Whether to show this item in a breadcrumb path.', db_index=True, verbose_name='Show in breadcrumb path')),
('insitetree', models.BooleanField(default=True, help_text='Whether to show this item in a site tree.', db_index=True, verbose_name='Show in site tree')),
Expand All @@ -45,6 +44,8 @@ class Migration(migrations.Migration):
('access_restricted', models.BooleanField(default=False, help_text='Check it to restrict user access to this item, using Django permissions system.', db_index=True, verbose_name='Restrict access to permissions')),
('access_perm_type', models.IntegerField(default=1, help_text='<b>Any</b> &mdash; user should have any of chosen permissions. <b>All</b> &mdash; user should have all chosen permissions.', verbose_name='Permissions interpretation', choices=[(1, 'Any'), (2, 'All')])),
('sort_order', models.IntegerField(default=0, help_text='Item position among other site tree items under the same parent.', verbose_name='Sort order', db_index=True)),
('softroot_for', models.CharField(default=b'', max_length=200, verbose_name='Soft root for menu', blank=True)),
('hide_from', models.CharField(default=b'', max_length=200, verbose_name='Hide from menu', blank=True)),
('access_permissions', models.ManyToManyField(to='auth.Permission', verbose_name='Permissions granting access', blank=True)),
('parent', models.ForeignKey(related_name='treeitem_parent', blank=True, to='sitetree.TreeItem', help_text='Parent site tree item.', null=True, verbose_name='Parent')),
('tree', models.ForeignKey(related_name='treeitem_tree', verbose_name='Site Tree', to='sitetree.Tree', help_text='Site tree this item belongs to.')),
Expand All @@ -54,7 +55,6 @@ class Migration(migrations.Migration):
'verbose_name': 'Site Tree Item',
'verbose_name_plural': 'Site Tree Items',
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='treeitem',
Expand Down
10 changes: 10 additions & 0 deletions sitetree/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ class TreeItemBase(models.Model):
sort_order = models.IntegerField(
_('Sort order'),
help_text=_('Item position among other site tree items under the same parent.'), db_index=True, default=0)
softroot_for = models.CharField(
_('Soft root for menu'), max_length=200, blank=True, default='')
hide_from = models.CharField(
_('Hide from menu'), max_length=200, blank=True, default='')

def save(self, force_insert=False, force_update=False, **kwargs):
"""We override parent save method to set item's sort order to its' primary
Expand All @@ -133,6 +137,12 @@ def save(self, force_insert=False, force_update=False, **kwargs):
if self.sort_order == 0:
self.sort_order = self.id
self.save()

def softroot_for_list(self):
return map(unicode.strip, self.softroot_for.replace(';', ',').split(','))

def hide_from_list(self):
return map(unicode.strip, self.hide_from.replace(';', ',').split(','))

class Meta(object):
abstract = True
Expand Down
4 changes: 3 additions & 1 deletion sitetree/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
ALIAS_THIS_SIBLINGS = 'this-siblings'
ALIAS_THIS_ANCESTOR_CHILDREN = 'this-ancestor-children'
ALIAS_THIS_PARENT_SIBLINGS = 'this-parent-siblings'
ALIAS_THIS_SOFTROOT = 'this-softroot'

TREE_ITEMS_ALIASES = [
ALIAS_TRUNK,
ALIAS_THIS_CHILDREN,
ALIAS_THIS_SIBLINGS,
ALIAS_THIS_ANCESTOR_CHILDREN,
ALIAS_THIS_PARENT_SIBLINGS
ALIAS_THIS_PARENT_SIBLINGS,
ALIAS_THIS_SOFTROOT
]
120 changes: 92 additions & 28 deletions sitetree/sitetreeapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
from django.conf import settings
from django import VERSION
from django.core.cache import cache
from django.core.urlresolvers import get_resolver, LocaleRegexURLResolver
from django.db.models import signals
from django.utils import six
from django.utils.http import urlquote
from django.utils.translation import get_language
from django.utils.translation import get_language, get_language_from_path
from django.utils.encoding import python_2_unicode_compatible
from django.template import Context
from django.template.loader import get_template
Expand All @@ -23,7 +24,7 @@

from .utils import get_tree_model, get_tree_item_model, import_app_sitetree_module, generate_id_for
from .settings import (
ALIAS_TRUNK, ALIAS_THIS_CHILDREN, ALIAS_THIS_SIBLINGS, ALIAS_THIS_PARENT_SIBLINGS, ALIAS_THIS_ANCESTOR_CHILDREN,
ALIAS_TRUNK, ALIAS_THIS_CHILDREN, ALIAS_THIS_SIBLINGS, ALIAS_THIS_PARENT_SIBLINGS, ALIAS_THIS_ANCESTOR_CHILDREN, ALIAS_THIS_SOFTROOT,
UNRESOLVED_ITEM_MARKER)


Expand All @@ -47,6 +48,9 @@
_IDX_TPL = '%s|:|%s'
# SiteTree app-wise object.
_SITETREE = None
#
_LOCALE_URL_PATTERNS = None


_THREAD_LOCAL = local()
_THREAD_LANG = 'sitetree_lang'
Expand Down Expand Up @@ -487,6 +491,16 @@ def get_tree_current_item(self, tree_alias):
urls_cache[url_item][1].is_current = False
if urls_cache[url_item][0] == current_url:
current_item = urls_cache[url_item][1]
# if not found, we should try url without language prefix
if current_item is None and self.is_locale_patterns_used():
language_from_path = get_language_from_path(current_url)
if language_from_path:
current_url = current_url.replace('/%s' % language_from_path, '', 1)
if self.translation_enabled_for_path(current_url):
for url_item in urls_cache:
urls_cache[url_item][1].is_current = False
if urls_cache[url_item][0] == current_url:
current_item = urls_cache[url_item][1]

if current_item is not None:
current_item.is_current = True
Expand Down Expand Up @@ -581,6 +595,9 @@ def url(self, sitetree_item, context=None):
resolved_url = url_pattern

self.update_cache_entry_value('urls', cache_key, {url_pattern: (resolved_url, sitetree_item)})

if self.is_locale_patterns_used() and self.translation_enabled_for_path(resolved_url):
resolved_url = '/%s%s' % (self.lang_get(), resolved_url)

return resolved_url

Expand Down Expand Up @@ -630,21 +647,23 @@ def get_ancestor_level(self, current_item, deep=1):
else:
return current_item

def menu(self, tree_alias, tree_branches, context):
def menu(self, tree_alias, tree_branches, context, menu_name=None, include_parent=None):
"""Builds and returns menu structure for 'sitetree_menu' tag."""
tree_alias, sitetree_items = self.init_tree(tree_alias, context)
# No items in tree, fail silently.
if not sitetree_items:
return ''
tree_branches = self.resolve_var(tree_branches)
if menu_name:
menu_name = self.resolve_var(menu_name)

parent_isnull = False
parent_ids = []
parent_aliases = []

current_item = self.get_tree_current_item(tree_alias)
self.tree_climber(tree_alias, current_item)

# Support item addressing both through identifiers and aliases.
for branch_id in tree_branches.split(','):
branch_id = branch_id.strip()
Expand All @@ -662,27 +681,44 @@ def menu(self, tree_alias, tree_branches, context):
elif branch_id == ALIAS_THIS_PARENT_SIBLINGS and current_item is not None:
branch_id = self.get_ancestor_level(current_item, deep=2).id
parent_ids.append(branch_id)
elif branch_id == ALIAS_THIS_SOFTROOT and current_item is not None:
softroot = self.get_softroot_item(tree_alias, current_item, menu_name)
if softroot is None:
parent_isnull = True
else:
branch_id = softroot.id
parent_ids.append(branch_id)
elif branch_id.isdigit():
parent_ids.append(int(branch_id))
else:
parent_aliases.append(branch_id)

menu_items = []
for item in sitetree_items:
if not item.hidden and item.inmenu and self.check_access(item, context):
if item.parent is None:
if parent_isnull:
menu_items.append(item)
else:
if item.parent.id in parent_ids or item.parent.alias in parent_aliases:
menu_items.append(item)
if include_parent and item.id in parent_ids or item.alias in parent_aliases:
menu_items.append(item)
elif item.parent is None:
if parent_isnull:
menu_items.append(item)
else:
if item.parent.id in parent_ids or item.parent.alias in parent_aliases:
menu_items.append(item)

# Parse titles for variables.
menu_items = self.apply_hook(menu_items, 'menu')
menu_items = self.update_has_children(tree_alias, menu_items, 'menu')
menu_items = self.filter_items(menu_items, 'menu', menu_name)
menu_items = self.apply_hook(menu_items, 'menu', menu_name)
menu_items = self.update_has_children(tree_alias, menu_items, 'menu', menu_name)

# clear has_children for parent items
if include_parent:
for item in menu_items:
if item.id in parent_ids or item.alias in parent_aliases:
item.has_children = False
setattr(item, 'is_parent', True)

return menu_items

def apply_hook(self, items, sender):
def apply_hook(self, items, sender, menu_name=None):
"""Applies item processing hook, registered with ``register_item_hook()``
to items supplied, and returns processed list.
Returns initial items list if no hook is registered.
Expand Down Expand Up @@ -740,44 +776,43 @@ def tree(self, tree_alias, context):
tree_items = self.update_has_children(tree_alias, tree_items, 'sitetree')
return tree_items

def children(self, parent_item, navigation_type, use_template, context):
def children(self, parent_item, navigation_type, context, menu_name=None):
"""Builds and returns site tree item children structure
for 'sitetree_children' tag.

"""
# Resolve parent item and current tree alias.
parent_item = self.resolve_var(parent_item, context)
if menu_name:
menu_name = self.resolve_var(menu_name, context)
tree_alias, tree_items = self.get_sitetree(parent_item.tree.alias)
# Mark path to current item.
self.tree_climber(tree_alias, self.get_tree_current_item(tree_alias))

tree_items = self.get_children(tree_alias, parent_item)
tree_items = self.filter_items(tree_items, navigation_type)
tree_items = self.apply_hook(tree_items, '%s.children' % navigation_type)
tree_items = self.update_has_children(tree_alias, tree_items, navigation_type)
tree_items = self.filter_items(tree_items, navigation_type, menu_name)
tree_items = self.apply_hook(tree_items, '%s.children' % navigation_type, menu_name)
tree_items = self.update_has_children(tree_alias, tree_items, navigation_type, menu_name)

my_template = get_template(use_template)
context.update({'sitetree_items': tree_items})
return my_template.render(context)
return tree_items

def get_children(self, tree_alias, item):
if not self.current_app_is_admin():
# We do not need i18n for a tree rendered in Admin dropdown.
tree_alias = self.resolve_tree_i18n_alias(tree_alias)
return self.get_cache_entry('parents', tree_alias)[item]

def update_has_children(self, tree_alias, tree_items, navigation_type):
def update_has_children(self, tree_alias, tree_items, navigation_type, menu_name=None):
"""Updates 'has_children' attribute for tree items."""
items = []
for tree_item in tree_items:
children = self.get_children(tree_alias, tree_item)
children = self.filter_items(children, navigation_type)
children = self.filter_items(children, navigation_type, menu_name)
children = self.apply_hook(children, '%s.has_children' % navigation_type)
tree_item.has_children = len(children) > 0
items.append(tree_item)
return items

def filter_items(self, items, navigation_type=None):
def filter_items(self, items, navigation_type=None, menu_name=None):
"""Filters site tree item's children if hidden and by navigation type.
NB: We do not apply any filters to sitetree in admin app.
"""
Expand All @@ -786,7 +821,8 @@ def filter_items(self, items, navigation_type=None):
for item in items:
no_access = not self.check_access(item, self._global_context)
hidden_for_nav_type = navigation_type is not None and not getattr(item, 'in' + navigation_type, False)
if item.hidden or no_access or hidden_for_nav_type:
hidden_for_menu_name = menu_name in item.hide_from_list()
if item.hidden or no_access or hidden_for_nav_type or hidden_for_menu_name:
items_out.remove(item)
return items_out

Expand All @@ -801,6 +837,14 @@ def get_ancestor_item(self, tree_alias, start_from):
return start_from

return parent

def get_softroot_item(self, tree_alias, item, menu_name):
"""Climbs up the site tree to find soft root item for chosen one, or None."""
if menu_name in item.softroot_for_list():
return item
if not hasattr(item, 'parent') or item.parent is None:
return None
return self.get_softroot_item(tree_alias, self.get_item_by_id(tree_alias, item.parent.id), menu_name)

def tree_climber(self, tree_alias, start_from):
"""Climbs up the site tree to mark items of current branch."""
Expand Down Expand Up @@ -837,7 +881,27 @@ def resolve_var(self, varname, context=None):
varname = varname

return varname


def get_locale_url_patterns(self):
global _LOCALE_URL_PATTERNS
if _LOCALE_URL_PATTERNS is None:
_LOCALE_URL_PATTERNS = []
for url_pattern in get_resolver(None).url_patterns:
if isinstance(url_pattern, LocaleRegexURLResolver):
_LOCALE_URL_PATTERNS.extend(url_pattern.url_patterns)
return _LOCALE_URL_PATTERNS

def is_locale_patterns_used(self):
return len(self.get_locale_url_patterns()) > 0

def translation_enabled_for_path(self, path):
if path.startswith('/'):
path = path[1:]
for url_pattern in self.get_locale_url_patterns():
match = url_pattern.regex.search(path)
if match:
return True
return False

class SiteTreeError(Exception):
"""Exception class for sitetree application."""
Expand Down
Loading