Skip to content

Commit

Permalink
fix: remove unnecessary signals, don't recompute on copy
Browse files Browse the repository at this point in the history
  • Loading branch information
czosel committed Dec 30, 2024
1 parent 64d4df6 commit 72a5918
Show file tree
Hide file tree
Showing 3 changed files with 8 additions and 110 deletions.
5 changes: 2 additions & 3 deletions caluma/caluma_form/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,9 @@ def set_family(self, root_doc):

def copy(self, family=None, user=None):
"""Create a copy including all its answers."""
from caluma.caluma_form.utils import recalculate_answers_from_document

# defer calculated questions, as many unecessary recomputations will happen otherwise
# no need to update calculated questions, since calculated answers
# are copied as well
meta = dict(self.meta)
meta["_defer_calculation"] = True

Expand All @@ -423,7 +423,6 @@ def copy(self, family=None, user=None):

new_document.meta.pop("_defer_calculation", None)
new_document.save()
recalculate_answers_from_document(new_document)

return new_document

Expand Down
91 changes: 1 addition & 90 deletions caluma/caluma_form/signals.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import itertools
from django.db.models import Prefetch

from django.db.models.signals import (
m2m_changed,
post_delete,
post_init,
post_save,
pre_delete,
pre_save,
)
Expand All @@ -16,11 +11,7 @@
from caluma.utils import disable_raw

from . import models
from .utils import (
recalculate_answers_from_document,
update_calc_dependents,
update_or_create_calc_answer,
)
from .utils import update_calc_dependents


@receiver(pre_create_historical_record, sender=models.HistoricalAnswer)
Expand Down Expand Up @@ -94,83 +85,3 @@ def remove_calc_dependents(sender, instance, **kwargs):
update_calc_dependents(
instance.slug, old_expr=instance.calc_expression, new_expr="false"
)


# Update calculated answer on post_save
#
# Try to update the calculated answer value whenever a mutation on a possibly
# related model is performed.


@receiver(post_save, sender=models.Question)
@disable_raw
@filter_events(lambda instance: instance.type == models.Question.TYPE_CALCULATED_FLOAT)
def update_calc_from_question(sender, instance, created, update_fields, **kwargs):
# TODO optimize: only update answers if calc_expression is updated
# needs to happen during save() or __init__()

for document in models.Document.objects.filter(form__questions=instance):
update_or_create_calc_answer(instance, document)


@receiver(post_save, sender=models.FormQuestion)
@disable_raw
@filter_events(
lambda instance: instance.question.type == models.Question.TYPE_CALCULATED_FLOAT
)
def update_calc_from_form_question(sender, instance, created, **kwargs):
for document in instance.form.documents.all():
update_or_create_calc_answer(instance.question, document)


# @receiver(post_save, sender=models.Answer)
# @disable_raw
# @filter_events(lambda instance: instance.document and instance.question.calc_dependents)
# def update_calc_from_answer(sender, instance, **kwargs):
# # If there is no document on the answer it means that it's a default
# # answer. They shouldn't trigger a recalculation of a calculated field
# # even when they are technically listed as a dependency.
# # Also skip non-referenced answers.
# if instance.document.family.meta.get("_defer_calculation"):
# return
#
# if instance.question.type == models.Question.TYPE_TABLE:
# print("skipping update calc of table questions in event layer, because we don't have access to the question slug here")
# return
#
# print(f"saved answer to {instance.question.pk}, recalculate dependents:")
# document = models.Document.objects.filter(pk=instance.document_id).prefetch_related(
# *build_document_prefetch_statements(
# "family", prefetch_options=True
# ),
# ).first()
#
# for question in models.Question.objects.filter(
# pk__in=instance.question.calc_dependents
# ):
# print(f"- {question.pk}")
# update_or_create_calc_answer(question, document)


@receiver(post_save, sender=models.Document)
@disable_raw
# We're only interested in table row forms
@filter_events(lambda instance, created: instance.pk != instance.family_id or created)
def update_calc_from_document(sender, instance, created, **kwargs):
# we do this in a more focused way (only updating the calc dependents in the domain logic
# recalculate_answers_from_document(instance)
pass


@receiver(m2m_changed, sender=models.AnswerDocument)
def update_calc_from_answerdocument(sender, instance, **kwargs):
dependents = instance.document.form.questions.exclude(
calc_dependents=[]
).values_list("calc_dependents", flat=True)

dependent_questions = list(itertools.chain(*dependents))
# TODO: when is this even called?
print(f"answerdocument {instance.pk} changed, update {dependent_questions}")

for question in models.Question.objects.filter(pk__in=dependent_questions):
update_or_create_calc_answer(question, instance.document)
22 changes: 5 additions & 17 deletions caluma/caluma_form/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,27 +101,20 @@ def update_calc_dependents(slug, old_expr, new_expr):
question.save()


def update_or_create_calc_answer(question, document, struc, update_dependents=True):
# print("callsite", inspect.stack()[1][3], flush=True)

def update_or_create_calc_answer(
question, document, struc=None, update_dependents=True
):
root_doc = document.family

if not struc:
print("init structure")
struc = structure.FieldSet(root_doc, root_doc.form)
else:
# print("reusing struc")
pass
start = time()

field = struc.get_field(question.slug)
# print(f"get_field: ", time() - start)

# skip if question doesn't exist in this document structure
if field is None:
print("-- didn't find question, stopping", question)
return

print("-- found field", question)
jexl = QuestionJexl(
{"form": field.form, "document": field.document, "structure": field.parent()}
)
Expand All @@ -136,21 +129,16 @@ def update_or_create_calc_answer(question, document, struc, update_dependents=Tr
)

if update_dependents:
print(
f"{question.pk}: updating {len(field.question.calc_dependents)} calc dependents)"
)
for _question in models.Question.objects.filter(
pk__in=field.question.calc_dependents
):
# print(f"{question.pk} -> {_question.pk}")
update_or_create_calc_answer(_question, document, struc)


def recalculate_answers_from_document(instance):
"""When a table row is added, update dependent questions"""
if (instance.family or instance).meta.get("_defer_calculation"):
print("- defered")
return

print(f"saved document {instance.pk}, recalculate answers")
for question in models.Form.get_all_questions(
[(instance.family or instance).form_id]
Expand Down

0 comments on commit 72a5918

Please sign in to comment.