Skip to content

Commit

Permalink
Create Entity only if submission is approved when form submission rev…
Browse files Browse the repository at this point in the history
…iew is enabled (#2673)

* disable automatic Entity creation for submission review

Create Entity only if submission is approved when form submission review is enabled

* update comment

* update comment

* allow updating Entity via approved Instance

* use importlib to fix cyclic dependency

* update docstring

* refactor code

* refactor code
  • Loading branch information
kelvin-muchiri committed Aug 22, 2024
1 parent f328ba8 commit 4bb2c1d
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 32 deletions.
17 changes: 17 additions & 0 deletions onadata/apps/logger/models/submission_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"""
from __future__ import unicode_literals

import importlib

from django.conf import settings
from django.db import models
from django.db.models.signals import post_save
Expand All @@ -22,6 +24,16 @@ def update_instance_json_on_save(sender, instance, **kwargs):
submission_instance.save()


def create_or_update_entity(sender, instance, created=False, **kwargs):
"""Signal handler to create/update Entity if submission is approved"""
module = importlib.import_module("onadata.libs.utils.logger_tools")

submission_instance = instance.instance

if instance.status == SubmissionReview.APPROVED:
module.create_or_update_entity_from_instance(submission_instance)


class SubmissionReview(models.Model):
"""
SubmissionReview Model Class
Expand Down Expand Up @@ -100,3 +112,8 @@ def set_deleted(self, deleted_at=timezone.now(), user=None):
sender=SubmissionReview,
dispatch_uid="update_instance_json_on_save",
)
post_save.connect(
create_or_update_entity,
sender=SubmissionReview,
dispatch_uid="create_or_update_entity_from_review",
)
55 changes: 24 additions & 31 deletions onadata/apps/logger/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,45 @@
"""
logger signals module
"""
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.db.models import F
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.utils import timezone

from onadata.apps.logger.models import Entity, EntityList, Instance, RegistrationForm
from onadata.apps.logger.models import Entity, EntityList, Instance, SubmissionReview
from onadata.apps.logger.models.xform import clear_project_cache
from onadata.apps.logger.xform_instance_parser import get_meta_from_xml
from onadata.apps.logger.tasks import set_entity_list_perms_async
from onadata.apps.main.models.meta_data import MetaData
from onadata.libs.utils.logger_tools import (
create_entity_from_instance,
update_entity_from_instance,
)
from onadata.libs.utils.logger_tools import create_or_update_entity_from_instance


# pylint: disable=unused-argument
@receiver(post_save, sender=Instance, dispatch_uid="create_or_update_entity")
def create_or_update_entity(sender, instance, created=False, **kwargs):
"""Create or update an Entity after Instance saved"""
if instance:
if RegistrationForm.objects.filter(
xform=instance.xform, is_active=True
).exists():
entity_node = get_meta_from_xml(instance.xml, "entity")
registration_form = RegistrationForm.objects.filter(
xform=instance.xform, is_active=True
).first()
mutation_success_checks = ["1", "true"]
entity_uuid = entity_node.getAttribute("id")
exists = False

if entity_uuid is not None:
exists = Entity.objects.filter(uuid=entity_uuid).exists()

if exists and entity_node.getAttribute("update") in mutation_success_checks:
# Update Entity
update_entity_from_instance(entity_uuid, instance, registration_form)

elif (
not exists
and entity_node.getAttribute("create") in mutation_success_checks
):
# Create Entity
create_entity_from_instance(instance, registration_form)
content_type = ContentType.objects.get_for_model(instance.xform)
is_review_enabled = MetaData.objects.filter(
content_type=content_type,
object_id=instance.xform.id,
data_type="submission_review",
data_value="true",
).exists()
should_create_or_update = False

if not is_review_enabled:
should_create_or_update = True

else:
if not created:
is_review_approved = SubmissionReview.objects.filter(
instance_id=instance.id, status=SubmissionReview.APPROVED
).exists()
should_create_or_update = is_review_approved

if should_create_or_update:
create_or_update_entity_from_instance(instance)


@receiver(post_save, sender=Entity, dispatch_uid="update_enti_el_inc_num_entities")
Expand Down
114 changes: 114 additions & 0 deletions onadata/apps/logger/tests/models/test_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
get_id_string_from_xml_str,
numeric_checker,
)
from onadata.apps.main.models.meta_data import MetaData
from onadata.apps.main.tests.test_base import TestBase
from onadata.apps.viewer.models.parsed_instance import (
ParsedInstance,
Expand Down Expand Up @@ -1006,6 +1007,119 @@ def test_create_entity_exists(self):
},
)

def test_submission_review_enabled_entity_create(self):
"""Submission review disables automatic creation of Entity"""
self.project = get_user_default_project(self.user)
xform = self._publish_registration_form(self.user)
MetaData.submission_review(xform, "true")
xml = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<data xmlns:jr="http://openrosa.org/javarosa" xmlns:orx='
'"http://openrosa.org/xforms" id="trees_registration" version="2022110901">'
"<formhub><uuid>d156a2dce4c34751af57f21ef5c4e6cc</uuid></formhub>"
"<location>-1.286905 36.772845 0 0</location>"
"<species>purpleheart</species>"
"<circumference>300</circumference>"
"<intake_notes />"
"<meta>"
"<instanceID>uuid:9d3f042e-cfec-4d2a-8b5b-212e3b04802b</instanceID>"
"<instanceName>300cm purpleheart</instanceName>"
'<entity create="1" dataset="trees" id="dbee4c32-a922-451c-9df7-42f40bf78f48">'
"<label>300cm purpleheart</label>"
"</entity>"
"</meta>"
"</data>"
)

Instance.objects.create(xml=xml, user=self.user, xform=xform)

self.assertEqual(Entity.objects.count(), 0)

def test_submission_review_enabled_entity_update(self):
"""Submission review disables automatic update of an Entity
Only an approved Instance will update an Entity
"""
self._simulate_existing_entity()
xform = self._publish_entity_update_form(self.user)
MetaData.submission_review(xform, "true")

# Try to update Entity via Instance creation
xml = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<data xmlns:jr="http://openrosa.org/javarosa" xmlns:orx='
'"http://openrosa.org/xforms" id="trees_update" version="2024050801">'
"<formhub><uuid>a9caf13e366b44a68f173bbb6746e3d4</uuid></formhub>"
"<tree>dbee4c32-a922-451c-9df7-42f40bf78f48</tree>"
"<circumference>30</circumference>"
"<today>2024-05-28</today>"
"<meta>"
"<instanceID>uuid:45d27780-48fd-4035-8655-9332649385bd</instanceID>"
"<instanceName>30cm dbee4c32-a922-451c-9df7-42f40bf78f48</instanceName>"
'<entity dataset="trees" id="dbee4c32-a922-451c-9df7-42f40bf78f48" update="1" baseVersion=""/>'
"</meta>"
"</data>"
)
instance = Instance.objects.create(xml=xml, user=self.user, xform=xform)
entity = Entity.objects.first()
expected_json = {
"label": "300cm purpleheart",
"species": "purpleheart",
"geometry": "-1.286905 36.772845 0 0",
"circumference_cm": 300,
}
# Entity not updated
self.assertDictEqual(entity.json, expected_json)
# Approve submission
SubmissionReview.objects.create(
instance=instance, status=SubmissionReview.APPROVED
)
entity.refresh_from_db()
expected_json = {
"species": "purpleheart",
"geometry": "-1.286905 36.772845 0 0",
"latest_visit": "2024-05-28",
"circumference_cm": 30,
"label": "300cm purpleheart",
}
self.assertDictEqual(entity.json, expected_json)

# Update Entity via Instance update
instance = Instance.objects.get(
pk=instance.pk
) # Get anew from DB to update Instance._parser
xml = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<data xmlns:jr="http://openrosa.org/javarosa" xmlns:orx='
'"http://openrosa.org/xforms" id="trees_update" version="2024050801">'
"<formhub><uuid>a9caf13e366b44a68f173bbb6746e3d4</uuid></formhub>"
"<tree>dbee4c32-a922-451c-9df7-42f40bf78f48</tree>"
"<circumference>32</circumference>" # Update to 32
"<today>2024-06-19</today>"
"<meta>"
"<instanceID>uuid:fa6bcdce-e344-4dbd-9227-0f1cbdddb09c</instanceID>"
"<instanceName>32cm dbee4c32-a922-451c-9df7-42f40bf78f48</instanceName>"
'<entity dataset="trees" id="dbee4c32-a922-451c-9df7-42f40bf78f48" update="1" baseVersion="">'
"<label>32cm purpleheart</label>"
"</entity>"
"<deprecatedID>uuid:45d27780-48fd-4035-8655-9332649385bd</deprecatedID>"
"</meta>"
"</data>"
)
instance.xml = xml
instance.uuid = "fa6bcdce-e344-4dbd-9227-0f1cbdddb09c"
instance.save()
entity.refresh_from_db()
expected_json = {
"species": "purpleheart",
"geometry": "-1.286905 36.772845 0 0",
"latest_visit": "2024-06-19",
"circumference_cm": 32,
"label": "32cm purpleheart",
}
# Entity updated since the submission is already approved
self.assertDictEqual(entity.json, expected_json)

def test_parse_numbers(self):
"""Integers and decimals are parsed correctly"""
md = """
Expand Down
111 changes: 110 additions & 1 deletion onadata/apps/logger/tests/models/test_submission_review.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
"""
Submission Review Model Tests Module
"""

from __future__ import unicode_literals

from django.utils import timezone

from onadata.apps.logger.models import Instance, Note, SubmissionReview

from onadata.apps.logger.models import Entity, Instance, Note, SubmissionReview
from onadata.apps.main.models.meta_data import MetaData
from onadata.apps.main.tests.test_base import TestBase

from onadata.libs.utils.user_auth import get_user_default_project


class TestSubmissionReview(TestBase):
"""
Expand Down Expand Up @@ -55,3 +60,107 @@ def test_set_deleted(self):

self.assertEqual(time, submission_review.deleted_at)
self.assertEqual(None, submission_review.deleted_by)

def test_entity_created(self):
"""Entity is created for when submission is approved"""
self.project = get_user_default_project(self.user)
xform = self._publish_registration_form(self.user)
MetaData.submission_review(xform, "true")
xml = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<data xmlns:jr="http://openrosa.org/javarosa" xmlns:orx='
'"http://openrosa.org/xforms" id="trees_registration" version="2022110901">'
"<formhub><uuid>d156a2dce4c34751af57f21ef5c4e6cc</uuid></formhub>"
"<location>-1.286905 36.772845 0 0</location>"
"<species>purpleheart</species>"
"<circumference>300</circumference>"
"<intake_notes />"
"<meta>"
"<instanceID>uuid:9d3f042e-cfec-4d2a-8b5b-212e3b04802b</instanceID>"
"<instanceName>300cm purpleheart</instanceName>"
'<entity create="1" dataset="trees" id="dbee4c32-a922-451c-9df7-42f40bf78f48">'
"<label>300cm purpleheart</label>"
"</entity>"
"</meta>"
"</data>"
)
instance = Instance.objects.create(xml=xml, user=self.user, xform=xform)

self.assertEqual(Entity.objects.count(), 0)

SubmissionReview.objects.create(
instance=instance, status=SubmissionReview.APPROVED
)

self.assertEqual(Entity.objects.count(), 1)

def test_entity_created_approved_only(self):
"""Entity is only created if status is Approved"""
self.project = get_user_default_project(self.user)
xform = self._publish_registration_form(self.user)
MetaData.submission_review(xform, "true")
xml = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<data xmlns:jr="http://openrosa.org/javarosa" xmlns:orx='
'"http://openrosa.org/xforms" id="trees_registration" version="2022110901">'
"<formhub><uuid>d156a2dce4c34751af57f21ef5c4e6cc</uuid></formhub>"
"<location>-1.286905 36.772845 0 0</location>"
"<species>purpleheart</species>"
"<circumference>300</circumference>"
"<intake_notes />"
"<meta>"
"<instanceID>uuid:9d3f042e-cfec-4d2a-8b5b-212e3b04802b</instanceID>"
"<instanceName>300cm purpleheart</instanceName>"
'<entity create="1" dataset="trees" id="dbee4c32-a922-451c-9df7-42f40bf78f48">'
"<label>300cm purpleheart</label>"
"</entity>"
"</meta>"
"</data>"
)
instance = Instance.objects.create(xml=xml, user=self.user, xform=xform)
SubmissionReview.objects.create(
instance=instance, status=SubmissionReview.REJECTED
)

self.assertEqual(Entity.objects.count(), 0)

SubmissionReview.objects.create(
instance=instance, status=SubmissionReview.PENDING
)

self.assertEqual(Entity.objects.count(), 0)

def test_entity_created_approved_edit(self):
"""Entity created when submission review changes to Approved"""
self.project = get_user_default_project(self.user)
xform = self._publish_registration_form(self.user)
MetaData.submission_review(xform, "true")
xml = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<data xmlns:jr="http://openrosa.org/javarosa" xmlns:orx='
'"http://openrosa.org/xforms" id="trees_registration" version="2022110901">'
"<formhub><uuid>d156a2dce4c34751af57f21ef5c4e6cc</uuid></formhub>"
"<location>-1.286905 36.772845 0 0</location>"
"<species>purpleheart</species>"
"<circumference>300</circumference>"
"<intake_notes />"
"<meta>"
"<instanceID>uuid:9d3f042e-cfec-4d2a-8b5b-212e3b04802b</instanceID>"
"<instanceName>300cm purpleheart</instanceName>"
'<entity create="1" dataset="trees" id="dbee4c32-a922-451c-9df7-42f40bf78f48">'
"<label>300cm purpleheart</label>"
"</entity>"
"</meta>"
"</data>"
)
instance = Instance.objects.create(xml=xml, user=self.user, xform=xform)
review = SubmissionReview.objects.create(
instance=instance, status=SubmissionReview.REJECTED
)

self.assertEqual(Entity.objects.count(), 0)

review.status = SubmissionReview.APPROVED
review.save()

self.assertEqual(Entity.objects.count(), 1)
Loading

0 comments on commit 4bb2c1d

Please sign in to comment.