Skip to content

Commit

Permalink
Merge a533695 into 61de460
Browse files Browse the repository at this point in the history
  • Loading branch information
idlesign committed May 1, 2019
2 parents 61de460 + a533695 commit 38bdc33
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 65 deletions.
2 changes: 1 addition & 1 deletion docs/source/admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Example:
.. note::

You might also be interested in using :ref:`Tree hooks <tree-hooks>`.
You might also be interested in using :ref:`Tree handler customization <tree-custom>`.


Inlines override example
Expand Down
52 changes: 52 additions & 0 deletions docs/source/customization.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Tree handler customization
==========================

What to do if a time comes and you need some fancy stuff done to tree items that
*django-sitetree* does not support?

.. _tree-custom:

It might be that you need some special tree items ordering in a menu, or you want to render
in a huge site tree with all articles titles that are described by one tree item in Django admin,
or god knows what else.

*django-sitetree* can facilitate on that as it allows tree handler customization
with the help of `SITETREE_CLS` setting.

1. Subclass `sitetreeapp.SiteTree` and place that class into a separate module for convenience.
2. Override methods you need for customization (usually `.apply_hook()`).
3. Define `SITETREE_CLS` in `settings.py` of your project, showing it a dotted path to subclass.


Example:

.. code-block:: python
# myapp/mysitetree.py
from sitetree.sitetreeapp import SiteTree
class MySiteTree(SiteTree):
"""Custom tree handler to test deep customization abilities."""
def apply_hook(self, items, sender):
# Suppose we want to process only menu child items.
if tree_sender == 'menu.children':
# Lets add 'Hooked: ' to resolved titles of every item.
for item in tree_items:
item.title_resolved = 'Hooked: %s' % item.title_resolved
# Return items list mutated or not.
return tree_items
# pyproject/settings.py
...
SITETREE_CLS = 'myapp.mysitetree.MySiteTree'
...
.. note::

You might also be interested in the notes on :ref:`Overriding SiteTree Admin representation <admin-ext>`.
52 changes: 0 additions & 52 deletions docs/source/hooks.rst

This file was deleted.

2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Table of Contents
management
templatesmod
tagsadv
hooks
customization
admin
forms
models
Expand Down
2 changes: 1 addition & 1 deletion docs/source/tags.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Usage example::

This command renders as a menu sitetree items from tree named 'mytree', including items **under** 'trunk' and 'topmenu' aliased items.
That means that 'trunk' and 'topmenu' themselves won't appear in a menu, but rather all their ancestors. If you need item filtering behaviour
please use :ref:`tree hooks <tree-hooks>`.
please use :ref:`tree handler customizations <tree-custom>`.

Aliases are given to items through Django's admin site.

Expand Down
3 changes: 3 additions & 0 deletions sitetree/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from django.conf import settings


SITETREE_CLS = getattr(settings, 'SITETREE_CLS', None)
"""Allows deep tree handling customization. Accepts sitetreeap.SiteTree subclass."""

MODEL_TREE = getattr(settings, 'SITETREE_MODEL_TREE', 'sitetree.Tree')
"""Path to a tree model (app.class)."""

Expand Down
47 changes: 37 additions & 10 deletions sitetree/sitetreeapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections import defaultdict
from copy import deepcopy
from functools import partial
from inspect import getargspec
from inspect import getfullargspec
from threading import local

from django import VERSION
Expand All @@ -16,7 +16,7 @@
VARIABLE_TAG_START)
from django.template.defaulttags import url as url_tag
from django.template.loader import get_template
from django.utils import six
from django.utils import six, module_loading
from django.utils.encoding import python_2_unicode_compatible
from django.utils.http import urlquote
from django.utils.translation import get_language
Expand All @@ -25,11 +25,12 @@
from .exceptions import SiteTreeError
from .settings import (
ALIAS_TRUNK, ALIAS_THIS_CHILDREN, ALIAS_THIS_SIBLINGS, ALIAS_THIS_PARENT_SIBLINGS, ALIAS_THIS_ANCESTOR_CHILDREN,
UNRESOLVED_ITEM_MARKER, RAISE_ITEMS_ERRORS_ON_DEBUG, CACHE_TIMEOUT, DYNAMIC_ONLY, ADMIN_APP_NAME)
UNRESOLVED_ITEM_MARKER, RAISE_ITEMS_ERRORS_ON_DEBUG, CACHE_TIMEOUT, DYNAMIC_ONLY, ADMIN_APP_NAME, SITETREE_CLS)
from .utils import get_tree_model, get_tree_item_model, import_app_sitetree_module, generate_id_for

if False: # pragma: nocover
from django.template import Context
from django.contrib.auth.models import User
from .models import TreeItemBase


Expand Down Expand Up @@ -71,13 +72,14 @@

def get_sitetree():
"""Returns SiteTree (thread-singleton) object, implementing utility methods.
This can return the built-in or a customized (see SITETREE_CLS setting) sitetree handler.
:rtype: SiteTree
"""
sitetree = getattr(_THREAD_LOCAL, _THREAD_SITETREE, None)

if sitetree is None:
sitetree = SiteTree()
sitetree = _SITETREE_CLS()
setattr(_THREAD_LOCAL, _THREAD_SITETREE, sitetree)

return sitetree
Expand All @@ -86,6 +88,10 @@ def get_sitetree():
def register_items_hook(func):
"""Registers a hook callable to process tree items right before they are passed to templates.
.. deprecated:: 1.13.0
Items hooking with `register_items_hook` is deprecated, please use `SITETREE_CLS`
setting customization instead and override .apply_hook() method.
Callable should be able to:
a) handle ``tree_items`` and ``tree_sender`` key params.
Expand Down Expand Up @@ -118,13 +124,18 @@ def my_items_processor(tree_items, tree_sender):
:param func:
"""
warnings.warn(
'Items hooking with `register_items_hook` is deprecated, '
'please use `SITETREE_CLS` settings customization instead.',
DeprecationWarning, 2)

global _ITEMS_PROCESSOR
global _ITEMS_PROCESSOR_ARGS_LEN

_ITEMS_PROCESSOR = func

if func:
args_len = len(getargspec(func).args)
args_len = len(getfullargspec(func).args)
if args_len not in {2, 3}:
raise SiteTreeError('`register_items_hook()` expects a function with two or three arguments.')
_ITEMS_PROCESSOR_ARGS_LEN = args_len
Expand Down Expand Up @@ -372,6 +383,8 @@ def set_entry(self, entry_name, key, value):
class SiteTree(object):
"""Main logic handler."""

cache_cls = Cache # Allow customizations.

def __init__(self):
self.init(context=None)

Expand All @@ -380,7 +393,7 @@ def init(self, context):
:param Context|None context:
"""
self.cache = Cache()
self.cache = self.cache_cls()
self.current_page_context = context
self.current_request = context.get('request', None) if context else None
self.current_lang = get_language()
Expand Down Expand Up @@ -849,11 +862,13 @@ def menu(self, tree_alias, tree_branches, context):
return menu_items

def apply_hook(self, items, sender):
"""Applies item processing hook, registered with ``register_item_hook()``
to items supplied, and returns processed list.
"""Applies a custom items processing hook to items supplied, and returns processed list.
Suitable for items filtering and other manipulations.
Returns initial items list if no hook is registered.
.. note:: Use `SITETREE_CLS` setting customization and override this method.
:param list items:
:param str|unicode sender: menu, breadcrumbs, sitetree, {type}.children, {type}.has_children
:rtype: list
Expand Down Expand Up @@ -890,7 +905,7 @@ def check_access(self, item, context):
user_perms = self._current_user_permissions

if user_perms is _UNSET:
user_perms = set(context['user'].get_all_permissions())
user_perms = self.get_permissions(context['user'], item)
self._current_user_permissions = user_perms

if item.access_perm_type == MODEL_TREE_ITEM_CLASS.PERM_TYPE_ALL:
Expand All @@ -902,6 +917,15 @@ def check_access(self, item, context):

return True

def get_permissions(self, user, item):
"""Returns a set of user and group level permissions for a given user.
:param User user:
:param TreeItemBase item:
:rtype: set
"""
return user.get_all_permissions()

def breadcrumbs(self, tree_alias, context):
"""Builds and returns breadcrumb trail structure for 'sitetree_breadcrumbs' tag.
Expand Down Expand Up @@ -1085,7 +1109,7 @@ def resolve_var(self, varname, context=None):
If no context specified page context' is considered as context.
:param str|unicode varname:
:param str|unicode|FilterExpression varname:
:param Context context:
:return:
"""
Expand All @@ -1102,3 +1126,6 @@ def resolve_var(self, varname, context=None):
varname = varname

return varname


_SITETREE_CLS = module_loading.import_string(SITETREE_CLS) if SITETREE_CLS else SiteTree
3 changes: 3 additions & 0 deletions sitetree/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from pytest_djangoapp import configure_djangoapp_plugin

pytest_plugins = configure_djangoapp_plugin(
settings=dict(
SITETREE_CLS='sitetree.tests.testapp.mysitetree.MySiteTree',
),
extend_INSTALLED_APPS=[
'django.contrib.admin',
],
Expand Down
7 changes: 7 additions & 0 deletions sitetree/tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,10 @@ def test_lazy_title(template_context):
get_sitetree().current_page_context = template_context()

assert title == 'herethere'


def test_customized_tree_handler(template_context):

from sitetree.sitetreeapp import get_sitetree

assert get_sitetree().customized # see MySiteTree
7 changes: 7 additions & 0 deletions sitetree/tests/testapp/mysitetree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from sitetree.sitetreeapp import SiteTree


class MySiteTree(SiteTree):
"""Custom tree handler to test deep customization abilities."""

customized = True

0 comments on commit 38bdc33

Please sign in to comment.