Skip to content

Commit

Permalink
OpenConceptLab/ocl_issues#1057 | Collection References | cascade sour…
Browse files Browse the repository at this point in the history
…ce to concepts option
  • Loading branch information
snyaggarwal committed Oct 28, 2021
1 parent 70df6bb commit b553a62
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 22 deletions.
1 change: 1 addition & 0 deletions core/collections/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
NO_MATCH = 'No Collection matches the given query.'
VERSION_ALREADY_EXISTS = "Collection version '{}' already exist."
SOURCE_MAPPINGS = 'sourcemappings'
SOURCE_TO_CONCEPTS = 'sourcetoconcepts'
27 changes: 13 additions & 14 deletions core/collections/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def get_source_from_uri(uri):
return Source.objects.filter(uri=uri).first()

@transaction.atomic
def add_expressions(self, data, user, cascade_mappings=False):
def add_expressions(self, data, user, cascade_mappings=False, cascade_to_concepts=False):
expressions = data.get('expressions', [])
concept_expressions = data.get('concepts', [])
mapping_expressions = data.get('mappings', [])
Expand All @@ -201,9 +201,8 @@ def get_child_expressions(queryset):
expressions.extend(concept_expressions)
expressions.extend(mapping_expressions)

if cascade_mappings:
all_related_mappings = self.get_all_related_mappings(expressions)
expressions += all_related_mappings
if cascade_mappings or cascade_to_concepts:
expressions += self.get_all_related_uris(expressions, cascade_to_concepts)

return self.add_references_in_bulk(expressions, user)

Expand Down Expand Up @@ -327,7 +326,7 @@ def __get_children_from_expressions(expressions):
mappings = Mapping.objects.filter(uri__in=expressions)
return concepts, mappings

def get_all_related_mappings(self, expressions):
def get_all_related_uris(self, expressions, cascade_to_concepts=False):
all_related_mappings = []
unversioned_mappings = []
concept_expressions = []
Expand All @@ -342,7 +341,7 @@ def get_all_related_mappings(self, expressions):
ref = CollectionReference(expression=concept_expression)
try:
self.validate(ref)
all_related_mappings += ref.get_related_mappings(unversioned_mappings)
all_related_mappings += ref.get_related_uris(unversioned_mappings, cascade_to_concepts)
except: # pylint: disable=bare-except
continue

Expand Down Expand Up @@ -429,15 +428,15 @@ def create_entities_from_expressions(self):
elif self.mappings and self.mappings.exists():
self.expression = self.mappings.first().uri

def get_related_mappings(self, exclude_mapping_uris):
mappings = []
def get_related_uris(self, exclude_mapping_uris, cascade_to_concepts=False):
uris = []
concepts = self.get_concepts()
if concepts.exists():
for concept in concepts:
mappings = list(
concept.get_unidirectional_mappings().exclude(
uri__in=exclude_mapping_uris
).values_list('uri', flat=True)
)
mapping_queryset = concept.get_unidirectional_mappings().exclude(uri__in=exclude_mapping_uris)
uris = list(mapping_queryset.values_list('uri', flat=True))
if cascade_to_concepts:
to_concepts_queryset = mapping_queryset.filter(to_concept__parent_id=concept.parent_id)
uris += list(to_concepts_queryset.values_list('to_concept__uri', flat=True))

return mappings
return uris
13 changes: 8 additions & 5 deletions core/collections/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
HEAD_OF_MAPPING_ADDED_TO_COLLECTION, CONCEPT_ADDED_TO_COLLECTION_FMT, MAPPING_ADDED_TO_COLLECTION_FMT,
DELETE_FAILURE, DELETE_SUCCESS, NO_MATCH, VERSION_ALREADY_EXISTS,
SOURCE_MAPPINGS,
UNKNOWN_REFERENCE_ADDED_TO_COLLECTION_FMT)
UNKNOWN_REFERENCE_ADDED_TO_COLLECTION_FMT, SOURCE_TO_CONCEPTS)
from core.collections.documents import CollectionDocument
from core.collections.models import Collection, CollectionReference
from core.collections.search import CollectionSearch
Expand Down Expand Up @@ -310,8 +310,8 @@ def destroy(self, request, *args, **kwargs):

def update(self, request, *args, **kwargs): # pylint: disable=too-many-locals,unused-argument # Fixme: Sny
collection = self.get_object()

cascade_mappings = self.should_cascade_mappings()
cascade_to_concepts = self.should_cascade_to_concepts()
cascade_mappings = cascade_to_concepts or self.should_cascade_mappings()
data = request.data.get('data')
concept_expressions = data.get('concepts', [])
mapping_expressions = data.get('mappings', [])
Expand All @@ -321,7 +321,7 @@ def update(self, request, *args, **kwargs): # pylint: disable=too-many-locals,u

if adding_all:
result = add_references.delay(
self.request.user.id, data, collection.id, cascade_mappings)
self.request.user.id, data, collection.id, cascade_mappings, cascade_to_concepts)
return Response(
dict(
state=result.state, username=request.user.username, task=result.task_id, queue='default'
Expand All @@ -330,7 +330,7 @@ def update(self, request, *args, **kwargs): # pylint: disable=too-many-locals,u
)

(added_references, errors) = collection.add_expressions(
data, request.user, cascade_mappings
data, request.user, cascade_mappings, cascade_to_concepts
)

all_expressions = expressions + concept_expressions + mapping_expressions
Expand Down Expand Up @@ -360,6 +360,9 @@ def update(self, request, *args, **kwargs): # pylint: disable=too-many-locals,u
def should_cascade_mappings(self):
return self.request.query_params.get('cascade', '').lower() == SOURCE_MAPPINGS

def should_cascade_to_concepts(self):
return self.request.query_params.get('cascade', '').lower() == SOURCE_TO_CONCEPTS

def create_response_item(self, added_expressions, errors, expression):
adding_expression_failed = len(errors) > 0 and expression in errors
if adding_expression_failed:
Expand Down
4 changes: 2 additions & 2 deletions core/common/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def export_collection(self, version_id):

@app.task(bind=True)
def add_references(
self, user_id, data, collection_id, cascade_mappings=False
self, user_id, data, collection_id, cascade_mappings=False, cascade_to_concepts=False
): # pylint: disable=too-many-arguments,too-many-locals
from core.users.models import UserProfile
from core.collections.models import Collection
Expand All @@ -117,7 +117,7 @@ def add_references(
head.add_processing(self.request.id)

try:
(added_references, errors) = collection.add_expressions(data, user, cascade_mappings)
(added_references, errors) = collection.add_expressions(data, user, cascade_mappings, cascade_to_concepts)
finally:
head.remove_processing(self.request.id)

Expand Down
67 changes: 66 additions & 1 deletion core/integration_tests/tests_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ def test_put_202_all(self, add_references_mock):
self.assertEqual(response.status_code, 202)
self.assertEqual(response.data, [])
add_references_mock.delay.assert_called_once_with(
self.user.id, dict(concepts='*'), self.collection.id, False
self.user.id, dict(concepts='*'), self.collection.id, False, False
)

def test_put_200_specific_expression(self):
Expand Down Expand Up @@ -521,6 +521,71 @@ def test_put_200_specific_expression(self):
self.assertTrue(self.collection.references.filter(expression=mapping2.get_latest_version().uri).exists())
self.assertTrue(self.collection.references.filter(expression=latest_version.uri).exists())

def test_put_expression_with_cascade_to_concepts(self):
source1 = OrganizationSourceFactory()
source2 = OrganizationSourceFactory()
concept1 = ConceptFactory(parent=source1)
concept2 = ConceptFactory(parent=source1)
concept3 = ConceptFactory(parent=source2)
concept4 = ConceptFactory(parent=source2)

mapping1 = MappingFactory(
mnemonic='m1-c1-c2-s1', from_concept=concept1.get_latest_version(),
to_concept=concept2.get_latest_version(), parent=source1
)
MappingFactory(
mnemonic='m2-c2-c1-s1', from_concept=concept2.get_latest_version(),
to_concept=concept1.get_latest_version(), parent=source1
)
MappingFactory(
mnemonic='m3-c1-c3-s2', from_concept=concept1.get_latest_version(),
to_concept=concept3.get_latest_version(), parent=source2
)
mapping4 = MappingFactory(
mnemonic='m4-c4-c3-s2', from_concept=concept4.get_latest_version(),
to_concept=concept3.get_latest_version(), parent=source2
)
MappingFactory(
mnemonic='m5-c4-c1-s1', from_concept=concept4.get_latest_version(),
to_concept=concept1.get_latest_version(), parent=source1
)

response = self.client.put(
self.collection.uri + 'references/?cascade=sourceToConcepts',
dict(data=dict(concepts=[concept1.get_latest_version().uri])),
HTTP_AUTHORIZATION='Token ' + self.token,
format='json'
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 3)
self.assertTrue(all(data['added'] for data in response.data))
self.assertEqual(
sorted([data['expression'] for data in response.data]),
sorted([
concept1.get_latest_version().uri, mapping1.get_latest_version().uri,
mapping1.to_concept.get_latest_version().uri
])
)

response = self.client.put(
self.collection.uri + 'references/?cascade=sourceToConcepts',
dict(data=dict(concepts=[concept4.get_latest_version().uri])),
HTTP_AUTHORIZATION='Token ' + self.token,
format='json'
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 3)
self.assertTrue(all(data['added'] for data in response.data))
self.assertEqual(
sorted([data['expression'] for data in response.data]),
sorted([
concept4.get_latest_version().uri, mapping4.get_latest_version().uri,
mapping4.to_concept.get_latest_version().uri
])
)


class CollectionVersionRetrieveUpdateDestroyViewTest(OCLAPITestCase):
def setUp(self):
Expand Down

0 comments on commit b553a62

Please sign in to comment.