Skip to content

Commit

Permalink
Merge pull request #35016 from dimagi/ze/form-expression-repeater
Browse files Browse the repository at this point in the history
Form Expression Repeater
  • Loading branch information
zandre-eng authored Sep 3, 2024
2 parents 1ac87d5 + f1fe252 commit 91f07d4
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 48 deletions.
2 changes: 1 addition & 1 deletion corehq/motech/repeaters/expression/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from corehq.motech.repeaters.forms import GenericRepeaterForm


class CaseExpressionRepeaterForm(GenericRepeaterForm):
class BaseExpressionRepeaterForm(GenericRepeaterForm):
configured_filter = JsonField(expected_type=dict, help_text=help_text.CONFIGURED_FILTER)
configured_expression = JsonField(expected_type=dict)
url_template = CharField(
Expand Down
15 changes: 12 additions & 3 deletions corehq/motech/repeaters/expression/repeater_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,17 @@ def get_url(self, repeat_record, url_template, payload_doc):
)


class ArcGISFormExpressionPayloadGenerator(ExpressionPayloadGenerator):
class FormExpressionPayloadGenerator(ExpressionPayloadGenerator):
def get_payload(self, repeat_record, payload_doc, parsed_expression):
result = self._parse_payload(payload_doc, parsed_expression)
return json.dumps(result, cls=DjangoJSONEncoder)

def _parse_payload(self, payload_doc, parsed_expression):
payload_doc_json = payload_doc.to_json()
return parsed_expression(payload_doc_json, EvaluationContext(payload_doc_json))


class ArcGISFormExpressionPayloadGenerator(FormExpressionPayloadGenerator):

def get_url(self, repeat_record, url_template, payload_doc):
if not (
Expand All @@ -55,8 +65,7 @@ def content_type(self):
return 'application/x-www-form-urlencoded'

def get_payload(self, repeat_record, payload_doc, parsed_expression):
payload_doc_json = payload_doc.to_json()
payload = parsed_expression(payload_doc_json, EvaluationContext(payload_doc_json))
payload = self._parse_payload(payload_doc, parsed_expression)
conn_settings = repeat_record.repeater.connection_settings
api_token = conn_settings.plaintext_password
formatted_payload = {
Expand Down
6 changes: 5 additions & 1 deletion corehq/motech/repeaters/expression/repeaters.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
ExpressionPayloadGenerator,
)
from corehq.motech.repeaters.models import OptionValue, Repeater
from corehq.motech.repeaters.expression.repeater_generators import ArcGISFormExpressionPayloadGenerator
from corehq.motech.repeaters.expression.repeater_generators import (
ArcGISFormExpressionPayloadGenerator,
FormExpressionPayloadGenerator,
)
from corehq.toggles import EXPRESSION_REPEATER, ARCGIS_INTEGRATION


Expand Down Expand Up @@ -84,6 +87,7 @@ def payload_doc(self, repeat_record):
class FormExpressionRepeater(BaseExpressionRepeater):

friendly_name = _("Configurable Form Repeater")
payload_generator_classes = (FormExpressionPayloadGenerator,)

class Meta:
app_label = 'repeaters'
Expand Down
135 changes: 100 additions & 35 deletions corehq/motech/repeaters/expression/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
from corehq.motech.models import ConnectionSettings
from corehq.motech.repeaters.expression.repeaters import (
CaseExpressionRepeater,
ArcGISFormExpressionRepeater
ArcGISFormExpressionRepeater,
FormExpressionRepeater,
)
from corehq.motech.repeaters.models import RepeatRecord
from corehq.util.test_utils import flag_enabled
Expand All @@ -24,6 +25,8 @@


class BaseExpressionRepeaterTest(TestCase, DomainSubscriptionMixin):
xmlns = 'http://foo.org/bar/123'

@classmethod
def setUpClass(cls):
super().setUpClass()
Expand Down Expand Up @@ -52,6 +55,20 @@ def repeat_records(cls, domain_name):
later = datetime.utcnow() + timedelta(hours=48 + 1)
return RepeatRecord.objects.filter(domain=domain_name, next_check__lt=later)

def _create_case_block(self, case_id):
return CaseBlock(
create=True,
case_id=case_id,
case_type='person',
case_name=uuid.uuid4().hex,
).as_text()

def _create_case(self, xmlns):
return self.factory.post_case_blocks(
[self._create_case_block(uuid.uuid4().hex)],
xmlns=xmlns,
)[0]


@flag_enabled('EXPRESSION_REPEATER')
class CaseExpressionRepeaterTest(BaseExpressionRepeaterTest):
Expand Down Expand Up @@ -161,9 +178,85 @@ def test_custom_url(self):
)


class ArcGISExpressionRepeaterTest(BaseExpressionRepeaterTest):
class FormExpressionRepeaterTest(BaseExpressionRepeaterTest):

xform_xml_template = """<?xml version='1.0' ?>
<data xmlns:jrm="http://dev.commcarehq.org/jr/xforms" xmlns="{}">
<meta>
<n3:location xmlns:n3="http://commcarehq.org/xforms">1.1 2.2 3.3 4.4</n3:location>
<instanceID>{}</instanceID>
</meta>
{}
</data>
"""

case_id = uuid.uuid4().hex

def _create_repeater(self):
self.repeater = FormExpressionRepeater(
domain=self.domain,
connection_settings_id=self.connection.id,
configured_filter={
"type": "boolean_expression",
"expression": {
"type": "property_name",
"property_name": "xmlns",
},
"operator": "eq",
"property_value": self.xmlns,
},
configured_expression={
"type": "dict",
"properties": {
"case_id": {
"type": "property_path",
"property_path": ["form", "case", "@case_id"],
},
"properties": {
"type": "dict",
"properties": {
"meta_gps_point": {
"type": "property_path",
"property_path": ["form", "meta", "location", "#text"],
},
},
},
},
},
)
self.repeater.save()

@property
def expected_payload(self):
return json.dumps({
'case_id': self.case_id,
'properties': {
'meta_gps_point': '1.1 2.2 3.3 4.4'
}
})

def test_filter_forms(self):
forwardable_form = self._create_case(self.xmlns)
self._create_case(xmlns='http://do-not.org/forward')
self.assertEqual(RepeatRecord.objects.filter(domain=self.domain).count(), 1)
repeat_records = self.repeat_records(self.domain).all()
self.assertEqual(repeat_records[0].payload_id, forwardable_form.form_id)

def test_payload(self):
instance_id = uuid.uuid4().hex
xform_xml = self.xform_xml_template.format(
self.xmlns,
instance_id,
self._create_case_block(self.case_id),
)
submit_form_locally(xform_xml, self.domain)
repeat_record = self.repeat_records(self.domain).all()[0]
self.assertEqual(repeat_record.get_payload(), self.expected_payload)


class ArcGISExpressionRepeaterTest(FormExpressionRepeaterTest):

xmlns = 'http://foo.org/bar/123'
xform_xml_template = """<?xml version='1.0' ?>
<data xmlns:jrm="http://dev.commcarehq.org/jr/xforms" xmlns="{}">
<person_name>Timmy</person_name>
Expand Down Expand Up @@ -239,37 +332,9 @@ def _create_repeater(self):
)
self.repeater.save()

def _create_case_block(self):
return CaseBlock(
create=True,
case_id=uuid.uuid4().hex,
case_type='person',
case_name=uuid.uuid4().hex,
).as_text()

def _create_case(self, xmlns):
return self.factory.post_case_blocks(
[self._create_case_block()],
xmlns=xmlns,
)[0]

def test_filter_forms(self):
forwardable_form = self._create_case(self.xmlns)
self._create_case(xmlns='http://do-not.org/forward')
self.assertEqual(RepeatRecord.objects.filter(domain=self.domain).count(), 1)
repeat_records = self.repeat_records(self.domain).all()
self.assertEqual(repeat_records[0].payload_id, forwardable_form.form_id)

def test_payload(self):
instance_id = uuid.uuid4().hex
xform_xml = self.xform_xml_template.format(
self.xmlns,
instance_id,
self._create_case_block(),
)
submit_form_locally(xform_xml, self.domain)
repeat_record = self.repeat_records(self.domain).all()[0]
self.assertEqual(repeat_record.get_payload(), {
@property
def expected_payload(self):
return {
'features': json.dumps([{
'attributes': {
'name': 'Timmy',
Expand All @@ -284,4 +349,4 @@ def test_payload(self):
}]),
'f': 'json',
'token': ''
})
}
26 changes: 19 additions & 7 deletions corehq/motech/repeaters/expression/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from corehq.motech.repeaters.expression.forms import CaseExpressionRepeaterForm
from corehq.motech.repeaters.expression.forms import BaseExpressionRepeaterForm
from corehq.motech.repeaters.views import AddRepeaterView, EditRepeaterView


class AddCaseExpressionRepeaterView(AddRepeaterView):
urlname = 'add_case_expression_repeater'
repeater_form_class = CaseExpressionRepeaterForm
class BaseExpressionRepeaterView(AddRepeaterView):
repeater_form_class = BaseExpressionRepeaterForm

@property
def page_url(self):
Expand All @@ -23,15 +22,28 @@ def set_repeater_attr(self, repeater, cleaned_data):
return repeater


class EditCaseExpressionRepeaterView(EditRepeaterView, AddCaseExpressionRepeaterView):
class AddCaseExpressionRepeaterView(BaseExpressionRepeaterView):
urlname = 'add_case_expression_repeater'


class EditCaseExpressionRepeaterView(EditRepeaterView, BaseExpressionRepeaterView):
urlname = 'edit_case_expression_repeater'
page_title = _("Edit Case Repeater")


class AddArcGISFormExpressionRepeaterView(AddCaseExpressionRepeaterView):
class AddFormExpressionRepeaterView(BaseExpressionRepeaterView):
urlname = 'add_form_expression_repeater'


class EditFormExpressionRepeaterView(EditRepeaterView, BaseExpressionRepeaterView):
urlname = 'edit_form_expression_repeater'
page_title = _('Edit Form Repeater')


class AddArcGISFormExpressionRepeaterView(BaseExpressionRepeaterView):
urlname = 'add_arcgis_form_expression_repeater'


class EditArcGISFormExpressionRepeaterView(EditRepeaterView, AddCaseExpressionRepeaterView):
class EditArcGISFormExpressionRepeaterView(EditRepeaterView, BaseExpressionRepeaterView):
urlname = 'edit_arcgis_form_expression_repeater'
page_title = _("Edit ArcGIS Form Repeater")
3 changes: 2 additions & 1 deletion corehq/motech/repeaters/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@

def create_form_repeat_records(sender, xform, **kwargs):
from corehq.motech.repeaters.models import FormRepeater
from corehq.motech.repeaters.expression.repeaters import ArcGISFormExpressionRepeater
from corehq.motech.repeaters.expression.repeaters import ArcGISFormExpressionRepeater, FormExpressionRepeater
if not xform.is_duplicate:
create_repeat_records(FormRepeater, xform)
create_repeat_records(FormExpressionRepeater, xform)
create_repeat_records(ArcGISFormExpressionRepeater, xform)


Expand Down
10 changes: 10 additions & 0 deletions corehq/motech/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from corehq.motech.repeaters.expression.views import (
AddCaseExpressionRepeaterView,
EditCaseExpressionRepeaterView,
AddFormExpressionRepeaterView,
EditFormExpressionRepeaterView,
AddArcGISFormExpressionRepeaterView,
EditArcGISFormExpressionRepeaterView,
)
Expand Down Expand Up @@ -80,6 +82,8 @@
{'repeater_type': 'DataRegistryCaseUpdateRepeater'}, name=AddCaseRepeaterView.urlname),
url(r'^forwarding/new/CaseExpressionRepeater/$', AddCaseExpressionRepeaterView.as_view(),
{'repeater_type': 'CaseExpressionRepeater'}, name=AddCaseExpressionRepeaterView.urlname),
url(r'^forwarding/new/FormExpressionRepeater/$', AddFormExpressionRepeaterView.as_view(),
{'repeater_type': 'FormExpressionRepeater'}, name=AddFormExpressionRepeaterView.urlname),
url(r'^forwarding/new/ArcGISFormExpressionRepeater/$', AddArcGISFormExpressionRepeaterView.as_view(),
{'repeater_type': 'ArcGISFormExpressionRepeater'}, name=AddArcGISFormExpressionRepeaterView.urlname),
url(r'^forwarding/new/(?P<repeater_type>\w+)/$', AddRepeaterView.as_view(), name=AddRepeaterView.urlname),
Expand Down Expand Up @@ -109,6 +113,12 @@
{'repeater_type': 'CaseExpressionRepeater'},
name=EditCaseExpressionRepeaterView.urlname
),
url(
r'^forwarding/edit/FormExpressionRepeater/(?P<repeater_id>\w+)/$',
EditFormExpressionRepeaterView.as_view(),
{'repeater_type': 'FormExpressionRepeater'},
name=EditFormExpressionRepeaterView.urlname
),
url(
r'^forwarding/edit/ArcGISFormExpressionRepeater/(?P<repeater_id>\w+)/$',
EditArcGISFormExpressionRepeaterView.as_view(),
Expand Down
1 change: 1 addition & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@
'custom.cowin.repeaters.BeneficiaryRegistrationRepeater',
'custom.cowin.repeaters.BeneficiaryVaccinationRepeater',
'corehq.motech.repeaters.expression.repeaters.CaseExpressionRepeater',
'corehq.motech.repeaters.expression.repeaters.FormExpressionRepeater',
'corehq.motech.repeaters.expression.repeaters.ArcGISFormExpressionRepeater',
]

Expand Down

0 comments on commit 91f07d4

Please sign in to comment.