From bf978b53767106c8aab6cf8879271faacf1a763b Mon Sep 17 00:00:00 2001 From: Fabre Florian Date: Fri, 12 Sep 2025 14:11:23 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(backend)=20refactor=20indexation=20si?= =?UTF-8?q?gnals=20and=20fix=20circular=20import=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fabre Florian --- CHANGELOG.md | 3 ++ src/backend/core/apps.py | 22 ++++++++++----- src/backend/core/models.py | 22 --------------- src/backend/core/signals.py | 28 +++++++++++++++++++ src/backend/core/tasks/find.py | 13 +++++---- .../core/tests/test_models_documents.py | 6 ++-- .../tests/test_services_search_indexers.py | 2 +- 7 files changed, 57 insertions(+), 39 deletions(-) create mode 100644 src/backend/core/signals.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b145151..81957d5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to ### Added - ✨(backend) allow to create a new user in a marketing system +- ✨(backend) add async indexation of documents on save (or access save) #1276 +- ✨(backend) add debounce mechanism to limit indexation jobs #1276 +- ✨(api) add API route to search for indexed documents in Find #1276 ### Changed diff --git a/src/backend/core/apps.py b/src/backend/core/apps.py index bba7de0f..b8ce6488 100644 --- a/src/backend/core/apps.py +++ b/src/backend/core/apps.py @@ -1,11 +1,19 @@ """Impress Core application""" -# from django.apps import AppConfig -# from django.utils.translation import gettext_lazy as _ + +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ -# class CoreConfig(AppConfig): -# """Configuration class for the impress core app.""" +class CoreConfig(AppConfig): + """Configuration class for the impress core app.""" -# name = "core" -# app_label = "core" -# verbose_name = _("impress core application") + name = "core" + app_label = "core" + verbose_name = _("Impress core application") + + def ready(self): + """ + Import signals when the app is ready. + """ + # pylint: disable=import-outside-toplevel, unused-import + from . import signals # noqa: PLC0415 diff --git a/src/backend/core/models.py b/src/backend/core/models.py index c05c1a83..424d6b89 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -20,9 +20,7 @@ from django.core.files.base import ContentFile from django.core.files.storage import default_storage from django.core.mail import send_mail from django.db import models, transaction -from django.db.models import signals from django.db.models.functions import Left, Length -from django.dispatch import receiver from django.template.loader import render_to_string from django.utils import timezone from django.utils.functional import cached_property @@ -41,7 +39,6 @@ from .choices import ( RoleChoices, get_equivalent_link_definition, ) -from .tasks.find import trigger_document_indexer from .validators import sub_validator logger = getLogger(__name__) @@ -955,16 +952,6 @@ class Document(MP_Node, BaseModel): ) -@receiver(signals.post_save, sender=Document) -def document_post_save(sender, instance, **kwargs): # pylint: disable=unused-argument - """ - Asynchronous call to the document indexer at the end of the transaction. - Note : Within the transaction we can have an empty content and a serialization - error. - """ - trigger_document_indexer(instance, on_commit=True) - - class LinkTrace(BaseModel): """ Relation model to trace accesses to a document via a link by a logged-in user. @@ -1195,15 +1182,6 @@ class DocumentAccess(BaseAccess): } -@receiver(signals.post_save, sender=DocumentAccess) -def document_access_post_save(sender, instance, created, **kwargs): # pylint: disable=unused-argument - """ - Asynchronous call to the document indexer at the end of the transaction. - """ - if not created: - trigger_document_indexer(instance.document, on_commit=True) - - class DocumentAskForAccess(BaseModel): """Relation model to ask for access to a document.""" diff --git a/src/backend/core/signals.py b/src/backend/core/signals.py new file mode 100644 index 00000000..b6a12929 --- /dev/null +++ b/src/backend/core/signals.py @@ -0,0 +1,28 @@ +""" +Declare and configure the signals for the impress core application +""" + +from django.db.models import signals +from django.dispatch import receiver + +from . import models +from .tasks.find import trigger_document_indexer + + +@receiver(signals.post_save, sender=models.Document) +def document_post_save(sender, instance, **kwargs): # pylint: disable=unused-argument + """ + Asynchronous call to the document indexer at the end of the transaction. + Note : Within the transaction we can have an empty content and a serialization + error. + """ + trigger_document_indexer(instance, on_commit=True) + + +@receiver(signals.post_save, sender=models.DocumentAccess) +def document_access_post_save(sender, instance, created, **kwargs): # pylint: disable=unused-argument + """ + Asynchronous call to the document indexer at the end of the transaction. + """ + if not created: + trigger_document_indexer(instance.document, on_commit=True) diff --git a/src/backend/core/tasks/find.py b/src/backend/core/tasks/find.py index 4a58b830..10b15c35 100644 --- a/src/backend/core/tasks/find.py +++ b/src/backend/core/tasks/find.py @@ -7,12 +7,6 @@ from django.conf import settings from django.core.cache import cache from django.db import transaction -from core import models -from core.services.search_indexers import ( - get_batch_accesses_by_users_and_teams, - get_document_indexer_class, -) - from impress.celery_app import app logger = getLogger(__file__) @@ -52,6 +46,13 @@ def document_indexer_task(document_id): logger.info("Skip document %s indexation", document_id) return + # pylint: disable=import-outside-toplevel + from core import models # noqa: PLC0415 + from core.services.search_indexers import ( # noqa: PLC0415 + get_batch_accesses_by_users_and_teams, + get_document_indexer_class, + ) + doc = models.Document.objects.get(pk=document_id) indexer = get_document_indexer_class()() accesses = get_batch_accesses_by_users_and_teams((doc.path,)) diff --git a/src/backend/core/tests/test_models_documents.py b/src/backend/core/tests/test_models_documents.py index 7931eacd..44165961 100644 --- a/src/backend/core/tests/test_models_documents.py +++ b/src/backend/core/tests/test_models_documents.py @@ -3,11 +3,11 @@ Unit tests for the Document model """ # pylint: disable=too-many-lines -from operator import itemgetter import random import smtplib import time from logging import Logger +from operator import itemgetter from unittest import mock from django.contrib.auth.models import AnonymousUser @@ -1651,13 +1651,13 @@ def test_models_documents_post_save_indexer(mock_push, settings): indexer = FindDocumentIndexer() - assert sorted(data, key=itemgetter('id')) == sorted( + assert sorted(data, key=itemgetter("id")) == sorted( [ indexer.serialize_document(doc1, accesses), indexer.serialize_document(doc2, accesses), indexer.serialize_document(doc3, accesses), ], - key=itemgetter('id'), + key=itemgetter("id"), ) # The debounce counters should be reset diff --git a/src/backend/core/tests/test_services_search_indexers.py b/src/backend/core/tests/test_services_search_indexers.py index 9b4309ac..e0f43fca 100644 --- a/src/backend/core/tests/test_services_search_indexers.py +++ b/src/backend/core/tests/test_services_search_indexers.py @@ -36,7 +36,6 @@ class FakeDocumentIndexer(BaseDocumentIndexer): return {} - @pytest.fixture(name="fake_indexer_settings") def fake_indexer_settings_fixture(settings): """Fixture to switch the indexer to the FakeDocumentIndexer.""" @@ -103,6 +102,7 @@ def test_services_search_indexer_class(fake_indexer_settings): "core.tests.test_services_search_indexers.FakeDocumentIndexer" ) + def test_services_search_indexer_url_is_none(settings): """ Indexer should raise RuntimeError if SEARCH_INDEXER_URL is None or empty.