Refactor file metadata app

Add translatable label to the label admin method.

Add access association from DocumentVersionDriverEntry to
document version.

Enclose process method and event commit in a transaction.

Update process method to not error out if EXIF tool
is not found.

Update views and tests to use ExternalObjectMixin and
comply with MERCs 5 and 6.

Signed-off-by: Roberto Rosario <Roberto.Rosario.Gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2019-02-03 19:22:49 -04:00
parent 4376d76c8a
commit dcea32ae38
8 changed files with 82 additions and 83 deletions

View File

@@ -1,13 +1,15 @@
from __future__ import unicode_literals
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from .models import StoredDriver
@admin.register(StoredDriver)
class StoredDriverAdmin(admin.ModelAdmin):
list_display = ('internal_name', 'label', 'driver_path')
list_display = ('internal_name', 'get_label', 'driver_path')
def label(self, instance):
def get_label(self, instance):
return instance.driver_label
get_label.short_description = _('Label')

View File

@@ -121,6 +121,9 @@ class FileMetadataApp(MayanAppConfig):
ModelPermission.register_inheritance(
model=DocumentTypeSettings, related='document_type',
)
ModelPermission.register_inheritance(
model=DocumentVersionDriverEntry, related='document_version',
)
SourceColumn(attribute='key', source=FileMetadataEntry)
SourceColumn(attribute='value', source=FileMetadataEntry)

View File

@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import logging
from django.apps import apps
from django.db import transaction
from .events import event_file_metadata_document_version_finish
from .exceptions import FileMetadataDriverError
@@ -14,35 +15,36 @@ logger = logging.getLogger(__name__)
class FileMetadataDriver(object):
_registry = {}
@classmethod
def register(cls, mimetypes):
for mimetype in mimetypes:
cls._registry.setdefault(mimetype, []).append(cls)
@classmethod
def process_document_version(cls, document_version):
for driver_class in cls._registry.get(document_version.mimetype, ()):
try:
driver = driver_class()
driver.process(document_version=document_version)
with transaction.atomic():
driver.process(document_version=document_version)
event_file_metadata_document_version_finish.commit(
action_object=document_version.document,
target=document_version
)
post_document_version_file_metadata_processing.send(
sender=document_version.__class__,
instance=document_version
)
except FileMetadataDriverError:
# If driver raises error, try next in the list
pass
else:
# If driver was successfull there is no need to try
# others in the list for this mimetype
event_file_metadata_document_version_finish.commit(
action_object=document_version.document,
target=document_version
)
post_document_version_file_metadata_processing.send(
sender=document_version.__class__,
instance=document_version
)
return
@classmethod
def register(cls, mimetypes):
for mimetype in mimetypes:
cls._registry.setdefault(mimetype, []).append(cls)
def process(self, document_version):
logger.info(
'Starting processing document version: %s', document_version
@@ -68,7 +70,9 @@ class FileMetadataDriver(object):
document_version=document_version
)
for key, value in self._process(document_version=document_version).items():
results = self._process(document_version=document_version) or {}
for key, value in results.items():
document_version_driver_entry.entries.create(
key=key, value=value
)

View File

@@ -30,14 +30,20 @@ class EXIFToolDriver(FileMetadataDriver):
self.command_exiftool = self.command_exiftool.bake('-j')
def _process(self, document_version):
new_file_object, temp_filename = mkstemp()
if self.command_exiftool:
new_file_object, temp_filename = mkstemp()
try:
document_version.save_to_file(filepath=temp_filename)
result = self.command_exiftool(temp_filename)
return json.loads(s=result.stdout)[0]
finally:
fs_cleanup(filename=temp_filename)
try:
document_version.save_to_file(filepath=temp_filename)
result = self.command_exiftool(temp_filename)
return json.loads(s=result.stdout)[0]
finally:
fs_cleanup(filename=temp_filename)
else:
logger.warning(
'EXIFTool binary not found, not processing document version: %s',
document_version
)
EXIFToolDriver.register(

View File

@@ -1,5 +1,9 @@
from __future__ import unicode_literals
class FileMetadataDriverError(Exception):
class FileMetadataError(Exception):
"""Base file metadata driver exception"""
class FileMetadataDriverError(FileMetadataError):
"""Exception raised when a driver encounters an unexpected error"""

View File

@@ -12,7 +12,7 @@ def handler_initialize_new_document_type_settings(sender, instance, **kwargs):
if kwargs['created']:
DocumentTypeSettings.objects.create(
document_type=instance, auto_process=setting_auto_process.value
auto_process=setting_auto_process.value, document_type=instance
)

View File

@@ -14,10 +14,6 @@ from .literals import TEST_FILE_METADATA_KEY
@override_settings(FILE_METADATA_AUTO_PROCESS=True)
class FileMetadataViewsTestCase(GenericDocumentViewTestCase):
def setUp(self):
super(FileMetadataViewsTestCase, self).setUp()
self.login_user()
def _request_document_version_driver_list_view(self):
return self.get(
viewname='file_metadata:document_driver_list',
@@ -26,7 +22,7 @@ class FileMetadataViewsTestCase(GenericDocumentViewTestCase):
def test_document_version_driver_list_view_no_permission(self):
response = self._request_document_version_driver_list_view()
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 404)
def test_document_version_driver_list_view_with_access(self):
self.grant_access(
@@ -46,12 +42,12 @@ class FileMetadataViewsTestCase(GenericDocumentViewTestCase):
def test_document_version_file_metadata_list_view_no_permission(self):
response = self._request_document_version_file_metadata_list_view()
self.assertNotContains(
response=response, text=TEST_FILE_METADATA_KEY, status_code=403
response=response, text=TEST_FILE_METADATA_KEY, status_code=404
)
def test_document_version_file_metadata_list_view_with_access(self):
self.grant_access(
permission=permission_file_metadata_view, obj=self.document
obj=self.document, permission=permission_file_metadata_view
)
response = self._request_document_version_file_metadata_list_view()
self.assertContains(
@@ -67,7 +63,7 @@ class FileMetadataViewsTestCase(GenericDocumentViewTestCase):
def test_document_submit_view_no_permission(self):
self.document.latest_version.file_metadata_drivers.all().delete()
response = self._request_document_submit_view()
self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 404)
self.assertEqual(
self.document.latest_version.file_metadata_drivers.count(), 0
)
@@ -94,7 +90,7 @@ class FileMetadataViewsTestCase(GenericDocumentViewTestCase):
def test_multiple_document_submit_view_no_permission(self):
self.document.latest_version.file_metadata_drivers.all().delete()
response = self._request_multiple_document_submit_view()
self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 404)
self.assertEqual(
self.document.latest_version.file_metadata_drivers.count(), 0
)
@@ -124,7 +120,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
def test_document_type_settings_view_no_permission(self):
response = self._request_document_type_settings_view()
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 404)
def test_document_type_settings_view_with_access(self):
self.grant_access(

View File

@@ -12,6 +12,7 @@ from mayan.apps.common.generics import (
FormView, MultipleObjectConfirmActionView, SingleObjectEditView,
SingleObjectListView
)
from mayan.apps.common.mixins import ExternalObjectMixin
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
from mayan.apps.documents.models import Document, DocumentType
@@ -23,7 +24,11 @@ from .permissions import (
)
class DocumentDriverListView(SingleObjectListView):
class DocumentDriverListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = Document
external_object_permission = permission_file_metadata_view
external_object_pk_url_kwarg = 'document_id'
def get_extra_context(self):
return {
'hide_object': True,
@@ -38,54 +43,36 @@ class DocumentDriverListView(SingleObjectListView):
'reside in the database.'
),
'no_results_title': _('No file metadata available.'),
'object': self.get_object(),
'object': self.external_object,
'title': _(
'File metadata drivers for: %s'
) % self.get_object(),
) % self.external_object,
}
def get_object(self):
document = get_object_or_404(
klass=Document, pk=self.kwargs['document_id']
)
AccessControlList.objects.check_access(
permissions=permission_file_metadata_view,
user=self.request.user, obj=document
)
return document
def get_source_queryset(self):
return self.get_object().latest_version.file_metadata_drivers.all()
return self.external_object.latest_version.file_metadata_drivers.all()
class DocumentVersionDriverEntryFileMetadataListView(SingleObjectListView):
class DocumentVersionDriverEntryFileMetadataListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = DocumentVersionDriverEntry
external_object_permission = permission_file_metadata_view
external_object_pk_url_kwarg = 'document_version_driver_id'
def get_extra_context(self):
return {
'hide_object': True,
'no_results_title': _('No file metadata available.'),
'object': self.get_object().document_version.document,
'object': self.external_object.document_version.document,
'title': _(
'File metadata attribures for: %(document)s, for driver: %(driver)s'
) % {
'document': self.get_object().document_version.document,
'driver': self.get_object().driver
'document': self.external_object.document_version.document,
'driver': self.external_object.driver
},
}
def get_object(self):
document_version_driver_entry = get_object_or_404(
klass=DocumentVersionDriverEntry,
pk=self.kwargs['document_version_driver_id']
)
AccessControlList.objects.check_access(
obj=document_version_driver_entry.document_version,
permissions=permission_file_metadata_view,
user=self.request.user,
)
return document_version_driver_entry
def get_source_queryset(self):
return self.get_object().entries.all()
return self.external_object.entries.all()
class DocumentSubmitView(MultipleObjectConfirmActionView):
@@ -96,13 +83,13 @@ class DocumentSubmitView(MultipleObjectConfirmActionView):
success_message_plural = '%(count)d documents submitted to the file metadata queue.'
def get_extra_context(self):
queryset = self.get_queryset()
queryset = self.get_object_list()
result = {
'title': ungettext(
'Submit the selected document to the file metadata queue?',
'Submit the selected documents to the file metadata queue?',
queryset.count()
singular='Submit the selected document to the file metadata queue?',
plural='Submit the selected documents to the file metadata queue?',
number=queryset.count()
)
}
@@ -112,26 +99,23 @@ class DocumentSubmitView(MultipleObjectConfirmActionView):
instance.submit_for_file_metadata_processing()
class DocumentTypeSettingsEditView(SingleObjectEditView):
class DocumentTypeSettingsEditView(ExternalObjectMixin, SingleObjectEditView):
external_object_class = DocumentType
external_object_permission = permission_document_type_file_metadata_setup
external_object_pk_url_kwarg = 'document_type_id'
fields = ('auto_process',)
object_permission = permission_document_type_file_metadata_setup
post_action_redirect = reverse_lazy(viewname='documents:document_type_list')
def get_document_type(self):
return get_object_or_404(
klass=DocumentType, pk=self.kwargs['document_type_id']
)
def get_extra_context(self):
return {
'object': self.get_document_type(),
'object': self.external_object,
'title': _(
'Edit file metadata settings for document type: %s'
) % self.get_document_type()
) % self.external_object
}
def get_object(self, queryset=None):
return self.get_document_type().file_metadata_settings
return self.external_object.file_metadata_settings
class DocumentTypeSubmitView(FormView):