Files
mayan-edms/mayan/apps/metadata/views.py
Roberto Rosario aa95a61451 Refactor metadata app
Update permission variable name from
"permission_document_metadata_" to "permission_metadata_".

Fix failing tests.

Add test for same metadata type mixin.

Split metadata add and remove test into test for GET and
POST requests.

Remove use of urlencode and instead use furl.

Simplify view using self.action_count and
self.action_id_list.

Use ExternalObjectMixin to remove repeated code.

Move the repeated code to test for all documents to
be of the same type into its own mixin.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
2019-02-03 23:37:52 -04:00

675 lines
23 KiB
Python

from __future__ import absolute_import, unicode_literals
from furl import furl
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template import RequestContext
from django.urls import reverse, reverse_lazy
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _, ungettext
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import (
FormView, MultipleObjectFormActionView, SingleObjectCreateView,
SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView
)
from mayan.apps.common.mixins import ExternalObjectMixin
from mayan.apps.documents.models import Document, DocumentType
from mayan.apps.documents.permissions import (
permission_document_type_edit
)
from .api import save_metadata_list
from .forms import (
DocumentMetadataAddForm, DocumentMetadataFormSet,
DocumentMetadataRemoveFormSet,
DocumentTypeMetadataTypeRelationshipFormSet, MetadataTypeForm
)
from .icons import icon_metadata
from .links import (
link_document_metadata_add, link_document_multiple_metadata_add,
link_metadata_type_create
)
from .models import DocumentMetadata, MetadataType
from .permissions import (
permission_metadata_add, permission_metadata_edit,
permission_metadata_remove, permission_metadata_view,
permission_metadata_type_create, permission_metadata_type_delete,
permission_metadata_type_edit, permission_metadata_type_view
)
class DocumentMetadataSameTypeMixin(object):
def dispatch(self, request, *args, **kwargs):
result = super(DocumentMetadataSameTypeMixin, self).dispatch(
request, *args, **kwargs
)
queryset = self.get_object_list()
for document in queryset:
document.add_as_recent_document_for_user(request.user)
if len(set([document.document_type.pk for document in queryset])) > 1:
messages.error(
message=_(
'Selected documents must be of the same type.'
), request=request
)
return HttpResponseRedirect(redirect_to=self.previous_url)
return result
class DocumentMetadataAddView(DocumentMetadataSameTypeMixin, MultipleObjectFormActionView):
form_class = DocumentMetadataAddForm
model = Document
object_permission = permission_metadata_add
pk_url_kwarg = 'document_id'
success_message = _('Metadata add request performed on %(count)d document')
success_message_plural = _(
'Metadata add request performed on %(count)d documents'
)
def get_post_object_action_url(self):
if self.action_count == 1:
return reverse(
viewname='metadata:document_metadata_edit',
kwargs={'document_id': self.action_id_list[0]}
)
elif self.action_count > 1:
url = furl(
path=reverse(
viewname='metadata:document_multiple_metadata_edit'
), args={'id_list': self.action_id_list}
)
return url.tostr()
def get_extra_context(self):
queryset = self.get_object_list()
result = {
'submit_label': _('Add'),
'title': ungettext(
singular='Add metadata types to document',
plural='Add metadata types to documents',
number=queryset.count()
)
}
if queryset.count() == 1:
result.update(
{
'object': queryset.first(),
'title': _(
'Add metadata types to document: %s'
) % queryset.first()
}
)
return result
def get_form_extra_kwargs(self):
queryset = self.get_object_list()
result = {}
if queryset.count():
result.update(
{
'document_type': queryset.first().document_type,
}
)
if queryset.count() == 1:
result.update(
{
'queryset': MetadataType.objects.get_for_document_type(
document_type=queryset.first().document_type
).exclude(
pk__in=MetadataType.objects.get_for_document(
document=queryset.first()
)
)
}
)
return result
def object_action(self, form, instance):
for metadata_type in form.cleaned_data['metadata_type']:
try:
created = False
try:
DocumentMetadata.objects.get(
document=instance,
metadata_type=metadata_type,
)
except DocumentMetadata.DoesNotExist:
document_metadata = DocumentMetadata(
document=instance,
metadata_type=metadata_type,
)
document_metadata.save(_user=self.request.user)
created = True
except Exception as exception:
messages.error(
message=_(
'Error adding metadata type '
'"%(metadata_type)s" to document: '
'%(document)s; %(exception)s'
) % {
'metadata_type': metadata_type,
'document': instance,
'exception': ', '.join(
getattr(exception, 'messages', exception)
)
},
request=self.request
)
else:
if created:
messages.success(
message=_(
'Metadata type: %(metadata_type)s '
'successfully added to document %(document)s.'
) % {
'metadata_type': metadata_type,
'document': instance
},
request=self.request
)
else:
messages.warning(
message=_(
'Metadata type: %(metadata_type)s already '
'present in document %(document)s.'
) % {
'metadata_type': metadata_type,
'document': instance
}, request=self.request
)
class DocumentMetadataEditView(DocumentMetadataSameTypeMixin, MultipleObjectFormActionView):
form_class = DocumentMetadataFormSet
model = Document
object_permission = permission_metadata_edit
pk_url_kwarg = 'document_id'
success_message = _(
'Metadata edit request performed on %(count)d document'
)
success_message_plural = _(
'Metadata edit request performed on %(count)d documents'
)
def get_extra_context(self):
queryset = self.get_object_list()
id_list = ','.join(
map(
force_text,
queryset.values_list('pk', flat=True)
)
)
if queryset.count() == 1:
no_results_main_link = link_document_metadata_add.resolve(
context=RequestContext(
request=self.request, dict_={'object': queryset.first()}
)
)
else:
no_results_main_link = link_document_multiple_metadata_add.resolve(
context=RequestContext(request=self.request)
)
no_results_main_link.url = '{}?id_list={}'.format(
no_results_main_link.url, id_list
)
result = {
'form_display_mode_table': True,
'no_results_icon': icon_metadata,
'no_results_main_link': no_results_main_link,
'no_results_text': _(
'Add metadata types available for this document\'s type '
'and assign them corresponding values.'
),
'no_results_title': _('There is no metadata to edit'),
'submit_label': _('Edit'),
'title': ungettext(
singular='Edit document metadata',
plural='Edit documents metadata',
number=queryset.count()
)
}
if queryset.count() == 1:
result.update(
{
'object': queryset.first(),
'title': _(
'Edit metadata for document: %s'
) % queryset.first()
}
)
return result
def get_initial(self):
queryset = self.get_object_list()
metadata_dict = {}
initial = []
for document in queryset:
document.add_as_recent_document_for_user(self.request.user)
for document_metadata in document.metadata.all():
metadata_dict.setdefault(
document_metadata.metadata_type, set()
)
if document_metadata.value:
metadata_dict[
document_metadata.metadata_type
].add(document_metadata.value)
for key, value in metadata_dict.items():
initial.append({
'document_type': document.document_type,
'metadata_type': key,
'value': ', '.join(value) if value else '',
})
return initial
def get_post_object_action_url(self):
if self.action_count == 1:
return reverse(
viewname='metadata:document_metadata_edit',
kwargs={'document_id': self.action_id_list[0]}
)
elif self.action_count > 1:
url = furl(
path=reverse(
viewname='metadata:document_multiple_metadata_edit'
), args={'id_list': self.action_id_list}
)
return url.tostr()
def object_action(self, form, instance):
errors = []
for form in form.forms:
if form.cleaned_data['update']:
try:
save_metadata_list(
metadata_list=[form.cleaned_data], document=instance,
_user=self.request.user
)
except Exception as exception:
errors.append(exception)
for error in errors:
if settings.DEBUG:
raise
else:
if isinstance(error, ValidationError):
exception_message = ', '.join(error.messages)
else:
exception_message = force_text(error)
messages.error(
message=_(
'Error editing metadata for document: '
'%(document)s; %(exception)s.'
) % {
'document': instance,
'exception': exception_message
}, request=self.request
)
else:
messages.success(
message=_(
'Metadata for document %s edited successfully.'
) % instance, request=self.request
)
class DocumentMetadataListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = Document
external_object_permission = permission_metadata_view
external_object_pk_url_kwarg = 'document_id'
def get_extra_context(self):
return {
'hide_object': True,
'object': self.external_object,
'no_results_icon': icon_metadata,
'no_results_main_link': link_document_metadata_add.resolve(
context=RequestContext(
request=self.request, dict_={'object': self.external_object}
)
),
'no_results_text': _(
'Add metadata types this document\'s type '
'to be able to add them to individual documents. '
'Once added to individual document, you can then edit their '
'values.'
),
'no_results_title': _('This document doesn\'t have any metadata'),
'title': _('Metadata for document: %s') % self.external_object,
}
def get_source_queryset(self):
return self.external_object.metadata.all()
class DocumentMetadataRemoveView(DocumentMetadataSameTypeMixin, MultipleObjectFormActionView):
form_class = DocumentMetadataRemoveFormSet
model = Document
object_permission = permission_metadata_remove
pk_url_kwarg = 'document_id'
success_message = _(
'Metadata remove request performed on %(count)d document'
)
success_message_plural = _(
'Metadata remove request performed on %(count)d documents'
)
def get_extra_context(self):
queryset = self.get_object_list()
result = {
'form_display_mode_table': True,
'submit_label': _('Remove'),
'title': ungettext(
singular='Remove metadata types from the document',
plural='Remove metadata types from the documents',
number=queryset.count()
)
}
if queryset.count() == 1:
result.update(
{
'object': queryset.first(),
'title': _(
'Remove metadata types from the document: %s'
) % queryset.first()
}
)
return result
def get_initial(self):
queryset = self.get_object_list()
metadata = {}
for document in queryset:
document.add_as_recent_document_for_user(self.request.user)
for document_metadata in document.metadata.all():
# Metadata value cannot be None here, fallback to an empty
# string
value = document_metadata.value or ''
if document_metadata.metadata_type in metadata:
if value not in metadata[document_metadata.metadata_type]:
metadata[document_metadata.metadata_type].append(value)
else:
metadata[document_metadata.metadata_type] = [value] if value else ''
initial = []
for key, value in metadata.items():
initial.append(
{
'document_type': queryset.first().document_type,
'metadata_type': key,
'value': ', '.join(value)
}
)
return initial
def get_post_object_action_url(self):
if self.action_count == 1:
return reverse(
viewname='metadata:document_metadata_edit',
kwargs={'document_id': self.action_id_list[0]}
)
elif self.action_count > 1:
url = furl(
path=reverse(
viewname='metadata:document_multiple_metadata_edit'
), args={'id_list': self.action_id_list}
)
return url.tostr()
def object_action(self, form, instance):
for form in form.forms:
if form.cleaned_data['update']:
metadata_type = get_object_or_404(
klass=MetadataType, pk=form.cleaned_data['id']
)
try:
document_metadata = DocumentMetadata.objects.get(
document=instance, metadata_type=metadata_type
)
document_metadata.delete(_user=self.request.user)
messages.success(
message=_(
'Successfully remove metadata type "%(metadata_type)s" from document: %(document)s.'
) % {
'metadata_type': metadata_type,
'document': instance
}, request=self.request
)
except Exception as exception:
messages.error(
message=_(
'Error removing metadata type "%(metadata_type)s" from document: %(document)s; %(exception)s'
) % {
'metadata_type': metadata_type,
'document': instance,
'exception': ', '.join(exception.messages)
}, request=self.request
)
# Setup views
class MetadataTypeCreateView(SingleObjectCreateView):
extra_context = {'title': _('Create metadata type')}
form_class = MetadataTypeForm
model = MetadataType
post_action_redirect = reverse_lazy(
viewname='metadata:metadata_type_list'
)
view_permission = permission_metadata_type_create
def get_save_extra_data(self):
return {
'_user': self.request.user,
}
class MetadataTypeDeleteView(SingleObjectDeleteView):
model = MetadataType
object_permission = permission_metadata_type_delete
pk_url_kwarg = 'metadata_type_id'
post_action_redirect = reverse_lazy(
viewname='metadata:metadata_type_list'
)
def get_extra_context(self):
return {
'delete_view': True,
'object': self.get_object(),
'title': _('Delete the metadata type: %s?') % self.get_object(),
}
class MetadataTypeEditView(SingleObjectEditView):
form_class = MetadataTypeForm
model = MetadataType
object_permission = permission_metadata_type_edit
pk_url_kwarg = 'metadata_type_id'
post_action_redirect = reverse_lazy(
viewname='metadata:metadata_type_list'
)
def get_extra_context(self):
return {
'object': self.get_object(),
'title': _('Edit metadata type: %s') % self.get_object(),
}
def get_save_extra_data(self):
return {
'_user': self.request.user,
}
class MetadataTypeListView(SingleObjectListView):
object_permission = permission_metadata_type_view
def get_extra_context(self):
return {
'hide_object': True,
'no_results_icon': icon_metadata,
'no_results_main_link': link_metadata_type_create.resolve(
context=RequestContext(request=self.request)
),
'no_results_text': _(
'Metadata types are users defined properties that can be '
'assigned values. Once created they must be associated to '
'document types, either as optional or required, for each. '
'Setting a metadata type as required for a document type '
'will block the upload of documents of that type until a '
'metadata value is provided.'
),
'no_results_title': _('There are no metadata types'),
'title': _('Metadata types'),
}
def get_source_queryset(self):
return MetadataType.objects.all()
class SetupDocumentTypeMetadataTypes(ExternalObjectMixin, FormView):
external_object_class = DocumentType
external_object_permission = permission_metadata_type_edit
external_object_pk_url_kwarg = 'document_type_id'
form_class = DocumentTypeMetadataTypeRelationshipFormSet
main_model = 'document_type'
submodel = MetadataType
submodel_permission = permission_metadata_type_edit
def form_valid(self, form):
try:
for instance in form:
instance.save()
except Exception as exception:
messages.error(
message=_('Error updating relationship; %s') % exception,
request=self.request
)
else:
messages.success(
message=_(
'Relationships updated successfully'
), request=self.request
)
return super(
SetupDocumentTypeMetadataTypes, self
).form_valid(form=form)
def get_extra_context(self):
return {
'form_display_mode_table': True,
'no_results_icon': icon_metadata,
'no_results_main_link': link_metadata_type_create.resolve(
context=RequestContext(request=self.request)
),
'no_results_text': _(
'Create metadata types to be able to associate them '
'to this document type.'
),
'no_results_title': _('There are no metadata types available'),
'object': self.external_object,
'title': _(
'Metadata types for document type: %s'
) % self.external_object
}
def get_form_extra_kwargs(self):
return {
'_user': self.request.user,
}
def get_initial(self):
initial = []
for element in self.get_object_list():
initial.append(
{
'document_type': self.external_object,
'main_model': self.main_model,
'metadata_type': element,
}
)
return initial
def get_object_list(self):
return AccessControlList.objects.restrict_queryset(
permission=self.submodel_permission,
queryset=self.submodel._meta.default_manager.all(),
user=self.request.user
)
def get_post_action_redirect(self):
return reverse(viewname='documents:document_type_list')
class SetupMetadataTypesDocumentTypes(SetupDocumentTypeMetadataTypes):
external_object_class = MetadataType
external_object_permission = permission_metadata_type_edit
external_object_pk_url_kwarg = 'metadata_type_id'
main_model = 'metadata_type'
submodel = DocumentType
submodel_permission = permission_document_type_edit
def get_extra_context(self):
return {
'form_display_mode_table': True,
'object': self.get_object(),
'title': _(
'Document types for metadata type: %s'
) % self.get_object()
}
def get_initial(self):
initial = []
for element in self.get_object_list():
initial.append(
{
'document_type': element,
'main_model': self.main_model,
'metadata_type': self.external_object,
}
)
return initial
def get_post_action_redirect(self):
return reverse(viewname='metadata:metadata_type_list')