Skip to content

Commit

Permalink
Merge pull request #502 from NaturalHistoryMuseum/ginger/grouping
Browse files Browse the repository at this point in the history
Grouping
  • Loading branch information
alycejenni authored Nov 18, 2021
2 parents 8642ab5 + d96401b commit f76cb26
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 41 deletions.
34 changes: 29 additions & 5 deletions ckanext/nhm/lib/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
#
# This file is part of ckanext-nhm
# Created by the Natural History Museum in London, UK
from collections import OrderedDict, defaultdict

import itertools
import json
import logging
import operator
import os
import re
import time
from collections import OrderedDict, defaultdict
from datetime import datetime
from operator import itemgetter
from urllib.parse import quote

from beaker.cache import cache_region
from ckan import model
from ckan.lib import helpers as core_helpers
Expand All @@ -24,11 +27,8 @@
resource_view_get_view)
from ckanext.nhm.logic.schema import DATASET_TYPE_VOCABULARY, UPDATE_FREQUENCIES
from ckanext.nhm.settings import COLLECTION_CONTACTS
from datetime import datetime
from jinja2.filters import do_truncate
from lxml import etree, html
from operator import itemgetter
from urllib.parse import quote

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -1393,3 +1393,27 @@ def get_specimen_jsonld(uuid, version=None):
return toolkit.get_action('object_rdf')({}, data_dict).decode('utf-8')
except toolkit.ValidationError:
return ''


def get_resource_group(resource):
group_name = resource.get('resource_group')
linked_specimen = resource.get('linked_specimen')
if linked_specimen and group_name and '$' in group_name:
# has to be imported here due to circular imports
from ckanext.nhm.lib.record import get_record_by_uuid
linked_specimen_record = get_record_by_uuid(linked_specimen)[0]
if linked_specimen_record and group_name:
tokens = [t for t in re.findall('\$[a-zA-Z]+', group_name) if t.strip('$') in linked_specimen_record]
for token in tokens:
group_name = group_name.replace(token, linked_specimen_record.get(token.strip('$')))
if (group_name or '').strip() == '':
group_name = None
return group_name


def group_resources(resource_list):
group_and_resource = sorted([(get_resource_group(r), r) for r in resource_list],
key=lambda x: x[0] or '')
return [(group_name, re.sub('[^A-Za-z0-9]', '', group_name or 'ungrouped'),
[x[1] for x in resources]) for group_name, resources in
itertools.groupby(group_and_resource, key=itemgetter(0))]
26 changes: 21 additions & 5 deletions ckanext/nhm/theme/assets/less/nhm.less
Original file line number Diff line number Diff line change
Expand Up @@ -1173,16 +1173,12 @@ iframe {
}
}

.navbar-toggle {
.collapse-toggle, .navbar-toggle {
z-index: 1;
padding: 0 10px;
height: 100%;
text-align: left;

@media (max-width: @collapse-width) {
display: block;
}

&:after {
.mixin-font-symbol(normal);
content: '\f0d8';
Expand Down Expand Up @@ -1210,6 +1206,18 @@ iframe {
}
}

.nav-toggle {
@media (max-width: @collapse-width) {
display: block;
}
}

.collapse-toggle {
display: block;
background: transparent;
border: none;
}

.pill {
color: @grey10;
background-color: @grey3;
Expand Down Expand Up @@ -2440,6 +2448,14 @@ select, .select2-choice {

.resource-list {
margin: 0 0 30px 0;

& .subgroup-header {
margin-top: 3em;
}

& .subgroup {
margin: 0.2em 1em;
}
}

.tags {
Expand Down
101 changes: 73 additions & 28 deletions ckanext/nhm/theme/templates/package/snippets/resource_form.html
Original file line number Diff line number Diff line change
@@ -1,48 +1,93 @@
{% ckan_extends %}

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

<div class="form-group">
<div class="flex-container flex-between flex-stretch-first">
<b class="no-pad-h">{{ _('Resource grouping') }}:</b>
<button type="button" class="collapse-toggle collapsed" data-toggle="collapse"
data-target="#grouping" aria-expanded="false">
<span class="sr-only">Expand</span>
</button>
</div>
<span class="info-block info-block-small">
<i class="fa fa-info-circle"></i>
{{ _('This section is optional and will not be relevant in most cases.') }}
</span>
<div id="grouping" class="collapse pad-h" aria-expanded="false">
<br>
{% call form.input('linked_specimen', id='field-linked-specimen', label=_('Associated
specimen'), placeholder=_('(optional) e.g. 7e4fe982-9ff7-4bf4-9aea-2759ab387a6a'), value=data.linked_specimen,
error=errors.linked_specimen, classes=['control-medium'], is_required=False) %}
<span class="info-block info-block-small">
<i class="fa fa-info-circle"></i>
{{ _('The occurrence ID of a record from the collection specimens dataset that this resource is associated with, e.g. this resource is a 3D model of the specimen. Leave blank if not relevant.') }}
</span>
{% endcall %}

{% call form.input('resource_group', id='field-resource_group', label=_('Group name'),
placeholder=_('(optional) e.g. "Supplementary Data" or "$genus"'),
value=data.resource_group, error=errors.resource_group, classes=['control-medium'],
is_required=False) %}
<span class="info-block info-block-small">
<i class="fa fa-info-circle"></i>
{{ _('The literal name of the group (e.g. "Appendix 1" or "Spreadsheets") or tokens (e.g. "$scientificName" or "Collection: $collectionCode") to extract metadata from the associated specimen record.') }}
</span>
{% endcall %}
</div>
</div>

{% endblock %}

{% block basic_fields_name %}
{{ form.input('name', id='field-name', label=_('Name'), placeholder=_('eg. Specimens'), value=data.name, error=errors.name, classes=['control-full'], is_required=True) }}
{{ form.input('name', id='field-name', label=_('Name'), placeholder=_('e.g. Specimens'), value=data.name, error=errors.name, classes=['control-full'], is_required=True) }}
{% endblock %}

{% block metadata_fields %}

{% set datastore_fields = h.form_select_datastore_field_options(data, allow_empty=True) %}
{% set datastore_fields = h.form_select_datastore_field_options(data, allow_empty=True) %}

{# We allow empty to has to be greater than 1 for there to be fields #}
{% if datastore_fields.__len__() > 1 %}
{# We allow empty to has to be greater than 1 for there to be fields #}
{% if datastore_fields.__len__() > 1 %}

{% call form.select('_title_field', label=_('Title field'), options=datastore_fields, selected=data._title_field, error=errors._title_field) %}
{{ form.info(_('Please select the field to use for the record title.'), inline=True) }}
{% endcall %}
{% call form.select('_title_field', label=_('Title field'), options=datastore_fields, selected=data._title_field, error=errors._title_field) %}
{{ form.info(_('Please select the field to use for the record title.'), inline=True) }}
{% endcall %}

{% call form.select('_image_field', label=_('Image field'), options=datastore_fields, selected=data._image_field, error=errors._image_field, attrs={'data-module': 'toggle-select', 'data-module-target': '#image-options'}) %}
{{ form.info(_('Does your dataset contain images? If so, please select the field here.'), inline=True) }}
{% endcall %}
{% call form.select('_image_field', label=_('Image field'), options=datastore_fields, selected=data._image_field, error=errors._image_field, attrs={'data-module': 'toggle-select', 'data-module-target': '#image-options'}) %}
{{ form.info(_('Does your dataset contain images? If so, please select the field here.'), inline=True) }}
{% endcall %}

{# If there's an image field in this dataset #}
<div id="image-options">
{% call form.select('_image_licence', label=_('Image licence'), options=h.get_image_licence_options(), selected=data._image_licence, error=errors._image_licence) %}
{{ form.info(_('Please select a licence for images in this dataset. This licence will be displayed next to the image.'), inline=True) }}
{% endcall %}
{# If there's an image field in this dataset #}
<div id="image-options">
{% call form.select('_image_licence', label=_('Image licence'),
options=h.get_image_licence_options(), selected=data._image_licence,
error=errors._image_licence) %}
{{ form.info(_('Please select a licence for images in this dataset. This licence will be
displayed next to the image.'), inline=True) }}
{% endcall %}

{% call form.input('_image_delimiter', label=_('Image list delimiter'), value=data._image_delimiter, error=errors._image_delimiter) %}
{{ form.info(_('If your image field contains a series of delimited image URLs instead of just a single one enter it here'), inline=True) }}
{% endcall %}
</div>
{% call form.input('_image_delimiter', label=_('Image list delimiter'),
value=data._image_delimiter, error=errors._image_delimiter) %}
{{ form.info(_('If your image field contains a series of delimited image URLs instead of just a
single one enter it here'), inline=True) }}
{% endcall %}
</div>

{% asset 'ckanext-nhm/toggle-select' %}
{% asset 'ckanext-nhm/toggle-select' %}

{% call form.select('_latitude_field', label=_('Decimal latitude field'), options=datastore_fields, selected=data._latitude_field, error=errors._latitude_field) %}
{{ form.info(_("Please select the field to use for the record's decimal latitude."), inline=True) }}
{% endcall %}
{% call form.select('_longitude_field', label=_('Decimal longitude field'), options=datastore_fields, selected=data._longitude_field, error=errors._longitude_field) %}
{{ form.info(_("Please select the field to use for the record's decimal longitude."), inline=True) }}
{% endcall %}
{% call form.select('_latitude_field', label=_('Decimal latitude field'), options=datastore_fields, selected=data._latitude_field, error=errors._latitude_field) %}
{{ form.info(_("Please select the field to use for the record's decimal latitude."), inline=True) }}
{% endcall %}
{% call form.select('_longitude_field', label=_('Decimal longitude field'), options=datastore_fields, selected=data._longitude_field, error=errors._longitude_field) %}
{{ form.info(_("Please select the field to use for the record's decimal longitude."), inline=True) }}
{% endcall %}

{% endif %}
{% endif %}

{% endblock %}

{% block again_button %}
<button class="btn" name="save" value="again" type="submit">{{ _('Add another file') }}</button>
<button class="btn" name="save" value="again" type="submit">{{ _('Add another file') }}</button>
{% endblock %}
20 changes: 18 additions & 2 deletions ckanext/nhm/theme/templates/package/snippets/resources_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,24 @@ <h2>{{ _('Data and Resources') }}</h2>
<ul class="{% block resource_list_class %}resource-list{% endblock %}">
{% block resource_list_inner %}
{% set can_edit = h.check_access('package_update', {'id':pkg.id }) %}
{% for resource in resources %}
{% snippet 'package/snippets/resource_item.html', pkg=pkg, res=resource, can_edit=can_edit %}
{% set grouped_resources = h.group_resources(resources) %}
{% for group_name, group_slug, group_items in grouped_resources %}
{% if group_name %}
<div class="flex-container flex-between subgroup-header">
<i class="fas fa-layer-group inline-icon-left"></i>
<h3 class="no-margin flex-child-grow">{{ group_name }}</h3>
<span>({{ group_items|length }} resource{% if (group_items|length) != 1 %}s{% endif %})</span>
<button type="button" class="collapse-toggle collapsed" data-toggle="collapse"
data-target="#grp-{{ group_slug }}" aria-expanded="false">
<span class="sr-only">Expand {{ group_name }}</span>
</button>
</div>
{% endif %}
<div id="grp-{{ group_slug }}" class="collapse {% if group_name %}subgroup{% else %}in{% endif %}" aria-expanded="false">
{% for resource in group_items %}
{% snippet 'package/snippets/resource_item.html', pkg=pkg, res=resource, can_edit=can_edit %}
{% endfor %}
</div>
{% endfor %}
{% endblock %}
</ul>
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from collections import OrderedDict
from setuptools import find_packages, setup

__version__ = '3.0.16'
__version__ = '3.0.17'

with open('README.md', 'r') as f:
__long_description__ = f.read()
Expand Down

0 comments on commit f76cb26

Please sign in to comment.