Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

nickfrez / api-browser   python

Repository URL to install this package:

/ api_browser / metadata.py

# -*- coding: utf-8 -*-
"""
#############
View Metadata
#############

The metadata API is used to allow customization of how `OPTIONS` requests
are handled. We currently provide a single default implementation that returns
some fairly ad-hoc information about the view.

Future implementations might use JSON schema or other definitions in order
to return this information in a more standardized way.
"""


from __future__ import print_function
from __future__ import unicode_literals

from collections import OrderedDict

from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.utils.encoding import force_text

from rest_framework import exceptions, serializers
from rest_framework.request import clone_request
from rest_framework.utils.field_mapping import ClassLookupDict


default_label_lookup = ClassLookupDict({
        serializers.Field: 'field',
        serializers.BooleanField: 'boolean',
        serializers.NullBooleanField: 'boolean',
        serializers.CharField: 'string',
        serializers.URLField: 'url',
        serializers.EmailField: 'email',
        serializers.RegexField: 'regex',
        serializers.SlugField: 'slug',
        serializers.IntegerField: 'integer',
        serializers.FloatField: 'float',
        serializers.DecimalField: 'decimal',
        serializers.DateField: 'date',
        serializers.DateTimeField: 'datetime',
        serializers.TimeField: 'time',
        serializers.ChoiceField: 'choice',
        serializers.MultipleChoiceField: 'multiple choice',
        serializers.FileField: 'file upload',
        serializers.ImageField: 'image upload',
        serializers.ListField: 'list',
        serializers.DictField: 'nested object',
        serializers.Serializer: 'nested object',
    })


class BaseMetadata(object):
    def determine_metadata(self, request, view):
        """
        Return a dictionary of metadata about the view.
        Used to return responses for OPTIONS requests.
        """
        raise NotImplementedError(".determine_metadata() must be overridden.")


class BasicMetadata(BaseMetadata):
    """Return metadata formatted for display in documentation."""

    label_lookup = default_label_lookup

    def determine_metadata(self, request, view):
        metadata = OrderedDict()
        # metadata['$schema'] = 'https://json-schema.org/draft-04/schema'
        metadata['title'] = self._get_view_name(view)
        metadata['description'] = self._get_view_description(view)
        metadata['parses'] = self._get_parses(view)
        metadata['renders'] = self._get_renders(view)
        actions = self._get_actions(request, view)
        if actions:
            metadata['actions'] = actions
        return metadata

    def _get_view_name(self, view):
        return view.get_view_name()

    def _get_view_description(self, view):
        return view.get_view_description()

    def _get_renders(self, view):
        return [renderer.media_type for renderer in view.renderer_classes]

    def _get_parses(self, view):
        return [parser.media_type for parser in view.parser_classes]

    def _get_actions(self, request, view):
        """Return a JSON Schema for GET, PATCH, PUT, and POST methods."""
        if not hasattr(view, 'get_serializer'):
            return None

        actions = OrderedDict()
        for method in ('GET', 'POST', 'PUT', 'PATCH'):
            if method in view.allowed_methods:
                view.request = clone_request(request, method)
                try:
                    # Test global permissions
                    if hasattr(view, 'check_permissions'):
                        view.check_permissions(view.request)
                    # Test object permissions
                    if method == 'PATCH' and hasattr(view, 'get_object'):
                        view.get_object()
                    if method == 'PUT' and hasattr(view, 'get_object'):
                        view.get_object()
                except (exceptions.APIException, PermissionDenied, Http404):
                    pass
                else:
                    # If user has appropriate permissions for the view, include
                    # appropriate metadata about the fields that should be
                    # supplied.
                    serializer = view.get_serializer()
                    actions[method] = self._get_serializer_fields(serializer)
                finally:
                    view.request = request

        return actions

    def _get_serializer_fields(self, serializer):
        pass
        if hasattr(serializer, 'child'):
            # If this is a `ListSerializer` then we want to examine the
            # underlying child serializer instance instead.
            serializer = serializer.child
        return OrderedDict([
            (field_name, self._get_field_properties(field))
            for field_name, field in serializer.fields.items()
        ])

    def _get_field_properties(self, field):
        """
        Given an instance of a serializer field, return a dictionary
        of metadata about it.
        """
        field_info = OrderedDict()
        field_info['type'] = self.label_lookup[field]
        field_info['required'] = getattr(field, 'required', False)

        attrs = [
            'read_only', 'label', 'help_text',
            'min_length', 'max_length',
            'min_value', 'max_value'
        ]

        for attr in attrs:
            value = getattr(field, attr, None)
            if value is not None and value != '':
                field_info[attr] = force_text(value, strings_only=True)

        if getattr(field, 'child', None):
            field_info['child'] = self.get_field_info(field.child)
        elif getattr(field, 'fields', None):
            field_info['children'] = self.get_serializer_info(field)

        if (not field_info.get('read_only') and
            # Don't list choices for related fields
            # https://github.com/tomchristie/django-rest-framework/issues/3751
            not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
            hasattr(field, 'choices')):
            field_info['choices'] = [
                {
                    'value': choice_value,
                    'display_name': force_text(choice_name, strings_only=True)
                }
                for choice_value, choice_name in field.choices.items()
            ]

        return field_info


class DocumentationMetadata(BasicMetadata):
    """Return metadata formatted for display in documentation."""
    pass


class JSONSchemaMetadata(BasicMetadata):
    """Renders metadata as a JSON Schema document.

    .. seealso:: http://json-schema.org/

    TODO(nick): It would probably be nice to return a JSON Schema for
                resources.  JSON Schema seems to be the predominant standard
                and this will enable clients to be more autonomous.

    """
    pass


# =============================================================================
# Original
# =============================================================================

class SimpleMetadata(BaseMetadata):
    """
    This is the default metadata implementation.
    It returns an ad-hoc set of information about the view.
    There are not any formalized standards for `OPTIONS` responses
    for us to base this on.
    """

    label_lookup = default_label_lookup

    def determine_metadata(self, request, view):
        metadata = OrderedDict()
        metadata['name'] = view.get_view_name()
        metadata['description'] = view.get_view_description()
        metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes]
        metadata['parses'] = [parser.media_type for parser in view.parser_classes]
        if hasattr(view, 'get_serializer'):
            actions = self.determine_actions(request, view)
            if actions:
                metadata['actions'] = actions
        return metadata

    def determine_actions(self, request, view):
        """
        For generic class based views we return information about
        the fields that are accepted for 'PUT' and 'POST' methods.
        """
        actions = {}
        for method in {'GET', 'PUT', 'POST'} & set(view.allowed_methods):
            view.request = clone_request(request, method)
            try:
                # Test global permissions
                if hasattr(view, 'check_permissions'):
                    view.check_permissions(view.request)
                # Test object permissions
                if method == 'PUT' and hasattr(view, 'get_object'):
                    view.get_object()
            except (exceptions.APIException, PermissionDenied, Http404):
                pass
            else:
                # If user has appropriate permissions for the view, include
                # appropriate metadata about the fields that should be supplied.
                serializer = view.get_serializer()
                actions[method] = self.get_serializer_info(serializer)
            finally:
                view.request = request

        return actions

    def get_serializer_info(self, serializer):
        """
        Given an instance of a serializer, return a dictionary of metadata
        about its fields.
        """
        if hasattr(serializer, 'child'):
            # If this is a `ListSerializer` then we want to examine the
            # underlying child serializer instance instead.
            serializer = serializer.child
        return OrderedDict([
            (field_name, self.get_field_info(field))
            for field_name, field in serializer.fields.items()
        ])

    def get_field_info(self, field):
        """
        Given an instance of a serializer field, return a dictionary
        of metadata about it.
        """
        field_info = OrderedDict()
        field_info['type'] = self.label_lookup[field]
        field_info['required'] = getattr(field, 'required', False)

        attrs = [
            'read_only', 'label', 'help_text',
            'min_length', 'max_length',
            'min_value', 'max_value'
        ]

        for attr in attrs:
            value = getattr(field, attr, None)
            if value is not None and value != '':
                field_info[attr] = force_text(value, strings_only=True)

        if getattr(field, 'child', None):
            field_info['child'] = self.get_field_info(field.child)
        elif getattr(field, 'fields', None):
            field_info['children'] = self.get_serializer_info(field)

        if not field_info.get('read_only') and hasattr(field, 'choices'):
            field_info['choices'] = [
                {
                    'value': choice_value,
                    'display_name': force_text(choice_name, strings_only=True)
                }
                for choice_value, choice_name in field.choices.items()
            ]

        return field_info

    # New Stuff
    # =========

    def get_metadata(self, request, view):
        metadata = OrderedDict()
        metadata['name'] = view.get_view_name()
        metadata['description'] = view.get_view_description()
        metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes]
        metadata['parses'] = [parser.media_type for parser in view.parser_classes]
        if hasattr(view, 'get_serializer'):
            actions = self.get_actions(request, view)
            if actions:
                metadata['actions'] = actions
        return metadata

    def get_actions(self, request, view):
        """
        For generic class based views we return information about
        the fields that are accepted for 'PUT' and 'POST' methods.
        """
        actions = {}
        print
        for method in {'PUT', 'POST'} & set(view.allowed_methods):
            view.request = clone_request(request, method)
            try:
                # Test global permissions
                if hasattr(view, 'check_permissions'):
                    view.check_permissions(view.request)
                # Test object permissions
                if method == 'PUT' and hasattr(view, 'get_object'):
                    view.get_object()
            except (exceptions.APIException, PermissionDenied, Http404):
                pass
            else:
                # If user has appropriate permissions for the view, include
                # appropriate metadata about the fields that should be supplied.
                serializer = view.get_serializer()
                actions[method] = self.get_serializer_info2(serializer)
            finally:
                view.request = request

        return actions

    def get_serializer_info2(self, serializer):
        if hasattr(serializer, 'child'):
            # If this is a `ListSerializer` then we want to examine the
            # underlying child serializer instance instead.
            serializer = serializer.child
Loading ...