Skip to content

Commit

Permalink
files: add operation logs for files CRUD operations.
Browse files Browse the repository at this point in the history
* Uses invenio records resources components to create operation logs.

Co-Authored-by: Johnny Mariéthoz <Johnny.Mariethoz@rero.ch>
  • Loading branch information
jma committed Mar 28, 2024
1 parent 3b2dc72 commit d68c06f
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 18 deletions.
9 changes: 7 additions & 2 deletions rero_ils/modules/files/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ def create_pdf_record_files(document, metadata, flush=False,
try:
record = next(document.get_records_files(lib_pids=metadata["owners"]))
except StopIteration:
record = record_service.record_cls.create(data={"metadata": metadata})
item = record_service.create(
identity=system_identity, data={"metadata": metadata})
record = item._record
record.commit()
# index the file record
record_service.indexer.index_by_id(record.id)
Expand Down Expand Up @@ -116,6 +118,7 @@ def create_pdf_record_files(document, metadata, flush=False,
uow=uow
)
uow.commit()
return record


def load_files_for_document(document, metadata, files):
Expand All @@ -133,7 +136,9 @@ def load_files_for_document(document, metadata, files):
try:
record = next(document.get_records_files(lib_pids=metadata["owners"]))
except StopIteration:
record = record_service.record_cls.create(data={"metadata": metadata})
item = record_service.create(
identity=system_identity, data={"metadata": metadata})
record = item._record
record.commit()
# index the file record
record_service.indexer.index_by_id(record.id)
Expand Down
193 changes: 188 additions & 5 deletions rero_ils/modules/files/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,215 @@

from invenio_records_resources.services.files.components import \
FileServiceComponent
from invenio_records_resources.services.records.components import \
ServiceComponent

from rero_ils.modules.documents.api import Document
from rero_ils.modules.libraries.api import Library
from rero_ils.modules.operation_logs.extensions import OperationLogFactory
from rero_ils.modules.operation_logs.logs.api import SpecificOperationLog
from rero_ils.modules.operation_logs.models import OperationLogOperation

from .operations import ReindexDoc


class OperationLogRecordFactory(OperationLogFactory):
"""Factory to create CURD operation logs."""

def get_additional_informations(self, record):
"""Get some informations to add into the operation log.
Subclasses can override this property to add some informations into
the operation log dictionary.
:param record: the observed record.
:return a dict with additional informations.
"""
data = {}
if doc := record.get('document'):
data.setdefault(
'file', {})['document'] = \
SpecificOperationLog._get_document_data(doc)
if recid := record.get('recid'):
data.setdefault('file', {})['recid'] = recid
return data


class OperationLogsComponent(ServiceComponent):
"""Component to create CRUD operation logs."""

def _create_operation_logs(self, record, operation):
"""Create operation logs.
:param record: obj - record instance.
:param operation: str - CRUD operation
"""
# as the invenio record resource record is different than ILSRecord
# a wrapper should be created
class Rec(dict):
class provider:
pid_type = 'recid'

rec = Rec()
rec['pid'] = record.pid.pid_value
if owners := record.get('metadata', {}).get('owners'):
lib_pid = owners[0].replace('lib_', '')
rec.library_pid = lib_pid
rec.organisation_pid = Library.get_record_by_pid(
lib_pid).organisation_pid
if links := record.get('metadata', {}).get('links'):
rec['document'] = Document.get_record_by_pid(
links[0].replace('doc_', ''))
OperationLogRecordFactory().create_operation_log(rec, operation)

def create(self, identity, data, record, errors=None, **kwargs):
"""Create handler.
:param identity: flask principal Identity
:param data: dict - creation data
:param record: obj - the created record
"""
self._create_operation_logs(
record=record, operation=OperationLogOperation.CREATE)

def update(self, identity, data, record, **kwargs):
"""Update handler.
:param identity: flask principal Identity
:param data: dict - data to update the record
:param record: obj - the updated record
"""
self._create_operation_logs(
record=record, operation=OperationLogOperation.UPDATE)

def delete(self, identity, record, **kwargs):
"""Delete handler.
:param identity: flask principal Identity
:param record: obj - the updated record
"""
self._create_operation_logs(
record=record, operation=OperationLogOperation.DELETE)


class OperationLogsFileComponent(FileServiceComponent):
"""Component to create files CRUD operation logs."""

def _create_operation_logs(
self, record, file_key, operation, deleted_file=None
):
"""Create operation logs.
:param record: obj - record instance.
:param file_key: str - file key in the file record.
:param operation: str - CRUD operation
:param deleted_file: file instance - the deleted file instance.
"""
# for deletion the file is not in the record anymore.
if deleted_file:
file_metadata = deleted_file.get('metadata', {})
else:
file_metadata = record.files.get(file_key).get('metadata', {})

# only for main files
if file_metadata.get('type') in ['fulltext', 'thumbnail']:
return

# as the invenio record resource record is different than ILSRecord
# a wrapper should be created
class Rec(dict):
class provider:
pid_type = 'file'

rec = Rec()
rec['pid'] = file_key
if owners := record.get('metadata', {}).get('owners'):
lib_pid = owners[0].replace('lib_', '')
rec.library_pid = lib_pid
rec.organisation_pid = Library.get_record_by_pid(
lib_pid).organisation_pid

if links := record.get('metadata', {}).get('links'):
rec['document'] = Document.get_record_by_pid(
links[0].replace('doc_', ''))
rec['recid'] = record['id']
OperationLogRecordFactory().create_operation_log(
record=rec, operation=operation)

def commit_file(self, identity, id_, file_key, record):
"""Commit file handler.
:param identity: flask principal Identity
:param id_: str - record file id.
:param file_key: str - file key in the file record.
:param record: obj - record instance.
"""
self._create_operation_logs(
record, file_key, OperationLogOperation.CREATE)

def delete_file(self, identity, id_, file_key, record, deleted_file):
"""Delete file handler.
:param identity: flask principal Identity
:param id_: str - record file id.
:param file_key: str - file key in the file record.
:param record: obj - record instance.
:param deleted_file: file instance - the deleted file instance.
"""
self._create_operation_logs(
record, file_key, OperationLogOperation.DELETE,
deleted_file=deleted_file)


class DocumentReindexComponent(FileServiceComponent):
"""Component to reindex document linked to the file record."""

def _register(self, record):
"""Register a document reindex operation."""
"""Register a document reindex operation.
:param record: obj - record instance.
"""
doc_pid = record["metadata"]["links"][0].replace("doc_", "")
operation = ReindexDoc(doc_pid)
if operation not in self.uow._operations:
self.uow.register(operation)

def commit_file(self, identity, id_, file_key, record):
"""Commit file handler."""
"""Commit file handler.
:param identity: flask principal Identity
:param id_: str - record file id.
"""
self._register(record)

def update_file_metadata(self, identity, id_, file_key, record, data):
"""Update file metadata handler."""
"""Update file metadata handler.
:param identity: flask principal Identity
:param id_: str - record file id.
:param file_key: str - file key in the file record.
:param record: obj - record instance.
:param deleted_file: file instance - the deleted file instance.
:param data: dict - data to update the record
"""
self._register(record)

def delete_file(self, identity, id_, file_key, record, deleted_file):
"""Delete file handler."""
"""Delete file handler.
:param identity: flask principal Identity
:param id_: str - record file id.
:param file_key: str - file key in the file record.
:param record: obj - record instance.
:param deleted_file: file instance - the deleted file instance.
"""
self._register(record)

def delete_all_files(self, identity, id_, record, results):
"""Delete all files handler."""
"""Delete all files handler.
:param identity: flask principal Identity
:param id_: str - record file id.
:param record: obj - record instance.
"""
self._register(record)
13 changes: 10 additions & 3 deletions rero_ils/modules/files/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,30 @@
from rero_invenio_files.records.services import FileServiceConfig, \
RecordServiceConfig

from .components import DocumentReindexComponent
from .components import DocumentReindexComponent, OperationLogsComponent, \
OperationLogsFileComponent
from .permissions import FilePermissionPolicy


class RecordServiceConfig(RecordServiceConfig):
"""File record service."""

# Common configuration
permission_policy_cls = FilePermissionPolicy

# Service components
components = RecordServiceConfig.components + [OperationLogsComponent]


class RecordFileServiceConfig(FileServiceConfig):
"""Files service configuration."""

# Common configuration
permission_policy_cls = FilePermissionPolicy

# maximum files per buckets
max_files_count = 1000

# reindex a document after files operations
components = FileServiceConfig.components + [DocumentReindexComponent]
# Service components
components = FileServiceConfig.components + [
DocumentReindexComponent, OperationLogsFileComponent]
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,28 @@
}
}
},
"file": {
"type": "object",
"properties": {
"document": {
"type": "object",
"properties": {
"pid": {
"type": "keyword"
},
"title": {
"type": "text"
},
"type": {
"type": "keyword"
}
}
},
"recid": {
"type": "keyword"
}
}
},
"patron": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -236,4 +258,4 @@
"aliases": {
"operation_logs": {}
}
}
}
16 changes: 10 additions & 6 deletions rero_ils/modules/operation_logs/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
from ..utils import extracted_data_from_ref


class OperationLogObserverExtension(RecordExtension):
"""Observe a resource and build operation log when it changes."""
class OperationLogFactory:
"""Factory to create CURD operation logs."""

def get_additional_informations(self, record):
"""Get some informations to add into the operation log.
Expand Down Expand Up @@ -103,26 +103,30 @@ def _build_operation_log(self, record, operation):
oplg |= (self.get_additional_informations(record) or {})
return oplg

def _create_operation_log(self, record, operation, **kwargs):
def create_operation_log(self, record, operation, **kwargs):
"""Build and register an operation log."""
from .api import OperationLog
data = self._build_operation_log(record, operation)
OperationLog.create(data)


class OperationLogObserverExtension(RecordExtension, OperationLogFactory):
"""Observe a resource and build operation log when it changes."""

post_create = partialmethod(
_create_operation_log,
OperationLogFactory.create_operation_log,
operation=OperationLogOperation.CREATE
)
"""Called after a record is created."""

pre_commit = partialmethod(
_create_operation_log,
OperationLogFactory.create_operation_log,
operation=OperationLogOperation.UPDATE
)
"""Called before a record is committed."""

post_delete = partialmethod(
_create_operation_log,
OperationLogFactory.create_operation_log,
operation=OperationLogOperation.DELETE
)
"""Called after a record is deleted."""
Expand Down

0 comments on commit d68c06f

Please sign in to comment.