Commit b20b52f3 authored by David Read's avatar David Read Committed by GitHub
Browse files

Merge pull request #17 from espona/master

Improved hierarchy extension
parents 61b82e48 9da87c0d
......@@ -37,6 +37,21 @@ For example, you may add next field:
}
```
Optionally one could also specify a full name and leave the field 'title' for
the short name or acronym (more convenient for display).
```
{
"field_name": "longname",
"label": "Full Name",
"validators": "ignore_missing unicode",
"form_snippet": "large_text.html",
"form_attrs": {"data-module": "slug-preview-target"},
"form_placeholder": "My Organization full name",
"display_snippet": null
}
```
TODO:
* make the trees prettier with JSTree
......
ul.hierarchy-tree-top {
list-style-type: disc;
background: none;
}
ul.hierarchy-tree {
list-style-type: none;
background: url(images/vline.png) repeat-y;
margin: 0; padding: 0;
}
ul.hierarchy-tree {
margin-left: 10px;
}
ul.hierarchy-tree li {
margin: 0;
padding: 0 12px;
line-height: 20px;
background: url(images/node.png) no-repeat;
}
ul.hierarchy-tree li:last-child {
background: #fff url(images/lastnode.png) no-repeat;
}
ul.hierarchy-tree-top li a, ul.hierarchy-tree li a{
font-weight: 400;
color: hsl(194, 72%, 34%);
}
ul.hierarchy-tree-top li.highlighted > a, ul.hierarchy-tree li.highlighted > a {
font-weight: 700;
color: hsl(194, 90%, 30%);
/*text-decoration: underline;*/
text-shadow: 1px 1px #dddddd;
}
"use strict";
(function (jQuery) {
jQuery.fn.hierarchy = function() {
$('ul.hierarchy-tree li:last-child').addClass('last');
return this;
};
})(this.jQuery);
import ckan.plugins as p
import ckan.model as model
from ckan.common import request
def group_tree(type_='organization'):
return p.toolkit.get_action('group_tree')({}, {'type': type_})
def group_tree(organizations=[], type_='organization'):
full_tree_list = p.toolkit.get_action('group_tree')({}, {'type': type_})
if not organizations:
return full_tree_list
else:
filtered_tree_list = group_tree_filter(organizations, full_tree_list)
return filtered_tree_list
def group_tree_filter(organizations, group_tree_list, highlight=False):
# this method leaves only the sections of the tree corresponding to the list
# since it was developed for the users, all children organizations from the
# organizations in the list are included
def traverse_select_highlighted(group_tree, selection=[], highlight=False):
# add highlighted branches to the filtered tree
if group_tree['highlighted']:
# add to the selection and remove highlighting if necessary
if highlight:
selection += [group_tree]
else:
selection += group_tree_highlight([], [group_tree])
else:
# check if there is any highlighted child tree
for child in group_tree.get('children', []):
traverse_select_highlighted(child, selection)
filtered_tree=[]
# first highlights all the organizations from the list in the three
for group in group_tree_highlight(organizations, group_tree_list):
traverse_select_highlighted(group, filtered_tree, highlight)
def group_tree_section(id_, type_='organization'):
return filtered_tree
def group_tree_section(id_, type_='organization', include_parents=True, include_siblings=True):
return p.toolkit.get_action('group_tree_section')(
{}, {'id': id_, 'type': type_})
{'include_parents':include_parents, 'include_siblings':include_siblings}, {'id': id_, 'type': type_,})
def group_tree_parents(id_, type_='organization'):
tree_node = p.toolkit.get_action('organization_show')({},{'id':id_})
if (tree_node['groups']):
parent_id = tree_node['groups'][0]['name']
parent_node = p.toolkit.get_action('organization_show')({},{'id':parent_id})
return group_tree_parents(parent_id) + [parent_node]
else:
return []
def group_tree_get_longname(id_, default="", type_='organization'):
tree_node = p.toolkit.get_action('organization_show')({},{'id':id_})
longname = tree_node.get("longname", default)
if not longname:
return default
return longname
def group_tree_highlight(organizations, group_tree_list):
def traverse_highlight(group_tree, name_list):
if group_tree.get('name', "") in name_list:
group_tree['highlighted'] = True
else:
group_tree['highlighted'] = False
for child in group_tree.get('children', []):
traverse_highlight(child, name_list)
selected_names = [ o.get('name',None) for o in organizations]
print(selected_names)
for group in group_tree_list:
traverse_highlight(group, selected_names)
return group_tree_list
def get_allowable_parent_groups(group_id):
if group_id:
......@@ -19,3 +83,10 @@ def get_allowable_parent_groups(group_id):
allowable_parent_groups = model.Group.all(
group_type='organization')
return allowable_parent_groups
def is_include_children_selected(fields):
include_children_selected = False
if request.params.get('include_children'):
include_children_selected = True
return include_children_selected
......@@ -26,6 +26,8 @@ def group_tree_section(context, data_dict):
group, from the top-level group downwards.
:param id: the id or name of the group to include in the tree
:param include_parents: if false, starts from given group
:param include_siblingss: if false, excludes given group siblings
:returns: the top GroupTreeNode of the tree section
'''
group_name_or_id = _get_or_bust(data_dict, 'id')
......@@ -40,9 +42,43 @@ def group_tree_section(context, data_dict):
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)
include_parents = context.get('include_parents', True)
include_siblings = context.get('include_siblings', True)
if include_parents:
root_group = (group.get_parent_group_hierarchy(type=group_type) or [group])[0]
else:
root_group = group
if include_siblings or root_group==group:
return _group_tree_branch(root_group, highlight_group_name=group.name,
type=group_type)
else:
section_subtree = _group_tree_branch(group, highlight_group_name=group.name,
type=group_type)
return _nest_group_tree_list(group.get_parent_group_hierarchy(type=group_type),
section_subtree)
def _nest_group_tree_list(group_tree_list, group_tree_leaf):
'''Returns a tree branch composed by nesting the groups in the list.
:param group_tree_list: list of groups to build a tree, first is root
:returns: the top GroupTreeNode of the tree
'''
root_node = None
last_node = None
log.debug(group_tree_list)
for group in group_tree_list:
log.debug(group)
node = GroupTreeNode(
{'id': group.id,
'name': group.name,
'title': group.title})
if not root_node:
root_node = last_node = node
else:
last_node.add_child_node(node)
last_node = node
last_node.add_child_node(group_tree_leaf)
return root_node
def _group_tree_branch(root_group, highlight_group_name=None, type='group'):
......
......@@ -2,16 +2,44 @@ import ckan.plugins as p
from ckanext.hierarchy.logic import action
from ckanext.hierarchy import helpers
from ckan.lib.plugins import DefaultOrganizationForm
from ckan.lib.plugins import DefaultGroupForm
import ckan.logic.schema as s
from ckan.common import c, request
import logging
import re
log = logging.getLogger(__name__)
# This plugin is designed to work only these versions of CKAN
p.toolkit.check_ckan_version(min_version='2.0')
def custom_convert_from_extras(key, data, errors, context):
'''Converts values from extras, tailored for groups.'''
# Set to empty string to remove Missing objects
data[key] = ""
to_remove = []
for data_key in data.keys():
if (data_key[0] == 'extras'):
data_value = data[data_key]
if( 'key' in data_value and data_value['key'] == key[-1]):
data[key] = data_value['value']
to_remove.append(data_key)
break
else:
return
for remove_key in to_remove:
del data[remove_key]
class HierarchyDisplay(p.SingletonPlugin):
p.implements(p.IConfigurer, inherit=True)
p.implements(p.IActions, inherit=True)
p.implements(p.ITemplateHelpers, inherit=True)
p.implements(p.IPackageController, inherit=True)
# IConfigurer
......@@ -19,6 +47,7 @@ class HierarchyDisplay(p.SingletonPlugin):
p.toolkit.add_template_directory(config, 'templates')
p.toolkit.add_template_directory(config, 'public')
p.toolkit.add_resource('public/scripts/vendor/jstree', 'jstree')
p.toolkit.add_resource('fanstatic', 'hierarchy')
# IActions
......@@ -31,14 +60,80 @@ class HierarchyDisplay(p.SingletonPlugin):
def get_helpers(self):
return {'group_tree': helpers.group_tree,
'group_tree_section': helpers.group_tree_section,
'group_tree_parents': helpers.group_tree_parents,
'group_tree_get_longname': helpers.group_tree_get_longname,
'group_tree_highlight': helpers.group_tree_highlight,
'get_allowable_parent_groups': helpers.get_allowable_parent_groups,
'is_include_children_selected': helpers.is_include_children_selected,
}
# IPackageController
# Modify the search query to include the datasets from
# the children organizations in the result list
def before_search(self, search_params):
''' If include children selected the query string is modified '''
def _children_name_list(children):
name_list = []
for child in children:
name = child.get('name', "")
name_list += [name] + _children_name_list(child.get('children', []))
return name_list
query = search_params.get('q', None)
c.include_children_selected = False
# fix the issues with multiple times repeated fields
# remove the param from the fields
new_fields = set()
for field,value in c.fields:
if (field != 'include_children'):
new_fields.add((field,value))
c.fields = list(new_fields)
# parse the query string to check if children are requested
if query:
base_query = []
# remove whitespaces between fields and values
query = re.sub(': +', ':', query)
for item in query.split(' '):
field = item.split(':')[0]
value = item.split(':')[-1]
# skip organization
if (field == 'owner_org'):
org_id = value
continue
# skip include children andset option value
if (field == 'include_children'):
if (value.upper() != "FALSE"):
c.include_children_selected = True
continue
base_query += [item]
if c.include_children_selected:
# add all the children organizations in an 'or' join
search_params['q'] = " ".join(base_query)
children = _children_name_list(helpers.group_tree_section(c.group_dict.get('id'), include_parents=False, include_siblings=False).get('children',[]))
if(children):
if (len(search_params['q'].strip())>0):
search_params['q'] += ' AND '
search_params['q'] += '(organization:%s' % c.group_dict.get('name')
for name in children:
if name:
search_params['q'] += ' OR organization:%s' % name
search_params['q'] += ")"
# add it back to fields
c.fields += [('include_children','True')]
return search_params
class HierarchyForm(p.SingletonPlugin, DefaultOrganizationForm):
p.implements(p.IGroupForm, inherit=True)
# IGroupForm
def group_types(self):
......@@ -49,5 +144,6 @@ class HierarchyForm(p.SingletonPlugin, DefaultOrganizationForm):
def setup_template_variables(self, context, data_dict):
from pylons import tmpl_context as c
group_id = data_dict.get('id')
c.allowable_parent_groups = helpers.get_allowable_parent_groups(group_id)
{% ckan_extends %}
{% block page_heading %}
{% if c.group_dict.longname %}
{{ c.group_dict.longname }} ({{ c.group_dict.display_name }})
{% else %}
{{ super() }}
{% endif %}
{% endblock %}
{% block organization_description %}
{{ super() }}
{# TODO: Add JSTree #}
<div id="publisher-tree">
{% snippet 'organization/snippets/organization_tree.html', top_nodes=[h.group_tree_section(id_=c.group_dict.id, type_=c.group_dict.type)] %}
<div id="organization-tree">
{% snippet 'organization/snippets/organization_tree.html', top_nodes=[h.group_tree_section(id_=c.group_dict.id, type_=c.group_dict.type)], use_longnames=True %}
</div>
{% endblock %}
{% ckan_extends %}
{% block groups_search_form %}
{% set facets = {
'fields': c.fields_grouped,
'search': c.search_facets,
'titles': c.facet_titles,
'translated_fields': c.translated_fields,
'remove_field': c.remove_field }
%}
{% set sorting = [
(_('Relevance'), 'score desc, metadata_modified desc'),
(_('Name Ascending'), 'title_string asc'),
(_('Name Descending'), 'title_string desc'),
(_('Last Modified'), 'metadata_modified desc'),
(_('Popular'), 'views_recent desc') if g.tracking_enabled else (false, false) ]
%}
{% snippet 'snippets/search_form.html', form_id='organization-datasets-search-form', type='dataset', query=c.q, sorting=sorting, sorting_selected=c.sort_by_selected, count=c.page.item_count, facets=facets, placeholder=_('Search datasets...'), show_empty=request.params, fields=c.fields, include_children_option=true %}
{% endblock %}
......@@ -8,6 +8,17 @@ See:
* https://github.com/datagovuk/ckanext-hierarchy/issues/9
* https://github.com/ckan/ckan/pull/2640
#}
{% block breadcrumb_content %}
<li>{% link_for _('Organizations'), controller='organization', action='index' %}</li>
{% set parent_list = h.group_tree_parents(c.group_dict.name) %}
{% for parent_node in parent_list %}
<li>{% link_for parent_node.title|truncate(35), controller='organization', action='read', id=parent_node.name, suppress_active_class=true %}</li>
{% endfor %}
<li class="active">{% link_for c.group_dict.title|truncate(35), controller='organization', action='read', id=c.group_dict.name %}</li>
{% endblock %}
{% block content_primary_nav %}
{{ h.build_nav_icon('organization_read', _('Datasets'), id=c.group_dict.name) }}
{{ h.build_nav_icon('organization_activity', _('Activity Stream'), id=c.group_dict.name, offset=0) }}
......
......@@ -8,5 +8,10 @@ Example:
#}
<div id="publisher-tree">
{% snippet 'organization/snippets/organization_tree.html', top_nodes=h.group_tree(type_='organization') %}
{% if c.q %}
{% set top_nodes = h.group_tree_highlight(organizations, h.group_tree(type_='organization')) %}
{% else %}
{% set top_nodes = h.group_tree(organizations=organizations, type_='organization') %}
{% endif %}
{% snippet 'organization/snippets/organization_tree.html', top_nodes=top_nodes, use_longnames=True %}
</div>
......@@ -10,22 +10,29 @@ orgs - List of organizations
Example:
{% snippet 'organization/snippets/organization_tree.html', top_nodes=h.group_tree(type_='organization') %}
{% snippet 'organization/snippets/organization_tree.html', top_nodes=h.group_tree(type_='organization'), use_longnames=False %}
#}
<ul>
{% resource 'hierarchy/hierarchy_theme.css' %}
{% resource 'hierarchy/jquery.hierarchy.js' %}
<ul class="hierarchy-tree-top">
{% for node in top_nodes recursive %}
<li id="node_{{ node.name }}">
{% set longname = h.group_tree_get_longname(node.name) %}
{% set display_text = node.title %}
{% if use_longnames and longname%}
{% set display_text = longname + " (" + display_text + ")" %}
{% endif %}
{% if node.highlighted %}
<strong>
<li class="highlighted" id="node_{{ node.name }}">
{% else %}
<li id="node_{{ node.name }}">
{% endif %}
{% link_for node.title, controller='organization', action='read', id=node.name %}
{% if node.highlighted %}
</strong>
{% endif %}
{% link_for display_text, controller='organization', action='read', id=node.name %}
{% if node.children %}
<ul> {{ loop(node.children) }} </ul>
<ul class="hierarchy-tree"> {{ loop(node.children) }} </ul>
{% endif %}
</li>
{% endfor %}
......
{% ckan_extends %}
{% block breadcrumb_content %}
{% if pkg %}
{% set dataset = h.dataset_display_name(pkg) %}
{% if pkg.organization %}
{% set organization = pkg.organization.title %}
<li>{% link_for _('Organizations'), controller='organization', action='index' %}</li>
{% set parent_list = h.group_tree_parents(pkg.organization.name) %}
{% for parent_node in parent_list %}
<li>{% link_for parent_node.title|truncate(35), controller='organization', action='read', id=parent_node.name %}</li>
{% endfor %}
<li>{% link_for organization|truncate(30), controller='organization', action='read', id=pkg.organization.name %}</li>
{% else %}
<li>{% link_for _('Datasets'), controller='package', action='search' %}</li>
{% endif %}
<li{{ self.breadcrumb_content_selected() }}>{% link_for dataset|truncate(30), controller='package', action='read', id=pkg.name %}</li>
{% else %}
<li>{% link_for _('Datasets'), controller='package', action='search' %}</li>
<li class="active"><a href="">{{ _('Create Dataset') }}</a></li>
{% endif %}
{% endblock %}
{% ckan_extends %}
{% block primary_content_inner %}
<h1>
{% block page_heading %}
{% if c.group_dict.longname %}
{{ c.group_dict.longname }} ({{ c.group_dict.display_name }})
{% else %}
{{ super() }}
{% endif %}
{% endblock %}
</h1>
<dl>
{% for f in c.scheming_fields %}
{% if f.display_snippet != None %}
<dt>{{ h.scheming_language_text(f.label) }}:</dt>
<dd>{{ c.group_dict[f.field_name] or ("&nbsp;"|safe) }}</dd>
{% endif %}
{% endfor %}
</dl>
<div id="organization-tree">
{% snippet 'organization/snippets/organization_tree.html', top_nodes=[h.group_tree_section(id_=c.group_dict.id, type_=c.group_dict.type)], use_longnames=True %}
</div>
{% endblock %}
{% ckan_extends %}
{% block heading %}
<h1 class="heading">
{% if organization.longname %}
{{ organization.longname }} ({{ organization.title or organization.name }})
{% else %}
{{ organization.title or organization.name }}
{% endif %}
{% if organization.state == 'deleted' %}
[{{ _('Deleted') }}]
{% endif %}
</h1>
{% endblock %}
{% block description %}
{{ super() }}
{% if c.group_dict %}
<hr>
{% snippet 'organization/snippets/organization_tree.html', top_nodes=[h.group_tree_section(id_=c.group_dict.id, type_=c.group_dict.type, include_siblings=False)], use_shortnames=True %}
{% endif %}
{% endblock %}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment