Skip to content

Commit

Permalink
add hierarchical organisation structure
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Sadleir committed Nov 12, 2014
1 parent 04b090b commit 1cf2f23
Show file tree
Hide file tree
Showing 11 changed files with 1,106 additions and 167 deletions.
17 changes: 1 addition & 16 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,031 changes: 884 additions & 147 deletions .idea/workspace.xml

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions ckanext/datagovau/action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import logging

import ckan.plugins as p
import ckan.logic as logic
from ckanext.datagovau.model import GroupTreeNode

log = logging.getLogger(__name__)
_get_or_bust = logic.get_or_bust


@logic.side_effect_free
def group_tree(context, data_dict):
'''Returns the full group tree hierarchy.
:returns: list of top-level GroupTreeNodes
'''
model = _get_or_bust(context, 'model')
group_type = data_dict.get('type', 'group')
return [_group_tree_branch(group, type=group_type)
for group in model.Group.get_top_level_groups(type=group_type)]


@logic.side_effect_free
def group_tree_section(context, data_dict):
'''Returns the section of the group tree hierarchy which includes the given
group, from the top-level group downwards.
:param id: the id or name of the group to inclue in the tree
:returns: the top GroupTreeNode of the tree section
'''
group_name_or_id = _get_or_bust(data_dict, 'id')
model = _get_or_bust(context, 'model')
group = model.Group.get(group_name_or_id)
if group is None:
raise p.toolkit.ObjectNotFound
group_type = data_dict.get('type', 'group')
if group.type != group_type:
how_type_was_set = 'was specified' if data_dict.get('type') \
else 'is filtered by default'
raise p.toolkit.ValidationError(
'Group type is "%s" not "%s" that %s' %
(group.type, group_type, how_type_was_set))
root_group = (group.get_parent_group_hierarchy(type=group_type) or [group])[0]
return _group_tree_branch(root_group, highlight_group_name=group.name,
type=group_type)


def _group_tree_branch(root_group, highlight_group_name=None, type='group'):
'''Returns a branch of the group tree hierarchy, rooted in the given group.
:param root_group_id: group object at the top of the part of the tree
:param highlight_group_name: group name that is to be flagged 'highlighted'
:returns: the top GroupTreeNode of the tree
'''
nodes = {} # group_id: GroupTreeNode()
root_node = nodes[root_group.id] = GroupTreeNode(
{'id': root_group.id,
'name': root_group.name,
'title': root_group.title})
if root_group.name == highlight_group_name:
nodes[root_group.id].highlight()
highlight_group_name = None
for group_id, group_name, group_title, parent_id in \
root_group.get_children_group_hierarchy(type=type):
node = GroupTreeNode({'id': group_id,
'name': group_name,
'title': group_title})
nodes[parent_id].add_child_node(node)
if highlight_group_name and group_name == highlight_group_name:
node.highlight()
nodes[group_id] = node
return root_node
41 changes: 41 additions & 0 deletions ckanext/datagovau/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from bisect import bisect_right


class GroupTreeNode(dict):
'''Represents a group in a tree, used when rendering the tree.
Is a dict, with links to child GroupTreeNodes, so that it is already
'dictized' for output from the logic layer.
'''
def __init__(self, group_dict):
dict.__init__(self)
self.update(group_dict)
self['highlighted'] = False
self['children'] = []
# self._children_titles has a 1:1 relationship with values in
# self.['children'], and is used to help keep them sorted by title
self._children_titles = []

def add_child_node(self, child_node):
'''Adds the child GroupTreeNode to this node, keeping the children
in alphabetical order by title.
'''
title = child_node['title']
insert_index = bisect_right(self._children_titles, title)
self['children'].insert(insert_index, child_node)
self._children_titles.insert(insert_index, title)

def highlight(self):
'''Flag this group to indicate it should be shown highlighted
when rendered.'''
self['highlighted'] = True


def group_dictize(group):
'''Convert a Group object into a dict suitable for GroupTreeNode
Much simpler and quicker to do this than run than the full package_show.
'''
return {'id': group.id,
'name': group.name,
'title': group.title,
'type': group.type}
33 changes: 33 additions & 0 deletions ckanext/datagovau/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from sqlalchemy import orm
import ckan.model

import ckanext.datagovau.action as action
from ckan.lib.plugins import DefaultGroupForm
from ckan.lib.plugins import DefaultOrganizationForm
# get user created datasets and those they have edited
def get_user_datasets(user_dict):
created_datasets_list = user_dict['datasets']
Expand Down Expand Up @@ -39,6 +42,7 @@ class DataGovAuPlugin(plugins.SingletonPlugin,
plugins.implements(plugins.IConfigurer, inherit=False)
plugins.implements(plugins.ITemplateHelpers, inherit=False)
plugins.implements(plugins.IAuthFunctions)
plugins.implements(plugins.IActions, inherit=True)

def get_auth_functions(self):
return {'related_create': related_create}
Expand All @@ -52,8 +56,37 @@ def update_config(self, config):
tk.add_template_directory(config, 'templates')
tk.add_public_directory(config, 'theme/public')
tk.add_resource('theme/public', 'ckanext-datagovau')
tk.add_resource('public/scripts/vendor/jstree', 'jstree')
# config['licenses_group_url'] = 'http://%(ckan.site_url)/licenses.json'

def get_helpers(self):
return {'get_user_datasets': get_user_datasets, 'get_related_dataset': get_related_dataset}

# IActions

def get_actions(self):
return {'group_tree': action.group_tree,
'group_tree_section': action.group_tree_section,
}


class HierarchyForm(plugins.SingletonPlugin, DefaultOrganizationForm):

plugins.implements(plugins.IGroupForm, inherit=True)

# IGroupForm

def group_types(self):
return ('organization',)

def setup_template_variables(self, context, data_dict):
from pylons import tmpl_context as c
model = context['model']
group_id = data_dict.get('id')
if group_id:
group = model.Group.get(group_id)
c.allowable_parent_groups = \
group.groups_allowed_to_be_its_parent(type='organization')
else:
c.allowable_parent_groups = model.Group.all(
group_type='organization')
11 changes: 11 additions & 0 deletions ckanext/datagovau/templates/organization/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% ckan_extends %}

{% block organization_description %}
{{ super() }}

{# TODO: Add JSTree #}

<div id="publisher-tree">
{% snippet 'organization/snippets/organization_tree.html', top_nodes=[h.get_action('group_tree_section', {'id': c.group_dict.id, 'type': c.group_dict.type})] %}
</div>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% ckan_extends %}

{% block basic_fields %}
{{ super() }}

<div class="control-group">
<label class="control-label" for="field-parent">{{ _("Parent") }}</label>
<div class="controls">
<select id="field-parent" name="groups__0__name" data-module="autocomplete">
{% set selected_parent = (data.get('groups') or [{'name': ''}])[0]['name'] %} {{ selected_parent }}
<option value="" {% if not selected_parent %} selected="selected" {% endif %}>{{ _('None - top level') }}</option>
{% for group in c.allowable_parent_groups %}
<option value="{{ group.name }}" {% if group.name == selected_parent %}selected="selected"{% endif %}>{{ group.title }}</option>
{% endfor %}
</select>
</div>
</div>

{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{#
Displays a tree of organzations

NB This template can be slow because it is recursive and uses link_for. At
DGU we speeded up display of the tree 10 times (necessary as we have 1000
organizations) by replacing this template with a recursive code routine:
https://github.com/datagovuk/ckanext-dgu/blob/5fb78b354517c2198245bdc9c98fb5d6c82c6bcc/ckanext/dgu/lib/helpers.py#L140

orgs - List of organizations

Example:

{% snippet 'organization/snippets/organization_tree.html', top_nodes=h.get_action('group_tree', {'type': 'organization'}) %}

#}
<ul>
{% for node in top_nodes recursive %}
<li id="node_{{ node.name }}">
{% if node.highlighted %}
<strong>
{% endif %}
{% link_for node.title, controller='organization', action='read', id=node.name %}
{% if node.highlighted %}
</strong>
{% endif %}

{% if node.children %}
<ul> {{ loop(node.children) }} </ul>
{% endif %}
</li>
{% endfor %}
</ul>
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
"""
[ckan.plugins]
datagovau=ckanext.datagovau.plugin:DataGovAuPlugin
datagovau_hierarchy=ckanext.datagovau.plugin:HierarchyForm
""",
)

0 comments on commit 1cf2f23

Please sign in to comment.