From 68d2f2530cb2718335e6a3b38f8721b438f07551 Mon Sep 17 00:00:00 2001
From: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com>
Date: Wed, 17 Feb 2021 09:29:28 +0100
Subject: [PATCH] [Fixes #6925] Thesauri improvements (#6951)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Thesaurus: Add model ThesaurusLabel, improve load_thesaurus managment command and add test coverage
* Thesaurus: Add new migration file, stab thesaurus rdf file and managment command test
* Thesaurus: Add copyright, file reformatted with black
* Thesaurus: Increase test coverage for thesaurus load and class refactor
* Resolves #6925: New Thesaurus form generated dynamically based on thesaurus avaiable, fix test on thesaurus load, add migration file for updated thesaurus schema
* Resolves #6925: refactor thesaurus form, fix validation issue
* Resolves #6925: fix metadata bug that prevent to save multiple key for each thesaurus
* Bump urllib3 from 1.26.2 to 1.26.3 (#6908)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.2 to 1.26.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/1.26.3/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.2...1.26.3)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Toni
* [Fixes #6880] Circle CI upload tests fail irregulary (#6881)
* [Fixes #6880] Circle CI upload tests fail irregulary
* CircleCI test fix: sometimes expires due to upload timeout in the test environment
* - Avoid infinite loop on upload testing
* Revert "CircleCI test fix: sometimes expires due to upload timeout in the test environment"
This reverts commit 66139fdbf0b7510a9829a3e01254f41782fb7e1d.
Co-authored-by: Alessio Fabiani
Co-authored-by: afabiani
* [Fixes #6914] Remove "add to basket" tool for documents and maps (#6915)
* Added malnajdi as contributor
* [Fixes #6910] meaningful filename for document download (#6911)
* get meaningful document filenames on download
* - Strip extension from document title before slugify it (e.g.: image.jpg instead of imagejpg.jpg)
Co-authored-by: afabiani
Co-authored-by: Alessio Fabiani
* - CircleCI Upload Tests: trying to reduce more the risk of infinite loop on "wait_for_progress"
* [Fixes #6916] gsimporter.api.NotFound caused by missing trailing slash at the end of GEOSERVER_LOCATION (#6913)
* [Fixes #6916] gsimporter.api.NotFound caused by missing trailing slash at the end of GEOSERVER_LOCATION
* [Fixes #6916] unit test for GEOSERVER_LOCATION
* Bump django-cors-headers from 3.6.0 to 3.7.0 (#6901)
Bumps [django-cors-headers](https://github.com/adamchainz/django-cors-headers) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/adamchainz/django-cors-headers/releases)
- [Changelog](https://github.com/adamchainz/django-cors-headers/blob/master/HISTORY.rst)
- [Commits](https://github.com/adamchainz/django-cors-headers/compare/3.6.0...3.7.0)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Bump amqp from 5.0.3 to 5.0.5 (#6905)
Bumps [amqp](https://github.com/celery/py-amqp) from 5.0.3 to 5.0.5.
- [Release notes](https://github.com/celery/py-amqp/releases)
- [Changelog](https://github.com/celery/py-amqp/blob/master/Changelog)
- [Commits](https://github.com/celery/py-amqp/compare/v5.0.3...v5.0.5)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Bump pip from 21.0 to 21.0.1 (#6900)
Bumps [pip](https://github.com/pypa/pip) from 21.0 to 21.0.1.
- [Release notes](https://github.com/pypa/pip/releases)
- [Changelog](https://github.com/pypa/pip/blob/master/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/21.0...21.0.1)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Bump coverage from 5.3.1 to 5.4 (#6903)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 5.3.1 to 5.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/coverage-5.3.1...coverage-5.4)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Bump pytest from 6.2.1 to 6.2.2 (#6907)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.1...6.2.2)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Bump djangorestframework-gis from 0.16 to 0.17 (#6902)
Bumps [djangorestframework-gis](https://github.com/openwisp/django-rest-framework-gis) from 0.16 to 0.17.
- [Release notes](https://github.com/openwisp/django-rest-framework-gis/releases)
- [Changelog](https://github.com/openwisp/django-rest-framework-gis/blob/master/CHANGES.rst)
- [Commits](https://github.com/openwisp/django-rest-framework-gis/compare/v0.16.0...v0.17.0)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* - Algin setup.cfg to requirements.txt
* [Fixes #6922][REST API v2] Expose the curated thumbnail URL if it has… (#6923)
* [Fixes #6922][REST API v2] Expose the curated thumbnail URL if it has been uploaded
* - Add REST APIs test suite to CircleCI
* [Fixes #6918] Removal of QGIS support (#6919)
* [Cleanup and Refactor] Remove QGIS server backend dependencies
* [Cleanup and Refactor] Remove QGIS server backend dependencies
* - Fix LGTM issues
* Resolves #6925: add deprecation warning for old thesaurus settings
* allow Basic authenticated requests in LOCKDOWN mode
* Resolves #6925: change metadata template to let it reads the new thesaurus information as template tags, add tests for new template tags
* Resolves #6925: add thesaurus in admin page
* fix to avoid circular import
* flake8 check fix
* Resolves #6925: reformatting code for match Flake8 requirements
* added tests
* Resolves #6925: fix default values for all required in Thesaurus Model
* Resolves #6925: add test for ThesaurusAvailableModelForm
* Resolves #6925: minor refactor in ThesaurusForm initialization
* Resolves #6925: thesaurus initial data
* Resolves #6925: fix flake8 style errors
* Refers #6925: add missing migration file
* Resolves #6925: fix thesaurus show method by adding facet as filter
* Resolves #6925: fix flake8 errors
* Resolves #6925: fix lgmt bot issue
* Resolves #6925: add missing prefix for thesaurus
* Fixes #6925: remote RNDT information from the tests
* Fixes #6925: italian wordings updated to english
Co-authored-by: Giovanni Allegri
Co-authored-by: allyoucanmap
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Toni
Co-authored-by: Alessio Fabiani
Co-authored-by: afabiani
Co-authored-by: Florian Hoedt
Co-authored-by: Mohammed Y. Alnajdi
Co-authored-by: biegan
---
geonode/base/fixtures/initial_data.json | 266 +++++++++++++++++-
geonode/base/forms.py | 82 ++++--
.../management/commands/load_thesaurus.py | 47 +++-
.../migrations/0050_auto_20210201_1008.py | 33 +++
.../migrations/0051_auto_20210202_1656.py | 28 ++
.../migrations/0052_auto_20210205_1631.py | 23 ++
geonode/base/models.py | 26 ++
geonode/base/templatetags/thesaurus.py | 26 ++
geonode/base/tests.py | 80 +++++-
geonode/base/urls.py | 7 +-
geonode/base/views.py | 18 ++
.../templates/catalogue/full_metadata.xml | 35 +++
geonode/context_processors.py | 12 +-
geonode/layers/admin.py | 7 +-
.../templates/layers/layer_metadata.html | 5 +-
geonode/layers/templates/layouts/panels.html | 18 +-
geonode/layers/views.py | 76 +++--
.../migrations/0030_auto_20210201_0953.py | 18 ++
.../migrations/0031_merge_20210205_0824.py | 14 +
geonode/tests/data/thesaurus.rdf | 22 ++
geonode/tests/test_load_thesaurus.py | 147 ++++++++++
21 files changed, 920 insertions(+), 70 deletions(-)
create mode 100644 geonode/base/migrations/0050_auto_20210201_1008.py
create mode 100644 geonode/base/migrations/0051_auto_20210202_1656.py
create mode 100644 geonode/base/migrations/0052_auto_20210205_1631.py
create mode 100644 geonode/base/templatetags/thesaurus.py
create mode 100644 geonode/people/migrations/0030_auto_20210201_0953.py
create mode 100644 geonode/people/migrations/0031_merge_20210205_0824.py
create mode 100644 geonode/tests/data/thesaurus.rdf
create mode 100644 geonode/tests/test_load_thesaurus.py
diff --git a/geonode/base/fixtures/initial_data.json b/geonode/base/fixtures/initial_data.json
index 74c433f5de3..564bf484af7 100644
--- a/geonode/base/fixtures/initial_data.json
+++ b/geonode/base/fixtures/initial_data.json
@@ -4852,5 +4852,269 @@
"fields": {
"name": "TOPBAR_MENU"
}
- }
+ },
+ {
+ "model": "base.thesaurus",
+ "pk": 1,
+ "fields": {
+ "identifier": "inspire_themes",
+ "title": "GEMET - INSPIRE themes, version 1.0",
+ "date": "2018-05-23T10:25:56",
+ "description": "GEMET - INSPIRE themes, version 1.0",
+ "slug": "",
+ "about": "http://inspire.ec.europa.eu/theme",
+ "card_min": 1,
+ "card_max": 0,
+ "facet": true
+ }
+ },
+ {
+ "model": "base.thesaurus",
+ "pk": 4,
+ "fields": {
+ "identifier": "abc-all1",
+ "title": "Register of data of general interest",
+ "date": "2018-06-25",
+ "description": "Register of data of general interest",
+ "slug": "",
+ "about": "https://registry.geodati.gov.it/abc-all1",
+ "card_min": 0,
+ "card_max": 1,
+ "facet": true
+ }
+ },
+ {
+ "model": "base.thesaurus",
+ "pk": 6,
+ "fields": {
+ "identifier": "limitation_on_public_access",
+ "title": "Limitations on public access",
+ "date": "2020-10-22T13:21:33",
+ "description": "Limitations on public access",
+ "slug": "",
+ "about": "http://inspire.ec.europa.eu/metadata-codelist/LimitationsOnPublicAccess",
+ "card_min": 1,
+ "card_max": 1,
+ "facet": true
+ }
+ },
+ {
+ "model": "base.thesaurus",
+ "pk": 7,
+ "fields": {
+ "identifier": "limitation_on_public_access-2",
+ "title": "Conditions Applying To Access and Use",
+ "date": "2020-10-30T16:58:34",
+ "description": "Conditions Applying To Access and Use",
+ "slug": "",
+ "about": "http://inspire.ec.europa.eu/metadata-codelist/ConditionsApplyingToAccessAndUse",
+ "card_min": 0,
+ "card_max": -1,
+ "facet": true
+ }
+ },
+ {
+ "model": "base.thesaurus",
+ "pk": 8,
+ "fields": {
+ "identifier": "foo_name",
+ "title": "Mocked Title",
+ "date": "2018-05-23T10:25:56",
+ "description": "Mocked Title",
+ "slug": "abc",
+ "about": "http://inspire.ec.europa.eu/theme",
+ "card_min": 1,
+ "card_max": -1,
+ "facet": true
+ }
+ },
+ {
+ "model": "base.thesauruskeyword",
+ "pk": 1,
+ "fields": {
+ "about": "http://inspire.ec.europa.eu/theme/ad",
+ "alt_label": "ad",
+ "thesaurus": 1
+ }
+ },
+ {
+ "model": "base.thesauruskeyword",
+ "pk": 2,
+ "fields": {
+ "about": "http://inspire.ec.europa.eu/theme/au",
+ "alt_label": "au",
+ "thesaurus": 1
+ }
+ },
+ {
+ "model": "base.thesauruskeyword",
+ "pk": 3,
+ "fields": {
+ "about": "http://inspire.ec.europa.eu/theme/rs",
+ "alt_label": "rs",
+ "thesaurus": 1
+ }
+ },
+ {
+ "model": "base.thesauruskeyword",
+ "pk": 35,
+ "fields": {
+ "about": "http://inspire.ec.europa.eu/metadata-codelist/LimitationsOnPublicAccess/INSPIRE_Directive_Article13_1a",
+ "alt_label": "public access limited according to Article 13(1)(a) of the INSPIRE Directive [ITA]",
+ "thesaurus": 6
+ }
+ },
+ {
+ "model": "base.thesauruskeyword",
+ "pk": 44,
+ "fields": {
+ "about": "http://inspire.ec.europa.eu/metadata-codelist/ConditionsApplyingToAccessAndUse/noConditionsApply",
+ "alt_label": "No condition applicable",
+ "thesaurus": 7
+ }
+ },
+ {
+ "model": "base.thesauruskeyword",
+ "pk": 45,
+ "fields": {
+ "about": "http://inspire.ec.europa.eu/metadata-codelist/ConditionsApplyingToAccessAndUse/conditionsUnknown",
+ "alt_label": "Unknown Conditions",
+ "thesaurus": 7
+ }
+ },
+ {
+ "model": "base.thesauruskeyword",
+ "pk": 100,
+ "fields": {
+ "about": "http://test.com",
+ "alt_label": "Protected sites",
+ "thesaurus": 8
+ }
+ },
+ {
+ "model": "base.thesauruskeywordlabel",
+ "pk": 1,
+ "fields": {
+ "lang": "en",
+ "label": "Addresses",
+ "keyword": 1
+ }
+ },
+ {
+ "model": "base.thesauruskeywordlabel",
+ "pk": 2,
+ "fields": {
+ "lang": "de",
+ "label": "Adressen",
+ "keyword": 1
+ }
+ },
+ {
+ "model": "base.thesauruskeywordlabel",
+ "pk": 3,
+ "fields": {
+ "lang": "en",
+ "label": "Administrative units",
+ "keyword": 2
+ }
+ },
+ {
+ "model": "base.thesauruskeywordlabel",
+ "pk": 4,
+ "fields": {
+ "lang": "en",
+ "label": "Coordinate reference systems",
+ "keyword": 3
+ }
+ },
+ {
+ "model": "base.thesauruskeywordlabel",
+ "pk": 36,
+ "fields": {
+ "lang": "en",
+ "label": "public access limited according to Article 13(1)(a) of the INSPIRE Directive",
+ "keyword": 35
+ }
+ },
+ {
+ "model": "base.thesauruskeywordlabel",
+ "pk": 54,
+ "fields": {
+ "lang": "en",
+ "label": "no conditions to access and use",
+ "keyword": 44
+ }
+ },
+ {
+ "model": "base.thesauruskeywordlabel",
+ "pk": 56,
+ "fields": {
+ "lang": "en",
+ "label": "conditions to access and use unknown",
+ "keyword": 45
+ }
+ },
+ {
+ "model": "base.thesauruskeywordlabel",
+ "pk": 102,
+ "fields": {
+ "lang": "en",
+ "label": "Protected sites",
+ "keyword": 100
+ }
+ },
+ {
+ "model": "base.thesauruslabel",
+ "pk": 1,
+ "fields": {
+ "lang": "en",
+ "label": "Addresses",
+ "thesaurus": 1
+ }
+ },
+ {
+ "model": "base.thesauruslabel",
+ "pk": 2,
+ "fields": {
+ "lang": "de",
+ "label": "Adressen",
+ "thesaurus": 1
+ }
+ },
+ {
+ "model": "base.thesauruslabel",
+ "pk": 4,
+ "fields": {
+ "lang": "en",
+ "label": "Register of the reference data sets",
+ "thesaurus": 4
+ }
+ },
+ {
+ "model": "base.thesauruslabel",
+ "pk": 5,
+ "fields": {
+ "lang": "en",
+ "label": "Limitations on public access",
+ "thesaurus": 6
+ }
+ },
+ {
+ "model": "base.thesauruslabel",
+ "pk": 7,
+ "fields": {
+ "lang": "en",
+ "label": "Conditions Applying To Access and Use",
+ "thesaurus": 7
+ }
+ },
+ {
+ "model": "base.thesauruslabel",
+ "pk": 9,
+ "fields": {
+ "lang": "en",
+ "label": "Register of the reference data sets",
+ "thesaurus": 8
+ }
+ }
]
diff --git a/geonode/base/forms.py b/geonode/base/forms.py
index f2817ad3df7..f695e82604e 100644
--- a/geonode/base/forms.py
+++ b/geonode/base/forms.py
@@ -20,43 +20,37 @@
import re
import html
import logging
-
-from tinymce.widgets import TinyMCE
-
-from .fields import MultiThesauriField
-
+from django.db.models.query import QuerySet
+from bootstrap3_datetime.widgets import DateTimePicker
from dal import autocomplete
-from taggit.forms import TagField
-
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core import validators
from django.db.models import Prefetch, Q
-from django.forms import models
-from django.forms import ModelForm
+from django.forms import ModelForm, models
from django.forms.fields import ChoiceField
from django.forms.utils import flatatt
+from django.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
-
-from django.utils.encoding import (
- force_text,
-)
-
-from bootstrap3_datetime.widgets import DateTimePicker
from modeltranslation.forms import TranslationModelForm
+from taggit.forms import TagField
+from tinymce.widgets import TinyMCE
-from geonode.base.models import HierarchicalKeyword, TopicCategory, Region, License, CuratedThumbnail, \
- ResourceBase
-from geonode.base.models import ThesaurusKeyword, ThesaurusKeywordLabel
-from geonode.documents.models import Document
from geonode.base.enumerations import ALL_LANGUAGES
+from geonode.base.models import (CuratedThumbnail, HierarchicalKeyword,
+ License, Region, ResourceBase, Thesaurus,
+ ThesaurusKeyword, ThesaurusKeywordLabel, ThesaurusLabel,
+ TopicCategory)
from geonode.base.widgets import TaggitSelect2Custom
+from geonode.documents.models import Document
from geonode.layers.models import Layer
+from .fields import MultiThesauriField
+
logger = logging.getLogger(__name__)
@@ -320,6 +314,56 @@ class Meta:
)
+class ThesaurusAvailableForm(forms.Form):
+ def __init__(self, *args, **kwargs):
+ super(ThesaurusAvailableForm, self).__init__(*args, **kwargs)
+ lang = settings.THESAURUS_DEFAULT_LANG if hasattr(settings, "THESAURUS_DEFAULT_LANG") else "en"
+ for item in Thesaurus.objects.all():
+ tname = ThesaurusLabel.objects.values_list("label", flat=True).filter(id=item.id).filter(lang=lang)
+ if item.card_max == 0:
+ continue
+ elif item.card_max == 1 and item.card_min == 0:
+ self.fields[f"{item.id}"] = self._define_choicefield(item, False, tname, lang)
+ elif item.card_max == 1 and item.card_min == 1:
+ self.fields[f"{item.id}"] = self._define_choicefield(item, True, tname, lang)
+ elif item.card_max == -1 and item.card_min == 0:
+ self.fields[f"{item.id}"] = self._define_multifield(item, False, tname, lang)
+ elif item.card_max == -1 and item.card_min == 1:
+ self.fields[f"{item.id}"] = self._define_multifield(item, True, tname, lang)
+
+ def cleanx(self, x):
+ cleaned_values = []
+ for key, value in x.items():
+ if isinstance(value, QuerySet):
+ for y in value:
+ cleaned_values.append(y.id)
+ elif value:
+ cleaned_values.append(value.id)
+ return ThesaurusKeyword.objects.filter(id__in=cleaned_values)
+
+ @staticmethod
+ def _define_multifield(item, required, tname, lang):
+ return MultiThesauriField(
+ ThesaurusKeyword.objects.prefetch_related(
+ Prefetch(
+ "keyword",
+ queryset=ThesaurusKeywordLabel.objects.filter(keyword__thesaurus_id=item.id).filter(lang=lang),
+ )
+ ),
+ widget=autocomplete.ModelSelect2Multiple(url=f"/base/thesaurus_available/?sysid={item.id}&lang={lang}"),
+ label=_(f"{tname[0] if len(tname) > 0 else item.title}"),
+ required=required,
+ )
+
+ @staticmethod
+ def _define_choicefield(item, required, tname, lang):
+ return models.ModelChoiceField(
+ label=f"{tname[0] if len(tname) > 0 else item.title}",
+ required=required,
+ queryset=ThesaurusKeywordLabel.objects.filter(keyword__thesaurus_id=item.id).filter(lang=lang),
+ )
+
+
class ResourceBaseDateTimePicker(DateTimePicker):
def build_attrs(self, base_attrs=None, extra_attrs=None, **kwargs):
diff --git a/geonode/base/management/commands/load_thesaurus.py b/geonode/base/management/commands/load_thesaurus.py
index b95f195cd04..01af25d3f5c 100644
--- a/geonode/base/management/commands/load_thesaurus.py
+++ b/geonode/base/management/commands/load_thesaurus.py
@@ -18,11 +18,13 @@
#
#########################################################################
+from typing import List
from defusedxml import lxml as dlxml
+from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
-from geonode.base.models import Thesaurus, ThesaurusKeyword, ThesaurusKeywordLabel
+from geonode.base.models import Thesaurus, ThesaurusKeyword, ThesaurusKeywordLabel, ThesaurusLabel
class Command(BaseCommand):
@@ -90,25 +92,46 @@ def load_thesaurus(self, input_file, name, store):
if not scheme:
raise CommandError("ConceptScheme not found in file")
- title = scheme.find('dc:title', ns).text
- descr = scheme.find('dc:description', ns).text if scheme.find('dc:description', ns) else title
+ titles = scheme.findall('dc:title', ns)
+
+ default_lang = getattr(settings, 'THESAURUS_DEFAULT_LANG', None)
+ available_lang = get_all_lang_available_with_title(titles, LANG_ATTRIB)
+ thesaurus_title = determinate_value(available_lang, default_lang)
+
+ descr = scheme.find('dc:description', ns).text if scheme.find('dc:description', ns) else thesaurus_title
date_issued = scheme.find('dcterms:issued', ns).text
+ about = scheme.attrib.get(ABOUT_ATTRIB)
- print('Thesaurus "{}" issued on {}'.format(title, date_issued))
+ print('Thesaurus "{}" issued on {}'.format(thesaurus_title, date_issued))
thesaurus = Thesaurus()
thesaurus.identifier = name
- thesaurus.title = title
+ thesaurus.title = thesaurus_title
thesaurus.description = descr
+ thesaurus.about = about
thesaurus.date = date_issued
if store:
thesaurus.save()
+ for lang in available_lang:
+ if lang[0] is not None:
+ thesaurus_label = ThesaurusLabel()
+ thesaurus_label.lang = lang[0]
+ thesaurus_label.label = lang[1]
+ thesaurus_label.thesaurus = thesaurus
+ thesaurus_label.save()
+
for concept in root.findall('skos:Concept', ns):
about = concept.attrib.get(ABOUT_ATTRIB)
- alt_label = concept.find('skos:altLabel', ns).text
+ alt_label = concept.find('skos:altLabel', ns)
+ if alt_label is not None:
+ alt_label = alt_label.text
+ else:
+ concepts = concept.findall('skos:prefLabel', ns)
+ available_lang = get_all_lang_available_with_title(concepts, LANG_ATTRIB)
+ alt_label = determinate_value(available_lang, default_lang)
print('Concept {} ({})'.format(alt_label, about))
@@ -157,3 +180,15 @@ def create_fake_thesaurus(self, name):
tkl.lang = l
tkl.label = keyword + "_l_" + l + "_t_" + name
tkl.save()
+
+def get_all_lang_available_with_title(items: List, LANG_ATTRIB: str):
+ return [(item.attrib.get(LANG_ATTRIB), item.text) for item in items]
+
+def determinate_value(available_lang: List, default_lang: str):
+ sorted_lang = sorted(available_lang, key= lambda lang: '' if lang[0] is None else lang[0])
+ for item in sorted_lang:
+ if item[0] is None:
+ return item[1]
+ elif item[0] == default_lang:
+ return item[1]
+ return available_lang[0][1]
diff --git a/geonode/base/migrations/0050_auto_20210201_1008.py b/geonode/base/migrations/0050_auto_20210201_1008.py
new file mode 100644
index 00000000000..f51b59453a8
--- /dev/null
+++ b/geonode/base/migrations/0050_auto_20210201_1008.py
@@ -0,0 +1,33 @@
+# Generated by Django 2.2.16 on 2021-02-01 10:08
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('base', '0049_resourcebase_resource_type'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='thesaurus',
+ name='about',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ migrations.CreateModel(
+ name='ThesaurusLabel',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('lang', models.CharField(max_length=3)),
+ ('label', models.CharField(max_length=255)),
+ ('thesaurus', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rel_thesaurus', to='base.Thesaurus')),
+ ],
+ options={
+ 'verbose_name_plural': 'Thesaurus Labels',
+ 'ordering': ('lang',),
+ 'unique_together': {('thesaurus', 'lang')},
+ },
+ ),
+ ]
diff --git a/geonode/base/migrations/0051_auto_20210202_1656.py b/geonode/base/migrations/0051_auto_20210202_1656.py
new file mode 100644
index 00000000000..711e15e0cbe
--- /dev/null
+++ b/geonode/base/migrations/0051_auto_20210202_1656.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.2.16 on 2021-02-02 16:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('base', '0050_auto_20210201_1008'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='thesaurus',
+ name='card_max',
+ field=models.IntegerField(default=True),
+ ),
+ migrations.AddField(
+ model_name='thesaurus',
+ name='card_min',
+ field=models.IntegerField(default=True),
+ ),
+ migrations.AddField(
+ model_name='thesaurus',
+ name='facet',
+ field=models.BooleanField(default=True),
+ ),
+ ]
diff --git a/geonode/base/migrations/0052_auto_20210205_1631.py b/geonode/base/migrations/0052_auto_20210205_1631.py
new file mode 100644
index 00000000000..2b7c17ede41
--- /dev/null
+++ b/geonode/base/migrations/0052_auto_20210205_1631.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.2.16 on 2021-02-05 16:31
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('base', '0051_auto_20210202_1656'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='thesaurus',
+ name='card_max',
+ field=models.IntegerField(default=-1),
+ ),
+ migrations.AlterField(
+ model_name='thesaurus',
+ name='card_min',
+ field=models.IntegerField(default=0),
+ ),
+ ]
diff --git a/geonode/base/models.py b/geonode/base/models.py
index d19d63e78a0..084a3380d3e 100644
--- a/geonode/base/models.py
+++ b/geonode/base/models.py
@@ -471,6 +471,12 @@ class Thesaurus(models.Model):
slug = models.CharField(max_length=64, default='')
+ about = models.CharField(max_length=255, null=True, blank=True)
+
+ card_min = models.IntegerField(default=0)
+ card_max = models.IntegerField(default=-1)
+ facet = models.BooleanField(default=True)
+
def __str__(self):
return "{0}".format(self.identifier)
@@ -529,6 +535,26 @@ class Meta:
unique_together = (("thesaurus", "alt_label"),)
+class ThesaurusLabel(models.Model):
+ """
+ Contains localized version of the thesaurus title
+ """
+ # read from the RDF file
+ lang = models.CharField(max_length=3)
+ # read from the RDF file
+ label = models.CharField(max_length=255)
+
+ thesaurus = models.ForeignKey('Thesaurus', related_name='rel_thesaurus', on_delete=models.CASCADE)
+
+ def __str__(self):
+ return "{0}".format(self.label)
+
+ class Meta:
+ ordering = ("lang",)
+ verbose_name_plural = 'Thesaurus Labels'
+ unique_together = (("thesaurus", "lang"),)
+
+
class ResourceBaseManager(PolymorphicManager):
def admin_contact(self):
# this assumes there is at least one superuser
diff --git a/geonode/base/templatetags/thesaurus.py b/geonode/base/templatetags/thesaurus.py
new file mode 100644
index 00000000000..d7d6cf6577f
--- /dev/null
+++ b/geonode/base/templatetags/thesaurus.py
@@ -0,0 +1,26 @@
+from django import template
+from geonode.base.models import Thesaurus, ThesaurusKeywordLabel
+from django.conf import settings
+
+register = template.Library()
+
+
+@register.filter
+def get_unique_thesaurus_set(thesaurus_from_keyword):
+ return set(thesaurus_from_keyword.values_list("thesaurus", flat=True))
+
+
+@register.filter
+def get_keyword_label(keyword):
+ lang = "en" if not hasattr(settings, "THESAURUS_DEFAULT_LANG") else settings.THESAURUS_DEFAULT_LANG
+ return ThesaurusKeywordLabel.objects.filter(keyword=keyword).filter(lang=lang).first().label
+
+
+@register.filter
+def get_thesaurus_title(thesaurus_id):
+ return Thesaurus.objects.get(id=thesaurus_id).title
+
+
+@register.filter
+def get_thesaurus_date(thesaurus_id):
+ return Thesaurus.objects.get(id=thesaurus_id).date
diff --git a/geonode/base/tests.py b/geonode/base/tests.py
index bdb307acfd4..2889e851928 100644
--- a/geonode/base/tests.py
+++ b/geonode/base/tests.py
@@ -40,7 +40,10 @@
Menu,
MenuItem,
Configuration,
- TopicCategory)
+ TopicCategory,
+ Thesaurus,
+ ThesaurusKeyword
+)
from django.conf import settings
from django.template import Template, Context
from django.contrib.auth import get_user_model
@@ -51,6 +54,12 @@
from geonode.base.middleware import ReadOnlyMiddleware, MaintenanceMiddleware
from geonode.base.models import CuratedThumbnail
from geonode.base.templatetags.base_tags import get_visibile_resources
+from geonode.base.templatetags.thesaurus import (
+ get_unique_thesaurus_set,
+ get_keyword_label,
+ get_thesaurus_title,
+ get_thesaurus_date,
+)
from geonode.base.templatetags.user_messages import show_notification
from geonode import geoserver
from geonode.decorators import on_ogc_backend
@@ -58,6 +67,8 @@
from django.core.files import File
from django.core.management import call_command
from django.core.management.base import CommandError
+from geonode.base.forms import ThesaurusAvailableForm
+
test_image = Image.new('RGBA', size=(50, 50), color=(155, 0, 0))
@@ -879,3 +890,70 @@ def test_complex_tags_in_attribute(self):
r = ResourceBase()
filtered_value = r._remove_html_tags(tagged_value)
self.assertEqual(filtered_value, attribute_target_value)
+
+
+class TestTagThesaurus(TestCase):
+ # loading test thesausurs
+ @classmethod
+ def setUpTestData(cls):
+ from django.core import management
+ from os.path import dirname, abspath
+
+ management.call_command(
+ "load_thesaurus",
+ file=f"{dirname(dirname(abspath(__file__)))}/tests/data/thesaurus.rdf",
+ name="foo_name",
+ stdout="out",
+ )
+
+ def setUp(self):
+ self.sut = Thesaurus(
+ identifier="foo_name",
+ title="Mocked Title",
+ date="2018-05-23T10:25:56",
+ description="Mocked Title",
+ slug="",
+ about="http://inspire.ec.europa.eu/theme",
+ )
+ self.tkeywords = ThesaurusKeyword.objects.all()
+
+ def test_get_unique_thesaurus_list(self):
+ tid = self.__get_last_thesaurus().id
+ actual = get_unique_thesaurus_set(self.tkeywords)
+ self.assertSetEqual({tid}, actual)
+
+ @patch.dict("os.environ", {"THESAURUS_DEFAULT_LANG": "en"})
+ def test_get_keyword_label(self):
+ actual = get_keyword_label(self.tkeywords[0])
+ self.assertEqual("Addresses", actual)
+
+ def test_get_thesaurus_title(self):
+ tid = self.__get_last_thesaurus().id
+ actual = get_thesaurus_title(tid)
+ self.assertEqual(self.sut.title, actual)
+
+ def test_get_thesaurus_date(self):
+ tid = self.__get_last_thesaurus().id
+ actual = get_thesaurus_date(tid)
+ self.assertEqual(self.sut.date, actual)
+
+ @staticmethod
+ def __get_last_thesaurus():
+ return Thesaurus.objects.all().order_by("-id")[0]
+
+
+class TestThesaurusAvailableForm(GeoNodeBaseTestSupport):
+ def setUp(self):
+ self.sut = ThesaurusAvailableForm
+
+ def test_form_is_invalid_if_required_fields_are_missing(self):
+ actual = self.sut(data={})
+ self.assertFalse(actual.is_valid())
+
+ def test_form_is_invalid_if_fileds_send_unexpected_values(self):
+ actual = self.sut(data={"8": [1, 2], "6": 1234})
+ self.assertFalse(actual.is_valid())
+
+ def test_form_is_valid_if_fileds_send_expected_values(self):
+ actual = self.sut(data={"8": [1, 2], "6": 36})
+ self.assertTrue(actual.is_valid())
diff --git a/geonode/base/urls.py b/geonode/base/urls.py
index f433e4549fc..3a7ade5251d 100644
--- a/geonode/base/urls.py
+++ b/geonode/base/urls.py
@@ -21,7 +21,7 @@
from .views import (
ResourceBaseAutocomplete, RegionAutocomplete,
- HierarchicalKeywordAutocomplete, ThesaurusKeywordLabelAutocomplete, OwnerRightsRequestView)
+ HierarchicalKeywordAutocomplete, ThesaurusKeywordLabelAutocomplete, OwnerRightsRequestView, ThesaurusAvailable)
urlpatterns = [
@@ -43,6 +43,11 @@
name='autocomplete_hierachical_keyword',
),
+ url(
+ r'^thesaurus_available',
+ ThesaurusAvailable.as_view(),
+ name='thesaurus_available',
+ ),
url(
r'^thesaurus_autocomplete/$',
ThesaurusKeywordLabelAutocomplete.as_view(),
diff --git a/geonode/base/views.py b/geonode/base/views.py
index c54e464c4ad..c5f7f4b1630 100644
--- a/geonode/base/views.py
+++ b/geonode/base/views.py
@@ -319,6 +319,24 @@ def get_results(self, context):
]
+class ThesaurusAvailable(autocomplete.Select2QuerySetView):
+ def get_queryset(self):
+ tid = self.request.GET.get("sysid")
+ lang = self.request.GET.get("lang")
+
+ qs = ThesaurusKeywordLabel.objects.filter(keyword__thesaurus__id=tid).filter(lang=lang).order_by('id')
+ return qs
+
+ def get_results(self, context):
+ return [
+ {
+ 'id': self.get_result_value(result.keyword),
+ 'text': self.get_result_label(result),
+ 'selected_text': self.get_selected_result_label(result),
+ } for result in context['object_list']
+ ]
+
+
class OwnerRightsRequestView(LoginRequiredMixin, FormView):
template_name = 'owner_rights_request.html'
form_class = OwnerRightsRequestForm
diff --git a/geonode/catalogue/templates/catalogue/full_metadata.xml b/geonode/catalogue/templates/catalogue/full_metadata.xml
index 485a815f269..60083b591e6 100644
--- a/geonode/catalogue/templates/catalogue/full_metadata.xml
+++ b/geonode/catalogue/templates/catalogue/full_metadata.xml
@@ -1,3 +1,4 @@
+{% load thesaurus %}
{{layer.uuid}}
@@ -350,6 +351,40 @@
{% endif %}
+ {% if layer.tkeywords %}
+ {% for thesaurus_id in layer.tkeywords|get_unique_thesaurus_set %}
+
+
+ {% for keyword in layer.tkeywords.all %}
+ {% if keyword.thesaurus.id == thesaurus_id %}
+
+ {{keyword|get_keyword_label}}
+
+ {% endif %}
+ {% endfor %}
+
+
+
+ {{thesaurus_id|get_thesaurus_title}}
+
+
+
+
+ {{thesaurus_id|get_thesaurus_date}}
+
+
+ pubblicazione
+
+
+
+
+
+
+
+ {% endfor %}
+ {% endif %}
{% for region in layer.regions.all %}
diff --git a/geonode/context_processors.py b/geonode/context_processors.py
index 0ea8255eb90..add7156795f 100644
--- a/geonode/context_processors.py
+++ b/geonode/context_processors.py
@@ -18,19 +18,25 @@
#
#########################################################################
+import warnings
from django.conf import settings
from geonode import get_version
from geonode.catalogue import default_catalogue_backend
from django.contrib.sites.models import Site
from geonode.notifications_helper import has_notifications
-from geonode.base.models import Configuration
+from geonode.base.models import Configuration, Thesaurus
def resource_urls(request):
"""Global values to pass to templates"""
site = Site.objects.get_current()
-
+ thesaurus = Thesaurus.objects.filter(facet=True).all()
+ if hasattr(settings, 'THESAURUS'):
+ warnings.warn(
+ 'Thesaurus settings is going to be'
+ 'deprecated in the future versions, please move the settings to '
+ 'the new configuration ', FutureWarning)
defaults = dict(
STATIC_URL=settings.STATIC_URL,
CATALOGUE_BASE_URL=default_catalogue_backend()['URL'],
@@ -170,7 +176,7 @@ def resource_urls(request):
False
),
THESAURI_FILTERS=[t['name'] for t in [settings.THESAURUS, ] if
- t.get('filter')] if hasattr(settings, 'THESAURUS') else None,
+ t.get('filter')] if hasattr(settings, 'THESAURUS') else [t.identifier for t in thesaurus],
MAP_CLIENT_USE_CROSS_ORIGIN_CREDENTIALS=getattr(
settings, 'MAP_CLIENT_USE_CROSS_ORIGIN_CREDENTIALS', False
),
diff --git a/geonode/layers/admin.py b/geonode/layers/admin.py
index 4f7c4bc0e33..10a65bca294 100644
--- a/geonode/layers/admin.py
+++ b/geonode/layers/admin.py
@@ -29,7 +29,7 @@
from geonode.layers.models import LayerFile, UploadSession
from geonode.base.fields import MultiThesauriField
-from geonode.base.models import ThesaurusKeyword, ThesaurusKeywordLabel
+from geonode.base.models import ThesaurusKeyword, ThesaurusKeywordLabel, Thesaurus
from dal import autocomplete
@@ -98,6 +98,10 @@ class AttributeAdmin(admin.ModelAdmin):
search_fields = ('attribute', 'attribute_label',)
+class ThesaurusAdmin(admin.ModelAdmin):
+ model = Thesaurus
+
+
class StyleAdmin(admin.ModelAdmin):
model = Style
list_display_links = ('sld_title',)
@@ -120,3 +124,4 @@ class UploadSessionAdmin(admin.ModelAdmin):
admin.site.register(Attribute, AttributeAdmin)
admin.site.register(Style, StyleAdmin)
admin.site.register(UploadSession, UploadSessionAdmin)
+admin.site.register(Thesaurus, ThesaurusAdmin)
diff --git a/geonode/layers/templates/layers/layer_metadata.html b/geonode/layers/templates/layers/layer_metadata.html
index ee9d784c07a..ff8237cb01c 100644
--- a/geonode/layers/templates/layers/layer_metadata.html
+++ b/geonode/layers/templates/layers/layer_metadata.html
@@ -42,7 +42,7 @@
{% trans "Metadata" %} {% blocktrans with layer.titl
Some of your original metadata may have been lost.{% endblocktrans %}
{% endif %}
- {% if layer_form.errors or attribute_form.errors or category_form.errors or author_form.errors or poc.errors %}
+ {% if layer_form.errors or attribute_form.errors or category_form.errors or author_form.errors or poc.errors or tkeywords_form.errors %}
{% blocktrans %}Error updating metadata. Please check the following fields: {% endblocktrans %}
{% if author_form.errors %}
@@ -69,6 +69,9 @@
{% trans "Metadata" %} {% blocktrans with layer.titl
{% if category_form.errors %}