(backend) refactor indexation signals and fix circular import issues

Signed-off-by: Fabre Florian <ffabre@hybird.org>
This commit is contained in:
Fabre Florian
2025-09-12 14:11:23 +02:00
committed by Quentin BEY
parent 24460ffc3a
commit bf978b5376
7 changed files with 57 additions and 39 deletions

View File

@@ -9,6 +9,9 @@ and this project adheres to
### Added ### Added
- ✨(backend) allow to create a new user in a marketing system - ✨(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 ### Changed

View File

@@ -1,11 +1,19 @@
"""Impress Core application""" """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): class CoreConfig(AppConfig):
# """Configuration class for the impress core app.""" """Configuration class for the impress core app."""
# name = "core" name = "core"
# app_label = "core" app_label = "core"
# verbose_name = _("impress core application") 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

View File

@@ -20,9 +20,7 @@ from django.core.files.base import ContentFile
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.core.mail import send_mail from django.core.mail import send_mail
from django.db import models, transaction from django.db import models, transaction
from django.db.models import signals
from django.db.models.functions import Left, Length from django.db.models.functions import Left, Length
from django.dispatch import receiver
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
@@ -41,7 +39,6 @@ from .choices import (
RoleChoices, RoleChoices,
get_equivalent_link_definition, get_equivalent_link_definition,
) )
from .tasks.find import trigger_document_indexer
from .validators import sub_validator from .validators import sub_validator
logger = getLogger(__name__) 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): class LinkTrace(BaseModel):
""" """
Relation model to trace accesses to a document via a link by a logged-in user. 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): class DocumentAskForAccess(BaseModel):
"""Relation model to ask for access to a document.""" """Relation model to ask for access to a document."""

View File

@@ -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)

View File

@@ -7,12 +7,6 @@ from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.db import transaction 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 from impress.celery_app import app
logger = getLogger(__file__) logger = getLogger(__file__)
@@ -52,6 +46,13 @@ def document_indexer_task(document_id):
logger.info("Skip document %s indexation", document_id) logger.info("Skip document %s indexation", document_id)
return 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) doc = models.Document.objects.get(pk=document_id)
indexer = get_document_indexer_class()() indexer = get_document_indexer_class()()
accesses = get_batch_accesses_by_users_and_teams((doc.path,)) accesses = get_batch_accesses_by_users_and_teams((doc.path,))

View File

@@ -3,11 +3,11 @@ Unit tests for the Document model
""" """
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
from operator import itemgetter
import random import random
import smtplib import smtplib
import time import time
from logging import Logger from logging import Logger
from operator import itemgetter
from unittest import mock from unittest import mock
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
@@ -1651,13 +1651,13 @@ def test_models_documents_post_save_indexer(mock_push, settings):
indexer = FindDocumentIndexer() indexer = FindDocumentIndexer()
assert sorted(data, key=itemgetter('id')) == sorted( assert sorted(data, key=itemgetter("id")) == sorted(
[ [
indexer.serialize_document(doc1, accesses), indexer.serialize_document(doc1, accesses),
indexer.serialize_document(doc2, accesses), indexer.serialize_document(doc2, accesses),
indexer.serialize_document(doc3, accesses), indexer.serialize_document(doc3, accesses),
], ],
key=itemgetter('id'), key=itemgetter("id"),
) )
# The debounce counters should be reset # The debounce counters should be reset

View File

@@ -36,7 +36,6 @@ class FakeDocumentIndexer(BaseDocumentIndexer):
return {} return {}
@pytest.fixture(name="fake_indexer_settings") @pytest.fixture(name="fake_indexer_settings")
def fake_indexer_settings_fixture(settings): def fake_indexer_settings_fixture(settings):
"""Fixture to switch the indexer to the FakeDocumentIndexer.""" """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" "core.tests.test_services_search_indexers.FakeDocumentIndexer"
) )
def test_services_search_indexer_url_is_none(settings): def test_services_search_indexer_url_is_none(settings):
""" """
Indexer should raise RuntimeError if SEARCH_INDEXER_URL is None or empty. Indexer should raise RuntimeError if SEARCH_INDEXER_URL is None or empty.