Skip to content

Commit 842f8d4

Browse files
committed
When contextualizing a form, remove unneeded elements in conditions
* Remove source fields and target fields if they don't exist in the context of the form * Remove conditions when the source fields (in `tests`) are not in the contextualized form * Remove conditions if their target fields (`fields_ids`) is empty * After the Proof of Concept, we've refactored the contextualize_conditions to make it faster
1 parent 43b4c59 commit 842f8d4

File tree

3 files changed

+232
-24
lines changed

3 files changed

+232
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"fields": [
3+
{
4+
"defaults": [],
5+
"description": "Yes/No",
6+
"disabled": false,
7+
"placeholder": null,
8+
"required": false,
9+
"slug": "to-check",
10+
"type_id": "checkbox",
11+
"validations": [],
12+
"accesses": [
13+
{ "access_id": "TEST_ROLE", "level": "EDITABLE" },
14+
{ "access_id": "TEST_ROLE2", "level": "EDITABLE" }
15+
]
16+
},
17+
{
18+
"defaults": [],
19+
"description": null,
20+
"disabled": false,
21+
"label": "second field",
22+
"placeholder": null,
23+
"required": false,
24+
"slug": "to-display",
25+
"type_id": "paragraph",
26+
"validations": [],
27+
"accesses": [
28+
{ "access_id": "TEST_ROLE2", "level": "EDITABLE" },
29+
{ "access_id": "TEST_ROLE", "level": "EDITABLE" }
30+
]
31+
},
32+
{
33+
"defaults": [],
34+
"description": null,
35+
"disabled": false,
36+
"label": "Always displayed field",
37+
"placeholder": null,
38+
"required": false,
39+
"slug": "always-displayed",
40+
"type_id": "paragraph",
41+
"validations": [],
42+
"accesses": [
43+
{ "access_id": "TEST_ROLE2", "level": "EDITABLE" },
44+
{ "access_id": "TEST_ROLE", "level": "EDITABLE" }
45+
]
46+
}
47+
],
48+
"id": 1,
49+
"label": "Some label here",
50+
"description": "Description",
51+
"conditions": [
52+
{
53+
"action": "display_iff",
54+
"fields_ids": [
55+
"to-display"
56+
],
57+
"name": "Rule",
58+
"tests": [
59+
{
60+
"field_id": "to-check",
61+
"operator": "eq",
62+
"values": [ true ]
63+
}
64+
]
65+
}
66+
]
67+
}

demo/tests/test_conditions.py

+141-17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import json
7+
import copy
78

89
from django.test import TestCase
910

@@ -224,23 +225,6 @@ def test_condition_field_doesnt_exist(self):
224225
else:
225226
self.assertTrue(True)
226227

227-
def test_filter_conditions_by_fields_ids(self):
228-
first_schema = contextualize(self.conditions_schema, 'TEST_ROLE')
229-
conditions = first_schema['conditions']
230-
self.assertEqual(len(conditions), 1)
231-
condition = conditions[0]
232-
233-
self.assertEqual(condition['fields_ids'], ['first-field'])
234-
235-
second_schema = contextualize(self.conditions_schema, 'TEST_ROLE2')
236-
conditions = second_schema['conditions']
237-
self.assertEqual(len(conditions), 1)
238-
condition = conditions[0]
239-
self.assertEqual(
240-
sorted(condition['fields_ids']),
241-
['first-field', 'second-field']
242-
)
243-
244228

245229
class ConditionFromSchemaTestCase(ConditionTestCase):
246230

@@ -297,3 +281,143 @@ def test_serializer(self):
297281
self.assertTrue(serializer.is_valid(), serializer.errors)
298282
instance = serializer.save()
299283
self.assertEqual(instance.conditions, self.payload['conditions'])
284+
285+
286+
class ConditionContextualizationTest(TestCase):
287+
288+
@classmethod
289+
def setUpTestData(cls):
290+
cls.conditions_schema = json.load(open(
291+
os.path.join(
292+
TESTS_DIR, 'fixtures', 'conditions-contextualization.json'
293+
)
294+
))
295+
296+
def test_condition_normal_context(self):
297+
schema = copy.deepcopy(self.conditions_schema)
298+
299+
first_schema = contextualize(schema, 'TEST_ROLE')
300+
conditions = first_schema['conditions']
301+
self.assertEqual(len(conditions), 1)
302+
303+
second_schema = contextualize(schema, 'TEST_ROLE2')
304+
conditions = second_schema['conditions']
305+
self.assertEqual(len(conditions), 1)
306+
307+
def test_condition_to_display_only_for_role2(self):
308+
schema = copy.deepcopy(self.conditions_schema)
309+
310+
# The field "to-display" will only be visible for TEST_ROLE2
311+
# As a consequence, the conditions for this role will filter it out.
312+
to_display = schema['fields'][1]
313+
to_display['accesses'] = [
314+
{"access_id": "TEST_ROLE", "level": "HIDDEN"},
315+
{"access_id": "TEST_ROLE2", "level": "EDITABLE"}
316+
]
317+
# Contextualized form for the first role
318+
first_schema = contextualize(schema, 'TEST_ROLE')
319+
conditions = first_schema['conditions']
320+
self.assertEqual(len(conditions), 0)
321+
322+
# Contextualized form for the second role
323+
second_schema = contextualize(schema, 'TEST_ROLE2')
324+
conditions = second_schema['conditions']
325+
# Conditions are still here
326+
self.assertEqual(len(conditions), 1)
327+
condition = conditions[0]
328+
self.assertEqual(condition['fields_ids'], ['to-display'])
329+
330+
# Correct tests
331+
self.assertEqual(len(condition['tests']), 1)
332+
tests = condition['tests'][0]
333+
self.assertEqual(tests, {
334+
"field_id": "to-check",
335+
"operator": "eq",
336+
"values": [True]
337+
})
338+
339+
def test_condition_to_check_only_for_role2(self):
340+
schema = copy.deepcopy(self.conditions_schema)
341+
342+
# The field "to-check" will only be visible for TEST_ROLE2
343+
# As a consequence, the conditions for this role will filter it out.
344+
to_check = schema['fields'][0]
345+
to_check['accesses'] = [
346+
{"access_id": "TEST_ROLE", "level": "HIDDEN"},
347+
{"access_id": "TEST_ROLE2", "level": "EDITABLE"}
348+
]
349+
first_schema = contextualize(schema, 'TEST_ROLE')
350+
conditions = first_schema['conditions']
351+
# Currently, it means that the conditions are still there, and they
352+
# are configured as if the field was still there.
353+
self.assertEqual(len(conditions), 0)
354+
355+
# Contextualized form for the second role
356+
second_schema = contextualize(schema, 'TEST_ROLE2')
357+
conditions = second_schema['conditions']
358+
self.assertEqual(len(conditions), 1)
359+
condition = conditions[0]
360+
self.assertEqual(condition['fields_ids'], ['to-display'])
361+
362+
# Correct tests (Which is normal)
363+
self.assertEqual(len(condition['tests']), 1)
364+
tests = condition['tests'][0]
365+
self.assertEqual(tests, {
366+
"field_id": "to-check",
367+
"operator": "eq",
368+
"values": [True]
369+
})
370+
371+
def test_condition_to_display_only_for_role2_plus_one_more_field(self):
372+
schema = copy.deepcopy(self.conditions_schema)
373+
374+
# Add a field to the conditions
375+
# The condition should be visible in the ROLE and ROLE2 Contextualized
376+
# forms, because you still have one field to display.
377+
schema['conditions'][0]['fields_ids'].append('always-displayed')
378+
379+
# The field "to-display" will only be visible for TEST_ROLE2
380+
# As a consequence, the conditions for this role will filter it out.
381+
to_display = schema['fields'][1]
382+
to_display['accesses'] = [
383+
{"access_id": "TEST_ROLE", "level": "HIDDEN"},
384+
{"access_id": "TEST_ROLE2", "level": "EDITABLE"}
385+
]
386+
# Contextualized form for the first role
387+
first_schema = contextualize(schema, 'TEST_ROLE')
388+
conditions = first_schema['conditions']
389+
self.assertEqual(len(conditions), 1)
390+
condition = conditions[0]
391+
# Currently, it means that conditions **targeting** this field will
392+
# see it removed from their "fields_ids" property
393+
# Here, the fields_ids is empty, it looks a bit wrong.
394+
self.assertEqual(condition['fields_ids'], ['always-displayed'])
395+
396+
# Correct tests
397+
self.assertEqual(len(condition['tests']), 1)
398+
tests = condition['tests'][0]
399+
self.assertEqual(tests, {
400+
"field_id": "to-check",
401+
"operator": "eq",
402+
"values": [True]
403+
})
404+
405+
# Contextualized form for the second role
406+
second_schema = contextualize(schema, 'TEST_ROLE2')
407+
conditions = second_schema['conditions']
408+
# Conditions are still here
409+
self.assertEqual(len(conditions), 1)
410+
condition = conditions[0]
411+
self.assertEqual(
412+
sorted(condition['fields_ids']),
413+
sorted(['to-display', 'always-displayed'])
414+
)
415+
416+
# Correct tests
417+
self.assertEqual(len(condition['tests']), 1)
418+
tests = condition['tests'][0]
419+
self.assertEqual(tests, {
420+
"field_id": "to-check",
421+
"operator": "eq",
422+
"values": [True]
423+
})

formidable/serializers/forms.py

+24-7
Original file line numberDiff line numberDiff line change
@@ -174,19 +174,36 @@ def contextualize_fields(fields, role):
174174
yield field
175175

176176

177-
def contextualize(form, role):
177+
def contextualize_conditions(form):
178178
"""
179-
Transform a FormidableJSON into a ContextFormJSON for a given role.
180-
179+
Extract conditions and filter them using the fields that exist in the form.
181180
"""
182-
form = copy.deepcopy(form)
183-
form['fields'] = list(contextualize_fields(form['fields'], role))
184-
185181
# filter conditions by fields ids for current role
186182
field_ids = {field['slug'] for field in form['fields']}
187-
for condition in form.get('conditions', []):
183+
conditions = form.get('conditions', [])
184+
185+
for condition in conditions:
186+
# Build the tests based on the existing fields in the form
187+
condition['tests'] = [
188+
t for t in condition['tests']
189+
if t['field_id'] in field_ids
190+
]
191+
# Build the fields_ids based on the existing fields in the form.
188192
condition['fields_ids'] = list(
189193
set(condition.get('fields_ids', [])) & field_ids
190194
)
195+
# 1. If the condition "tests" is empty, remove it
196+
# 2. if the condition "fields_ids" is empty, remove it
197+
if condition['tests'] and condition['fields_ids']:
198+
yield condition
199+
191200

201+
def contextualize(form, role):
202+
"""
203+
Transform a FormidableJSON into a ContextFormJSON for a given role.
204+
205+
"""
206+
form = copy.deepcopy(form)
207+
form['fields'] = list(contextualize_fields(form['fields'], role))
208+
form['conditions'] = list(contextualize_conditions(form))
192209
return form

0 commit comments

Comments
 (0)