Refactor metadata app API

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

View File

@@ -8,13 +8,14 @@ from mayan.apps.common.mixins import ContentTypeViewMixin, ExternalObjectMixin
from mayan.apps.permissions.serializers import (
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'

View File

@@ -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'

View File

@@ -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

View File

@@ -13,7 +13,7 @@ from mayan.apps.acls.links import link_acl_list
from mayan.apps.acls.permissions import permission_acl_edit, permission_acl_view
from mayan.apps.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
)

View File

@@ -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']
)

View File

@@ -21,7 +21,7 @@ def handler_post_document_type_change(sender, instance, **kwargs):
# First get the existing metadata types not found in the new document
# 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'))

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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'

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,9 @@ from .mixins import MetadataTypeTestMixin
class MetadataTestCase(DocumentTestMixin, MetadataTypeTestMixin, BaseTestCase):
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
)

View File

@@ -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,

View File

@@ -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
)

View File

@@ -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<metadata_type_id>\d+)/document_types/$',
name='metadata_type_document_types',
view=SetupMetadataTypesDocumentTypes.as_view()
view=MetadataTypesDocumentTypes.as_view()
),
url(
regex=r'^document_types/(?P<document_type_id>\d+)/metadata_types/$',
name='document_type_metadata_types',
view=SetupDocumentTypeMetadataTypes.as_view()
view=DocumentTypeMetadataTypes.as_view()
),
url(
regex=r'^documents/(?P<document_id>\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<metadata_type_id>\d+)/$',
name='metadatatype-detail',
view=APIMetadataTypeView.as_view()
),
url(
regex=r'^document_types/(?P<document_type_id>\d+)/metadata_types/$',
name='documenttypemetadatatype-list',
view=APIDocumentTypeMetadataTypeListView.as_view()
),
url(
regex=r'^document_types/(?P<document_type_id>\d+)/metadata_types/(?P<metadata_type_id>\d+)/$',
name='documenttypemetadatatype-detail',
view=APIDocumentTypeMetadataTypeView.as_view()
),
url(
regex=r'^documents/(?P<document_id>\d+)/metadata/$',
name='documentmetadata-list',
view=APIDocumentMetadataListView.as_view()
),
url(
regex=r'^documents/(?P<document_id>\d+)/metadata/(?P<metadata_id>\d+)/$',
name='documentmetadata-detail',
view=APIDocumentMetadataView.as_view()
)
]
api_router_entries = (
{
'prefix': r'metadata_types', 'viewset': MetadataTypeAPIViewSet,
'basename': 'metadata_type'
},
{
'prefix': r'metadata_types/(?P<metadata_type_id>[^/.]+)/document_type_relations',
'viewset': MetadataTypeDocumentTypeRelationAPIViewSet,
'basename': 'metadata_type-document_type_relation'
},
{
'prefix': r'documents/(?P<document_id>[^/.]+)/metadata',
'viewset': DocumentMetadataAPIViewSet, 'basename': 'document-metadata'
}
)

View File

@@ -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):

View File

@@ -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:

View File

@@ -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

View File

@@ -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):

View File

@@ -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,)