diff --git a/mayan/apps/acls/api_views.py b/mayan/apps/acls/api_views.py index 7ea8b81144..6dd0f1ec4f 100644 --- a/mayan/apps/acls/api_views.py +++ b/mayan/apps/acls/api_views.py @@ -8,13 +8,14 @@ from mayan.apps.common.mixins import ContentTypeViewMixin, ExternalObjectMixin from mayan.apps.permissions.serializers import ( PermissionSerializer, RolePermissionAddRemoveSerializer ) +from mayan.apps.rest_api.mixins import ExternalObjectAPIViewSetMixin from mayan.apps.rest_api.viewsets import MayanAPIModelViewSet from .permissions import permission_acl_edit, permission_acl_view from .serializers import AccessControlListSerializer -class ObjectACLAPIViewSet(ContentTypeViewMixin, ExternalObjectMixin, MayanAPIModelViewSet): +class ObjectACLAPIViewSet(ContentTypeViewMixin, ExternalObjectAPIViewSetMixin, MayanAPIModelViewSet): content_type_url_kw_args = { 'app_label': 'app_label', 'model': 'model_name' diff --git a/mayan/apps/events/api_views.py b/mayan/apps/events/api_views.py index feffb55333..5147f5240f 100644 --- a/mayan/apps/events/api_views.py +++ b/mayan/apps/events/api_views.py @@ -5,7 +5,8 @@ from rest_framework import viewsets from rest_framework.decorators import action 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 .classes import EventTypeNamespace @@ -80,7 +81,7 @@ class NotificationAPIViewSet(MayanAPIReadOnlyModelViewSet): return queryset -class ObjectEventAPIViewSet(ContentTypeViewMixin, ExternalObjectMixin, MayanAPIReadOnlyModelViewSet): +class ObjectEventAPIViewSet(ContentTypeViewMixin, ExternalObjectAPIViewSetMixin, MayanAPIReadOnlyModelViewSet): content_type_url_kw_args = { 'app_label': 'app_label', 'model': 'model_name' diff --git a/mayan/apps/metadata/api_views.py b/mayan/apps/metadata/api_views.py index 042b8a903e..0756b91d82 100644 --- a/mayan/apps/metadata/api_views.py +++ b/mayan/apps/metadata/api_views.py @@ -1,16 +1,17 @@ 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.acls.models import AccessControlList -from mayan.apps.documents.models import Document, DocumentType +from mayan.apps.documents.models import Document 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 .permissions import ( @@ -20,235 +21,140 @@ from .permissions import ( permission_metadata_type_edit, permission_metadata_type_view ) from .serializers import ( - DocumentMetadataSerializer, DocumentTypeMetadataTypeSerializer, - MetadataTypeSerializer, NewDocumentMetadataSerializer, - NewDocumentTypeMetadataTypeSerializer, - WritableDocumentTypeMetadataTypeSerializer + DocumentMetadataAddSerializer, + DocumentMetadataSerializer, + MetadataTypeSerializer, + MetadataTypeDocumentTypeRelationSerializer, + MetadataTypeDocumentTypeRelationAddRemoveSerializer, ) -class APIDocumentMetadataListView(generics.ListCreateAPIView): - """ - get: Returns a list of selected document's metadata types and values. - post: Add an existing metadata type and value to the selected document. - """ - def get_document(self): - if self.request.method == 'GET': - permission_required = permission_metadata_view - else: - permission_required = permission_metadata_add - - 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(APIDocumentMetadataListView, self).get_serializer(*args, **kwargs) - - def get_serializer_class(self): - if self.request.method == 'GET': - return DocumentMetadataSerializer - else: - return NewDocumentMetadataSerializer - - def get_serializer_context(self): - """ - Extra context provided to the serializer class. - """ - context = super(APIDocumentMetadataListView, self).get_serializer_context() - if self.kwargs: - context.update( - { - 'document': self.get_document(), - } - ) - - 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,) +class MetadataTypeDocumentTypeRelationAPIViewSet(ExternalObjectAPIViewSetMixin, MayanRetrieveUpdateAPIViewSet): + external_object_class = MetadataType + external_object_pk_url_kwarg = 'metadata_type_id' + lookup_url_kwarg = 'metadata_type_document_type_relation_id' + object_permission_map = { + 'add': permission_metadata_type_edit, + 'list': permission_document_type_view, + 'partial_update': permission_document_type_edit, + 'remove': permission_metadata_type_edit, + 'retrieve': permission_document_type_view, + 'update': permission_document_type_edit, } - permission_classes = (MayanPermission,) - queryset = MetadataType.objects.all() - serializer_class = MetadataTypeSerializer + serializer_class = MetadataTypeDocumentTypeRelationSerializer - -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 + 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: - 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 + return permission_metadata_type_edit def get_queryset(self): - return self.get_document_type().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): - if not self.request: + @action( + 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 + 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(APIDocumentTypeMetadataTypeListView, self).get_serializer(*args, **kwargs) + def get_queryset(self): + return self.get_external_object().metadata.all() def get_serializer_class(self): - if self.request.method == 'GET': - return DocumentTypeMetadataTypeSerializer + action = getattr(self, 'action', None) + if action is None: + return None + if action == 'create': + return DocumentMetadataAddSerializer else: - return NewDocumentTypeMetadataTypeSerializer + return DocumentMetadataSerializer def get_serializer_context(self): """ Extra context provided to the serializer class. """ - context = super(APIDocumentTypeMetadataTypeListView, self).get_serializer_context() + context = super(DocumentMetadataAPIViewSet, self).get_serializer_context() if self.kwargs: context.update( { - 'document_type': self.get_document_type(), + 'document': self.get_external_object(), } ) 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 diff --git a/mayan/apps/metadata/apps.py b/mayan/apps/metadata/apps.py index 6973a6811d..4f1e7d1134 100644 --- a/mayan/apps/metadata/apps.py +++ b/mayan/apps/metadata/apps.py @@ -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.common import ( 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.widgets import TwoStateWidget @@ -26,6 +26,8 @@ from mayan.apps.events.links import ( from mayan.apps.events.permissions import permission_events_view from mayan.celery import app 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 ( event_document_metadata_added, event_document_metadata_edited, @@ -80,11 +82,9 @@ class MetadataApp(MayanAppConfig): DocumentPageSearchResult = apps.get_model( app_label='documents', model_name='DocumentPageSearchResult' ) - DocumentType = apps.get_model( app_label='documents', model_name='DocumentType' ) - DocumentMetadata = self.get_model('DocumentMetadata') DocumentTypeMetadataType = self.get_model('DocumentTypeMetadataType') MetadataType = self.get_model('MetadataType') @@ -93,6 +93,15 @@ class MetadataApp(MayanAppConfig): 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') ModelField( @@ -140,10 +149,25 @@ class MetadataApp(MayanAppConfig): model=MetadataType, permissions=( permission_acl_edit, permission_acl_view, 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 ) ) + 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( func=widget_get_metadata_string, source=Document ) diff --git a/mayan/apps/metadata/forms.py b/mayan/apps/metadata/forms.py index a20ecc4692..bda397ab47 100644 --- a/mayan/apps/metadata/forms.py +++ b/mayan/apps/metadata/forms.py @@ -198,12 +198,12 @@ class DocumentTypeMetadataTypeRelationshipForm(forms.Form): self.fields['relationship_type'].initial = self.initial_relationship_type 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'] ) 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'] ) diff --git a/mayan/apps/metadata/handlers.py b/mayan/apps/metadata/handlers.py index 97da1133a1..12bf7551b3 100644 --- a/mayan/apps/metadata/handlers.py +++ b/mayan/apps/metadata/handlers.py @@ -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 # type 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' ) ) @@ -39,7 +39,7 @@ def handler_post_document_type_change(sender, instance, **kwargs): # excluding existing document metadata # get_or_create is not used to avoid a possible triggering of indexes # 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 ).exclude(metadata_type__in=instance.metadata.values('metadata_type')) diff --git a/mayan/apps/metadata/managers.py b/mayan/apps/metadata/managers.py index 76f8830d19..5b13f6f2f9 100644 --- a/mayan/apps/metadata/managers.py +++ b/mayan/apps/metadata/managers.py @@ -9,18 +9,10 @@ class MetadataTypeManager(models.Manager): return self.get(name=name) def get_for_document(self, document): - return self.filter( - pk__in=document.metadata.values_list( - 'metadata_type', flat=True - ) - ) + return self.filter(document_metadata__document=document) def get_for_document_type(self, document_type): - return self.filter( - pk__in=document_type.metadata.values_list( - 'metadata_type', flat=True - ) - ) + return self.filter(document_type_relations__document_type=document_type) class DocumentTypeMetadataTypeManager(models.Manager): diff --git a/mayan/apps/metadata/models.py b/mayan/apps/metadata/models.py index f5ccdb319e..1458bfcd41 100644 --- a/mayan/apps/metadata/models.py +++ b/mayan/apps/metadata/models.py @@ -5,13 +5,15 @@ import shlex from jinja2 import Template from django.core.exceptions import ValidationError -from django.db import models +from django.db import models, transaction from django.urls import reverse from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.module_loading import import_string from django.utils.six import PY2 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 .classes import MetadataLookup @@ -126,6 +128,38 @@ class MetadataType(models.Model): template = Template(self.default) 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): template = Template(self.lookup) context = MetadataLookup.get_as_context() @@ -146,7 +180,7 @@ class MetadataType(models.Model): Return a queryset of metadata types that are required for the specified document type. """ - return document_type.metadata.filter( + return document_type.metadata_type_relations.filter( required=True, metadata_type=self ).exists() @@ -157,16 +191,17 @@ class MetadataType(models.Model): user = kwargs.pop('_user', None) created = not self.pk - result = super(MetadataType, self).save(*args, **kwargs) + with transaction.atomic(): + result = super(MetadataType, self).save(*args, **kwargs) - if created: - event_metadata_type_created.commit( - actor=user, target=self - ) - else: - event_metadata_type_edited.commit( - actor=user, target=self - ) + if created: + event_metadata_type_created.commit( + actor=user, target=self + ) + else: + event_metadata_type_edited.commit( + actor=user, target=self + ) return result @@ -210,7 +245,8 @@ class DocumentMetadata(models.Model): verbose_name=_('Document') ) 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( 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 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( _('Metadata type is required for this document type.') ) user = kwargs.pop('_user', None) - result = super(DocumentMetadata, self).delete(*args, **kwargs) + with transaction.atomic(): + result = super(DocumentMetadata, self).delete(*args, **kwargs) + + event_document_metadata_removed.commit( + action_object=self.metadata_type, actor=user, target=self.document, + ) - event_document_metadata_removed.commit( - action_object=self.metadata_type, actor=user, target=self.document, - ) return result def natural_key(self): @@ -271,7 +309,7 @@ class DocumentMetadata(models.Model): is_required.fget.short_description = _('Required') 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( _('Metadata type is not valid for this document type.') ) @@ -279,18 +317,19 @@ class DocumentMetadata(models.Model): user = kwargs.pop('_user', None) created = not self.pk - result = super(DocumentMetadata, self).save(*args, **kwargs) + with transaction.atomic(): + result = super(DocumentMetadata, self).save(*args, **kwargs) - if created: - event_document_metadata_added.commit( - action_object=self.metadata_type, actor=user, - target=self.document, - ) - else: - event_document_metadata_edited.commit( - action_object=self.metadata_type, actor=user, - target=self.document, - ) + if created: + event_document_metadata_added.commit( + action_object=self.metadata_type, actor=user, + target=self.document, + ) + else: + event_document_metadata_edited.commit( + action_object=self.metadata_type, actor=user, + target=self.document, + ) return result @@ -302,12 +341,12 @@ class DocumentTypeMetadataType(models.Model): document type. """ document_type = models.ForeignKey( - on_delete=models.CASCADE, related_name='metadata', to=DocumentType, - verbose_name=_('Document type') + on_delete=models.CASCADE, related_name='metadata_type_relations', + to=DocumentType, verbose_name=_('Document type') ) metadata_type = models.ForeignKey( - on_delete=models.CASCADE, to=MetadataType, - verbose_name=_('Metadata type') + on_delete=models.CASCADE, related_name='document_type_relations', + to=MetadataType, verbose_name=_('Metadata type') ) required = models.BooleanField(default=False, verbose_name=_('Required')) @@ -325,21 +364,23 @@ class DocumentTypeMetadataType(models.Model): def delete(self, *args, **kwargs): user = kwargs.pop('_user', None) - result = super(DocumentTypeMetadataType, self).delete(*args, **kwargs) + with transaction.atomic(): + result = super(DocumentTypeMetadataType, self).delete(*args, **kwargs) - event_metadata_type_relationship.commit( - action_object=self.document_type, actor=user, target=self.metadata_type, - ) + event_metadata_type_relationship.commit( + action_object=self.document_type, actor=user, target=self.metadata_type, + ) return result def save(self, *args, **kwargs): user = kwargs.pop('_user', None) - result = super(DocumentTypeMetadataType, self).save(*args, **kwargs) + with transaction.atomic(): + result = super(DocumentTypeMetadataType, self).save(*args, **kwargs) - event_metadata_type_relationship.commit( - action_object=self.document_type, actor=user, target=self.metadata_type, - ) + event_metadata_type_relationship.commit( + action_object=self.document_type, actor=user, target=self.metadata_type, + ) return result diff --git a/mayan/apps/metadata/serializers.py b/mayan/apps/metadata/serializers.py index c8f47d2bf9..747c7978e4 100644 --- a/mayan/apps/metadata/serializers.py +++ b/mayan/apps/metadata/serializers.py @@ -5,158 +5,149 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers 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 ( 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 .permissions import permission_metadata_add 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: extra_kwargs = { 'url': { - 'lookup_field': 'pk', 'lookup_url_kwarg': 'metadata_type_pk', - 'view_name': 'rest_api:metadatatype-detail' + 'lookup_url_kwarg': 'metadata_type_id', + 'view_name': 'rest_api:metadata_type-detail' }, } fields = ( - 'default', 'id', 'label', 'lookup', 'name', 'parser', 'url', - 'validation' + 'default', 'document_type_relation_add_url', 'document_type_relation_list_url', + 'document_type_relation_remove_url', 'id', 'label', 'lookup', 'name', + 'parser', 'url', 'validation' ) model = MetadataType -class DocumentTypeMetadataTypeSerializer(serializers.HyperlinkedModelSerializer): +class MetadataTypeDocumentTypeRelationSerializer(serializers.HyperlinkedModelSerializer): document_type = DocumentTypeSerializer(read_only=True) - metadata_type = MetadataTypeSerializer(read_only=True) - url = serializers.SerializerMethodField() - - class Meta: - fields = ('document_type', 'id', 'metadata_type', '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 NewDocumentTypeMetadataTypeSerializer(serializers.ModelSerializer): - metadata_type_pk = serializers.IntegerField( - help_text=_('Primary key of the metadata type to be added.'), - write_only=True + url = MultiKwargHyperlinkedIdentityField( + 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' ) - url = serializers.SerializerMethodField() class Meta: - fields = ( - 'id', 'metadata_type_pk', 'required', 'url' - ) + fields = ('document_type', 'id', 'required', 'url') model = DocumentTypeMetadataType - def get_url(self, instance): - return reverse( - viewname='rest_api:documenttypemetadatatype-detail', kwargs={ - 'document_type': instance.document_type.pk, 'metadata_type': instance.pk - }, request=self.context['request'], format=self.context['format'] - ) - def validate(self, attrs): - attrs['document_type'] = self.context['document_type'] - attrs['metadata_type'] = MetadataType.objects.get( - pk=attrs.pop('metadata_type_pk') - ) - - instance = DocumentTypeMetadataType(**attrs) - try: - instance.full_clean() - except DjangoValidationError as exception: - raise ValidationError(exception) - - return attrs - - -class WritableDocumentTypeMetadataTypeSerializer(serializers.ModelSerializer): - url = serializers.SerializerMethodField() +class MetadataTypeDocumentTypeRelationAddRemoveSerializer(ExternalObjectListSerializerMixin, serializers.Serializer): + document_type_id_list = serializers.CharField( + help_text=_( + 'Comma separated list of document type primary keys that will be ' + 'added or removed.' + ), label=_('Document Type ID list'), required=False, write_only=True + ) + required = serializers.BooleanField( + label=_('Required'), required=False, write_only=True + ) class Meta: - fields = ( - 'id', 'required', 'url' - ) - model = DocumentTypeMetadataType + external_object_list_model = DocumentType + external_object_list_permission = permission_document_type_edit + external_object_list_pk_list_field = 'document_type_id_list' - 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'] + def document_type_relations_add(self, instance): + instance.document_types_add( + queryset=self.get_external_object_list(), + required=self.validated_data['required'], + _user=self.context['request'].user + ) + + def document_type_relations_remove(self, instance): + instance.document_types_remove( + queryset=self.get_external_object_list(), + _user=self.context['request'].user ) class DocumentMetadataSerializer(serializers.HyperlinkedModelSerializer): document = DocumentSerializer(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: fields = ('document', 'id', 'metadata_type', 'url', 'value') 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): - metadata_type_pk = serializers.IntegerField( - help_text=_( - 'Primary key of the metadata type to be added to the document.' - ), +class DocumentMetadataAddSerializer(DocumentMetadataSerializer): + metadata_type = FilteredPrimaryKeyRelatedField( + source_model=MetadataType, source_permission=permission_metadata_add, write_only=True ) - url = serializers.SerializerMethodField() - class Meta: - fields = ('id', 'metadata_type_pk', 'url', 'value') - model = DocumentMetadata + class Meta(DocumentMetadataSerializer.Meta): + read_only_fields = ('document',) - 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 create(self, validated_data): + validated_data['document'] = self.context['document'] + + return super(DocumentMetadataAddSerializer, self).create( + validated_data=validated_data ) def validate(self, attrs): attrs['document'] = self.context['document'] - attrs['metadata_type'] = MetadataType.objects.get( - pk=attrs.pop('metadata_type_pk') - ) instance = DocumentMetadata(**attrs) + try: instance.full_clean() except DjangoValidationError as exception: - raise ValidationError(exception) + raise ValidationError( + { + api_settings.NON_FIELD_ERRORS_KEY: exception.messages + }, code='invalid' + ) return attrs diff --git a/mayan/apps/metadata/tests/literals.py b/mayan/apps/metadata/tests/literals.py index 1530ca0f38..a5b7977af7 100644 --- a/mayan/apps/metadata/tests/literals.py +++ b/mayan/apps/metadata/tests/literals.py @@ -9,6 +9,7 @@ TEST_DEFAULT_VALUE = 'test' TEST_INCORRECT_LOOKUP_VALUE = '0' TEST_INVALID_DATE = '___________' 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_2 = 'test metadata type label 2' TEST_METADATA_TYPE_LABEL_EDITED = 'test metadata type label edited' diff --git a/mayan/apps/metadata/tests/test_api.py b/mayan/apps/metadata/tests/test_api.py index 860d09f7ab..a9a91c7a03 100644 --- a/mayan/apps/metadata/tests/test_api.py +++ b/mayan/apps/metadata/tests/test_api.py @@ -1,15 +1,16 @@ from __future__ import unicode_literals +import copy + from rest_framework import status -from mayan.apps.documents.models import DocumentType from mayan.apps.documents.permissions import ( permission_document_type_edit, permission_document_type_view ) -from mayan.apps.documents.tests import TEST_DOCUMENT_TYPE_LABEL, TEST_SMALL_DOCUMENT_PATH +from mayan.apps.documents.tests.mixins import DocumentTestMixin from mayan.apps.rest_api.tests import BaseAPITestCase -from ..models import DocumentTypeMetadataType, MetadataType +from ..models import MetadataType from ..permissions import ( permission_metadata_add, permission_metadata_edit, permission_metadata_remove, permission_metadata_view, @@ -18,506 +19,873 @@ from ..permissions import ( ) from .literals import ( - TEST_METADATA_TYPE_LABEL, TEST_METADATA_TYPE_LABEL_2, - TEST_METADATA_TYPE_NAME, TEST_METADATA_TYPE_NAME_2, TEST_METADATA_VALUE, + TEST_METADATA_TYPE_INVALID_LOOKUP, TEST_METADATA_TYPE_LABEL, + TEST_METADATA_TYPE_LABEL_EDITED, TEST_METADATA_TYPE_NAME, + TEST_METADATA_TYPE_NAME_EDITED, TEST_METADATA_VALUE, TEST_METADATA_VALUE_EDITED ) -class MetadataTypeAPITestCase(BaseAPITestCase): - def setUp(self): - super(MetadataTypeAPITestCase, self).setUp() - self.login_user() - - def _create_metadata_type(self): - self.metadata_type = MetadataType.objects.create( +class MetadataTypeTestMixin(object): + def _create_test_metadata_type(self): + self.test_metadata_type = MetadataType.objects.create( label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME ) - def _request_metadata_type_create_view(self): + +class MetadataTypeAPITestCase(MetadataTypeTestMixin, BaseAPITestCase): + def _request_metadata_type_create_api_view(self): return self.post( - viewname='rest_api:metadatatype-list', data={ + viewname='rest_api:metadata_type-list', data={ 'label': TEST_METADATA_TYPE_LABEL, 'name': TEST_METADATA_TYPE_NAME } ) - def test_metadata_type_create_no_permission(self): - response = self._request_metadata_type_create_view() + def test_metadata_type_create_api_view_no_permission(self): + response = self._request_metadata_type_create_api_view() self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(MetadataType.objects.count(), 0) - def test_metadata_type_create_with_permission(self): + def test_metadata_type_create_api_view_with_permission(self): self.grant_permission(permission=permission_metadata_type_create) - response = self._request_metadata_type_create_view() + + response = self._request_metadata_type_create_api_view() self.assertEqual(response.status_code, status.HTTP_201_CREATED) - metadata_type = MetadataType.objects.first() - self.assertEqual(response.data['id'], metadata_type.pk) - self.assertEqual(response.data['label'], TEST_METADATA_TYPE_LABEL) - self.assertEqual(response.data['name'], TEST_METADATA_TYPE_NAME) - - self.assertEqual(metadata_type.label, TEST_METADATA_TYPE_LABEL) - self.assertEqual(metadata_type.name, TEST_METADATA_TYPE_NAME) - - def _request_metadata_type_delete_view(self): - return self.delete( - viewname='rest_api:metadatatype-detail', - kwargs={'metadata_type_pk': self.metadata_type.pk} - ) - - def test_metadata_type_delete_no_access(self): - self._create_metadata_type() - response = self._request_metadata_type_delete_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(MetadataType.objects.count(), 1) - def test_metadata_type_delete_with_access(self): - self._create_metadata_type() - self.grant_access( - permission=permission_metadata_type_delete, obj=self.metadata_type + def _request_metadata_type_delete_api_view(self): + return self.delete( + viewname='rest_api:metadata_type-detail', + kwargs={'metadata_type_id': self.test_metadata_type.pk} ) - response = self._request_metadata_type_delete_view() + + def test_metadata_type_delete_api_view_no_permission(self): + self._create_test_metadata_type() + + response = self._request_metadata_type_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertTrue(self.test_metadata_type in MetadataType.objects.all()) + + def test_metadata_type_delete_api_view_with_access(self): + self.expected_content_type = None + + self._create_test_metadata_type() + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_delete + ) + + response = self._request_metadata_type_delete_api_view() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(MetadataType.objects.count(), 0) - def _request_metadata_type_detail_view(self): + self.assertTrue(self.test_metadata_type not in MetadataType.objects.all()) + + def _request_metadata_type_detail_api_view(self): return self.get( - viewname='rest_api:metadatatype-detail', - kwargs={'metadata_type_pk': self.metadata_type.pk} + viewname='rest_api:metadata_type-detail', + kwargs={'metadata_type_id': self.test_metadata_type.pk} ) - def test_metadata_type_detail_view_no_access(self): - self._create_metadata_type() - response = self._request_metadata_type_detail_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_metadata_type_detail_api_view_no_permission(self): + self._create_test_metadata_type() - def test_metadata_type_detail_view_with_access(self): - self._create_metadata_type() + response = self._request_metadata_type_detail_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_metadata_type_detail_api_view_with_access(self): + self._create_test_metadata_type() self.grant_access( - permission=permission_metadata_type_view, obj=self.metadata_type + permission=permission_metadata_type_view, obj=self.test_metadata_type ) - response = self._request_metadata_type_detail_view() + + response = self._request_metadata_type_detail_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( - response.data['label'], TEST_METADATA_TYPE_LABEL + response.data['label'], self.test_metadata_type.label ) - def _request_metadata_type_edit_view_via_patch(self): + def _request_metadata_type_edit_patch_api_view(self): return self.patch( - viewname='rest_api:metadatatype-detail', - kwargs={'metadata_type_pk': self.metadata_type.pk}, data={ - 'label': TEST_METADATA_TYPE_LABEL_2, - 'name': TEST_METADATA_TYPE_NAME_2 + viewname='rest_api:metadata_type-detail', + kwargs={'metadata_type_id': self.test_metadata_type.pk}, data={ + 'label': TEST_METADATA_TYPE_LABEL_EDITED, + 'name': TEST_METADATA_TYPE_NAME_EDITED } ) - def test_metadata_type_patch_view_no_access(self): - self._create_metadata_type() - response = self._request_metadata_type_edit_view_via_patch() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_metadata_type_patch_api_view_no_permission(self): + self._create_test_metadata_type() + test_metadata_type_label = self.test_metadata_type.label + test_metadata_type_name = self.test_metadata_type.name - self.metadata_type.refresh_from_db() - self.assertEqual(self.metadata_type.label, TEST_METADATA_TYPE_LABEL) - self.assertEqual(self.metadata_type.name, TEST_METADATA_TYPE_NAME) + response = self._request_metadata_type_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_metadata_type_patch_view_with_access(self): - self._create_metadata_type() + self.test_metadata_type.refresh_from_db() + self.assertEqual(self.test_metadata_type.label, test_metadata_type_label) + self.assertEqual(self.test_metadata_type.name, test_metadata_type_name) + + def test_metadata_type_patch_api_view_with_access(self): + self._create_test_metadata_type() + test_metadata_type_label = self.test_metadata_type.label + test_metadata_type_name = self.test_metadata_type.name self.grant_access( - permission=permission_metadata_type_edit, obj=self.metadata_type + permission=permission_metadata_type_edit, obj=self.test_metadata_type ) - response = self._request_metadata_type_edit_view_via_patch() + + response = self._request_metadata_type_edit_patch_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.metadata_type.refresh_from_db() - self.assertEqual(self.metadata_type.label, TEST_METADATA_TYPE_LABEL_2) - self.assertEqual(self.metadata_type.name, TEST_METADATA_TYPE_NAME_2) + self.test_metadata_type.refresh_from_db() + self.assertNotEqual(self.test_metadata_type.label, test_metadata_type_label) + self.assertNotEqual(self.test_metadata_type.name, test_metadata_type_name) - def _request_metadata_type_edit_view_via_put(self): + def _request_metadata_type_edit_put_api_view(self): return self.put( - viewname='rest_api:metadatatype-detail', - kwargs={'metadata_type_pk': self.metadata_type.pk}, data={ - 'label': TEST_METADATA_TYPE_LABEL_2, - 'name': TEST_METADATA_TYPE_NAME_2 + viewname='rest_api:metadata_type-detail', + kwargs={'metadata_type_id': self.test_metadata_type.pk}, data={ + 'label': TEST_METADATA_TYPE_LABEL_EDITED, + 'name': TEST_METADATA_TYPE_NAME_EDITED } ) - def test_metadata_type_put_view_no_access(self): - self._create_metadata_type() - response = self._request_metadata_type_edit_view_via_put() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_metadata_type_edit_put_api_view_no_permission(self): + self._create_test_metadata_type() + test_metadata_type_label = self.test_metadata_type.label + test_metadata_type_name = self.test_metadata_type.name - self.metadata_type.refresh_from_db() - self.assertEqual(self.metadata_type.label, TEST_METADATA_TYPE_LABEL) - self.assertEqual(self.metadata_type.name, TEST_METADATA_TYPE_NAME) + response = self._request_metadata_type_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_metadata_type_put_view_with_access(self): - self._create_metadata_type() + self.test_metadata_type.refresh_from_db() + self.assertEqual(self.test_metadata_type.label, test_metadata_type_label) + self.assertEqual(self.test_metadata_type.name, test_metadata_type_name) + + def test_metadata_type_edit_put_api_view_with_access(self): + self._create_test_metadata_type() + test_metadata_type_label = self.test_metadata_type.label + test_metadata_type_name = self.test_metadata_type.name self.grant_access( - permission=permission_metadata_type_edit, obj=self.metadata_type + permission=permission_metadata_type_edit, obj=self.test_metadata_type ) - response = self._request_metadata_type_edit_view_via_put() + response = self._request_metadata_type_edit_put_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.metadata_type.refresh_from_db() - self.assertEqual(self.metadata_type.label, TEST_METADATA_TYPE_LABEL_2) - self.assertEqual(self.metadata_type.name, TEST_METADATA_TYPE_NAME_2) + self.test_metadata_type.refresh_from_db() + self.assertNotEqual(self.test_metadata_type.label, test_metadata_type_label) + self.assertNotEqual(self.test_metadata_type.name, test_metadata_type_name) - def _request_metadata_type_list_view(self): - return self.get(viewname='rest_api:metadatatype-list') + def _request_metadata_type_list_api_view(self): + return self.get(viewname='rest_api:metadata_type-list') - def test_metadata_type_list_view_no_access(self): - self._create_metadata_type() - response = self._request_metadata_type_list_view() + def test_metadata_type_list_api_view_no_permission(self): + self._create_test_metadata_type() + response = self._request_metadata_type_list_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 0) - def test_metadata_type_list_view_with_access(self): - self._create_metadata_type() + def test_metadata_type_list_api_view_with_access(self): + self._create_test_metadata_type() self.grant_access( - permission=permission_metadata_type_view, obj=self.metadata_type + permission=permission_metadata_type_view, obj=self.test_metadata_type ) - response = self._request_metadata_type_list_view() + + response = self._request_metadata_type_list_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( response.data['results'][0]['label'], TEST_METADATA_TYPE_LABEL ) -class DocumentTypeMetadataTypeAPITestCase(BaseAPITestCase): +class MetadataTypeDocumentTypeRelationTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseAPITestCase): + auto_upload_document = False + def setUp(self): - super(DocumentTypeMetadataTypeAPITestCase, self).setUp() - self.login_user() + super(MetadataTypeDocumentTypeRelationTestCase, self).setUp() + self._create_test_metadata_type() - self.document_type = DocumentType.objects.create( - label=TEST_DOCUMENT_TYPE_LABEL + def _create_test_relation(self): + self.test_object = self.test_metadata_type.document_type_relations.create( + document_type=self.test_document_type, required=False ) + self.relation_required = self.test_object.required - self.metadata_type = MetadataType.objects.create( - label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME - ) - - def _create_document_type_metadata_type(self): - self.document_type_metadata_type = self.document_type.metadata.create( - metadata_type=self.metadata_type, required=False - ) - - def _request_document_type_metadata_type_create_view(self): + def _request_metadata_type_document_type_relation_add_api_view(self): return self.post( - viewname='rest_api:documenttypemetadatatype-list', - kwargs={'document_type_pk': self.document_type.pk}, data={ - 'metadata_type_pk': self.metadata_type.pk, 'required': False - } + viewname='rest_api:metadata_type-document_type_relation-add', + kwargs={ + 'metadata_type_id': self.test_metadata_type.pk, + }, + data={'document_type_id_list': self.test_document_type.pk} ) - def test_document_type_metadata_type_create_view_no_access(self): - response = self._request_document_type_metadata_type_create_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(self.document_type.metadata.count(), 0) + def test_metadata_type_document_type_relation_add_api_view_no_permission(self): + response = self._request_metadata_type_document_type_relation_add_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_document_type_metadata_type_create_view_with_access(self): + self.assertFalse( + self.test_metadata_type.document_type_relations.filter( + document_type=self.test_document_type + ).exists() + ) + + def test_metadata_type_document_type_relation_add_api_view_with_metadata_type_access(self): self.grant_access( - permission=permission_document_type_edit, obj=self.document_type - ) - response = self._request_document_type_metadata_type_create_view() - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - document_type_metadata_type = DocumentTypeMetadataType.objects.first() - self.assertEqual(response.data['id'], document_type_metadata_type.pk) - - def test_document_type_metadata_type_create_dupicate_view(self): - self._create_document_type_metadata_type() - self.grant_permission(permission=permission_document_type_edit) - response = self._request_document_type_metadata_type_create_view() - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(list(response.data.keys())[0], 'non_field_errors') - - def _request_document_type_metadata_type_delete_view(self): - return self.delete( - viewname='rest_api:documenttypemetadatatype-detail', - kwargs={ - 'document_type_pk': self.document_type.pk, - 'metadata_type_pk': self.document_type_metadata_type.pk - } + obj=self.test_metadata_type, permission=permission_metadata_type_edit ) - def test_document_type_metadata_type_delete_view_no_access(self): - self._create_document_type_metadata_type() - response = self._request_document_type_metadata_type_delete_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(self.document_type.metadata.count(), 1) - - def test_document_type_metadata_type_delete_view_with_access(self): - self._create_document_type_metadata_type() - self.grant_access(permission=permission_document_type_edit, obj=self.document_type) - response = self._request_document_type_metadata_type_delete_view() - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(self.document_type.metadata.all().count(), 0) - - def _request_document_type_metadata_type_list_view(self): - return self.get( - viewname='rest_api:documenttypemetadatatype-list', - kwargs={'document_type_pk': self.document_type.pk} - ) - - def test_document_type_metadata_type_list_view_no_access(self): - self._create_document_type_metadata_type() - response = self._request_document_type_metadata_type_list_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_document_type_metadata_type_list_view_with_access(self): - self._create_document_type_metadata_type() - self.grant_access(permission=permission_document_type_view, obj=self.document_type) - response = self._request_document_type_metadata_type_list_view() + response = self._request_metadata_type_document_type_relation_add_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.data['results'][0]['id'], - self.document_type_metadata_type.pk + self.assertFalse( + self.test_metadata_type.document_type_relations.filter( + document_type=self.test_document_type + ).exists() ) - def _request_document_type_metadata_type_edit_view_via_patch(self): + def test_metadata_type_document_type_relation_add_api_view_with_document_type_access(self): + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_edit + ) + + response = self._request_metadata_type_document_type_relation_add_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertFalse( + self.test_metadata_type.document_type_relations.filter( + document_type=self.test_document_type + ).exists() + ) + + def test_metadata_type_document_type_relation_add_api_view_with_full_access(self): + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_edit + ) + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_edit + ) + + response = self._request_metadata_type_document_type_relation_add_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertTrue( + self.test_metadata_type.document_type_relations.filter( + document_type=self.test_document_type + ).exists() + ) + + def _request_document_type_metadata_type_relation_edit_patch_api_view(self): return self.patch( - viewname='rest_api:documenttypemetadatatype-detail', + viewname='rest_api:metadata_type-document_type_relation-detail', kwargs={ - 'document_type_pk': self.document_type.pk, - 'metadata_type_pk': self.document_type_metadata_type.pk + 'metadata_type_id': self.test_metadata_type.pk, + 'metadata_type_document_type_relation_id': self.test_object.pk, }, data={ 'required': True } ) - def test_document_type_metadata_type_patch_view_no_access(self): - self._create_document_type_metadata_type() - response = self._request_document_type_metadata_type_edit_view_via_patch() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - document_type_metadata_type = DocumentTypeMetadataType.objects.first() - self.assertFalse(document_type_metadata_type.required, True) + def test_document_type_metadata_type_relation_edit_patch_api_view_no_permission(self): + self._create_test_relation() - def test_document_type_metadata_type_patch_view_with_access(self): - self._create_document_type_metadata_type() - self.grant_access(permission=permission_document_type_edit, obj=self.document_type) - response = self._request_document_type_metadata_type_edit_view_via_patch() + response = self._request_document_type_metadata_type_relation_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_object.refresh_from_db() + self.assertEqual(self.test_object.required, self.relation_required) + + def test_document_type_metadata_type_relation_edit_patch_api_view_with_metadata_type_permission(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_edit + ) + + response = self._request_document_type_metadata_type_relation_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_object.refresh_from_db() + self.assertEqual(self.test_object.required, self.relation_required) + + def test_document_type_metadata_type_relation_edit_patch_api_view_with_document_type_permission(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_edit + ) + + response = self._request_document_type_metadata_type_relation_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_object.refresh_from_db() + self.assertEqual(self.test_object.required, self.relation_required) + + def test_document_type_metadata_type_relation_edit_patch_api_view_with_full_permission(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_edit + ) + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_edit + ) + + response = self._request_document_type_metadata_type_relation_edit_patch_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - document_type_metadata_type = DocumentTypeMetadataType.objects.first() - self.assertEqual(document_type_metadata_type.required, True) - def _request_document_type_metadata_type_edit_view_via_put(self): + self.test_object.refresh_from_db() + self.assertNotEqual(self.test_object.required, self.relation_required) + + def _request_document_type_metadata_type_relation_edit_put_api_view(self): return self.put( - viewname='rest_api:documenttypemetadatatype-detail', + viewname='rest_api:metadata_type-document_type_relation-detail', kwargs={ - 'document_type_pk': self.document_type.pk, - 'metadata_type_pk': self.document_type_metadata_type.pk + 'metadata_type_id': self.test_metadata_type.pk, + 'metadata_type_document_type_relation_id': self.test_object.pk, }, data={ 'required': True } ) - def test_document_type_metadata_type_put_view_no_access(self): - self._create_document_type_metadata_type() - response = self._request_document_type_metadata_type_edit_view_via_put() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - document_type_metadata_type = DocumentTypeMetadataType.objects.first() - self.assertFalse(document_type_metadata_type.required, True) + def test_document_type_metadata_type_relation_edit_put_api_view_no_permission(self): + self._create_test_relation() - def test_document_type_metadata_type_put_view_with_access(self): - self._create_document_type_metadata_type() - self.grant_access(permission=permission_document_type_edit, obj=self.document_type) - response = self._request_document_type_metadata_type_edit_view_via_put() + response = self._request_document_type_metadata_type_relation_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_object.refresh_from_db() + self.assertEqual(self.test_object.required, self.relation_required) + + def test_document_type_metadata_type_relation_edit_put_api_view_with_metadata_type_permission(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_edit + ) + + response = self._request_document_type_metadata_type_relation_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_object.refresh_from_db() + self.assertEqual(self.test_object.required, self.relation_required) + + def test_document_type_metadata_type_relation_edit_put_api_view_with_document_type_permission(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_edit + ) + + response = self._request_document_type_metadata_type_relation_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_object.refresh_from_db() + self.assertEqual(self.test_object.required, self.relation_required) + + def test_document_type_metadata_type_relation_edit_put_api_view_with_full_permission(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_edit + ) + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_edit + ) + + response = self._request_document_type_metadata_type_relation_edit_put_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - document_type_metadata_type = DocumentTypeMetadataType.objects.first() - self.assertEqual(document_type_metadata_type.required, True) + + self.test_object.refresh_from_db() + self.assertNotEqual(self.test_object.required, self.relation_required) + + def _metadata_type_document_type_relation_list_api_view(self): + return self.get( + viewname='rest_api:metadata_type-document_type_relation-list', + kwargs={'metadata_type_id': self.test_metadata_type.pk} + ) + + def test_metadata_type_document_type_relation_list_api_view_with_no_permission(self): + self._create_test_relation() + + response = self._metadata_type_document_type_relation_list_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertTrue('results' not in response.json()) + + def test_metadata_type_document_type_relation_list_api_view_with_metadata_type_access(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_view + ) + + response = self._metadata_type_document_type_relation_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json()['count'], 0) + + def test_metadata_type_document_type_relation_list_api_view_with_document_type_access(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_view + ) + + response = self._metadata_type_document_type_relation_list_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertTrue('results' not in response.json()) + + def test_metadata_type_document_type_relation_list_api_view_with_full_access(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_view + ) + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_view + ) + + response = self._metadata_type_document_type_relation_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json()['results'][0]['id'], self.test_object.pk) + + def _request_metadata_type_document_type_relation_remove_api_view(self): + return self.post( + viewname='rest_api:metadata_type-document_type_relation-remove', + kwargs={ + 'metadata_type_id': self.test_metadata_type.pk, + }, + data={'document_type_id_list': self.test_document_type.pk} + ) + + def test_metadata_type_document_type_relation_remove_api_view_no_permission(self): + self._create_test_relation() + + response = self._request_metadata_type_document_type_relation_remove_api_view() + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertTrue( + self.test_metadata_type.document_type_relations.filter( + document_type=self.test_document_type + ).exists() + ) + + def test_metadata_type_document_type_relation_remove_api_view_with_metadata_type_access(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_edit + ) + + response = self._request_metadata_type_document_type_relation_remove_api_view() + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertTrue( + self.test_metadata_type.document_type_relations.filter( + document_type=self.test_document_type + ).exists() + ) + + def test_metadata_type_document_type_relation_remove_api_view_with_document_type_access(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_edit + ) + + response = self._request_metadata_type_document_type_relation_remove_api_view() + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertTrue( + self.test_metadata_type.document_type_relations.filter( + document_type=self.test_document_type + ).exists() + ) + + def test_metadata_type_document_type_relation_remove_api_view_with_full_access(self): + self._create_test_relation() + + self.grant_access( + obj=self.test_document_type, permission=permission_document_type_edit + ) + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_type_edit + ) + + response = self._request_metadata_type_document_type_relation_remove_api_view() + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse( + self.test_metadata_type.document_type_relations.filter( + document_type=self.test_document_type + ).exists() + ) -class DocumentMetadataAPITestCase(BaseAPITestCase): +class DocumentMetadataAPITestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseAPITestCase): def setUp(self): super(DocumentMetadataAPITestCase, self).setUp() - self.login_user() + self._create_test_metadata_type() + self._create_test_relation() - self.document_type = DocumentType.objects.create( - label=TEST_DOCUMENT_TYPE_LABEL - ) - - self.metadata_type = MetadataType.objects.create( - label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME - ) - - self.document_type.metadata.create( - metadata_type=self.metadata_type, required=False - ) - - with open(TEST_SMALL_DOCUMENT_PATH, mode='rb') as file_object: - self.document = self.document_type.new_document( - file_object=file_object, - ) - - def tearDown(self): - self.document_type.delete() - super(DocumentMetadataAPITestCase, self).tearDown() - - def _create_document_metadata(self): - self.document_metadata = self.document.metadata.create( - metadata_type=self.metadata_type, value=TEST_METADATA_VALUE + def _create_test_relation(self): + self.test_object = self.test_metadata_type.document_type_relations.create( + document_type=self.test_document_type, required=False ) + self.relation_required = self.test_object.required def _request_document_metadata_create_view(self): return self.post( - viewname='rest_api:documentmetadata-list', - kwargs={'document_pk': self.document.pk}, data={ - 'metadata_type_pk': self.metadata_type.pk, + viewname='rest_api:document-metadata-list', + kwargs={'document_id': self.test_document.pk}, data={ + 'metadata_type': self.test_metadata_type.pk, 'value': TEST_METADATA_VALUE } ) - def test_document_metadata_create_view_no_access(self): + def test_document_metadata_create_api_view_no_permission(self): response = self._request_document_metadata_create_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(self.document.metadata.count(), 0) - def test_document_metadata_create_view_with_access(self): - self.grant_access(permission=permission_metadata_add, obj=self.document) + def test_document_metadata_create_api_view_with_document_access(self): + self.grant_access( + obj=self.test_document, permission=permission_metadata_add + ) + response = self._request_document_metadata_create_view() + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertEqual(self.document.metadata.count(), 0) + + def test_document_metadata_create_api_view_with_metadata_type_access(self): + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_add + ) + + response = self._request_document_metadata_create_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(self.document.metadata.count(), 0) + + def test_document_metadata_create_api_view_with_full_access(self): + self.grant_access( + obj=self.test_document, permission=permission_metadata_add + ) + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_add + ) + response = self._request_document_metadata_create_view() self.assertEqual(response.status_code, status.HTTP_201_CREATED) + document_metadata = self.document.metadata.first() self.assertEqual(response.data['id'], document_metadata.pk) - self.assertEqual(document_metadata.metadata_type, self.metadata_type) + self.assertEqual(document_metadata.metadata_type, self.test_metadata_type) self.assertEqual(document_metadata.value, TEST_METADATA_VALUE) - def test_document_metadata_create_duplicate_view(self): - self._create_document_metadata() - self.grant_permission(permission=permission_metadata_add) + def _create_test_document_metadata(self): + self.test_document_metadata = self.test_document.metadata.create( + metadata_type=self.test_metadata_type, value=TEST_METADATA_VALUE + ) + + def test_document_metadata_create_duplicate_api_view_with_full_access(self): + self.grant_access( + obj=self.test_document, permission=permission_metadata_add + ) + self.grant_access( + obj=self.test_metadata_type, permission=permission_metadata_add + ) + self._create_test_document_metadata() + document_metadata_count = self.test_document.metadata.count() + response = self._request_document_metadata_create_view() self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(list(response.data.keys())[0], 'non_field_errors') + self.assertEqual(response.json().keys()[0], 'non_field_errors') - def test_document_metadata_create_invalid_lookup_value_view(self): - self.metadata_type.lookup = 'invalid,lookup,values,on,purpose' - self.metadata_type.save() + self.assertEqual( + document_metadata_count, self.test_document.metadata.count() + ) + + def test_document_metadata_create_invalid_lookup_value_api_view_with_full_access(self): + self.test_metadata_type.lookup = TEST_METADATA_TYPE_INVALID_LOOKUP + self.test_metadata_type.save() self.grant_permission(permission=permission_metadata_add) + document_metadata_count = self.test_document.metadata.count() + response = self._request_document_metadata_create_view() self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(list(response.data.keys())[0], 'non_field_errors') + self.assertEqual(response.json().keys()[0], 'non_field_errors') - def _request_document_metadata_delete_view(self): + self.assertEqual( + document_metadata_count, self.test_document.metadata.count() + ) + + def _request_document_metadata_delete_api_view(self): return self.delete( - viewname='rest_api:documentmetadata-detail', + viewname='rest_api:document-metadata-detail', kwargs={ - 'document_pk': self.document.pk, - 'metadata_pk': self.document_metadata.pk + 'document_id': self.test_document.pk, + 'document_metadata_id': self.test_document_metadata.pk } ) - def test_document_metadata_delete_view_no_access(self): - self._create_document_metadata() - response = self._request_document_metadata_delete_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(self.document.metadata.all().count(), 1) + def test_document_metadata_delete_api_view_no_permission(self): + self._create_test_document_metadata() - def test_document_metadata_delete_view_with_access(self): - self._create_document_metadata() - self.grant_access( - permission=permission_metadata_remove, obj=self.document + response = self._request_document_metadata_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertTrue( + self.test_document_metadata in self.document.metadata.all() ) - response = self._request_document_metadata_delete_view() + + def test_document_metadata_delete_api_view_with_document_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_remove, obj=self.test_document + ) + + response = self._request_document_metadata_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertTrue( + self.test_document_metadata in self.document.metadata.all() + ) + + def test_document_metadata_delete_api_view_with_metadata_type_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_remove, obj=self.test_metadata_type + ) + + response = self._request_document_metadata_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertTrue( + self.test_document_metadata in self.document.metadata.all() + ) + + def test_document_metadata_delete_api_view_with_full_access(self): + self.expected_content_type = None + + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_remove, obj=self.test_document + ) + self.grant_access( + permission=permission_metadata_remove, obj=self.test_metadata_type + ) + + response = self._request_document_metadata_delete_api_view() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(self.document.metadata.all().count(), 0) - def _request_document_metadata_list_view(self): - return self.get( - viewname='rest_api:documentmetadata-list', - kwargs={'document_pk': self.document.pk} + self.assertTrue( + self.test_document_metadata not in self.document.metadata.all() ) - def test_document_metadata_list_view_no_access(self): - self._create_document_metadata() - response = self._request_document_metadata_list_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_document_metadata_list_view_with_access(self): - self._create_document_metadata() - self.grant_access( - permission=permission_metadata_view, obj=self.document - ) - response = self._request_document_metadata_list_view() - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.data['results'][0]['document']['id'], self.document.pk - ) - self.assertEqual( - response.data['results'][0]['metadata_type']['id'], - self.metadata_type.pk - ) - self.assertEqual( - response.data['results'][0]['value'], TEST_METADATA_VALUE - ) - self.assertEqual( - response.data['results'][0]['id'], self.document_metadata.pk - ) - - def _request_document_metadata_edit_view_via_patch(self): + def _request_document_metadata_edit_patch_api_view(self): return self.patch( - viewname='rest_api:documentmetadata-detail', + viewname='rest_api:document-metadata-detail', kwargs={ - 'document_pk': self.document.pk, - 'metadata_pk': self.document_metadata.pk + 'document_id': self.test_document.pk, + 'document_metadata_id': self.test_document_metadata.pk }, data={ 'value': TEST_METADATA_VALUE_EDITED } ) - def test_document_metadata_patch_view_no_access(self): - self._create_document_metadata() - response = self._request_document_metadata_edit_view_via_patch() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.document_metadata.refresh_from_db() - self.assertEqual(self.document_metadata.value, TEST_METADATA_VALUE) + def test_document_metadata_edit_patch_api_view_no_permission(self): + self._create_test_document_metadata() + test_document_metadata = copy.copy(self.test_document_metadata) - def test_document_metadata_patch_view_with_access(self): - self._create_document_metadata() - self.grant_access( - permission=permission_metadata_edit, obj=self.document + response = self._request_document_metadata_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_document_metadata.refresh_from_db() + self.assertTrue( + test_document_metadata.value == self.test_document_metadata.value ) - response = self._request_document_metadata_edit_view_via_patch() + + def test_document_metadata_edit_patch_api_view_with_document_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_edit, obj=self.test_document + ) + test_document_metadata = copy.copy(self.test_document_metadata) + + response = self._request_document_metadata_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_document_metadata.refresh_from_db() + self.assertTrue( + test_document_metadata.value == self.test_document_metadata.value + ) + + def test_document_metadata_edit_patch_api_view_with_metadata_type_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_edit, obj=self.test_metadata_type + ) + test_document_metadata = copy.copy(self.test_document_metadata) + + response = self._request_document_metadata_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_document_metadata.refresh_from_db() + self.assertTrue( + test_document_metadata.value == self.test_document_metadata.value + ) + + def test_document_metadata_edit_patch_api_view_with_full_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_edit, obj=self.test_document + ) + self.grant_access( + permission=permission_metadata_edit, obj=self.test_metadata_type + ) + test_document_metadata = copy.copy(self.test_document_metadata) + + response = self._request_document_metadata_edit_patch_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.document_metadata.refresh_from_db() self.assertEqual( response.data['value'], TEST_METADATA_VALUE_EDITED ) - self.assertEqual( - self.document_metadata.value, TEST_METADATA_VALUE_EDITED + + self.test_document_metadata.refresh_from_db() + self.assertFalse( + test_document_metadata.value == self.test_document_metadata.value ) - def _request_document_metadata_edit_view_via_put(self): + def _request_document_metadata_edit_put_api_view(self): return self.put( - viewname='rest_api:documentmetadata-detail', + viewname='rest_api:document-metadata-detail', kwargs={ - 'document_pk': self.document.pk, - 'metadata_pk': self.document_metadata.pk + 'document_id': self.test_document.pk, + 'document_metadata_id': self.test_document_metadata.pk }, data={ 'value': TEST_METADATA_VALUE_EDITED } ) - def test_document_metadata_put_view_no_access(self): - self._create_document_metadata() - response = self._request_document_metadata_edit_view_via_put() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.document_metadata.refresh_from_db() - self.assertEqual(self.document_metadata.value, TEST_METADATA_VALUE) + def test_document_metadata_edit_put_api_view_no_permission(self): + self._create_test_document_metadata() + test_document_metadata = copy.copy(self.test_document_metadata) - def test_document_metadata_put_view_with_access(self): - self._create_document_metadata() - self.grant_access( - permission=permission_metadata_edit, obj=self.document + response = self._request_document_metadata_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_document_metadata.refresh_from_db() + self.assertTrue( + test_document_metadata.value == self.test_document_metadata.value ) - response = self._request_document_metadata_edit_view_via_put() + + def test_document_metadata_edit_put_api_view_with_document_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_edit, obj=self.test_document + ) + test_document_metadata = copy.copy(self.test_document_metadata) + + response = self._request_document_metadata_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_document_metadata.refresh_from_db() + self.assertTrue( + test_document_metadata.value == self.test_document_metadata.value + ) + + def test_document_metadata_edit_put_api_view_with_metadata_type_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_edit, obj=self.test_metadata_type + ) + test_document_metadata = copy.copy(self.test_document_metadata) + + response = self._request_document_metadata_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_document_metadata.refresh_from_db() + self.assertTrue( + test_document_metadata.value == self.test_document_metadata.value + ) + + def test_document_metadata_edit_put_api_view_with_full_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_edit, obj=self.test_document + ) + self.grant_access( + permission=permission_metadata_edit, obj=self.test_metadata_type + ) + test_document_metadata = copy.copy(self.test_document_metadata) + + response = self._request_document_metadata_edit_put_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.document_metadata.refresh_from_db() self.assertEqual( response.data['value'], TEST_METADATA_VALUE_EDITED ) - self.assertEqual( - self.document_metadata.value, TEST_METADATA_VALUE_EDITED + + self.test_document_metadata.refresh_from_db() + self.assertFalse( + test_document_metadata.value == self.test_document_metadata.value + ) + + def _request_document_metadata_list_api_view(self): + return self.get( + viewname='rest_api:document-metadata-list', + kwargs={'document_id': self.test_document.pk} + ) + + def test_document_metadata_list_api_view_no_permission(self): + self._create_test_document_metadata() + + response = self._request_document_metadata_list_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_document_metadata_list_api_view_document_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_view, obj=self.test_document + ) + + response = self._request_document_metadata_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json()['count'], 0) + + def test_document_metadata_list_api_view_metadata_type_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_view, obj=self.test_metadata_type + ) + + response = self._request_document_metadata_list_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_document_metadata_list_api_view_with_full_access(self): + self._create_test_document_metadata() + self.grant_access( + permission=permission_metadata_view, obj=self.test_document + ) + self.grant_access( + permission=permission_metadata_view, obj=self.test_metadata_type + ) + + response = self._request_document_metadata_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.json()['results'][0]['document']['id'], self.test_document.pk + ) + self.assertEqual( + response.json()['results'][0]['metadata_type']['id'], + self.test_metadata_type.pk + ) + self.assertEqual( + response.json()['results'][0]['value'], self.test_document_metadata.value + ) + self.assertEqual( + response.json()['results'][0]['id'], self.test_document_metadata.pk ) diff --git a/mayan/apps/metadata/tests/test_models.py b/mayan/apps/metadata/tests/test_models.py index c2a30f4818..893b8bc6bd 100644 --- a/mayan/apps/metadata/tests/test_models.py +++ b/mayan/apps/metadata/tests/test_models.py @@ -23,7 +23,9 @@ from .mixins import MetadataTypeTestMixin class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase): def setUp(self): 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): document_metadata = DocumentMetadata( @@ -146,13 +148,13 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase): ) def test_required_metadata(self): - self.document_type.metadata.all().delete() + self.document_type.metadata_type_relations.all().delete() self.assertFalse( 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 ) @@ -160,9 +162,9 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase): 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 ) @@ -198,7 +200,7 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase): 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 ) @@ -226,7 +228,9 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase): 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) @@ -276,7 +280,7 @@ class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase): 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 ) diff --git a/mayan/apps/metadata/tests/test_views.py b/mayan/apps/metadata/tests/test_views.py index a4a457ced4..f91c686d20 100644 --- a/mayan/apps/metadata/tests/test_views.py +++ b/mayan/apps/metadata/tests/test_views.py @@ -34,7 +34,9 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa def setUp(self): super(DocumentMetadataViewTestCase, self).setUp() 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): return self.get( @@ -76,7 +78,9 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa ) 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.grant_access( obj=self.test_document, permission=permission_metadata_add @@ -94,7 +98,9 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa ) 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.grant_access( obj=self.test_document, permission=permission_metadata_add @@ -112,7 +118,9 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa ) 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.id_list.append(self.test_document.pk) self.grant_access( @@ -159,7 +167,7 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa 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 ) @@ -400,7 +408,7 @@ class DocumentMetadataViewTestCase(MetadataTestsMixin, GenericDocumentViewTestCa 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 ) @@ -540,7 +548,7 @@ class MetadataTypeViewViewTestCase(DocumentTestMixin, MetadataTestsMixin, Generi 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): self._create_metadata_type() @@ -556,7 +564,7 @@ class MetadataTypeViewViewTestCase(DocumentTestMixin, MetadataTestsMixin, Generi 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): self._create_metadata_type() @@ -573,7 +581,7 @@ class MetadataTypeViewViewTestCase(DocumentTestMixin, MetadataTestsMixin, Generi 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): self._create_metadata_type() @@ -594,8 +602,9 @@ class MetadataTypeViewViewTestCase(DocumentTestMixin, MetadataTestsMixin, Generi self.document_type.refresh_from_db() self.assertQuerysetEqual( - qs=self.document_type.metadata.values('metadata_type', 'required'), - values=[ + qs=self.document_type.metadata_type_relations.values( + 'metadata_type', 'required' + ), values=[ { 'metadata_type': self.metadata_type.pk, 'required': True, diff --git a/mayan/apps/metadata/tests/test_wizard_steps.py b/mayan/apps/metadata/tests/test_wizard_steps.py index b513ba4922..66ab9ffd1f 100644 --- a/mayan/apps/metadata/tests/test_wizard_steps.py +++ b/mayan/apps/metadata/tests/test_wizard_steps.py @@ -31,7 +31,7 @@ class DocumentUploadMetadataTestCase(MetadataTypeTestMixin, GenericDocumentViewT self.document.delete() - self.document_type.metadata.create( + self.document_type.metadata_type_relations.create( metadata_type=self.metadata_type, required=True ) diff --git a/mayan/apps/metadata/urls.py b/mayan/apps/metadata/urls.py index d070ed5576..2282ebc3c5 100644 --- a/mayan/apps/metadata/urls.py +++ b/mayan/apps/metadata/urls.py @@ -3,16 +3,14 @@ from __future__ import unicode_literals from django.conf.urls import url from .api_views import ( - APIDocumentMetadataListView, APIDocumentMetadataView, - APIDocumentTypeMetadataTypeListView, APIDocumentTypeMetadataTypeView, - APIMetadataTypeListView, APIMetadataTypeView + DocumentMetadataAPIViewSet, MetadataTypeAPIViewSet, + MetadataTypeDocumentTypeRelationAPIViewSet ) from .views import ( DocumentMetadataAddView, DocumentMetadataEditView, DocumentMetadataListView, DocumentMetadataRemoveView, - MetadataTypeCreateView, MetadataTypeDeleteView, MetadataTypeEditView, - MetadataTypeListView, SetupDocumentTypeMetadataTypes, - SetupMetadataTypesDocumentTypes + DocumentTypeMetadataTypes, MetadataTypeCreateView, MetadataTypeDeleteView, + MetadataTypesDocumentTypes, MetadataTypeEditView, MetadataTypeListView ) urlpatterns = [ @@ -35,12 +33,12 @@ urlpatterns = [ url( regex=r'^metadata_types/(?P\d+)/document_types/$', name='metadata_type_document_types', - view=SetupMetadataTypesDocumentTypes.as_view() + view=MetadataTypesDocumentTypes.as_view() ), url( regex=r'^document_types/(?P\d+)/metadata_types/$', name='document_type_metadata_types', - view=SetupDocumentTypeMetadataTypes.as_view() + view=DocumentTypeMetadataTypes.as_view() ), url( regex=r'^documents/(?P\d+)/edit/$', @@ -77,34 +75,18 @@ urlpatterns = [ ) ] -api_urls = [ - url( - regex=r'^metadata_types/$', name='metadatatype-list', - view=APIMetadataTypeListView.as_view() - ), - url( - regex=r'^metadata_types/(?P\d+)/$', - name='metadatatype-detail', - view=APIMetadataTypeView.as_view() - ), - url( - regex=r'^document_types/(?P\d+)/metadata_types/$', - name='documenttypemetadatatype-list', - view=APIDocumentTypeMetadataTypeListView.as_view() - ), - url( - regex=r'^document_types/(?P\d+)/metadata_types/(?P\d+)/$', - name='documenttypemetadatatype-detail', - view=APIDocumentTypeMetadataTypeView.as_view() - ), - url( - regex=r'^documents/(?P\d+)/metadata/$', - name='documentmetadata-list', - view=APIDocumentMetadataListView.as_view() - ), - url( - regex=r'^documents/(?P\d+)/metadata/(?P\d+)/$', - name='documentmetadata-detail', - view=APIDocumentMetadataView.as_view() - ) -] +api_router_entries = ( + { + 'prefix': r'metadata_types', 'viewset': MetadataTypeAPIViewSet, + 'basename': 'metadata_type' + }, + { + 'prefix': r'metadata_types/(?P[^/.]+)/document_type_relations', + 'viewset': MetadataTypeDocumentTypeRelationAPIViewSet, + 'basename': 'metadata_type-document_type_relation' + }, + { + 'prefix': r'documents/(?P[^/.]+)/metadata', + 'viewset': DocumentMetadataAPIViewSet, 'basename': 'document-metadata' + } +) diff --git a/mayan/apps/metadata/views.py b/mayan/apps/metadata/views.py index 7ac30a5fc6..22e0eeebf2 100644 --- a/mayan/apps/metadata/views.py +++ b/mayan/apps/metadata/views.py @@ -564,7 +564,7 @@ class MetadataTypeListView(SingleObjectListView): return MetadataType.objects.all() -class SetupDocumentTypeMetadataTypes(ExternalObjectMixin, FormView): +class DocumentTypeMetadataTypes(ExternalObjectMixin, FormView): external_object_class = DocumentType external_object_permission = permission_metadata_type_edit external_object_pk_url_kwarg = 'document_type_id' @@ -590,7 +590,7 @@ class SetupDocumentTypeMetadataTypes(ExternalObjectMixin, FormView): ) return super( - SetupDocumentTypeMetadataTypes, self + DocumentTypeMetadataTypes, self ).form_valid(form=form) def get_extra_context(self): @@ -640,7 +640,7 @@ class SetupDocumentTypeMetadataTypes(ExternalObjectMixin, FormView): return reverse(viewname='documents:document_type_list') -class SetupMetadataTypesDocumentTypes(SetupDocumentTypeMetadataTypes): +class MetadataTypesDocumentTypes(DocumentTypeMetadataTypes): external_object_class = MetadataType external_object_permission = permission_metadata_type_edit external_object_pk_url_kwarg = 'metadata_type_id' @@ -651,10 +651,10 @@ class SetupMetadataTypesDocumentTypes(SetupDocumentTypeMetadataTypes): def get_extra_context(self): return { 'form_display_mode_table': True, - 'object': self.get_object(), + 'object': self.external_object, 'title': _( 'Document types for metadata type: %s' - ) % self.get_object() + ) % self.external_object, } def get_initial(self): diff --git a/mayan/apps/rest_api/mixins.py b/mayan/apps/rest_api/mixins.py index 597246351a..d7f1d95cdb 100644 --- a/mayan/apps/rest_api/mixins.py +++ b/mayan/apps/rest_api/mixins.py @@ -3,9 +3,23 @@ from __future__ import absolute_import, unicode_literals from django.core.exceptions import ImproperlyConfigured from rest_framework.exceptions import ValidationError +from rest_framework.generics import get_object_or_404 from rest_framework.settings import api_settings 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): @@ -127,6 +141,7 @@ class ExternalObjectSerializerMixin(object): external_object_permission external_object_queryset external_object_pk_field + external_object_pk_type The source queryset can also be provided overriding the .get_external_object_queryset() method. """ @@ -157,7 +172,19 @@ class ExternalObjectSerializerMixin(object): else: pk_field_value = None + pk_type = self.get_external_object_option('pk_type') + 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: return queryset.get(pk=pk_field_value) except Exception as exception: diff --git a/mayan/apps/rest_api/permissions.py b/mayan/apps/rest_api/permissions.py index faaf8ebc94..4ea36a61e7 100644 --- a/mayan/apps/rest_api/permissions.py +++ b/mayan/apps/rest_api/permissions.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals 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 diff --git a/mayan/apps/rest_api/serializers.py b/mayan/apps/rest_api/serializers.py index b7959fe30e..6e5fb7f8d7 100644 --- a/mayan/apps/rest_api/serializers.py +++ b/mayan/apps/rest_api/serializers.py @@ -1,11 +1,6 @@ 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.generics import get_object_or_404 -from rest_framework.reverse import reverse class LazyExtraFieldsSerializerMixin(object): diff --git a/mayan/apps/rest_api/viewsets.py b/mayan/apps/rest_api/viewsets.py index a442e914af..e31732dce4 100644 --- a/mayan/apps/rest_api/viewsets.py +++ b/mayan/apps/rest_api/viewsets.py @@ -1,7 +1,6 @@ from __future__ import absolute_import, unicode_literals -from rest_framework import viewsets -from rest_framework.settings import api_settings +from rest_framework import mixins, viewsets from .filters import MayanViewSetObjectPermissionsFilter from .mixins import SuccessHeadersMixin @@ -26,3 +25,13 @@ class MayanAPIReadOnlyModelViewSet(SuccessHeadersMixin, viewsets.ReadOnlyModelVi class MayanAPIViewSet(SuccessHeadersMixin, viewsets.GenericViewSet): filter_backends = (MayanViewSetObjectPermissionsFilter,) 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,)