Skip to content

Commit

Permalink
Merge branch 'master' into admin_match_label_lang
Browse files Browse the repository at this point in the history
  • Loading branch information
Vanderhaegen Cedrik committed Dec 22, 2014
2 parents f209215 + ee382eb commit 9e16130
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 15 deletions.
19 changes: 14 additions & 5 deletions CHANGES.rst
@@ -1,5 +1,5 @@
0.4.0 (???)
-----------
0.4.0 (??-12-2014)
------------------

- Update to skosprovider_ 0.5.0. Among other things, this makes it possible
to handle relations between Concepts and Collections using the
Expand All @@ -22,15 +22,22 @@
external ConceptSchemes through properties such as *skos:exactMatch* and
*skos:closeMatch*.
- Ability to import Concepts and Collections from external providers. This
makes it possible to import Concepts from eg. the AAT (via skosprovider_getty_)
or any other SKOS vocabulary for which a skosprovider_ has been written.
makes it possible to import Concepts from eg. the AAT (via skosprovider_getty_),
Flanders Heritage Thesauri (via skosprovider_oe_),
English Heritage Thesauri (via skosprovider_heritagedata_) or any other
SKOS vocabulary for which a skosprovider_ has been written. Currently only
the concept or collection itself can be imported, without its relations to
other concepts or collections.
- Add the ability to have a delete of a concept or collection fail if it is
being used in other systems.
- Implement a delete permission.
- Add validation rule that a Concept must have at least one label.
- Update to skosprovider_sqlalchemy_ 0.4.1.
- Update to pyramid_skosprovider_ 0.5.0.
- Update to skosprovider_rdf_ 0.3.0. This update adds support for dumping
ConceptScheme in an RDF file and also handles *subordinate_arrays* and
*superordinates*.
- Update to language_tags_ 0.3.0.

0.3.1 (05-09-2014)
------------------
Expand Down Expand Up @@ -82,5 +89,7 @@
.. _skosprovider_sqlalchemy: http://skosprovider-sqlalchemy.readthedocs.org
.. _skosprovider_rdf: http://skosprovider-rdf.readthedocs.org
.. _skosprovider_getty: http://skosprovider-getty.readthedocs.org
.. _skosprovider_oe: https://github.com/koenedaele/skosprovider_oe
.. _skosprovider_heritagedata: http://skosprovider-heritagedata.readthedocs.org
.. _pyramid_skosprovider: http://pyramid-skosprovider.readthedocs.org
.. _language_tag: http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
.. _language_tags: http://language-tags.readthedocs.org
4 changes: 3 additions & 1 deletion atramhasis/renderers.py
Expand Up @@ -82,6 +82,7 @@ def concept_adapter(obj, request):
'narrower': [map_relation(c) for c in obj.narrower_concepts],
'related': [map_relation(c) for c in obj.related_concepts],
'member_of': [map_relation(c) for c in obj.member_of],
'subordinate_arrays': [map_relation(c) for c in obj.narrower_collections],
'matches': matches
}

Expand All @@ -100,7 +101,8 @@ def collection_adapter(obj, request):
'label': obj.label().label if obj.label() else None,
'labels': obj.labels,
'members': [map_relation(c) for c in obj.members],
'member_of': [map_relation(c) for c in obj.member_of]
'member_of': [map_relation(c) for c in obj.member_of],
'superordinates': [map_relation(c) for c in obj.broader_concepts]
}


Expand Down
6 changes: 6 additions & 0 deletions atramhasis/skos/__init__.py
Expand Up @@ -52,6 +52,11 @@ def includeme(config): # pragma: no cover
scheme_uri='http://purl.org/heritagedata/schemes/eh_tmt2'
)

EH_MATERIALS = HeritagedataProvider(
{'id': 'EH_MATERIALS', 'subject': ['external']},
scheme_uri='http://purl.org/heritagedata/schemes/eh_tbm'
)

skosregis = config.get_skos_registry()
skosregis.register_provider(TREES)
skosregis.register_provider(GEO)
Expand All @@ -61,3 +66,4 @@ def includeme(config): # pragma: no cover
skosregis.register_provider(TGN)
skosregis.register_provider(EH_PERIOD)
skosregis.register_provider(EH_MONUMENT_TYPE)
skosregis.register_provider(EH_MATERIALS)
103 changes: 99 additions & 4 deletions atramhasis/validators.py
Expand Up @@ -83,6 +83,8 @@ class Concept(colander.MappingSchema):
related = Concepts(missing=[])
members = Concepts(missing=[])
member_of = Concepts(missing=[])
subordinate_arrays = Concepts(missing=[])
superordinates = Concepts(missing=[])
matches = Matches(missing={})


Expand Down Expand Up @@ -163,6 +165,20 @@ def concept_schema_validator(node, cstruct):
concept_matches_rule(errors, node['matches'], matches, concept_type)
concept_matches_unique_rule(errors, node['matches'], matches)

if 'subordinate_arrays' in cstruct:
subordinate_arrays = copy.deepcopy(cstruct['subordinate_arrays'])
subordinate_arrays = [m['id'] for m in subordinate_arrays]
subordinate_arrays_only_in_concept_rule(errors, node['subordinate_arrays'], concept_type, subordinate_arrays)
subordinate_arrays_type_rule(errors, node['subordinate_arrays'], request, conceptscheme_id, subordinate_arrays)
subordinate_arrays_hierarchy_rule(errors, node['subordinate_arrays'], request, conceptscheme_id, cstruct)

if 'superordinates' in cstruct:
superordinates = copy.deepcopy(cstruct['superordinates'])
superordinates = [m['id'] for m in superordinates]
superordinates_only_in_concept_rule(errors, node['superordinates'], concept_type, superordinates)
superordinates_type_rule(errors, node['superordinates'], request, conceptscheme_id, superordinates)
superordinates_hierarchy_rule(errors, node['superordinates'], request, conceptscheme_id, cstruct)

if len(errors) > 0:
raise ValidationError(
'Concept could not be validated',
Expand Down Expand Up @@ -286,7 +302,7 @@ def broader_hierarchy_rule(errors, node_location, request, conceptscheme_id, cst
def narrower_hierarchy_build(request, conceptscheme_id, narrower, narrower_hierarchy):
for narrower_concept_id in narrower:
narrower_concept = request.db.query(Thing).filter_by(concept_id=narrower_concept_id,
conceptscheme_id=conceptscheme_id).one()
conceptscheme_id=conceptscheme_id).one()
if narrower_concept is not None and narrower_concept.type == 'concept':
narrower_concepts = [n.concept_id for n in narrower_concept.narrower_concepts]
for narrower_id in narrower_concepts:
Expand Down Expand Up @@ -361,8 +377,11 @@ def memberof_hierarchy_rule(errors, node_location, request, conceptscheme_id, cs

def members_hierarchy_build(request, conceptscheme_id, members, members_hierarchy):
for members_concept_id in members:
members_concept = request.db.query(Thing).filter_by(concept_id=members_concept_id,
conceptscheme_id=conceptscheme_id).one()
try:
members_concept = request.db.query(Thing).filter_by(concept_id=members_concept_id,
conceptscheme_id=conceptscheme_id).one()
except NoResultFound:
members_concept = None
if members_concept is not None and members_concept.type == 'collection':
members_concepts = [n.concept_id for n in members_concept.members]
for members_id in members_concepts:
Expand Down Expand Up @@ -451,4 +470,80 @@ def languagetag_checkduplicate(node, language_tag, request, errors):
errors.append(colander.Invalid(
node,
'Duplicate language tag: %s' % language_tag)
)
)


def subordinate_arrays_only_in_concept_rule(errors, node, concept_type, subordinate_arrays):
if concept_type != 'concept' and len(subordinate_arrays) > 0:
errors.append(colander.Invalid(
node,
'Only concept can have subordinate arrays.'
))


def subordinate_arrays_type_rule(errors, node_location, request, conceptscheme_id, subordinate_arrays):
for subordinate_id in subordinate_arrays:
subordinate = request.db.query(Thing).filter_by(concept_id=subordinate_id,
conceptscheme_id=conceptscheme_id).one()
if subordinate.type != 'collection':
errors.append(colander.Invalid(
node_location,
'A subordinate array should always be a collection'
))


def subordinate_arrays_hierarchy_rule(errors, node_location, request, conceptscheme_id, cstruct):
member_of_hierarchy = []
subordinate_arrays = []
if 'subordinate_arrays' in cstruct:
subordinate_arrays = copy.deepcopy(cstruct['subordinate_arrays'])
subordinate_arrays = [m['id'] for m in subordinate_arrays]
if 'member_of' in cstruct:
member_of = copy.deepcopy(cstruct['member_of'])
member_of = [m['id'] for m in member_of]
member_of_hierarchy = member_of
members_hierarchy_build(request, conceptscheme_id, member_of, member_of_hierarchy)
for subordinate_array_id in subordinate_arrays:
if subordinate_array_id in member_of_hierarchy:
errors.append(colander.Invalid(
node_location,
'The subordinate_array collection of a concept must not itself be a parent of the concept being edited.'
))


def superordinates_only_in_concept_rule(errors, node, concept_type, superordinates):
if concept_type != 'collection' and len(superordinates) > 0:
errors.append(colander.Invalid(
node,
'Only collection can have superordinates.'
))


def superordinates_type_rule(errors, node_location, request, conceptscheme_id, superordinates):
for superordinate_id in superordinates:
superordinate = request.db.query(Thing).filter_by(concept_id=superordinate_id,
conceptscheme_id=conceptscheme_id).one()
if superordinate.type != 'concept':
errors.append(colander.Invalid(
node_location,
'A superordinate should always be a concept'
))


def superordinates_hierarchy_rule(errors, node_location, request, conceptscheme_id, cstruct):
members_hierarchy = []
superordinates = []
if 'superordinates' in cstruct:
superordinates = copy.deepcopy(cstruct['superordinates'])
superordinates = [m['id'] for m in superordinates]
if 'members' in cstruct:
members = copy.deepcopy(cstruct['members'])
members = [m['id'] for m in members]
members_hierarchy = members
members_hierarchy_build(request, conceptscheme_id, members, members_hierarchy)
for superordinates_id in superordinates:
if superordinates_id in members_hierarchy:
errors.append(colander.Invalid(
node_location,
'The superordinates of a collection must not itself be a member of the collection being edited.'
))
14 changes: 14 additions & 0 deletions docs/source/intro.rst
Expand Up @@ -45,6 +45,18 @@ application.
accessible through the :class:`pyramid.request.Request`. Is also exposes a
set of readonly :ref:`REST services <pyramidskosprovider:services>` on the
registered providers.
* skosprovider_getty_:
An implemenation of the
:class:`VocabularyProvider <skosprovider.providers.VocabularyProvider>`
against the Linked Open Data vocabularies published by the Getty Research
Institute at `http://vocab.getty.edu <http://vocab.getty.edu>`_ such as the
`Art and Architecture Thesaurus (AAT)` and the
`Thesaurus of Geographic Names (TGN)`.
* skosprovider_heritagedata_:
An implementation of the
:class:`VocabularyProvider <skosprovider.providers.VocabularyProvider>` against
the vocabularies published by EH, RCAHMS and RCAHMW at
`heritagedata.org <http://heritagedata.org>`_.

.. _skos_spec: http://www.w3.org/TR/skos-reference/
.. _Flanders Heritage Agency: https://www.onroerenderfgoed.be
Expand All @@ -56,3 +68,5 @@ application.
.. _skosprovider_sqlalchemy: http://skosprovider-sqlalchemy.readthedocs.org
.. _skosprovider_rdf: http://skosprovider-rdf.readthedocs.org
.. _pyramid_skosprovider: http://pyramid-skosprovider.readthedocs.org
.. _skosprovider_getty: http://skosprovider-getty.readthedocs.org
.. _skosprovider_heritagedata: http://skosprovider-heritagedata.readthedocs.org
6 changes: 2 additions & 4 deletions requirements.txt
Expand Up @@ -8,12 +8,10 @@ pyramid_rewrite==0.2
skosprovider==0.5.0
skosprovider_sqlalchemy==0.4.1
pyramid_skosprovider==0.5.0
#skosprovider_rdf==0.3.0
-e git+https://github.com/OnroerendErfgoed/skosprovider_rdf#egg=skosprovider_rdf
skosprovider_rdf==0.3.0
#skosprovider_getty
-e git+https://github.com/OnroerendErfgoed/skosprovider_getty#egg=skosprovider_getty
#skosprovider_heritagedata
-e git+https://github.com/OnroerendErfgoed/skosprovider_heritagedata#egg=skosprovider_heritagedata
skosprovider_heritagedata==0.2.0

#language-tags
language-tags==0.3.0
Expand Down
115 changes: 114 additions & 1 deletion tests/test_validation.py
Expand Up @@ -885,4 +885,117 @@ def test_languages_invalid(self):
self.assertTrue(error_raised)
self.assertIsNone(validated_language)
self.assertIsNotNone(error)
self.assertIn({"id": "Invalid language tag: Unknown code 'flup', Missing language tag in 'flup'."}, error.errors)
self.assertIn({"id": "Invalid language tag: Unknown code 'flup', Missing language tag in 'flup'."}, error.errors)

def test_subordinate_arrays(self):
error_raised = False
validated_json = None
self.json_concept['subordinate_arrays'] = [{"id": 667}]
try:
validated_json = self.concept_schema.deserialize(self.json_concept)
except ValidationError:
error_raised = True
self.assertFalse(error_raised)
self.assertIsNotNone(validated_json)

def test_subordinate_arrays_no_concept(self):
error_raised = False
validated_json = None
error = None
self.json_collection['subordinate_arrays'] = [{"id": 666}]
try:
validated_json = self.concept_schema.deserialize(self.json_collection)
except ValidationError as e:
error_raised = True
error = e
self.assertTrue(error_raised)
self.assertIsNone(validated_json)
self.assertIsNotNone(error)
self.assertIn({'subordinate_arrays': 'Only concept can have subordinate arrays.'}, error.errors)

def test_subordinate_arrays_no_collection(self):
error_raised = False
validated_json = None
error = None
self.json_concept['subordinate_arrays'] = [{"id": 7}]
try:
validated_json = self.concept_schema.deserialize(self.json_concept)
except ValidationError as e:
error_raised = True
error = e
self.assertTrue(error_raised)
self.assertIsNone(validated_json)
self.assertIsNotNone(error)
self.assertIn({'subordinate_arrays': 'A subordinate array should always be a collection'}, error.errors)

def test_subordinate_arrays_hierarchy(self):
error_raised = False
validated_json = None
error = None
self.json_concept['subordinate_arrays'] = [{"id": 666}]
try:
validated_json = self.concept_schema.deserialize(self.json_concept)
except ValidationError as e:
error_raised = True
error = e
self.assertTrue(error_raised)
self.assertIsNone(validated_json)
self.assertIsNotNone(error)
self.assertIn({'subordinate_arrays': 'The subordinate_array collection of a concept must not itself be a parent of the concept being edited.'}, error.errors)

def test_superordinates(self):
error_raised = False
validated_json = None
self.json_collection['superordinates'] = [{"id": 7}]
try:
validated_json = self.concept_schema.deserialize(self.json_collection)
except ValidationError:
error_raised = True
self.assertFalse(error_raised)
self.assertIsNotNone(validated_json)

def test_superordinates_no_concept(self):
error_raised = False
validated_json = None
error = None
self.json_collection['superordinates'] = [{"id": 666}]
try:
validated_json = self.concept_schema.deserialize(self.json_collection)
except ValidationError as e:
error_raised = True
error = e
self.assertTrue(error_raised)
self.assertIsNone(validated_json)
self.assertIsNotNone(error)
self.assertIn({'superordinates': 'A superordinate should always be a concept'}, error.errors)

def test_superordinates_no_collection(self):
error_raised = False
validated_json = None
error = None
self.json_concept['superordinates'] = [{"id": 7}]
try:
validated_json = self.concept_schema.deserialize(self.json_concept)
except ValidationError as e:
error_raised = True
error = e
self.assertTrue(error_raised)
self.assertIsNone(validated_json)
self.assertIsNotNone(error)
self.assertIn({'superordinates': 'Only collection can have superordinates.'}, error.errors)

def test_superordinates_hierarchy(self):
error_raised = False
validated_json = None
error = None
self.json_collection['superordinates'] = [{"id": 61}]
try:
validated_json = self.concept_schema.deserialize(self.json_collection)
except ValidationError as e:
error_raised = True
error = e
self.assertTrue(error_raised)
self.assertIsNone(validated_json)
self.assertIsNotNone(error)
self.assertIn({'superordinates': 'The superordinates of a collection must not itself be a member of the collection being edited.'}, error.errors)

0 comments on commit 9e16130

Please sign in to comment.