Refactor metadata app API

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-03-15 04:49:51 -04:00
parent 0c312b343e
commit c152156a11
20 changed files with 1130 additions and 779 deletions

View File

@@ -8,13 +8,14 @@ from mayan.apps.common.mixins import ContentTypeViewMixin, ExternalObjectMixin
from mayan.apps.permissions.serializers import ( from mayan.apps.permissions.serializers import (
PermissionSerializer, RolePermissionAddRemoveSerializer PermissionSerializer, RolePermissionAddRemoveSerializer
) )
from mayan.apps.rest_api.mixins import ExternalObjectAPIViewSetMixin
from mayan.apps.rest_api.viewsets import MayanAPIModelViewSet from mayan.apps.rest_api.viewsets import MayanAPIModelViewSet
from .permissions import permission_acl_edit, permission_acl_view from .permissions import permission_acl_edit, permission_acl_view
from .serializers import AccessControlListSerializer from .serializers import AccessControlListSerializer
class ObjectACLAPIViewSet(ContentTypeViewMixin, ExternalObjectMixin, MayanAPIModelViewSet): class ObjectACLAPIViewSet(ContentTypeViewMixin, ExternalObjectAPIViewSetMixin, MayanAPIModelViewSet):
content_type_url_kw_args = { content_type_url_kw_args = {
'app_label': 'app_label', 'app_label': 'app_label',
'model': 'model_name' 'model': 'model_name'

View File

@@ -5,7 +5,8 @@ from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from mayan.apps.common.mixins import ContentTypeViewMixin, ExternalObjectMixin from mayan.apps.common.mixins import ContentTypeViewMixin
from mayan.apps.rest_api.mixins import ExternalObjectAPIViewSetMixin
from mayan.apps.rest_api.viewsets import MayanAPIReadOnlyModelViewSet from mayan.apps.rest_api.viewsets import MayanAPIReadOnlyModelViewSet
from .classes import EventTypeNamespace from .classes import EventTypeNamespace
@@ -80,7 +81,7 @@ class NotificationAPIViewSet(MayanAPIReadOnlyModelViewSet):
return queryset return queryset
class ObjectEventAPIViewSet(ContentTypeViewMixin, ExternalObjectMixin, MayanAPIReadOnlyModelViewSet): class ObjectEventAPIViewSet(ContentTypeViewMixin, ExternalObjectAPIViewSetMixin, MayanAPIReadOnlyModelViewSet):
content_type_url_kw_args = { content_type_url_kw_args = {
'app_label': 'app_label', 'app_label': 'app_label',
'model': 'model_name' 'model': 'model_name'

View File

@@ -1,16 +1,17 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.shortcuts import get_object_or_404 from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import generics from mayan.apps.documents.models import Document
from mayan.apps.acls.models import AccessControlList
from mayan.apps.documents.models import Document, DocumentType
from mayan.apps.documents.permissions import ( from mayan.apps.documents.permissions import (
permission_document_type_view, permission_document_type_edit permission_document_type_view, permission_document_type_edit,
)
from mayan.apps.rest_api.mixins import ExternalObjectAPIViewSetMixin
from mayan.apps.rest_api.viewsets import (
MayanAPIModelViewSet, MayanRetrieveUpdateAPIViewSet,
) )
from mayan.apps.rest_api.filters import MayanObjectPermissionsFilter
from mayan.apps.rest_api.permissions import MayanPermission
from .models import MetadataType from .models import MetadataType
from .permissions import ( from .permissions import (
@@ -20,235 +21,140 @@ from .permissions import (
permission_metadata_type_edit, permission_metadata_type_view permission_metadata_type_edit, permission_metadata_type_view
) )
from .serializers import ( from .serializers import (
DocumentMetadataSerializer, DocumentTypeMetadataTypeSerializer, DocumentMetadataAddSerializer,
MetadataTypeSerializer, NewDocumentMetadataSerializer, DocumentMetadataSerializer,
NewDocumentTypeMetadataTypeSerializer, MetadataTypeSerializer,
WritableDocumentTypeMetadataTypeSerializer MetadataTypeDocumentTypeRelationSerializer,
MetadataTypeDocumentTypeRelationAddRemoveSerializer,
) )
class APIDocumentMetadataListView(generics.ListCreateAPIView): class MetadataTypeDocumentTypeRelationAPIViewSet(ExternalObjectAPIViewSetMixin, MayanRetrieveUpdateAPIViewSet):
""" external_object_class = MetadataType
get: Returns a list of selected document's metadata types and values. external_object_pk_url_kwarg = 'metadata_type_id'
post: Add an existing metadata type and value to the selected document. lookup_url_kwarg = 'metadata_type_document_type_relation_id'
""" object_permission_map = {
def get_document(self): 'add': permission_metadata_type_edit,
if self.request.method == 'GET': 'list': permission_document_type_view,
permission_required = permission_metadata_view 'partial_update': permission_document_type_edit,
'remove': permission_metadata_type_edit,
'retrieve': permission_document_type_view,
'update': permission_document_type_edit,
}
serializer_class = MetadataTypeDocumentTypeRelationSerializer
def get_external_object_permission(self):
action = getattr(self, 'action', None)
if action is None:
return None
elif action in ['list', 'retrieve']:
return permission_metadata_type_view
else: else:
permission_required = permission_metadata_add return permission_metadata_type_edit
document = get_object_or_404(
klass=Document, pk=self.kwargs['document_pk']
)
AccessControlList.objects.check_access(
permissions=permission_required, user=self.request.user,
obj=document
)
return document
def get_queryset(self): def get_queryset(self):
return self.get_document().metadata.all() action = getattr(self, 'action', None)
if action in ['add', 'remove']:
# Return metadata types
return self.get_external_object_queryset()
else:
# Return relations
return self.get_external_object().document_type_relations.all()
def get_serializer(self, *args, **kwargs): @action(
if not self.request: detail=False, lookup_url_kwarg='metadata_type_id', methods=('post',),
serializer_class=MetadataTypeDocumentTypeRelationAddRemoveSerializer,
url_name='add', url_path='add'
)
def add(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.document_type_relations_add(instance=instance)
headers = self.get_success_headers(data=serializer.data)
return Response(
serializer.data, status=status.HTTP_200_OK, headers=headers
)
@action(
detail=False, lookup_url_kwarg='metadata_type_id', methods=('post',),
serializer_class=MetadataTypeDocumentTypeRelationAddRemoveSerializer,
url_name='remove', url_path='remove'
)
def remove(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.document_type_relations_remove(instance=instance)
headers = self.get_success_headers(data=serializer.data)
return Response(
serializer.data, status=status.HTTP_200_OK, headers=headers
)
class MetadataTypeAPIViewSet(MayanAPIModelViewSet):
lookup_url_kwarg = 'metadata_type_id'
object_permission_map = {
'destroy': permission_metadata_type_delete,
'list': permission_metadata_type_view,
'partial_update': permission_metadata_type_edit,
'retrieve': permission_metadata_type_view,
'update': permission_metadata_type_edit,
}
queryset = MetadataType.objects.all()
serializer_class = MetadataTypeSerializer
view_permission_map = {
'create': permission_metadata_type_create
}
class DocumentMetadataAPIViewSet(ExternalObjectAPIViewSetMixin, MayanAPIModelViewSet):
external_object_class = Document
external_object_pk_url_kwarg = 'document_id'
lookup_url_kwarg = 'document_metadata_id'
object_permission_map = {
'list': permission_metadata_view,
'partial_update': permission_metadata_edit,
'destroy': permission_metadata_remove,
'retrieve': permission_metadata_view,
'update': permission_metadata_edit,
}
def get_external_object_permission(self):
action = getattr(self, 'action', None)
if action is None:
return None return None
elif action == 'create':
return permission_metadata_add
elif action == 'destroy':
return permission_metadata_remove
elif action in ['partial_update', 'update']:
return permission_metadata_edit
else:
return permission_metadata_view
return super(APIDocumentMetadataListView, self).get_serializer(*args, **kwargs) def get_queryset(self):
return self.get_external_object().metadata.all()
def get_serializer_class(self): def get_serializer_class(self):
if self.request.method == 'GET': action = getattr(self, 'action', None)
return DocumentMetadataSerializer if action is None:
return None
if action == 'create':
return DocumentMetadataAddSerializer
else: else:
return NewDocumentMetadataSerializer return DocumentMetadataSerializer
def get_serializer_context(self): def get_serializer_context(self):
""" """
Extra context provided to the serializer class. Extra context provided to the serializer class.
""" """
context = super(APIDocumentMetadataListView, self).get_serializer_context() context = super(DocumentMetadataAPIViewSet, self).get_serializer_context()
if self.kwargs: if self.kwargs:
context.update( context.update(
{ {
'document': self.get_document(), 'document': self.get_external_object(),
} }
) )
return context return context
class APIDocumentMetadataView(generics.RetrieveUpdateDestroyAPIView):
"""
delete: Remove this metadata entry from the selected document.
get: Return the details of the selected document metadata type and value.
patch: Edit the selected document metadata type and value.
put: Edit the selected document metadata type and value.
"""
lookup_url_kwarg = 'metadata_pk'
def get_document(self):
if self.request.method == 'GET':
permission_required = permission_metadata_view
elif self.request.method == 'PUT':
permission_required = permission_metadata_edit
elif self.request.method == 'PATCH':
permission_required = permission_metadata_edit
elif self.request.method == 'DELETE':
permission_required = permission_metadata_remove
document = get_object_or_404(
klass=Document, pk=self.kwargs['document_pk']
)
AccessControlList.objects.check_access(
permissions=permission_required, user=self.request.user,
obj=document
)
return document
def get_queryset(self):
return self.get_document().metadata.all()
def get_serializer(self, *args, **kwargs):
if not self.request:
return None
return super(APIDocumentMetadataView, self).get_serializer(*args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return DocumentMetadataSerializer
else:
return DocumentMetadataSerializer
class APIMetadataTypeListView(generics.ListCreateAPIView):
"""
get: Returns a list of all the metadata types.
post: Create a new metadata type.
"""
filter_backends = (MayanObjectPermissionsFilter,)
mayan_object_permissions = {'GET': (permission_metadata_type_view,)}
mayan_view_permissions = {'POST': (permission_metadata_type_create,)}
permission_classes = (MayanPermission,)
queryset = MetadataType.objects.all()
serializer_class = MetadataTypeSerializer
class APIMetadataTypeView(generics.RetrieveUpdateDestroyAPIView):
"""
delete: Delete the selected metadata type.
get: Return the details of the selected metadata type.
patch: Edit the selected metadata type.
put: Edit the selected metadata type.
"""
lookup_url_kwarg = 'metadata_type_pk'
mayan_object_permissions = {
'GET': (permission_metadata_type_view,),
'PUT': (permission_metadata_type_edit,),
'PATCH': (permission_metadata_type_edit,),
'DELETE': (permission_metadata_type_delete,)
}
permission_classes = (MayanPermission,)
queryset = MetadataType.objects.all()
serializer_class = MetadataTypeSerializer
class APIDocumentTypeMetadataTypeListView(generics.ListCreateAPIView):
"""
get: Returns a list of selected document type's metadata types.
post: Add a metadata type to the selected document type.
"""
lookup_url_kwarg = 'metadata_type_pk'
def get_document_type(self):
if self.request.method == 'GET':
permission_required = permission_document_type_view
else:
permission_required = permission_document_type_edit
document_type = get_object_or_404(
klass=DocumentType, pk=self.kwargs['document_type_pk']
)
AccessControlList.objects.check_access(
permissions=permission_required, user=self.request.user,
obj=document_type
)
return document_type
def get_queryset(self):
return self.get_document_type().metadata.all()
def get_serializer(self, *args, **kwargs):
if not self.request:
return None
return super(APIDocumentTypeMetadataTypeListView, self).get_serializer(*args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return DocumentTypeMetadataTypeSerializer
else:
return NewDocumentTypeMetadataTypeSerializer
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
context = super(APIDocumentTypeMetadataTypeListView, self).get_serializer_context()
if self.kwargs:
context.update(
{
'document_type': self.get_document_type(),
}
)
return context
class APIDocumentTypeMetadataTypeView(generics.RetrieveUpdateDestroyAPIView):
"""
delete: Remove a metadata type from a document type.
get: Retrieve the details of a document type metadata type.
patch: Edit the selected document type metadata type.
put: Edit the selected document type metadata type.
"""
lookup_url_kwarg = 'metadata_type_pk'
serializer_class = DocumentTypeMetadataTypeSerializer
def get_document_type(self):
if self.request.method == 'GET':
permission_required = permission_document_type_view
else:
permission_required = permission_document_type_edit
document_type = get_object_or_404(
klass=DocumentType, pk=self.kwargs['document_type_pk']
)
AccessControlList.objects.check_access(
permissions=permission_required, user=self.request.user,
obj=document_type
)
return document_type
def get_queryset(self):
return self.get_document_type().metadata.all()
def get_serializer(self, *args, **kwargs):
if not self.request:
return None
return super(APIDocumentTypeMetadataTypeView, self).get_serializer(*args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return DocumentTypeMetadataTypeSerializer
else:
return WritableDocumentTypeMetadataTypeSerializer

View File

@@ -13,7 +13,7 @@ from mayan.apps.acls.links import link_acl_list
from mayan.apps.acls.permissions import permission_acl_edit, permission_acl_view from mayan.apps.acls.permissions import permission_acl_edit, permission_acl_view
from mayan.apps.common import ( from mayan.apps.common import (
MayanAppConfig, menu_facet, menu_list_facet, menu_multi_item, menu_object, MayanAppConfig, menu_facet, menu_list_facet, menu_multi_item, menu_object,
menu_secondary, menu_setup, menu_secondary menu_secondary, menu_setup
) )
from mayan.apps.common.classes import ModelAttribute, ModelField from mayan.apps.common.classes import ModelAttribute, ModelField
from mayan.apps.common.widgets import TwoStateWidget from mayan.apps.common.widgets import TwoStateWidget
@@ -26,6 +26,8 @@ from mayan.apps.events.links import (
from mayan.apps.events.permissions import permission_events_view from mayan.apps.events.permissions import permission_events_view
from mayan.celery import app from mayan.celery import app
from mayan.apps.navigation import SourceColumn from mayan.apps.navigation import SourceColumn
from mayan.apps.rest_api.fields import HyperlinkField
from mayan.apps.rest_api.serializers import LazyExtraFieldsSerializerMixin
from .events import ( from .events import (
event_document_metadata_added, event_document_metadata_edited, event_document_metadata_added, event_document_metadata_edited,
@@ -80,11 +82,9 @@ class MetadataApp(MayanAppConfig):
DocumentPageSearchResult = apps.get_model( DocumentPageSearchResult = apps.get_model(
app_label='documents', model_name='DocumentPageSearchResult' app_label='documents', model_name='DocumentPageSearchResult'
) )
DocumentType = apps.get_model( DocumentType = apps.get_model(
app_label='documents', model_name='DocumentType' app_label='documents', model_name='DocumentType'
) )
DocumentMetadata = self.get_model('DocumentMetadata') DocumentMetadata = self.get_model('DocumentMetadata')
DocumentTypeMetadataType = self.get_model('DocumentTypeMetadataType') DocumentTypeMetadataType = self.get_model('DocumentTypeMetadataType')
MetadataType = self.get_model('MetadataType') MetadataType = self.get_model('MetadataType')
@@ -93,6 +93,15 @@ class MetadataApp(MayanAppConfig):
name='get_metadata', value=method_get_metadata name='get_metadata', value=method_get_metadata
) )
LazyExtraFieldsSerializerMixin.add_field(
dotted_path='mayan.apps.documents.serializers.DocumentSerializer',
field_name='metadata_list_url',
field=HyperlinkField(
lookup_url_kwarg='document_id',
view_name='rest_api:document-metadata-list'
)
)
ModelAttribute(model=Document, name='get_metadata') ModelAttribute(model=Document, name='get_metadata')
ModelField( ModelField(
@@ -140,10 +149,25 @@ class MetadataApp(MayanAppConfig):
model=MetadataType, permissions=( model=MetadataType, permissions=(
permission_acl_edit, permission_acl_view, permission_acl_edit, permission_acl_view,
permission_events_view, permission_metadata_type_delete, permission_events_view, permission_metadata_type_delete,
permission_metadata_add, permission_metadata_edit,
permission_metadata_remove, permission_metadata_view,
permission_metadata_type_edit, permission_metadata_type_view permission_metadata_type_edit, permission_metadata_type_view
) )
) )
ModelPermission.register_inheritance(
model=DocumentMetadata, related='document',
)
ModelPermission.register_inheritance(
model=DocumentMetadata, related='metadata_type',
)
ModelPermission.register_inheritance(
model=DocumentTypeMetadataType, related='document_type',
)
ModelPermission.register_inheritance(
model=DocumentTypeMetadataType, related='metadata_type',
)
SourceColumn( SourceColumn(
func=widget_get_metadata_string, source=Document func=widget_get_metadata_string, source=Document
) )

View File

@@ -198,12 +198,12 @@ class DocumentTypeMetadataTypeRelationshipForm(forms.Form):
self.fields['relationship_type'].initial = self.initial_relationship_type self.fields['relationship_type'].initial = self.initial_relationship_type
def get_relationship(self): def get_relationship(self):
return self.initial['document_type'].metadata.filter( return self.initial['document_type'].metadata_type_relations.filter(
metadata_type=self.initial['metadata_type'] metadata_type=self.initial['metadata_type']
) )
def get_relationship_choices(self): def get_relationship_choices(self):
return self.initial['document_type'].metadata.filter( return self.initial['document_type'].metadata_type_relations.filter(
metadata_type=self.initial['metadata_type'] metadata_type=self.initial['metadata_type']
) )

View File

@@ -21,7 +21,7 @@ def handler_post_document_type_change(sender, instance, **kwargs):
# First get the existing metadata types not found in the new document # First get the existing metadata types not found in the new document
# type # type
unneeded_metadata = instance.metadata.exclude( unneeded_metadata = instance.metadata.exclude(
metadata_type__in=instance.document_type.metadata.values( metadata_type__in=instance.document_type.metadata_type_relations.values(
'metadata_type' 'metadata_type'
) )
) )
@@ -39,7 +39,7 @@ def handler_post_document_type_change(sender, instance, **kwargs):
# excluding existing document metadata # excluding existing document metadata
# get_or_create is not used to avoid a possible triggering of indexes # get_or_create is not used to avoid a possible triggering of indexes
# or workflow on document change by metadata save signal # or workflow on document change by metadata save signal
new_document_type_metadata_types = instance.document_type.metadata.filter( new_document_type_metadata_types = instance.document_type.metadata_type_relations.filter(
required=True required=True
).exclude(metadata_type__in=instance.metadata.values('metadata_type')) ).exclude(metadata_type__in=instance.metadata.values('metadata_type'))

View File

@@ -9,18 +9,10 @@ class MetadataTypeManager(models.Manager):
return self.get(name=name) return self.get(name=name)
def get_for_document(self, document): def get_for_document(self, document):
return self.filter( return self.filter(document_metadata__document=document)
pk__in=document.metadata.values_list(
'metadata_type', flat=True
)
)
def get_for_document_type(self, document_type): def get_for_document_type(self, document_type):
return self.filter( return self.filter(document_type_relations__document_type=document_type)
pk__in=document_type.metadata.values_list(
'metadata_type', flat=True
)
)
class DocumentTypeMetadataTypeManager(models.Manager): class DocumentTypeMetadataTypeManager(models.Manager):

View File

@@ -5,13 +5,15 @@ import shlex
from jinja2 import Template from jinja2 import Template
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models, transaction
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
from django.utils.six import PY2 from django.utils.six import PY2
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
from mayan.apps.documents.events import event_document_type_edited
from mayan.apps.documents.models import Document, DocumentType from mayan.apps.documents.models import Document, DocumentType
from .classes import MetadataLookup from .classes import MetadataLookup
@@ -126,6 +128,38 @@ class MetadataType(models.Model):
template = Template(self.default) template = Template(self.default)
return template.render() return template.render()
def get_document_type_relations(self, permission, user):
return AccessControlList.objects.restrict_queryset(
permission=permission, queryset=self.document_type_relations.all(),
user=user
)
def document_types_add(self, queryset, required=False, _user=None):
with transaction.atomic():
event_metadata_type_edited.commit(
actor=_user, target=self
)
for obj in queryset:
self.document_type_relations.create(
document_type=obj, required=required
)
event_document_type_edited.commit(
actor=_user, action_object=self, target=obj
)
def document_types_remove(self, queryset, _user=None):
with transaction.atomic():
event_metadata_type_edited.commit(
actor=_user, target=self
)
for obj in queryset:
self.document_type_relations.filter(
document_type=obj
).delete()
event_document_type_edited.commit(
actor=_user, action_object=self, target=obj
)
def get_lookup_choices(self, first_choice=None): def get_lookup_choices(self, first_choice=None):
template = Template(self.lookup) template = Template(self.lookup)
context = MetadataLookup.get_as_context() context = MetadataLookup.get_as_context()
@@ -146,7 +180,7 @@ class MetadataType(models.Model):
Return a queryset of metadata types that are required for the Return a queryset of metadata types that are required for the
specified document type. specified document type.
""" """
return document_type.metadata.filter( return document_type.metadata_type_relations.filter(
required=True, metadata_type=self required=True, metadata_type=self
).exists() ).exists()
@@ -157,6 +191,7 @@ class MetadataType(models.Model):
user = kwargs.pop('_user', None) user = kwargs.pop('_user', None)
created = not self.pk created = not self.pk
with transaction.atomic():
result = super(MetadataType, self).save(*args, **kwargs) result = super(MetadataType, self).save(*args, **kwargs)
if created: if created:
@@ -210,7 +245,8 @@ class DocumentMetadata(models.Model):
verbose_name=_('Document') verbose_name=_('Document')
) )
metadata_type = models.ForeignKey( metadata_type = models.ForeignKey(
on_delete=models.CASCADE, to=MetadataType, verbose_name=_('Type') on_delete=models.CASCADE, related_name='document_metadata', to=MetadataType,
verbose_name=_('Type')
) )
value = models.CharField( value = models.CharField(
blank=True, db_index=True, help_text=_( blank=True, db_index=True, help_text=_(
@@ -242,17 +278,19 @@ class DocumentMetadata(models.Model):
It used set to False when deleting document metadata on document It used set to False when deleting document metadata on document
type change. type change.
""" """
if enforce_required and self.metadata_type.pk in self.document.document_type.metadata.filter(required=True).values_list('metadata_type', flat=True): if enforce_required and self.metadata_type.pk in self.document.document_type.metadata_type_relations.filter(required=True).values_list('metadata_type', flat=True):
raise ValidationError( raise ValidationError(
_('Metadata type is required for this document type.') _('Metadata type is required for this document type.')
) )
user = kwargs.pop('_user', None) user = kwargs.pop('_user', None)
with transaction.atomic():
result = super(DocumentMetadata, self).delete(*args, **kwargs) result = super(DocumentMetadata, self).delete(*args, **kwargs)
event_document_metadata_removed.commit( event_document_metadata_removed.commit(
action_object=self.metadata_type, actor=user, target=self.document, action_object=self.metadata_type, actor=user, target=self.document,
) )
return result return result
def natural_key(self): def natural_key(self):
@@ -271,7 +309,7 @@ class DocumentMetadata(models.Model):
is_required.fget.short_description = _('Required') is_required.fget.short_description = _('Required')
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.metadata_type.pk not in self.document.document_type.metadata.values_list('metadata_type', flat=True): if self.metadata_type.pk not in self.document.document_type.metadata_type_relations.values_list('metadata_type', flat=True):
raise ValidationError( raise ValidationError(
_('Metadata type is not valid for this document type.') _('Metadata type is not valid for this document type.')
) )
@@ -279,6 +317,7 @@ class DocumentMetadata(models.Model):
user = kwargs.pop('_user', None) user = kwargs.pop('_user', None)
created = not self.pk created = not self.pk
with transaction.atomic():
result = super(DocumentMetadata, self).save(*args, **kwargs) result = super(DocumentMetadata, self).save(*args, **kwargs)
if created: if created:
@@ -302,12 +341,12 @@ class DocumentTypeMetadataType(models.Model):
document type. document type.
""" """
document_type = models.ForeignKey( document_type = models.ForeignKey(
on_delete=models.CASCADE, related_name='metadata', to=DocumentType, on_delete=models.CASCADE, related_name='metadata_type_relations',
verbose_name=_('Document type') to=DocumentType, verbose_name=_('Document type')
) )
metadata_type = models.ForeignKey( metadata_type = models.ForeignKey(
on_delete=models.CASCADE, to=MetadataType, on_delete=models.CASCADE, related_name='document_type_relations',
verbose_name=_('Metadata type') to=MetadataType, verbose_name=_('Metadata type')
) )
required = models.BooleanField(default=False, verbose_name=_('Required')) required = models.BooleanField(default=False, verbose_name=_('Required'))
@@ -325,6 +364,7 @@ class DocumentTypeMetadataType(models.Model):
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
user = kwargs.pop('_user', None) user = kwargs.pop('_user', None)
with transaction.atomic():
result = super(DocumentTypeMetadataType, self).delete(*args, **kwargs) result = super(DocumentTypeMetadataType, self).delete(*args, **kwargs)
event_metadata_type_relationship.commit( event_metadata_type_relationship.commit(
@@ -336,6 +376,7 @@ class DocumentTypeMetadataType(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
user = kwargs.pop('_user', None) user = kwargs.pop('_user', None)
with transaction.atomic():
result = super(DocumentTypeMetadataType, self).save(*args, **kwargs) result = super(DocumentTypeMetadataType, self).save(*args, **kwargs)
event_metadata_type_relationship.commit( event_metadata_type_relationship.commit(

View File

@@ -5,158 +5,149 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.reverse import reverse from rest_framework.settings import api_settings
from mayan.apps.documents.models import DocumentType
from mayan.apps.documents.permissions import permission_document_type_edit
from mayan.apps.documents.serializers import ( from mayan.apps.documents.serializers import (
DocumentSerializer, DocumentTypeSerializer DocumentSerializer, DocumentTypeSerializer
) )
from mayan.apps.rest_api.mixins import ExternalObjectListSerializerMixin
from mayan.apps.rest_api.relations import (
FilteredPrimaryKeyRelatedField, MultiKwargHyperlinkedIdentityField
)
from .models import DocumentMetadata, DocumentTypeMetadataType, MetadataType from .models import DocumentMetadata, DocumentTypeMetadataType, MetadataType
from .permissions import permission_metadata_add
class MetadataTypeSerializer(serializers.HyperlinkedModelSerializer): class MetadataTypeSerializer(serializers.HyperlinkedModelSerializer):
document_type_relation_add_url = serializers.HyperlinkedIdentityField(
lookup_url_kwarg='metadata_type_id', view_name='rest_api:metadata_type-document_type_relation-add'
)
document_type_relation_list_url = serializers.HyperlinkedIdentityField(
lookup_url_kwarg='metadata_type_id', view_name='rest_api:metadata_type-document_type_relation-list'
)
document_type_relation_remove_url = serializers.HyperlinkedIdentityField(
lookup_url_kwarg='metadata_type_id', view_name='rest_api:metadata_type-document_type_relation-remove'
)
class Meta: class Meta:
extra_kwargs = { extra_kwargs = {
'url': { 'url': {
'lookup_field': 'pk', 'lookup_url_kwarg': 'metadata_type_pk', 'lookup_url_kwarg': 'metadata_type_id',
'view_name': 'rest_api:metadatatype-detail' 'view_name': 'rest_api:metadata_type-detail'
}, },
} }
fields = ( fields = (
'default', 'id', 'label', 'lookup', 'name', 'parser', 'url', 'default', 'document_type_relation_add_url', 'document_type_relation_list_url',
'validation' 'document_type_relation_remove_url', 'id', 'label', 'lookup', 'name',
'parser', 'url', 'validation'
) )
model = MetadataType model = MetadataType
class DocumentTypeMetadataTypeSerializer(serializers.HyperlinkedModelSerializer): class MetadataTypeDocumentTypeRelationSerializer(serializers.HyperlinkedModelSerializer):
document_type = DocumentTypeSerializer(read_only=True) document_type = DocumentTypeSerializer(read_only=True)
metadata_type = MetadataTypeSerializer(read_only=True) url = MultiKwargHyperlinkedIdentityField(
url = serializers.SerializerMethodField() view_kwargs=(
{
'lookup_field': 'metadata_type_id',
'lookup_url_kwarg': 'metadata_type_id',
},
{
'lookup_field': 'pk',
'lookup_url_kwarg': 'metadata_type_document_type_relation_id',
}
),
view_name='rest_api:metadata_type-document_type_relation-detail'
)
class Meta: class Meta:
fields = ('document_type', 'id', 'metadata_type', 'required', 'url') fields = ('document_type', 'id', 'required', 'url')
model = DocumentTypeMetadataType model = DocumentTypeMetadataType
def get_url(self, instance):
return reverse(
viewname='rest_api:documenttypemetadatatype-detail', kwargs={
'document_type_pk': instance.document_type.pk, 'metadata_type': instance.pk
}, request=self.context['request'], format=self.context['format']
)
class MetadataTypeDocumentTypeRelationAddRemoveSerializer(ExternalObjectListSerializerMixin, serializers.Serializer):
class NewDocumentTypeMetadataTypeSerializer(serializers.ModelSerializer): document_type_id_list = serializers.CharField(
metadata_type_pk = serializers.IntegerField( help_text=_(
help_text=_('Primary key of the metadata type to be added.'), 'Comma separated list of document type primary keys that will be '
write_only=True 'added or removed.'
), label=_('Document Type ID list'), required=False, write_only=True
)
required = serializers.BooleanField(
label=_('Required'), required=False, write_only=True
) )
url = serializers.SerializerMethodField()
class Meta: class Meta:
fields = ( external_object_list_model = DocumentType
'id', 'metadata_type_pk', 'required', 'url' external_object_list_permission = permission_document_type_edit
) external_object_list_pk_list_field = 'document_type_id_list'
model = DocumentTypeMetadataType
def get_url(self, instance): def document_type_relations_add(self, instance):
return reverse( instance.document_types_add(
viewname='rest_api:documenttypemetadatatype-detail', kwargs={ queryset=self.get_external_object_list(),
'document_type': instance.document_type.pk, 'metadata_type': instance.pk required=self.validated_data['required'],
}, request=self.context['request'], format=self.context['format'] _user=self.context['request'].user
) )
def validate(self, attrs): def document_type_relations_remove(self, instance):
attrs['document_type'] = self.context['document_type'] instance.document_types_remove(
attrs['metadata_type'] = MetadataType.objects.get( queryset=self.get_external_object_list(),
pk=attrs.pop('metadata_type_pk') _user=self.context['request'].user
)
instance = DocumentTypeMetadataType(**attrs)
try:
instance.full_clean()
except DjangoValidationError as exception:
raise ValidationError(exception)
return attrs
class WritableDocumentTypeMetadataTypeSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
class Meta:
fields = (
'id', 'required', 'url'
)
model = DocumentTypeMetadataType
def get_url(self, instance):
return reverse(
viewname='rest_api:documenttypemetadatatype-detail', kwargs={
'document_type_pk': instance.document_type.pk,
'metadata_type': instance.pk
}, request=self.context['request'], format=self.context['format']
) )
class DocumentMetadataSerializer(serializers.HyperlinkedModelSerializer): class DocumentMetadataSerializer(serializers.HyperlinkedModelSerializer):
document = DocumentSerializer(read_only=True) document = DocumentSerializer(read_only=True)
metadata_type = MetadataTypeSerializer(read_only=True) metadata_type = MetadataTypeSerializer(read_only=True)
url = serializers.SerializerMethodField() url = MultiKwargHyperlinkedIdentityField(
view_kwargs=(
{
'lookup_field': 'document_id',
'lookup_url_kwarg': 'document_id',
},
{
'lookup_field': 'pk',
'lookup_url_kwarg': 'document_metadata_id',
}
),
view_name='rest_api:document-metadata-detail'
)
class Meta: class Meta:
fields = ('document', 'id', 'metadata_type', 'url', 'value') fields = ('document', 'id', 'metadata_type', 'url', 'value')
model = DocumentMetadata model = DocumentMetadata
read_only_fields = ('document', 'metadata_type',)
def get_url(self, instance):
return reverse(
viewname='rest_api:documentmetadata-detail', kwargs={
'document_pk': instance.document.pk, 'metadata_pk': instance.pk
}, request=self.context['request'], format=self.context['format']
)
def validate(self, attrs):
self.instance.value = attrs['value']
try:
self.instance.full_clean()
except DjangoValidationError as exception:
raise ValidationError(exception)
return attrs
class NewDocumentMetadataSerializer(serializers.ModelSerializer): class DocumentMetadataAddSerializer(DocumentMetadataSerializer):
metadata_type_pk = serializers.IntegerField( metadata_type = FilteredPrimaryKeyRelatedField(
help_text=_( source_model=MetadataType, source_permission=permission_metadata_add,
'Primary key of the metadata type to be added to the document.'
),
write_only=True write_only=True
) )
url = serializers.SerializerMethodField()
class Meta: class Meta(DocumentMetadataSerializer.Meta):
fields = ('id', 'metadata_type_pk', 'url', 'value') read_only_fields = ('document',)
model = DocumentMetadata
def get_url(self, instance): def create(self, validated_data):
return reverse( validated_data['document'] = self.context['document']
viewname='rest_api:documentmetadata-detail', kwargs={
'document_pk': instance.document.pk, 'metadata_pk': instance.pk return super(DocumentMetadataAddSerializer, self).create(
}, request=self.context['request'], format=self.context['format'] validated_data=validated_data
) )
def validate(self, attrs): def validate(self, attrs):
attrs['document'] = self.context['document'] attrs['document'] = self.context['document']
attrs['metadata_type'] = MetadataType.objects.get(
pk=attrs.pop('metadata_type_pk')
)
instance = DocumentMetadata(**attrs) instance = DocumentMetadata(**attrs)
try: try:
instance.full_clean() instance.full_clean()
except DjangoValidationError as exception: except DjangoValidationError as exception:
raise ValidationError(exception) raise ValidationError(
{
api_settings.NON_FIELD_ERRORS_KEY: exception.messages
}, code='invalid'
)
return attrs return attrs

View File

@@ -9,6 +9,7 @@ TEST_DEFAULT_VALUE = 'test'
TEST_INCORRECT_LOOKUP_VALUE = '0' TEST_INCORRECT_LOOKUP_VALUE = '0'
TEST_INVALID_DATE = '___________' TEST_INVALID_DATE = '___________'
TEST_LOOKUP_TEMPLATE = '1,2,3' TEST_LOOKUP_TEMPLATE = '1,2,3'
TEST_METADATA_TYPE_INVALID_LOOKUP = 'invalid,lookup,values,on,purpose'
TEST_METADATA_TYPE_LABEL = 'test metadata type' TEST_METADATA_TYPE_LABEL = 'test metadata type'
TEST_METADATA_TYPE_LABEL_2 = 'test metadata type label 2' TEST_METADATA_TYPE_LABEL_2 = 'test metadata type label 2'
TEST_METADATA_TYPE_LABEL_EDITED = 'test metadata type label edited' TEST_METADATA_TYPE_LABEL_EDITED = 'test metadata type label edited'

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,9 @@ from .mixins import MetadataTypeTestMixin
class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase): class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase):
def setUp(self): def setUp(self):
super(MetadataTestCase, self).setUp() super(MetadataTestCase, self).setUp()
self.document_type.metadata.create(metadata_type=self.metadata_type) self.document_type.metadata_type_relations.create(
metadata_type=self.metadata_type
)
def test_no_default(self): def test_no_default(self):
document_metadata = DocumentMetadata( document_metadata = DocumentMetadata(
@@ -146,13 +148,13 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase):
) )
def test_required_metadata(self): def test_required_metadata(self):
self.document_type.metadata.all().delete() self.document_type.metadata_type_relations.all().delete()
self.assertFalse( self.assertFalse(
self.metadata_type.get_required_for(self.document_type) self.metadata_type.get_required_for(self.document_type)
) )
self.document_type.metadata.create( self.document_type.metadata_type_relations.create(
metadata_type=self.metadata_type, required=False metadata_type=self.metadata_type, required=False
) )
@@ -160,9 +162,9 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase):
self.metadata_type.get_required_for(self.document_type) self.metadata_type.get_required_for(self.document_type)
) )
self.document_type.metadata.all().delete() self.document_type.metadata_type_relations.all().delete()
self.document_type.metadata.create( self.document_type.metadata_type_relations.create(
metadata_type=self.metadata_type, required=True metadata_type=self.metadata_type, required=True
) )
@@ -198,7 +200,7 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase):
label=TEST_DOCUMENT_TYPE_2_LABEL label=TEST_DOCUMENT_TYPE_2_LABEL
) )
self.document_type_2.metadata.create( self.document_type_2.metadata_type_relations.create(
metadata_type=self.metadata_type, required=True metadata_type=self.metadata_type, required=True
) )
@@ -226,7 +228,9 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase):
label=TEST_DOCUMENT_TYPE_2_LABEL label=TEST_DOCUMENT_TYPE_2_LABEL
) )
self.document_type_2.metadata.create(metadata_type=self.metadata_type) self.document_type_2.metadata_type_relations.create(
metadata_type=self.metadata_type
)
self.document.set_document_type(document_type=self.document_type_2) self.document.set_document_type(document_type=self.document_type_2)
@@ -276,7 +280,7 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase):
label=TEST_DOCUMENT_TYPE_2_LABEL label=TEST_DOCUMENT_TYPE_2_LABEL
) )
self.document_type_2.metadata.create( self.document_type_2.metadata_type_relations.create(
metadata_type=self.metadata_type, required=True metadata_type=self.metadata_type, required=True
) )

View File

@@ -34,7 +34,9 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa
def setUp(self): def setUp(self):
super(DocumentMetadataViewTestCase, self).setUp() super(DocumentMetadataViewTestCase, self).setUp()
self._create_metadata_type() self._create_metadata_type()
self.document_type.metadata.create(metadata_type=self.metadata_type) self.document_type.metadata_type_relations.create(
metadata_type=self.metadata_type
)
def _request_document_metadata_add_get_view(self): def _request_document_metadata_add_get_view(self):
return self.get( return self.get(
@@ -76,7 +78,9 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa
) )
self._create_document_type_random() self._create_document_type_random()
self.test_document_type.metadata.create(metadata_type=self.metadata_type) self.test_document_type.metadata_type_relations.create(
metadata_type=self.metadata_type
)
self._create_document() self._create_document()
self.grant_access( self.grant_access(
obj=self.test_document, permission=permission_metadata_add obj=self.test_document, permission=permission_metadata_add
@@ -94,7 +98,9 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa
) )
self._create_document_type_random() self._create_document_type_random()
self.test_document_type.metadata.create(metadata_type=self.metadata_type) self.test_document_type.metadata_type_relations.create(
metadata_type=self.metadata_type
)
self._create_document() self._create_document()
self.grant_access( self.grant_access(
obj=self.test_document, permission=permission_metadata_add obj=self.test_document, permission=permission_metadata_add
@@ -112,7 +118,9 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa
) )
self._create_document_type_random() self._create_document_type_random()
self.test_document_type.metadata.create(metadata_type=self.metadata_type) self.test_document_type.metadata_type_relations.create(
metadata_type=self.metadata_type
)
self._create_document() self._create_document()
self.id_list.append(self.test_document.pk) self.id_list.append(self.test_document.pk)
self.grant_access( self.grant_access(
@@ -159,7 +167,7 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa
name=TEST_METADATA_TYPE_NAME_2, label=TEST_METADATA_TYPE_LABEL_2 name=TEST_METADATA_TYPE_NAME_2, label=TEST_METADATA_TYPE_LABEL_2
) )
document_metadata_2 = document_type_2.metadata.create( document_metadata_2 = document_type_2.metadata_type_relations.create(
metadata_type=metadata_type_2, required=True metadata_type=metadata_type_2, required=True
) )
@@ -400,7 +408,7 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa
name=TEST_METADATA_TYPE_NAME_2, label=TEST_METADATA_TYPE_LABEL_2 name=TEST_METADATA_TYPE_NAME_2, label=TEST_METADATA_TYPE_LABEL_2
) )
self.document_type.metadata.create( self.document_type.metadata_type_relations.create(
metadata_type=metadata_type_2 metadata_type=metadata_type_2
) )
@@ -540,7 +548,7 @@ class MetadataTypeViewViewTestCase(DocumentTestMixin, MetadataTestsMixin, Generi
self.document_type.refresh_from_db() self.document_type.refresh_from_db()
self.assertEqual(self.document_type.metadata.count(), 0) self.assertEqual(self.document_type.metadata_type_relations.count(), 0)
def test_metadata_type_relationship_view_with_document_type_access(self): def test_metadata_type_relationship_view_with_document_type_access(self):
self._create_metadata_type() self._create_metadata_type()
@@ -556,7 +564,7 @@ class MetadataTypeViewViewTestCase(DocumentTestMixin, MetadataTestsMixin, Generi
self.document_type.refresh_from_db() self.document_type.refresh_from_db()
self.assertEqual(self.document_type.metadata.count(), 0) self.assertEqual(self.document_type.metadata_type_relations.count(), 0)
def test_metadata_type_relationship_view_with_metadata_type_access(self): def test_metadata_type_relationship_view_with_metadata_type_access(self):
self._create_metadata_type() self._create_metadata_type()
@@ -573,7 +581,7 @@ class MetadataTypeViewViewTestCase(DocumentTestMixin, MetadataTestsMixin, Generi
self.document_type.refresh_from_db() self.document_type.refresh_from_db()
self.assertEqual(self.document_type.metadata.count(), 0) self.assertEqual(self.document_type.metadata_type_relations.count(), 0)
def test_metadata_type_relationship_view_with_metadata_type_and_document_type_access(self): def test_metadata_type_relationship_view_with_metadata_type_and_document_type_access(self):
self._create_metadata_type() self._create_metadata_type()
@@ -594,8 +602,9 @@ class MetadataTypeViewViewTestCase(DocumentTestMixin, MetadataTestsMixin, Generi
self.document_type.refresh_from_db() self.document_type.refresh_from_db()
self.assertQuerysetEqual( self.assertQuerysetEqual(
qs=self.document_type.metadata.values('metadata_type', 'required'), qs=self.document_type.metadata_type_relations.values(
values=[ 'metadata_type', 'required'
), values=[
{ {
'metadata_type': self.metadata_type.pk, 'metadata_type': self.metadata_type.pk,
'required': True, 'required': True,

View File

@@ -31,7 +31,7 @@ class DocumentUploadMetadataTestCase(MetadataTypeTestMixin, GenericDocumentViewT
self.document.delete() self.document.delete()
self.document_type.metadata.create( self.document_type.metadata_type_relations.create(
metadata_type=self.metadata_type, required=True metadata_type=self.metadata_type, required=True
) )

View File

@@ -3,16 +3,14 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from .api_views import ( from .api_views import (
APIDocumentMetadataListView, APIDocumentMetadataView, DocumentMetadataAPIViewSet, MetadataTypeAPIViewSet,
APIDocumentTypeMetadataTypeListView, APIDocumentTypeMetadataTypeView, MetadataTypeDocumentTypeRelationAPIViewSet
APIMetadataTypeListView, APIMetadataTypeView
) )
from .views import ( from .views import (
DocumentMetadataAddView, DocumentMetadataEditView, DocumentMetadataAddView, DocumentMetadataEditView,
DocumentMetadataListView, DocumentMetadataRemoveView, DocumentMetadataListView, DocumentMetadataRemoveView,
MetadataTypeCreateView, MetadataTypeDeleteView, MetadataTypeEditView, DocumentTypeMetadataTypes, MetadataTypeCreateView, MetadataTypeDeleteView,
MetadataTypeListView, SetupDocumentTypeMetadataTypes, MetadataTypesDocumentTypes, MetadataTypeEditView, MetadataTypeListView
SetupMetadataTypesDocumentTypes
) )
urlpatterns = [ urlpatterns = [
@@ -35,12 +33,12 @@ urlpatterns = [
url( url(
regex=r'^metadata_types/(?P<metadata_type_id>\d+)/document_types/$', regex=r'^metadata_types/(?P<metadata_type_id>\d+)/document_types/$',
name='metadata_type_document_types', name='metadata_type_document_types',
view=SetupMetadataTypesDocumentTypes.as_view() view=MetadataTypesDocumentTypes.as_view()
), ),
url( url(
regex=r'^document_types/(?P<document_type_id>\d+)/metadata_types/$', regex=r'^document_types/(?P<document_type_id>\d+)/metadata_types/$',
name='document_type_metadata_types', name='document_type_metadata_types',
view=SetupDocumentTypeMetadataTypes.as_view() view=DocumentTypeMetadataTypes.as_view()
), ),
url( url(
regex=r'^documents/(?P<document_id>\d+)/edit/$', regex=r'^documents/(?P<document_id>\d+)/edit/$',
@@ -77,34 +75,18 @@ urlpatterns = [
) )
] ]
api_urls = [ api_router_entries = (
url( {
regex=r'^metadata_types/$', name='metadatatype-list', 'prefix': r'metadata_types', 'viewset': MetadataTypeAPIViewSet,
view=APIMetadataTypeListView.as_view() 'basename': 'metadata_type'
), },
url( {
regex=r'^metadata_types/(?P<metadata_type_id>\d+)/$', 'prefix': r'metadata_types/(?P<metadata_type_id>[^/.]+)/document_type_relations',
name='metadatatype-detail', 'viewset': MetadataTypeDocumentTypeRelationAPIViewSet,
view=APIMetadataTypeView.as_view() 'basename': 'metadata_type-document_type_relation'
), },
url( {
regex=r'^document_types/(?P<document_type_id>\d+)/metadata_types/$', 'prefix': r'documents/(?P<document_id>[^/.]+)/metadata',
name='documenttypemetadatatype-list', 'viewset': DocumentMetadataAPIViewSet, 'basename': 'document-metadata'
view=APIDocumentTypeMetadataTypeListView.as_view() }
), )
url(
regex=r'^document_types/(?P<document_type_id>\d+)/metadata_types/(?P<metadata_type_id>\d+)/$',
name='documenttypemetadatatype-detail',
view=APIDocumentTypeMetadataTypeView.as_view()
),
url(
regex=r'^documents/(?P<document_id>\d+)/metadata/$',
name='documentmetadata-list',
view=APIDocumentMetadataListView.as_view()
),
url(
regex=r'^documents/(?P<document_id>\d+)/metadata/(?P<metadata_id>\d+)/$',
name='documentmetadata-detail',
view=APIDocumentMetadataView.as_view()
)
]

View File

@@ -564,7 +564,7 @@ class MetadataTypeListView(SingleObjectListView):
return MetadataType.objects.all() return MetadataType.objects.all()
class SetupDocumentTypeMetadataTypes(ExternalObjectMixin, FormView): class DocumentTypeMetadataTypes(ExternalObjectMixin, FormView):
external_object_class = DocumentType external_object_class = DocumentType
external_object_permission = permission_metadata_type_edit external_object_permission = permission_metadata_type_edit
external_object_pk_url_kwarg = 'document_type_id' external_object_pk_url_kwarg = 'document_type_id'
@@ -590,7 +590,7 @@ class SetupDocumentTypeMetadataTypes(ExternalObjectMixin, FormView):
) )
return super( return super(
SetupDocumentTypeMetadataTypes, self DocumentTypeMetadataTypes, self
).form_valid(form=form) ).form_valid(form=form)
def get_extra_context(self): def get_extra_context(self):
@@ -640,7 +640,7 @@ class SetupDocumentTypeMetadataTypes(ExternalObjectMixin, FormView):
return reverse(viewname='documents:document_type_list') return reverse(viewname='documents:document_type_list')
class SetupMetadataTypesDocumentTypes(SetupDocumentTypeMetadataTypes): class MetadataTypesDocumentTypes(DocumentTypeMetadataTypes):
external_object_class = MetadataType external_object_class = MetadataType
external_object_permission = permission_metadata_type_edit external_object_permission = permission_metadata_type_edit
external_object_pk_url_kwarg = 'metadata_type_id' external_object_pk_url_kwarg = 'metadata_type_id'
@@ -651,10 +651,10 @@ class SetupMetadataTypesDocumentTypes(SetupDocumentTypeMetadataTypes):
def get_extra_context(self): def get_extra_context(self):
return { return {
'form_display_mode_table': True, 'form_display_mode_table': True,
'object': self.get_object(), 'object': self.external_object,
'title': _( 'title': _(
'Document types for metadata type: %s' 'Document types for metadata type: %s'
) % self.get_object() ) % self.external_object,
} }
def get_initial(self): def get_initial(self):

View File

@@ -3,9 +3,23 @@ from __future__ import absolute_import, unicode_literals
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from mayan.apps.acls.models import AccessControlList from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.mixins import ExternalObjectMixin
class ExternalObjectAPIViewSetMixin(ExternalObjectMixin):
"""Override get_external_object to use REST API get_object_or_404"""
def dispatch(self, *args, **kwargs):
return super(ExternalObjectMixin, self).dispatch(*args, **kwargs)
def get_external_object(self):
return get_object_or_404(
queryset=self.get_external_object_queryset_filtered(),
**self.get_pk_url_kwargs()
)
class ExternalObjectListSerializerMixin(object): class ExternalObjectListSerializerMixin(object):
@@ -127,6 +141,7 @@ class ExternalObjectSerializerMixin(object):
external_object_permission external_object_permission
external_object_queryset external_object_queryset
external_object_pk_field external_object_pk_field
external_object_pk_type
The source queryset can also be provided overriding the The source queryset can also be provided overriding the
.get_external_object_queryset() method. .get_external_object_queryset() method.
""" """
@@ -157,7 +172,19 @@ class ExternalObjectSerializerMixin(object):
else: else:
pk_field_value = None pk_field_value = None
pk_type = self.get_external_object_option('pk_type')
if pk_field_value: if pk_field_value:
if pk_type:
try:
pk_field_value = pk_type(pk_field_value)
except Exception as exception:
raise ValidationError(
{
pk_field: [exception]
}, code='invalid'
)
try: try:
return queryset.get(pk=pk_field_value) return queryset.get(pk=pk_field_value)
except Exception as exception: except Exception as exception:

View File

@@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from rest_framework.permissions import BasePermission, IsAuthenticated from rest_framework.permissions import BasePermission
from mayan.apps.permissions import Permission from mayan.apps.permissions import Permission

View File

@@ -1,11 +1,6 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from rest_framework.generics import get_object_or_404
from rest_framework.reverse import reverse
class LazyExtraFieldsSerializerMixin(object): class LazyExtraFieldsSerializerMixin(object):

View File

@@ -1,7 +1,6 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from rest_framework import viewsets from rest_framework import mixins, viewsets
from rest_framework.settings import api_settings
from .filters import MayanViewSetObjectPermissionsFilter from .filters import MayanViewSetObjectPermissionsFilter
from .mixins import SuccessHeadersMixin from .mixins import SuccessHeadersMixin
@@ -26,3 +25,13 @@ class MayanAPIReadOnlyModelViewSet(SuccessHeadersMixin, viewsets.ReadOnlyModelVi
class MayanAPIViewSet(SuccessHeadersMixin, viewsets.GenericViewSet): class MayanAPIViewSet(SuccessHeadersMixin, viewsets.GenericViewSet):
filter_backends = (MayanViewSetObjectPermissionsFilter,) filter_backends = (MayanViewSetObjectPermissionsFilter,)
permission_classes = (MayanViewSetPermission,) permission_classes = (MayanViewSetPermission,)
class MayanRetrieveUpdateAPIViewSet(SuccessHeadersMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
filter_backends = (MayanViewSetObjectPermissionsFilter,)
permission_classes = (MayanViewSetPermission,)
class MayanRetrieveUpdateDestroyAPIViewSet(mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
filter_backends = (MayanViewSetObjectPermissionsFilter,)
permission_classes = (MayanViewSetPermission,)