From e88b25f3e0d010a5f89e0a163b90e14f933f7740 Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Wed, 23 Oct 2024 13:31:54 +1000 Subject: [PATCH] Release 5.11.3 --- doc/backend.rst | 2 +- doc/locale/ru/LC_MESSAGES/backend.po | 71 ++++++++++++++++++++----- doc/locale/ru/LC_MESSAGES/quickstart.po | 14 ++++- test_src/test_proj/settings.py | 3 ++ test_src/test_proj/tests.py | 4 ++ test_src/test_proj/views.py | 5 ++ vstutils/__init__.py | 2 +- vstutils/api/actions.py | 8 +-- vstutils/api/serializers.py | 41 +++++++++++--- vstutils/models/__init__.py | 4 ++ vstutils/models/base.py | 8 ++- vstutils/models/base.pyi | 3 ++ 12 files changed, 134 insertions(+), 31 deletions(-) diff --git a/doc/backend.rst b/doc/backend.rst index c45f9ec7..da1aee35 100644 --- a/doc/backend.rst +++ b/doc/backend.rst @@ -64,7 +64,7 @@ Serializers ~~~~~~~~~~~ .. automodule:: vstutils.api.serializers - :members: DisplayMode,DisplayModeList,BaseSerializer,VSTSerializer,EmptySerializer,JsonObjectSerializer + :members: DisplayMode,DisplayModeList,BaseSerializer,VSTSerializer,DetailsResponseSerializer,EmptySerializer,JsonObjectSerializer Views ~~~~~ diff --git a/doc/locale/ru/LC_MESSAGES/backend.po b/doc/locale/ru/LC_MESSAGES/backend.po index 0c4c61d0..0659b491 100644 --- a/doc/locale/ru/LC_MESSAGES/backend.po +++ b/doc/locale/ru/LC_MESSAGES/backend.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-09-25 05:35+0000\n" +"POT-Creation-Date: 2024-10-23 02:50+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -452,6 +452,19 @@ msgstr "" "использует такие же атрибуты, которые были объявлены в мета-атрибутах, но" " позволяет перегружать отдельные части." +#: of vstutils.models.BModel:196 +msgid "" +"An important aspect that distinguishes ``get_view_class()`` is the " +"``ignore_meta`` argument. It takes ``True`` when it's necessary to " +"completely ignore the values set in the model's ``Meta`` class. This " +"allows ``get_view_class()`` to generate a fully independent view." +msgstr "" +"Важным аспектом, который отличает ``get_view_class()``, является аргумент" +" ``ignore_meta``. Он принимает значение ``True``, когда необходимо " +"полностью игнорировать значения, установленные в классе ``Meta`` модели. " +"Это позволяет ``get_view_class()`` создать полностью независимое " +"представление." + #: ../../docstring of vstutils.models.BModel.hidden:1 msgid "If hidden is set to True, entry will be excluded from query in BQuerySet." msgstr "" @@ -2900,6 +2913,38 @@ msgstr "" "В этом примере класс ``MySerializer`` расширяет ``BaseSerializer`` и " "включает дополнительное созданное поле." +#: of vstutils.api.serializers.DetailsResponseSerializer:1 +msgid "Serializer class inheriting from :class:`.BaseSerializer`." +msgstr "Класс сериализатора, наследующийся от :class:`.BaseSerializer`." + +#: of vstutils.api.serializers.DetailsResponseSerializer:3 +msgid "" +"This serializer is primarily intended for use as the " +"``result_serializer_class`` argument in " +":class:`vstutils.api.actions.Action` and its derivatives. It defines a " +"single read-only ``details`` field, which is useful for returning detail " +"messages in API responses." +msgstr "" +"Этот сериализатор в первую очередь предназначен для использования в качестве " +"аргумента ``result_serializer_class`` в " +":class:`vstutils.api.actions.Action` и его производных. Он определяет одно " +"поле только для чтения ``details``, которое полезно для возврата сообщений " +"с деталями в ответах API." + +#: of vstutils.api.serializers.DetailsResponseSerializer:7 +msgid "" +"Additionally, it can serve as a placeholder for schemas involving various" +" errors, where the error text is placed in the ``details`` field (the " +"default behavior in Django REST Framework)." +msgstr "" +"Кроме того, он может служить в качестве шаблона для схем, связанных с " +"различными ошибками, где текст ошибки помещается в поле ``details`` (это " +"поведение по умолчанию в Django REST Framework)." + +#: of vstutils.api.serializers.DetailsResponseSerializer:10 +msgid "Example usage with an Action:" +msgstr "Пример использования с Action:" + #: of vstutils.api.serializers.DisplayMode:1 msgid "" "Enumeration for specifying how a serializer should be displayed on the " @@ -3832,7 +3877,7 @@ msgstr "" "использованием :class:`.SimpleAction` или алгоритм значительно более " "сложный, чем стандартные операции CRUD." -#: of vstutils.api.actions.Action:39 +#: of vstutils.api.actions.Action:35 msgid "" "Flag indicating which type of action is used: on a list or on a single " "entity. Affects where this action will be displayed - on a detailed " @@ -3842,19 +3887,19 @@ msgstr "" "отдельной сущности. Влияет на то, где будет отображаться это действие - " "на детальной записи или на списке записей." -#: of vstutils.api.actions.Action:42 +#: of vstutils.api.actions.Action:38 msgid "" "List of available HTTP-methods for this action. Default has only `POST` " "method." msgstr "Список доступных методов HTTP. По умолчанию ``[\"post\"]``." -#: of vstutils.api.actions.Action:44 +#: of vstutils.api.actions.Action:40 msgid "Request body serializer. Also used for default response." msgstr "" "Сериализатор для тела запроса. Используется также для формирования ответа" " по умолчанию." -#: of vstutils.api.actions.Action:46 +#: of vstutils.api.actions.Action:42 msgid "" "Response body serializer. Required, when request and response has " "different set of fields." @@ -3862,7 +3907,7 @@ msgstr "" "Сериализатор для тела ответа. Необходим, когда запрос и ответ имеют " "различные наборы полей." -#: of vstutils.api.actions.Action:49 +#: of vstutils.api.actions.Action:45 msgid "" "GET-request query data serializer. It is used when it is necessary to get" " valid data in the query data of a GET-request and cast it to the " @@ -3872,7 +3917,7 @@ msgstr "" "получить корректные данные в строке запроса типа GET и привести их к " "нужному типу." -#: of vstutils.api.actions.Action:52 +#: of vstutils.api.actions.Action:48 msgid "" "Used only with non-GET requests and notify GUI, that this action should " "be rendered over the selected list items." @@ -3880,7 +3925,7 @@ msgstr "" "Используется только с не-GET запросами и уведомляет GUI, что это действие" " должно быть применено к выбранным элементам списка." -#: of vstutils.api.actions.Action:55 +#: of vstutils.api.actions.Action:51 msgid "" "Title for action in UI. For non-GET actions, title is generated from " "method's name." @@ -3888,11 +3933,11 @@ msgstr "" "Заголовок действия в пользовательском интерфейсе. Для действий, отличных " "от GET, заголовок генерируется на основе имени метода." -#: of vstutils.api.actions.Action:57 +#: of vstutils.api.actions.Action:53 msgid "List of icons for UI button." msgstr "Список иконок для кнопки пользовательского интерфейса." -#: of vstutils.api.actions.Action:59 +#: of vstutils.api.actions.Action:55 msgid "" "Flag indicating whether the action type is a list or a single entity. " "Typically used with GET actions." @@ -3900,7 +3945,7 @@ msgstr "" "Флаг, указывающий, является ли тип действия списком или отдельной " "сущностью. Обычно используется с действиями GET." -#: of vstutils.api.actions.Action:62 +#: of vstutils.api.actions.Action:58 msgid "" "Flag indicating whether the action will only use edit mode, without a " "view page. This is used for actions where there is a GET method and any " @@ -3911,13 +3956,13 @@ msgstr "" "экшенов, где есть метод GET и любые другие изменяющие методы (POST, PUT, " "PATCH)." -#: of vstutils.api.actions.Action:66 +#: of vstutils.api.actions.Action:62 msgid "If true user will be asked to confirm action execution on frontend." msgstr "" "Если истина, то в интерфейсе пользователь должен будет подтвердить " "действие перед выполнением." -#: of vstutils.api.actions.Action:68 +#: of vstutils.api.actions.Action:64 msgid "Set of named arguments for :func:`rest_framework.decorators.action`." msgstr "Набор именованных аргументов для :func:`rest_framework.decorators.action`." diff --git a/doc/locale/ru/LC_MESSAGES/quickstart.po b/doc/locale/ru/LC_MESSAGES/quickstart.po index b6d57ac5..681a652e 100644 --- a/doc/locale/ru/LC_MESSAGES/quickstart.po +++ b/doc/locale/ru/LC_MESSAGES/quickstart.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-09-25 05:35+0000\n" +"POT-Creation-Date: 2024-10-23 02:07+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -830,6 +830,18 @@ msgstr "" "использует такие же атрибуты, которые были объявлены в мета-атрибутах, но" " позволяет перегружать отдельные части." +#: of vstutils.models.BModel:196 +msgid "" +"An important aspect that distinguishes ``get_view_class()`` is the " +"``ignore_meta`` argument. It takes ``True`` when it's necessary to " +"completely ignore the values set in the model's ``Meta`` class. This " +"allows ``get_view_class()`` to generate a fully independent view." +msgstr "" +"Важным аспектом, который отличает ``get_view_class()``, является аргумент " +"``ignore_meta``. Он принимает значение ``True``, когда необходимо полностью " +"игнорировать значения, установленные в классе ``Meta`` модели. Это позволяет " +"``get_view_class()`` создать полностью независимое представление." + #: ../../quickstart.rst:206 msgid "" "More information about Models you can find in `Django Models " diff --git a/test_src/test_proj/settings.py b/test_src/test_proj/settings.py index a2bade64..230f72ac 100644 --- a/test_src/test_proj/settings.py +++ b/test_src/test_proj/settings.py @@ -115,6 +115,9 @@ API[VST_API_VERSION][r'cacheable'] = dict( view='test_proj.views.CacheableViewSet' ) +API[VST_API_VERSION][r'cacheable_check_generation'] = dict( + view='test_proj.views.CacheableViewCheckGeneration' +) API[VST_API_VERSION][r'dynamic_fields'] = dict( model='test_proj.models.dynamic_fields.DynamicFields' ) diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index 3e92aa30..183d6508 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -4150,6 +4150,10 @@ def test_swagger_schema(self): self.assertIn('Etag', data['paths']['/cacheable/{id}/']['put']['responses']['200']['headers']) self.assertIn('412', data['paths']['/cacheable/{id}/']['put']['responses']) + # check that generated view ignore values from CachableProxyModel.Meta + self.assertCount(data['paths']['/cacheable_check_generation/'].keys(), 2, data['paths']['/cacheable_check_generation/'].keys()) # get and parameters + self.assertFalse('/cacheable_check_generation/{id}/' in data['paths']) # list only view + self.assertEqual( data['paths']['/testbinaryfiles/{id}/test_some_filefield_default/']['get']['responses']['200']['schema']['type'], 'file' diff --git a/test_src/test_proj/views.py b/test_src/test_proj/views.py index faf17dd9..615941f0 100644 --- a/test_src/test_proj/views.py +++ b/test_src/test_proj/views.py @@ -290,6 +290,11 @@ def get_etag_value(self, model_class, request): return super().get_etag_value((model_class, gui_version), request) +CacheableViewCheckGeneration = CachableProxyModel.get_view_class( + ignore_meta=True, + view_class=('list_only',), +) + class TestOauth2ViewSet(GenericViewSet): serializer_class = EmptySerializer diff --git a/vstutils/__init__.py b/vstutils/__init__.py index 8e12d4b4..41fb2342 100644 --- a/vstutils/__init__.py +++ b/vstutils/__init__.py @@ -1,2 +1,2 @@ # pylint: disable=django-not-available -__version__: str = '5.11.2' +__version__: str = '5.11.3' diff --git a/vstutils/api/actions.py b/vstutils/api/actions.py index 59d4a2c5..0d1501fa 100644 --- a/vstutils/api/actions.py +++ b/vstutils/api/actions.py @@ -40,7 +40,7 @@ class Action: ... from vstutils.api.fields import VSTCharField - from vstutils.api.serializers import BaseSerializer + from vstutils.api.serializers import BaseSerializer, DetailsResponseSerializer from vstutils.api.base import ModelViewSet from vstutils.api.actions import Action ... @@ -50,15 +50,11 @@ class RequestSerializer(BaseSerializer): ... - class ResponseSerializer(BaseSerializer): - detail = VSTCharField(read_only=True) - - class AuthorViewSet(ModelViewSet): model = ... ... - @Action(serializer_class=RequestSerializer, result_serializer_class=ResponseSerializer, ...) + @Action(serializer_class=RequestSerializer, result_serializer_class=DetailsResponseSerializer, ...) def profile(self, request, *args, **kwargs): ''' Got `serializer_class` body and response with `result_serializer_class`. ''' serializer = self.get_serializer(self.get_object(), data=request.data) diff --git a/vstutils/api/serializers.py b/vstutils/api/serializers.py index 4939fa58..a5ccc5b3 100644 --- a/vstutils/api/serializers.py +++ b/vstutils/api/serializers.py @@ -284,6 +284,39 @@ def build_relational_field(self, field_name, relation_info): return super().build_relational_field(field_name, relation_info) +class DetailsResponseSerializer(BaseSerializer): + """ + Serializer class inheriting from :class:`.BaseSerializer`. + + This serializer is primarily intended for use as the ``result_serializer_class`` argument in + :class:`vstutils.api.actions.Action` and its derivatives. It defines a single read-only + ``details`` field, which is useful for returning detail messages in API responses. + + Additionally, it can serve as a placeholder for schemas involving various errors, where the + error text is placed in the ``details`` field (the default behavior in Django REST Framework). + + Example usage with an Action: + + .. sourcecode:: python + + class AuthorViewSet(ModelViewSet): + model = ... + ... + + @Action( + serializer_class=RequestSerializer, + result_serializer_class=DetailsResponseSerializer, # used + ... + ) + def profile(self, request, *args, **kwargs): + '''Process the request data and respond with a detail message.''' + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + return {"details": "Executed!"} + """ + details = drf_fields.CharField(read_only=True, allow_blank=True) + + class EmptySerializer(BaseSerializer): """ Default serializer for empty responses. @@ -320,15 +353,9 @@ class JsonObjectSerializer(DataSerializer): pass -class ErrorSerializer(DataSerializer): +class ErrorSerializer(DetailsResponseSerializer): detail = fields.VSTCharField(required=True) - def to_internal_value(self, data): - return data - - def to_representation(self, instance): - return instance - class ValidationErrorSerializer(ErrorSerializer): detail = serializers.DictField(required=True) # type: ignore diff --git a/vstutils/models/__init__.py b/vstutils/models/__init__.py index aab3fd0a..8677a142 100644 --- a/vstutils/models/__init__.py +++ b/vstutils/models/__init__.py @@ -228,6 +228,10 @@ class or string to import with base class ViewSet. Developers can use this method to customize various aspects of the associated view, such as serializer classes, field configurations, filter backends, permission classes, etc. It uses attributes declared in meta attributes, but allows individual parts to be overriden. + + An important aspect that distinguishes ``get_view_class()`` is the ``ignore_meta`` argument. + It takes ``True`` when it's necessary to completely ignore the values set in the model's ``Meta`` class. + This allows ``get_view_class()`` to generate a fully independent view. """ #: Primary field for select and search in API. diff --git a/vstutils/models/base.py b/vstutils/models/base.py index 8df4fca2..a60851c4 100644 --- a/vstutils/models/base.py +++ b/vstutils/models/base.py @@ -590,8 +590,12 @@ def get_view_class(cls, **extra_options): # noqa: CFQ001,R0914 Method which return autogenerated ViewSet based on model's Meta class. """ # pylint: disable=no-value-for-parameter - metadata = cls.get_extra_metadata() - metadata.update(extra_options) + if not extra_options.get('ignore_meta', False): + metadata = cls.get_extra_metadata() + metadata.update(extra_options) + else: + metadata = {**default_extra_metadata, **extra_options} + list_fields = _ensure_pk_in_fields(cls, metadata['list_fields']) detail_fields = _ensure_pk_in_fields(cls, metadata['detail_fields'] or list_fields) diff --git a/vstutils/models/base.pyi b/vstutils/models/base.pyi index 7dcf6f2b..581ac652 100644 --- a/vstutils/models/base.pyi +++ b/vstutils/models/base.pyi @@ -29,6 +29,7 @@ class NestedViewOptionArgs(TypedDict, total=False): NestedOptionType = Dict[Text, Union[NestedModelOptionArgs, NestedViewOptionArgs, dict]] class ExtraMetadata(TypedDict, total=False): + ignore_meta: Optional[bool] view_class: Optional[Union[Tuple, List[Union[Text, ConstantViewType]], Text, ConstantViewType]] serializer_class: Optional[Serializer] serializer_class_name: Optional[Text] @@ -93,6 +94,8 @@ class ModelBaseClass(ModelBase): def get_view_class( cls, + *, + ignore_meta: Optional[bool], view_class: Optional[Union[Tuple, List[Union[Text, ConstantViewType]], Text, ConstantViewType]] = None, serializer_class: Optional[Serializer] = None,