From 10cb08f9376ba7c05c8e47103c812b8da2ac96c8 Mon Sep 17 00:00:00 2001 From: Mart Ratas Date: Tue, 30 Jun 2026 15:56:10 +0100 Subject: [PATCH 1/5] Rework post-save project cui update to avoid saving the same model twice --- medcat-trainer/webapp/api/api/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/medcat-trainer/webapp/api/api/utils.py b/medcat-trainer/webapp/api/api/utils.py index c786fa3b8..46941405b 100644 --- a/medcat-trainer/webapp/api/api/utils.py +++ b/medcat-trainer/webapp/api/api/utils.py @@ -485,12 +485,12 @@ def prep_docs(project_id: List[int], doc_ids: List[int], user_id: int): @receiver(post_save, sender=ProjectAnnotateEntities) def save_project_anno(sender, instance, **kwargs): if instance.cuis_file: - post_save.disconnect(save_project_anno, sender=ProjectAnnotateEntities) cuis_from_file = json.load(open(instance.cuis_file.path)) cui_list = [c.strip() for c in instance.cuis.split(',')] - instance.cuis = ','.join(set(cui_list) - set(cuis_from_file)) - instance.save() - post_save.connect(save_project_anno, sender=ProjectAnnotateEntities) + new_cuis = ','.join(set(cui_list) - set(cuis_from_file)) + if new_cuis != instance.cuis: + instance.cuis = new_cuis + ProjectAnnotateEntities.objects.filter(pk=instance.pk).update(cuis=new_cuis) def env_str_to_bool(var: str, default: bool): From 2a14cda32bfc7df08a271ad21e2d04703aebe790 Mon Sep 17 00:00:00 2001 From: Mart Ratas Date: Tue, 30 Jun 2026 17:57:57 +0100 Subject: [PATCH 2/5] Cache CUI load from file to avoid IO on every entity save --- medcat-trainer/webapp/api/api/utils.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/medcat-trainer/webapp/api/api/utils.py b/medcat-trainer/webapp/api/api/utils.py index 46941405b..5f7ff39fc 100644 --- a/medcat-trainer/webapp/api/api/utils.py +++ b/medcat-trainer/webapp/api/api/utils.py @@ -3,6 +3,7 @@ import os from typing import List import dill +from functools import lru_cache import requests from background_task import background @@ -482,10 +483,16 @@ def prep_docs(project_id: List[int], doc_ids: List[int], user_id: int): project.id, project.prepared_documents) +@lru_cache(maxsize=8) +def _load_cuis_from_file(path): + with open(path) as f: + return json.load(f) + + @receiver(post_save, sender=ProjectAnnotateEntities) def save_project_anno(sender, instance, **kwargs): if instance.cuis_file: - cuis_from_file = json.load(open(instance.cuis_file.path)) + cuis_from_file = _load_cuis_from_file(instance.cuis_file.path) cui_list = [c.strip() for c in instance.cuis.split(',')] new_cuis = ','.join(set(cui_list) - set(cuis_from_file)) if new_cuis != instance.cuis: From 93780f344352bba3ceb1b3e03e3c7da5699a67fa Mon Sep 17 00:00:00 2001 From: Mart Ratas Date: Tue, 30 Jun 2026 18:00:47 +0100 Subject: [PATCH 3/5] Use model update instead of save for modification date --- medcat-trainer/webapp/api/api/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/medcat-trainer/webapp/api/api/models.py b/medcat-trainer/webapp/api/api/models.py index eab137b41..50e7fde3b 100644 --- a/medcat-trainer/webapp/api/api/models.py +++ b/medcat-trainer/webapp/api/api/models.py @@ -370,8 +370,12 @@ class Meta: def save(self, *args, **kwargs): super().save(*args, **kwargs) + # so that the local project last_modified is updated self.project.last_modified = self.last_modified - self.project.save() + # so that the project last_modified is updated in the database + ProjectAnnotateEntities.objects.filter(pk=self.project_id).update( + last_modified=self.last_modified + ) def __str__(self): return f'{self.start_entity} - {self.relation} - {self.end_entity}' From 95b6946b14151642418f3c5ebab50bbff60ba82a Mon Sep 17 00:00:00 2001 From: Mart Ratas Date: Tue, 30 Jun 2026 18:05:09 +0100 Subject: [PATCH 4/5] Change project last modified for all such saves --- medcat-trainer/webapp/api/api/models.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/medcat-trainer/webapp/api/api/models.py b/medcat-trainer/webapp/api/api/models.py index 50e7fde3b..a81c450d0 100644 --- a/medcat-trainer/webapp/api/api/models.py +++ b/medcat-trainer/webapp/api/api/models.py @@ -354,6 +354,14 @@ def __str__(self): return str(self.label) +def update_project_last_modified(project: ProjectAnnotateEntities, last_modified): + # so that the local project last_modified is updated + project.last_modified = last_modified + # so that the project last_modified is updated in the database + ProjectAnnotateEntities.objects.filter(pk=project.id).update( + last_modified=last_modified + ) + class EntityRelation(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) project = models.ForeignKey('Project', on_delete=models.CASCADE) @@ -370,12 +378,7 @@ class Meta: def save(self, *args, **kwargs): super().save(*args, **kwargs) - # so that the local project last_modified is updated - self.project.last_modified = self.last_modified - # so that the project last_modified is updated in the database - ProjectAnnotateEntities.objects.filter(pk=self.project_id).update( - last_modified=self.last_modified - ) + update_project_last_modified(self.project, self.last_modified) def __str__(self): return f'{self.start_entity} - {self.relation} - {self.end_entity}' @@ -406,8 +409,7 @@ class Meta: def save(self, *args, **kwargs): super().save(*args, **kwargs) - self.project.last_modified = self.last_modified - self.project.save() + update_project_last_modified(self.project, self.last_modified) def __str__(self): return str(self.entity) @@ -555,8 +557,7 @@ class MetaAnnotation(models.Model): def save(self, *args, **kwargs): super().save(*args, **kwargs) - self.annotated_entity.last_modified = self.last_modified - self.annotated_entity.save() + update_project_last_modified(self.annotated_entity.project, self.last_modified) def __str__(self): return str(self.annotated_entity) From 4115137cd3bb74844fae1be7e1e5cc4e5e126a9f Mon Sep 17 00:00:00 2001 From: Mart Ratas Date: Tue, 30 Jun 2026 18:18:26 +0100 Subject: [PATCH 5/5] Fix typing issue --- medcat-trainer/webapp/api/api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/medcat-trainer/webapp/api/api/models.py b/medcat-trainer/webapp/api/api/models.py index a81c450d0..32a5c9273 100644 --- a/medcat-trainer/webapp/api/api/models.py +++ b/medcat-trainer/webapp/api/api/models.py @@ -354,7 +354,7 @@ def __str__(self): return str(self.label) -def update_project_last_modified(project: ProjectAnnotateEntities, last_modified): +def update_project_last_modified(project: 'ProjectAnnotateEntities', last_modified): # so that the local project last_modified is updated project.last_modified = last_modified # so that the project last_modified is updated in the database