Skip to content

Commit

Permalink
Release 5.11.5
Browse files Browse the repository at this point in the history
### Changelog:
* Fix(backend): ``filterset_fields`` types.
* Fix(backend): Add default value for ``ignore_meta`` in ``get_view_class`` types.
* Fix(backend): ``BaseModel.DoesNotExist`` type.
* Chore(backend): Update ``drf-yasg`` to ``1.21.8``.
* Feature(frontend): Add ``RouterLinkField``.
* Fix(frontend): Nested object field wrapper classes.

See merge request vst/vst-utils!675
  • Loading branch information
onegreyonewhite committed Nov 5, 2024
2 parents 8ed1567 + 57e9a71 commit 9c28ebd
Show file tree
Hide file tree
Showing 13 changed files with 359 additions and 18 deletions.
146 changes: 140 additions & 6 deletions doc/locale/ru/LC_MESSAGES/backend.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VST Utils 5.0.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-24 00:09+0000\n"
"POT-Creation-Date: 2024-11-05 03:35+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -208,8 +208,8 @@ msgid ""
"serializer class, but only if they are included in the corresponding "
"lists."
msgstr ""
"``_override_list_fields`` или ``_override_detail_fields`` — отображение с "
"именами и типами полей, которые будут переопределены в атрибутах "
"``_override_list_fields`` или ``_override_detail_fields`` — отображение с"
" именами и типами полей, которые будут переопределены в атрибутах "
"сериализатора (считайте это как объявление полей в DRF ModelSerializer). "
"Имейте в виду, что указанные здесь поля не обязательно окажутся в классе "
"сериализатора, а только если они включены в соответствующие списки."
Expand Down Expand Up @@ -444,9 +444,10 @@ msgstr ""
"Метод ``get_view_class()`` — это служебный метод в ORM Django моделях, "
"предназначенный для облегчения настройки и создания экземпляров "
"представлений Django Rest Framework (DRF). Это позволяет разработчикам "
"определить и настроить различные аспекты класса представления DRF."
"Аргументы, переданные в функцию, полностью соответствуют тем, которые были указаны ранее для класса ``Meta`` модели, "
"но без префикса подчёркивания."
"определить и настроить различные аспекты класса представления "
"DRF.Аргументы, переданные в функцию, полностью соответствуют тем, которые"
" были указаны ранее для класса ``Meta`` модели, но без префикса "
"подчёркивания."

#: of vstutils.models.BModel:196
msgid ""
Expand Down Expand Up @@ -2562,6 +2563,112 @@ msgstr ""
"Чтобы изменить отображение на странице, используйте "
":const:`vstutils.api.serializers.DisplayModeList`."

#: of vstutils.api.fields.RouterLinkField:1
msgid ""
"A read-only serializer field that displays a link to another page or a "
"simple text label in the interface."
msgstr ""
"Поле сериализатора, позволяющее отображать ссылку на другую страницу или "
"простой текст в интерфейсе."

#: of vstutils.api.fields.RouterLinkField:3
msgid "**Expected Input:**"
msgstr "**Ожидаемый ввод:**"

#: of vstutils.api.fields.RouterLinkField:5
msgid "This field expects a dictionary with the following keys:"
msgstr "Этот поле ожидает словарь с следующими ключами:"

#: of vstutils.api.fields.RouterLinkField:7
msgid ""
"**link** *(optional)*: The URL or route to another page. If provided, the"
" label will be displayed as a clickable link. If not provided, only the "
"text label is shown. The value should be compatible with the `Vue "
"Router's push method parameter "
"<https://router.vuejs.org/api/interfaces/Router.html#push>`_. Ensure that"
" the link points to an existing resource in the interface to avoid 404 "
"errors."
msgstr ""
"**link** *(необязательно)*: URL для другой страницы. Если "
"указан, будет отображаться текст как ссылка. Если не указан, будет "
"отображаться просто текст. Значение должно быть совместимым с "
"`параметром метода push Vue Router <https://router.vuejs.org/api/interfaces/Router.html#push>`_. "
"Убедитесь, что ссылка указывает на существующий ресурс в интерфейсе для "
"избежания ошибок 404."

#: of vstutils.api.fields.RouterLinkField:11
msgid ""
"**label**: The text to display. This is required whether or not a link is"
" provided."
msgstr "**label**: Текст для отображения. Обязательное поле."

#: of vstutils.api.fields.RouterLinkField:14
msgid "For simpler use cases, you might consider using :class:`.FkField`."
msgstr "Для простых случаев использования см. :class:`.FkField`."

#: of vstutils.api.fields.RouterLinkField:16
msgid "**Examples:**"
msgstr "**Примеры:**"

#: of vstutils.api.fields.RouterLinkField:18
msgid "*Using a model class method:*"
msgstr "*Использование метода класса модели:*"

#: of vstutils.api.fields.RouterLinkField:42
msgid ""
"In this example, the ``get_link`` method in the ``Author`` model returns "
"a dictionary containing the ``link`` and ``label``. The "
"``RouterLinkField`` uses this method to display the author's name as a "
"clickable link to their detail page."
msgstr ""
"В этом примере метод ``get_link`` в модели ``Author`` возвращает словарь "
"с ключами ``link`` и ``label``. Поле ``RouterLinkField`` использует этот "
"метод для отображения имени автора как ссылку на страницу с "
"подробностями."

#: of vstutils.api.fields.RouterLinkField:45
msgid "*Using a custom field class:*"
msgstr "*Использование пользовательского класса поля:*"

#: of vstutils.api.fields.RouterLinkField:70
msgid ""
"In this example, we create a custom field ``AuthorLinkField`` by "
"subclassing ``RouterLinkField``. We override the ``to_representation`` "
"method to return a dictionary with the ``link`` and ``label`` for each "
"``Author`` instance. This custom field is then used in the viewset to "
"display each author's name as a clickable link."
msgstr ""
"В этом примере мы создаем пользовательский класс ``AuthorLinkField`` "
"подклассом ``RouterLinkField``. Мы переопределяем метод "
"``to_representation`` для возвращения словаря с ключами ``link`` и "
"``label`` для каждого экземпляра ``Author``. Этот пользовательский поле "
"используется в вьюсете для отображения имени автора как кликабельной "
"ссылки."

#: of vstutils.api.fields.RouterLinkField:75
msgid ""
"The field is read-only and is intended to display dynamic links based on "
"the instance data."
msgstr ""
"Поле является только для чтения и предназначено для отображения "
"динамических ссылок на основе данных экземпляра."

#: of vstutils.api.fields.RouterLinkField:76
msgid ""
"If the ``link`` key is omitted or ``None``, the field will display the "
"``label`` as plain text instead of a link."
msgstr ""
"Если ключ ``link`` отсутствует или имеет значение ``None``, поле "
"отображает текст как обычный текст вместо ссылки."

#: of vstutils.api.fields.RouterLinkField:79
msgid ""
"Always ensure that the ``link`` provided points to a valid route within "
"your application to prevent users from encountering 404 errors."
msgstr ""
"Всегда убедитесь, что предоставленная ссылка указывает на действительную"
" страницу в вашем приложении для избежания ошибок 404."

#: of vstutils.api.fields.SecretFileInString:1
msgid ""
"This field extends :class:`.FileInStringField` but hides its value in the"
Expand Down Expand Up @@ -6244,3 +6351,30 @@ msgstr ""
"``VE100`` - Код ошибки целостности. Используется при появляении "
"``django.db.utils.IntegrityError``."

#~ msgid "Field that shows link to another page. Expected dict with fields:"
#~ msgstr ""
#~ "Поле, которое показывает ссылку на "
#~ "другую страницу. Ожидается словарь с "
#~ "полями:"

#~ msgid "``link`` - link to another page, if not provided simple text is shown."
#~ msgstr ""
#~ "``link`` - ссылка на другую страницу,"
#~ " если не указана то будет показан "
#~ "просто текст."

#~ msgid ""
#~ "For details see `parameter of Vue "
#~ "Router's push method "
#~ "<https://router.vuejs.org/api/interfaces/Router.html#push>`_."
#~ msgstr ""
#~ "Подробнее см. `параметр метода Vue "
#~ "Router's push "
#~ "<https://router.vuejs.org/api/interfaces/Router.html#push>`_."

#~ msgid "``label`` - text to show."
#~ msgstr "``label`` - текст ссылки."

#~ msgid "**Usage Tips:**"
#~ msgstr "**Примеры:**"

2 changes: 2 additions & 0 deletions frontend_src/vstutils/fields/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { WYSIWYGField } from './text/WYSIWYGField';
import { UUIDField } from './text/UUIDField';
import { URIField } from './text/URIField';
import type { Field } from './base';
import { RouterLinkField } from './router-link';
export { FieldLabelIdMixin, ModalWindowAndButtonMixin, TableRowMixin };

export function addDefaultFields(fieldsResolver: FieldsResolver) {
Expand Down Expand Up @@ -156,6 +157,7 @@ export function addDefaultFields(fieldsResolver: FieldsResolver) {
['json', JSONField],
['namedbinfile', files.namedBinaryFile.NamedBinaryFileField],
['namedbinimage', files.namedBinaryImage.NamedBinaryImageField],
['router-link', RouterLinkField],
] as [string | typeof FieldsResolver.DEFAULT_FIELD_KEY, new (options: any) => Field][]) {
fieldsResolver.registerField(SCHEMA_DATA_TYPE.object, format, field);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend_src/vstutils/fields/nested-object/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export const NestedObjectFieldMixin = defineComponent({
return () =>
h(
'div',
{ staticClass: 'field-component', class: wrapperClasses },
{ staticClass: 'field-component', class: wrapperClasses.value },
props.type === 'list' ? renderList() : renderDetail(),
);
},
Expand Down
40 changes: 40 additions & 0 deletions frontend_src/vstutils/fields/router-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { computed, h } from 'vue';
import { BaseField, defineFieldComponent } from './base';
import FieldWrapper from './base/FieldWrapper.vue';
import { InnerData } from '#vstutils/utils';

type Data = { link?: string; label: string };

const RouterLinkFieldComponent = defineFieldComponent<RouterLinkField>((props) => {
const value = computed(() => props.field.getValue(props.data));
return () => {
const link = value.value;
return h(FieldWrapper, { props }, [
link?.link
? h('router-link', { props: { to: link.link } }, [link.label])
: h('span', [link?.label ?? '']),
]);
};
});

export class RouterLinkField extends BaseField<Data, Data> {
override toRepresent(data: InnerData) {
const value = this.getValue(data);
if (value) {
if (typeof value !== 'object') {
this.error(`Value must be an object, got: ${value}`);
}
if (typeof value.label !== 'string') {
this.error(`Label must be a string, got: ${value.label}`);
}
if (value.link && typeof value.link !== 'string') {
this.error(`Link must be a string, got: ${value.link}`);
}
}
return value;
}

override getComponent() {
return RouterLinkFieldComponent;
}
}
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ django-environ~=0.11.2

# REST API packages
djangorestframework~=3.15.2
drf-yasg==1.21.7
drf-yasg==1.21.8
django-filter==24.3
drf_orjson_renderer==1.7.1
ormsgpack~=1.5.0
Expand Down
13 changes: 13 additions & 0 deletions test_src/test_proj/models/fields_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class Meta:
'decimal',
'decimal_without_max_digits',
'some_hidden_field',
'router_link',
]
_extra_serializer_classes = {
'serializer_class_update': UpdateAuthorSerializer,
Expand All @@ -172,6 +173,11 @@ class Meta:
'decimal': DecimalField(default='13.37', decimal_places=2, max_digits=5),
'decimal_without_max_digits': DecimalField(source='decimal', decimal_places=2, max_digits=None, read_only=True),
'some_hidden_field': SomeHiddenFieldSerializer(required=False),
'router_link': fields.RouterLinkField(
# read_only expected to be True in schema
read_only=False,
source='get_router_link',
),
}
_hidden_on_frontend_detail_fields=['some_hidden_field']
_nested = {
Expand Down Expand Up @@ -205,6 +211,13 @@ class Meta:
)(),
}

def get_router_link(self):
return {
'link': f'/author/{self.id}/',
'label': f'Author: {self.name}',
'unexpected_key': 'some value',
}


class Category(BModel):
name = models.CharField(max_length=256)
Expand Down
22 changes: 21 additions & 1 deletion test_src/test_proj/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1671,7 +1671,7 @@ def test_openapi_schema_content(self):
# Grouping model properties for GUI
self.assertEqual(
api['definitions']['OneAuthor']['x-properties-groups'],
{'Main': ['id', 'name'], '': ['registerDate', 'posts', 'phone', 'masked', 'decimal', 'decimal_without_max_digits', 'some_hidden_field']}
{'Main': ['id', 'name'], '': ['registerDate', 'posts', 'phone', 'masked', 'decimal', 'decimal_without_max_digits', 'some_hidden_field', 'router_link']}
)
# Check view field name
self.assertEqual(api['definitions']['OneExtraPost']['x-view-field-name'], 'title')
Expand Down Expand Up @@ -1705,6 +1705,22 @@ def test_openapi_schema_content(self):
# Check that minLength is set if field allow_null is False
self.assertEqual(api['definitions']['OneAuthor']['properties']['decimal']['minLength'], 1)

# Check RouterLinkField
self.assertEqual(
api["definitions"]["OneAuthor"]["properties"]["router_link"],
{
"type": "object",
"x-format": "router-link",
"title": "Router link",
"readOnly": True,
"required": ["label"],
"properties": {
"label": {"type": "string"},
"link": {"type": "string"},
},
},
)

# Check properly format for RatingField
self.assertEqual(
api['definitions']['OneExtraPost']['properties']['rating'],
Expand Down Expand Up @@ -4218,6 +4234,10 @@ def test_model_related_list_field(self):
'title': post_2.title
}
],
'router_link': {
'label': 'Author: author_1',
'link': f'/author/{author_1.id}/',
},
}
results = self.bulk([
{'method': 'get', 'path': ['author']},
Expand Down
2 changes: 1 addition & 1 deletion vstutils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# pylint: disable=django-not-available
__version__: str = '5.11.4'
__version__: str = '5.11.5'
Loading

0 comments on commit 9c28ebd

Please sign in to comment.