This commit is contained in:
Fabre Florian 2025-09-26 14:30:59 +02:00
parent 8a483a7da0
commit b4d9d2bbf3
No known key found for this signature in database
GPG key ID: F3016C12F952FFBA
5 changed files with 36 additions and 74 deletions

View file

@ -1,19 +0,0 @@
"""Impress Core application"""
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class CoreConfig(AppConfig):
"""Configuration class for the impress core app."""
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

View file

@ -7,6 +7,7 @@ import hashlib
import smtplib
import uuid
from datetime import timedelta
from functools import partial
from logging import getLogger
from django.conf import settings
@ -433,6 +434,8 @@ class Document(MP_Node, BaseModel):
if self._content:
self.save_content(self._content)
self.trigger_indexer()
def save_content(self, content):
"""Save content to object storage."""
@ -460,6 +463,18 @@ class Document(MP_Node, BaseModel):
content_file = ContentFile(bytes_content)
default_storage.save(file_key, content_file)
def trigger_indexer(self):
"""
Asynchronously 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.
"""
# Prevents some circular imports
# pylint: disable=import-outside-toplevel
from core.tasks.find import trigger_document_indexer # noqa: PLC0415
transaction.on_commit(partial(trigger_document_indexer, self))
def is_leaf(self):
"""
:returns: True if the node is has no children
@ -1051,9 +1066,14 @@ class DocumentAccess(BaseAccess):
def save(self, *args, **kwargs):
"""Override save to clear the document's cache for number of accesses."""
created = self.pk is None
super().save(*args, **kwargs)
self.document.invalidate_nb_accesses_cache()
if not created:
self.document.trigger_indexer()
@property
def target_key(self):
"""Get a unique key for the actor targeted by the access, without possible conflict."""

View file

@ -1,31 +0,0 @@
"""
Declare and configure the signals for the impress core application
"""
from functools import partial
from django.db import transaction
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.
"""
transaction.on_commit(partial(trigger_document_indexer, instance))
@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:
transaction.on_commit(partial(trigger_document_indexer, instance.document))

View file

@ -3,7 +3,7 @@
from logging import getLogger
from django.conf import settings
from django.core.cache import cache
from django.core.cache import cache as django_cache
from impress.celery_app import app
@ -15,9 +15,9 @@ def indexer_debounce_lock(document_id):
key = f"doc-indexer-debounce-{document_id}"
try:
return cache.incr(key)
return django_cache.incr(key)
except ValueError:
cache.set(key, 1)
django_cache.set(key, 1)
return 1
@ -26,9 +26,9 @@ def indexer_debounce_release(document_id):
key = f"doc-indexer-debounce-{document_id}"
try:
return cache.decr(key)
return django_cache.decr(key)
except ValueError:
cache.set(key, 0)
django_cache.set(key, 0)
return 0
@ -37,7 +37,7 @@ def document_indexer_task(document_id):
"""Celery Task : Sends indexation query for a document."""
# Prevents some circular imports
# pylint: disable=import-outside-toplevel
from core import models # noqa : PLC0415
from core import models
from core.services.search_indexers import ( # noqa : PLC0415
get_batch_accesses_by_users_and_teams,
get_document_indexer,

View file

@ -1488,6 +1488,10 @@ def test_models_documents_post_save_indexer_with_accesses(mock_push, indexer_set
indexer.serialize_document(doc1, accesses),
indexer.serialize_document(doc2, accesses),
indexer.serialize_document(doc3, accesses),
# Called twice : one for the document.save(), one for the documentaccess.save()
indexer.serialize_document(doc1, accesses),
indexer.serialize_document(doc2, accesses),
indexer.serialize_document(doc3, accesses),
],
key=itemgetter("id"),
)
@ -1504,8 +1508,6 @@ def test_models_documents_post_save_indexer_deleted(mock_push, indexer_settings)
"""Indexation task on deleted or ancestor_deleted documents"""
indexer_settings.SEARCH_INDEXER_COUNTDOWN = 0
user = factories.UserFactory()
with transaction.atomic():
doc = factories.DocumentFactory(
link_reach=models.LinkReachChoices.AUTHENTICATED
@ -1520,10 +1522,6 @@ def test_models_documents_post_save_indexer_deleted(mock_push, indexer_settings)
doc_deleted.soft_delete()
doc_ancestor_deleted.ancestors_deleted_at = doc_deleted.deleted_at
factories.UserDocumentAccessFactory(document=doc, user=user)
factories.UserDocumentAccessFactory(document=doc_deleted, user=user)
factories.UserDocumentAccessFactory(document=doc_ancestor_deleted, user=user)
doc_deleted.refresh_from_db()
doc_ancestor_deleted.refresh_from_db()
@ -1534,9 +1532,9 @@ def test_models_documents_post_save_indexer_deleted(mock_push, indexer_settings)
assert doc_ancestor_deleted.ancestors_deleted_at is not None
accesses = {
str(doc.path): {"users": [user.sub]},
str(doc_deleted.path): {"users": [user.sub]},
str(doc_ancestor_deleted.path): {"users": [user.sub]},
str(doc.path): {"users": []},
str(doc_deleted.path): {"users": []},
str(doc_ancestor_deleted.path): {"users": []},
}
data = [call.args[0] for call in mock_push.call_args_list]
@ -1566,8 +1564,6 @@ def test_models_documents_post_save_indexer_restored(mock_push, indexer_settings
"""Restart indexation task on restored documents"""
indexer_settings.SEARCH_INDEXER_COUNTDOWN = 0
user = factories.UserFactory()
with transaction.atomic():
doc = factories.DocumentFactory(
link_reach=models.LinkReachChoices.AUTHENTICATED
@ -1582,10 +1578,6 @@ def test_models_documents_post_save_indexer_restored(mock_push, indexer_settings
doc_deleted.soft_delete()
doc_ancestor_deleted.ancestors_deleted_at = doc_deleted.deleted_at
factories.UserDocumentAccessFactory(document=doc, user=user)
factories.UserDocumentAccessFactory(document=doc_deleted, user=user)
factories.UserDocumentAccessFactory(document=doc_ancestor_deleted, user=user)
doc_deleted.refresh_from_db()
doc_ancestor_deleted.refresh_from_db()
@ -1607,9 +1599,9 @@ def test_models_documents_post_save_indexer_restored(mock_push, indexer_settings
assert doc_ancestor_restored.ancestors_deleted_at is None
accesses = {
str(doc.path): {"users": [user.sub]},
str(doc_deleted.path): {"users": [user.sub]},
str(doc_ancestor_deleted.path): {"users": [user.sub]},
str(doc.path): {"users": []},
str(doc_deleted.path): {"users": []},
str(doc_ancestor_deleted.path): {"users": []},
}
data = [call.args[0] for call in mock_push.call_args_list]