Определение модели
Экран Dynamic model → Dynamic model settings предоставляет административный UI для редактирования динамической модели. Вы добавляете сущности, атрибуты, перечисления и другие элементы через визуальный редактор, а затем применяете изменения с помощью кнопки Apply, чтобы проверить их и опубликовать новую версию модели.
Каждый из приведённых ниже разделов показывает YAML, который создаёт редактор для соответствующего элемента — это то же самое определение, которое вы можете просматривать и редактировать напрямую в YAML-представлении редактора.
См. раздел Концепции, где описано, как работает применение модели и как управляются версии, и Справочник по YAML — полный справочник по каждому элементу.
Использование редактора модели
Откройте экран Dynamic model settings. Редактор Visual представляет текущую модель как структуру связанных таблиц с сущностями, атрибутами, перечислениями, экранами. Вы можете добавлять и редактировать элементы с помощью диалоговых окон.
В любой момент вы можете переключиться на представление Code, чтобы просмотреть или отредактировать YAML-определение напрямую; оба представления синхронизированы.
Когда модель готова, нажмите Apply. Фреймворк сравнивает отредактированную модель с активной в данный момент и открывает промежуточный экран Dynamic model changes, который показывает, что будет применено, прежде чем что-либо будет зафиксировано. В разделе Model migrations перечисляются все отдельные изменения — такие как создание сущности, добавление или удаление атрибута либо добавление ограничения уникальности. Если затронуты какие-либо экраны, они перечисляются в разделе Changed views.
Просмотрите список и нажмите Apply changes для подтверждения. Модель проверяется и, если она корректна, изменения применяются и публикуются как новая версия модели, которая сразу становится активной. Закрытие экрана Dynamic model changes без применения отменяет изменения и оставляет активную модель без изменений.
Порядок атрибутов
Начиная с Jmix 3.1
На вкладке Attributes выберите атрибут и используйте кнопки Move the attribute up и Move the attribute down, чтобы изменить его позицию. Порядок атрибутов сохраняется в модели. Он определяет порядок полей в сгенерированных экранах деталей и столбцов в сгенерированных экранах списка, а также порядок свойств в метаданных, который используется REST API, инспектором сущностей и компонентом genericFilter. Новый порядок вступает в силу после применения модели.
Перемещать можно только динамические атрибуты. Собственные свойства статической сущности и идентификатор динамической сущности сохраняют свои позиции. Порядок столбцов в базе данных не меняется.
История модели
Начиная с Jmix 3.1
В представлении Code нажмите History, чтобы увидеть все сохранённые версии модели, начиная с самой новой. Для каждой версии диалог показывает дату, пользователя, который её применил, и является ли она активной. Выберите версию, чтобы увидеть её YAML, и нажмите Copy to clipboard, чтобы скопировать его.
Диалог не применяет более раннюю модель. Чтобы вернуться к более ранней версии, скопируйте её YAML, вставьте его в представление Code и примените модель.
Расширение статических сущностей
Чтобы добавить динамические атрибуты к сущности, которая уже существует в вашем приложении (статической сущности, такой как Customer), выберите эту сущность в редакторе и добавьте к ней атрибуты. Динамические атрибуты хранятся отдельно от собственной таблицы сущности и становятся доступными наряду со статическими атрибутами во время выполнения.
Пример ниже добавляет строковый атрибут taxId к Customer с длиной 20, флагом обязательности, уникальностью и правилом валидации:
- name: "Customer"
attributes:
- name: "taxId"
javaClass: "java.lang.String"
length: 20
required: true
unique: true
validation:
constraints:
- annotation: "Pattern"
parameters:
regexp: "^[A-Z0-9-]+$"
message:
en: "Tax ID can contain uppercase letters, digits and dashes only"
messages:
en: "Tax ID"
Определение динамических сущностей
Динамическая сущность — это совершенно новая сущность, существующая только во время выполнения; у неё нет Java-класса или таблицы в вашем исходном коде. Чтобы создать её, добавьте в редакторе сущность, имя которой не совпадает ни с одной существующей сущностью, затем определите её атрибуты и, при необходимости, её экраны. См. Экраны и пункты меню, где описано создание экранов и пунктов меню для динамических сущностей.
Пример ниже определяет динамическую сущность LoyaltyLevel с несколькими атрибутами, ограничениями валидации и парой сгенерированных экранов:
- name: "LoyaltyLevel"
resourceRoles:
read:
- "employee"
- "manager"
create:
- "manager"
update:
- "manager"
delete:
- "manager"
messages:
en: "Loyalty level"
de: "Treuestufe"
validation:
constraints:
- name: "discountDescriptionRequired"
target: "entity"
type: "expression"
evaluator: "spel"
expression: "discount == null || description != null"
attributes:
- "discount"
- "description"
path: "description"
groups:
- "UiCrossFieldChecks"
- "RestApiChecks"
message:
en: "Description is required when discount is specified"
attributes:
- name: "name"
javaClass: "java.lang.String"
instanceName: true
messages:
en: "Name"
- name: "description"
javaClass: "java.lang.String"
lob: true
messages:
en: "Description"
- name: "publicSummary"
javaClass: "java.lang.String"
calculated:
evaluator: "spel"
expression: "(name ?: '') + ': ' + (description ?: '')"
dependsOn:
- "name"
- "description"
messages:
en: "Public summary"
- name: "discount"
javaClass: "java.math.BigDecimal"
validation:
constraints:
- annotation: "DecimalMin"
parameters:
value: "0"
- annotation: "DecimalMax"
parameters:
value: "100"
messages:
en: "Discount"
views:
- type: "list"
viewId: "LoyaltyLevel.list"
viewRoute: "loyalty-levels"
viewTitle:
en: "Loyalty levels"
de: "Treuestufen"
templateParams:
includeProperties: ["name", "discount"]
excludeProperties: ["publicSummary"]
resourceRoles:
- "employee"
- "manager"
menuItem:
parentMenu: "application"
insertBefore: "Customer.list"
icon: "vaadin:star"
title:
en: "Loyalty levels"
resourceRoles:
- "employee"
- "manager"
descriptor:
template: "default"
- type: "detail"
viewId: "LoyaltyLevel.detail"
viewTitle:
en: "Loyalty level"
descriptor:
template: "default"
handlers:
- event: "initEntity"
expression: "entity.name = 'New level'"
- event: "valueChange"
componentId: "discountField"
expression: "components.setRequired('descriptionField', event.value != null)"
- type: "read"
viewId: "LoyaltyLevel.read"
viewTitle:
en: "Loyalty level"
Атрибуты
Каждый атрибут относится к одному из следующих видов, различающихся по тому, какое поле он определяет: атрибут типа данных, ссылочный атрибут, атрибут-коллекция или атрибут перечисления.
Атрибуты типа данных
Атрибут типа данных хранит простое значение, такое как строка, число, дата или булево значение. Вы определяете его, выбирая его тип, который хранится как javaClass. Для строковых атрибутов и атрибутов-массивов байтов вы также можете задать length и пометить их как большие объекты с помощью lob. Атрибут taxId, показанный в разделе Расширение статических сущностей, является атрибутом типа данных.
Полный список поддерживаемых типов см. в секции поддерживаемые типы.
Имя экземпляра
Отметьте Instance name для атрибута, чтобы его значение использовалось как имя экземпляра сущности. Сущность может иметь только один атрибут имени экземпляра. Если этот флаг уже установлен у другого атрибута, диалог атрибута показывает его имя, и установка флага переносит его на текущий атрибут. Вычисляемый атрибут тоже может быть именем экземпляра.
Ссылочные атрибуты
Ссылочный атрибут — это одиночная ссылка на другую сущность. Вы выбираете целевую сущность, и во время выполнения атрибут хранит ссылку на один её экземпляр.
- name: "loyaltyLevel"
entityName: "LoyaltyLevel"
lookup:
type: "DROPDOWN"
actions:
- "entity_lookup"
- "entity_clear"
messages:
en: "Loyalty level"
Поле выбора
Начиная с Jmix 3.1
Для одиночной ссылки можно выбрать UI-компонент, который сгенерированные экраны используют для выбора экземпляра, на который указывает ссылка. Задайте его в полях Lookup field type и Lookup actions диалога атрибута. Атрибут loyaltyLevel в примере выше использует выпадающий список.
Lookup field type может принимать следующие значения:
-
DROPDOWN– компонент entityComboBox. Элементы загружаются по мере ввода пользователя по имени экземпляра связанной сущности, поэтому этот тип подходит для больших таблиц. Если имя экземпляра связанной сущности не основано на строковых атрибутах, вместо этого поле использует экран выбора. -
VIEW– компонент entityPicker, который открывает экран выбора. Если действия не заданы, поле получает стандартные действия выбора и очистки.
Если тип не задан, используется компонент по умолчанию.
Lookup actions задаёт кнопки поля: entity_lookup, entity_open, entity_clear.
Эти настройки работают так же, как аннотация @LookupField на ссылочном атрибуте обычной сущности.
Атрибуты-коллекции
Атрибут-коллекция хранит упорядоченный список дочерних экземпляров (композицию). Целевая сущность должна быть динамической. Пометьте атрибут как коллекцию в редакторе, чтобы ссылка хранила несколько упорядоченных значений:
- name: "benefits"
entityName: "Benefit"
collection: true
messages:
en: "Benefits"
Перечисления
Динамическое перечисление определяет фиксированный набор именованных значений, которые затем могут использоваться атрибутами перечисления. Добавьте перечисление в редакторе, дайте ему имя и определите его значения с их хранимыми идентификаторами и подписями.
enumerations:
- name: "CustomerGrade"
messages:
en: "Customer grade"
values:
- name: "PLATINUM"
id: "30"
messages:
en: "Platinum"
- name: "GOLD"
id: "20"
messages:
en: "Gold"
- name: "BRONZE"
id: "10"
messages:
en: "Bronze"
Все идентификаторы значений в рамках одного перечисления должны быть одного вида: либо все целочисленные идентификаторы (как в примере выше), либо все строковые.
Вычисляемые атрибуты
Вычисляемый атрибут доступен только для чтения и вычисляется при обращении на основе выражения, а не хранится. Вы пишете выражение на Spring Expression Language (SpEL) и перечисляете атрибуты, от которых оно зависит, в dependsOn, чтобы они загружались вместе с ним.
- name: "publicSummary"
javaClass: "java.lang.String"
calculated:
evaluator: "spel"
expression: "(name ?: '') + ': ' + (description ?: '')"
dependsOn:
- "name"
- "description"
messages:
en: "Public summary"
Поскольку их значения не хранятся в базе данных, вычисляемые атрибуты нельзя использовать для фильтрации или сортировки на уровне базы данных.
Валидация
Вы можете прикрепить к атрибуту ограничения валидации, чтобы ограничить значения, которые он принимает. Каждое ограничение имеет тип (такой как Pattern, Size или DecimalMax), необязательные параметры и необязательное сообщение.
validation:
constraints:
- annotation: "Pattern"
parameters:
regexp: "^[A-Z0-9-]+$"
message:
en: "Tax ID can contain uppercase letters, digits and dashes only"
Также поддерживается валидация на уровне сущности, позволяющая ограничению охватывать несколько атрибутов сущности. Полный список ограничений и валидацию на уровне сущности см. в разделе Справочник по YAML.
Ограничения уникальности
Чтобы потребовать уникальности значения атрибута среди всех экземпляров, пометьте его как уникальный. Это сокращённая запись для ограничения уникальности по одному атрибуту:
unique: true
Чтобы потребовать уникальности комбинации нескольких атрибутов, определите составное ограничение уникальности для сущности:
uniqueConstraints:
- name: "customerCountryTaxIdUnique"
attributes:
- "countryCode"
- "taxId"
message:
en: "Tax ID must be unique within a country"
Уникальность обеспечивается ограничениями базы данных. Она не поддерживается для статических сущностей с мягким удалением.
Безопасность
Вы можете ограничить доступ к динамическим сущностям и атрибутам с помощью ресурсных ролей. Разрешения являются аддитивными — указание роли предоставляет ей соответствующий доступ, а доступ, который не предоставлен, запрещается. Указанные роли должны уже существовать в приложении; в приведённых ниже примерах используются иллюстративные роли employee и manager.
На уровне сущности вы предоставляете доступ read, create, update и delete. Это поддерживается для динамических сущностей:
resourceRoles:
read:
- "employee"
- "manager"
create:
- "manager"
update:
- "manager"
delete:
- "manager"
На уровне атрибута вы предоставляете доступ view и modify:
- name: "countryCode"
javaClass: "java.lang.String"
length: 2
resourceRoles:
view:
- "employee"
- "manager"
modify:
- "manager"
messages:
en: "Country code"
Подробнее о ресурсных ролях см. в разделе Ресурсные роли.
Доступ к настройкам модели
Дополнение предоставляет следующие ресурсные роли для экрана Dynamic model settings. Назначьте одну из них пользователям, которые работают с моделью.
| Имя роли | Код | Доступ |
|---|---|---|
Dynamic model: view settings |
|
Открытие настроек модели и истории модели. Модель нельзя изменить. |
Dynamic model: modify settings |
|
Полный доступ. Применение любых изменений, включая изменения, которые удаляют данные. |
Dynamic model: modify settings without data loss |
|
Применение только тех изменений, которые не могут привести к потере сохранённых данных. |
Изменения без потери данных
Начиная с Jmix 3.1
Роль "Dynamic model: modify settings without data loss" позволяет пользователям расширять модель без риска потерять сохранённые данные. Пользователь с этой ролью может редактировать модель в представлениях Visual и Code и применять следующие изменения: новые сущности, атрибуты, перечисления и значения перечислений, а также изменения валидации, экранов, пунктов меню, сообщений и ресурсных ролей.
Пользователь не может применять изменения, которые могут привести к потере данных:
-
удаление применённой сущности или атрибута;
-
переименование применённой сущности или атрибута в YAML, так как переименование применяется как удаление и создание;
-
удаление применённого перечисления;
-
удаление значения из применённого перечисления.
В представлении Visual применённые элементы удалить нельзя. Элементы, добавленные после последнего применения, по-прежнему можно удалить. Если редактируемая модель содержит недопустимые изменения, экран Dynamic model changes показывает их список, и модель нельзя применить, пока вы не удалите эти изменения.
Ограничение проверяется на сервере. Его нельзя обойти, сохранив настройки модели другим способом, например через инспектор сущностей.