"""
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 django.core.exceptions import PermissionDenied
from django.http import Http404
from django.utils.encoding import force_str

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


class BaseMetadata:
    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 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 = ClassLookupDict({
        serializers.Field: 'field',
        serializers.BooleanField: 'boolean',
        serializers.CharField: 'string',
        serializers.UUIDField: '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.DurationField: 'duration',
        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',
    })

    def determine_metadata(self, request, view):
        metadata = {
            "name": view.get_view_name(),
            "description": view.get_view_description(),
            "renders": [renderer.media_type for renderer in view.renderer_classes],
            "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 {'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 {
            field_name: self.get_field_info(field)
            for field_name, field in serializer.fields.items()
            if not isinstance(field, serializers.HiddenField)
        }

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

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

        for attr in attrs:
            value = getattr(field, attr, None)
            if value is not None and value != '':
                field_info[attr] = force_str(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
            not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
                hasattr(field, 'choices')):
            field_info['choices'] = [
                {
                    'value': choice_value,
                    'display_name': force_str(choice_name, strings_only=True)
                }
                for choice_value, choice_name in field.choices.items()
            ]

        return field_info
create a new custommetadata file with a custommetadata class that inherits from simplemetadata and in which 
    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 {'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 In addition to put and post, there is also get, which is necessary for it to include methods     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 {
            field_name: self.get_field_info(field)
            for field_name, field in serializer.fields.items()
            if not isinstance(field, serializers.HiddenField)
        }

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

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

        for attr in attrs:
            value = getattr(field, attr, None)
            if value is not None and value != '':
                field_info[attr] = force_str(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
            not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
                hasattr(field, 'choices')):
            field_info['choices'] = [
                {
                    'value': choice_value,
                    'display_name': force_str(choice_name, strings_only=True)
                }
                for choice_value, choice_name in field.choices.items()
            ]

        return field_info. example of code in which it perfomed: from contextlib import suppress

from rest_framework import serializers
from rest_framework.metadata import SimpleMetadata
from rest_framework.request import clone_request
from rest_framework.utils.field_mapping import ClassLookupDict
from ww_filters.updaters import Choice, ChoiceIn, ModelChoice, ModelChoiceIn


def get_model_verbose_name(view):
    if view.queryset is not None:
        return view.queryset.model._meta.verbose_name_plural
    elif hasattr(view, "get_queryset"):
        # TODO: надо бы как-то не вызывать get_queryset здесь
        return view.get_queryset().model._meta.verbose_name_plural


def get_filter_fields(view):
    def __update_choice():
        if isinstance(updater_value, Choice | ChoiceIn):
            choices = [{"index": index, "value": value} for index, value in updater_value.queryset]
            updater_result_data["choices"] = choices
        if isinstance(updater_value, ModelChoice | ModelChoiceIn):
            choices = [{"index": instance.pk, "value": str(instance)} for instance in updater_value.queryset]
            updater_result_data["choices"] = sorted(choices, key=lambda item: item["value"])

    if hasattr(view, "filter_form") and view.filter_form is not None:
        result_data = {}
        base_filters = (
            view.filter_form.get_base_filters(view)
            if hasattr(view.filter_form, "get_base_filters")
            else view.filter_form.base_filters
        )
        for filter_key, filter_value in base_filters.items():
            filter_key = f"{filter_key}_filter"
            result_data[filter_key] = {"title": filter_value.title, "value": filter_key, "updaters": []}
            for updater_key, updater_value in filter_value.updaters.items():
                updater_result_data = {
                    "value": updater_key,
                    "title": updater_value.title,
                    "type": updater_value.type,
                }
                __update_choice()
                result_data[filter_key]["updaters"].append(updater_result_data)
        result_data = dict(sorted(result_data.items(), key=lambda item: item[1]["title"]))
        if view.__class__.__name__ == "StatementViewSet":
            result_data = view.update_statements_filters(result_data)
        return result_data
    return {}


class SerializerFieldsMetadata(SimpleMetadata):
    # в случае использования BSelectMultiple и если мы используем дополнительный атрибут
    # в сериалайзере autocomplete_choice_kwargs
    # => задаём лимит изначально подгружаемых данных в select при загрузке формы
    _limit_multiple_choices = 30

    widget_lookup = ClassLookupDict(
        {
            serializers.SlugRelatedField: "SelectAutocomplete",
            serializers.PrimaryKeyRelatedField: "SelectAutocomplete",
            serializers.Field: "BInput",
            serializers.BooleanField: "BCheckBox",
            serializers.CharField: "BInput",
            serializers.UUIDField: "BInput",
            serializers.URLField: "BInput",
            serializers.EmailField: "BInput",
            serializers.RegexField: "BInput",
            serializers.SlugField: "SelectAutocomplete",
            serializers.IntegerField: "BNumeric",
            serializers.FloatField: "BInput",
            serializers.DecimalField: "BInput",
            serializers.DateField: "BDate",
            serializers.DateTimeField: "BDate",
            serializers.TimeField: "BInput",
            serializers.ChoiceField: "SelectAutocomplete",
            serializers.MultipleChoiceField: "BSelectMultiple",
            serializers.FileField: "BInput",
            serializers.ImageField: "BImage",
            serializers.ListField: "BInput",
            serializers.DictField: "BInput",
            serializers.Serializer: "BInput",
        }
    )

    def get_field_info(self, field):
        field_info = super().get_field_info(field)
        field_info["widget"] = self.widget_lookup[field]
        if isinstance(field, serializers.SlugRelatedField | serializers.PrimaryKeyRelatedField):
            if hasattr(field, "autocomplete_choice_kwargs"):
                field_info["choices"] = [
                    {"display_name": str(item), "value": item.id}
                    for item in field.queryset.all().order_by("-id")[: self._limit_multiple_choices]
                ]
                # для подгрузки initial в список choices в форме редактирования
                if self._view.kwargs and hasattr(instance := self._view.get_object(), field.field_name):
                    initial = getattr(instance, field.field_name)
                    if initial and str(initial) not in field_info["choices"]:
                        field_info["choices"].append({"display_name": str(initial), "value": initial.id})
                field_info["download_autocomplete_info"] = field.autocomplete_choice_kwargs
            else:
                field_info["choices"] = [{"display_name": value, "value": key} for key, value in field.choices.items()]
        elif isinstance(field, serializers.ManyRelatedField) and field.child_relation.queryset is not None:
            if hasattr(field, "autocomplete_choice_kwargs"):
                field_info["choices"] = [
                    {"display_name": str(item), "value": item.id}
                    for item in field.child_relation.queryset.all().order_by("-id")[: self._limit_multiple_choices]
                ]
                # для подгрузки initial в список choices в форме редактирования
                if self._view.kwargs and hasattr(instance := self._view.get_object(), field.field_name):
                    [
                        field_info["choices"].append({"display_name": str(item), "value": item.id})
                        for item in getattr(instance, field.field_name).all()
                        if {"display_name": str(item), "value": item.id} not in field_info["choices"]
                    ]
                field_info["download_autocomplete_info"] = field.autocomplete_choice_kwargs
            else:
                field_info["choices"] = [
                    {"display_name": str(item), "value": item.id} for item in field.child_relation.queryset.all()
                ]
        with suppress(AttributeError):
            if widget := field.parent.Meta.widgets.get(field.source):
                field_info["widget"] = widget
        if hasattr(field, "initial"):
            field_info["initial"] = field.initial
        return field_info

    def determine_actions(self, request, view):
        self._view = view  # возможно эта строчка юзлес
        actions = {}
        for permission in view.get_permissions():
            for method in {"PUT", "POST", "GET"} & set(view.allowed_methods):
                view.request = clone_request(request, method)
                if hasattr(view, "action_map"):
                    for action in set(view.action_map.values()):
                        if not permission.has_action_permission(view.request, view, action):
                            continue
                        if hasattr(view, "serializer_classes") and action in view.serializer_classes:
                            serializer_class = view.serializer_classes[action]
                        else:
                            serializer_class = view.get_serializer_class()
                        if action == "update" and hasattr(view, "get_object"):
                            serializer = self._get_serializer_instance(
                                serializer_class, view, instance=view.get_object()
                            )
                            self._set_update_initial(serializer)
                        else:
                            serializer = self._get_serializer_instance(serializer_class, view)
                        actions[action] = self.get_serializer_info(serializer)
                else:
                    serializer = self._get_serializer_instance(view.get_serializer_class(), view)
                    if hasattr(view, "list"):
                        actions["list"] = self.get_serializer_info(serializer)
        view.request = request
        return actions

    @staticmethod
    def _get_serializer_instance(serializer_class, view, *args, **kwargs):
        """
        Возвращает инстанс сериализатора.

        Если у view аттрибут add_options_action_context установлен в True,
        то добавляет к инстансу контекст из view.get_serializer_context().
        """
        if hasattr(view, "add_options_action_context") and view.add_options_action_context:
            kwargs.setdefault("context", view.get_serializer_context())
        return serializer_class(*args, **kwargs)

    @staticmethod
    def _set_update_initial(serializer):
        for field_name, field in serializer.fields.items():
            field.initial = serializer.data.get(field_name)

    def determine_metadata(self, request, view):
        metadata = super().determine_metadata(request, view)
        metadata["filter_fields"] = get_filter_fields(view)
        metadata["verbose_name"] = get_model_verbose_name(view)
        return metadata