Skip to content

Commit

Permalink
Merge pull request #2250 from projectcaluma/allow-nested-calculated-f…
Browse files Browse the repository at this point in the history
…ields

feat: allow calculated fields to depend on other calculated fields
  • Loading branch information
winged authored Jul 15, 2024
2 parents 592ce8b + 92a8c1b commit ce0a4e8
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 39 deletions.
60 changes: 29 additions & 31 deletions caluma/caluma_form/tests/test_question.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from graphql_relay import to_global_id

from caluma.caluma_core.relay import extract_global_id
from caluma.caluma_core.tests import (
Expand Down Expand Up @@ -1064,51 +1065,48 @@ def test_nested_calculated_question(
assert questions[dep].calc_dependents == ["calc"]


@pytest.mark.parametrize("question__slug", ["simple-question"])
@pytest.mark.parametrize(
"calc_expression,has_error",
[
("1.0", False),
("'simple-question'|answer", False),
("'other-calc-question'|answer + 10", True),
],
)
def test_recursive_calculated_question(
db, schema_executor, form_question, question_factory, calc_expression, has_error
db, schema_executor, form, form_question_factory, question_factory, document_factory
):
form = form_question.form

other_q = question_factory(
slug="other-calc-question",
base = question_factory(
slug="base",
type=models.Question.TYPE_INTEGER,
)
calc_1 = question_factory(
slug="calc-1",
type=models.Question.TYPE_CALCULATED_FLOAT,
calc_expression="0.0",
calc_expression='"base"|answer(0) + 1',
)
form.questions.add(other_q)
calc_2 = question_factory(
slug="calc-2",
type=models.Question.TYPE_CALCULATED_FLOAT,
calc_expression='"calc-1"|answer(0) * 2',
)
form_question_factory(form=form, question=base)
form_question_factory(form=form, question=calc_1)
form_question_factory(form=form, question=calc_2)

document = document_factory(form=form)
query = """
mutation SaveCalculatedQuestion($input: SaveCalculatedFloatQuestionInput!) {
saveCalculatedFloatQuestion (input: $input) {
question {
slug
__typename
... on CalculatedFloatQuestion {
calcExpression
}
}
mutation saveDocumentIntegerAnswer($input: SaveDocumentIntegerAnswerInput!) {
saveDocumentIntegerAnswer(input: $input) {
clientMutationId
}
}
"""

inp = {
variables = {
"input": {
"slug": "calc-question",
"label": "Calculated Float Question",
"calcExpression": calc_expression,
"document": to_global_id("Document", document.pk),
"question": to_global_id("Question", base.pk),
"value": 3,
}
}
result = schema_executor(query, variable_values=inp)

assert has_error == bool(result.errors)
schema_executor(query, variable_values=variables)

calc_ans = document.answers.get(question_id="calc-2")
assert calc_ans.value == 8


def test_calculated_question_update_calc_expr(
Expand Down
11 changes: 3 additions & 8 deletions caluma/caluma_form/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,19 +501,14 @@ def _validate_calc_expression(data):
question_jexl = jexl.QuestionJexl()
deps = set(question_jexl.extract_referenced_questions(expr))

calc_slugs = set(
models.Question.objects.filter(
pk__in=deps, type=models.Question.TYPE_CALCULATED_FLOAT
).values_list("slug", flat=True)
)
inexistent_slugs = deps - set(
models.Question.objects.filter(pk__in=deps).values_list("slug", flat=True)
)
illegal_deps = ", ".join(calc_slugs.union(inexistent_slugs))
illegal_deps = ", ".join(inexistent_slugs)

if illegal_deps:
if illegal_deps: # pragma: no cover
raise exceptions.ValidationError(
f"Calc expression references other calculated or inexistent questions: {illegal_deps}"
f"Calc expression references inexistent questions: {illegal_deps}"
)

def validate(self, data):
Expand Down

0 comments on commit ce0a4e8

Please sign in to comment.