[IMP] mail,hr: implement required field in JS models
Fields can now be marqued as required inside a field definition. If the field
value is `undefined` or updated to `undefined`, an error will be throw.
```
stringifiedDomain: attr({
default: '[]',
required: true,
}),
```
task-2323665
closes odoo/odoo#59094
Related: odoo/enterprise#13883
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
@@ -178,7 +178,9 @@ function factory(dependencies) {
|
||||
/**
|
||||
* Unique identifier for this employee.
|
||||
*/
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Partner related to this employee.
|
||||
*/
|
||||
|
||||
@@ -27,6 +27,7 @@ class ModelField {
|
||||
isCausal = false,
|
||||
related,
|
||||
relationType,
|
||||
required = false,
|
||||
to,
|
||||
} = {}) {
|
||||
const id = _.uniqueId('field_');
|
||||
@@ -151,6 +152,13 @@ class ModelField {
|
||||
* and 'many2many'.
|
||||
*/
|
||||
this.relationType = relationType;
|
||||
/**
|
||||
* Determine whether the field is required or not.
|
||||
*
|
||||
* Empty value is systematically undefined.
|
||||
* null or empty string are NOT considered empty value, meaning these values meet the requirement.
|
||||
*/
|
||||
this.required = required;
|
||||
/**
|
||||
* This prop only makes sense in a relational field. Determine which
|
||||
* model name this relation refers to.
|
||||
|
||||
@@ -307,6 +307,7 @@ class ModelManager {
|
||||
'dependencies',
|
||||
'fieldType',
|
||||
'related',
|
||||
'required',
|
||||
].includes(key)
|
||||
);
|
||||
if (invalidKeys.length > 0) {
|
||||
@@ -324,6 +325,7 @@ class ModelManager {
|
||||
'isCausal',
|
||||
'related',
|
||||
'relationType',
|
||||
'required',
|
||||
'to',
|
||||
].includes(key)
|
||||
);
|
||||
@@ -336,6 +338,9 @@ class ModelManager {
|
||||
if (field.isCausal && !(['one2many', 'one2one'].includes(field.relationType))) {
|
||||
throw new Error(`Relational field "${Model.modelName}/${fieldName}" has "isCausal" true with a relation of type "${field.relationType}" but "isCausal" is only supported for "one2many" and "one2one".`);
|
||||
}
|
||||
if (field.required && !(['one2one', 'many2one'].includes(field.relationType))) {
|
||||
throw new Error(`Relational field "${Model.modelName}/${fieldName}" has "required" true with a relation of type "${field.relationType}" but "required" is only supported for "one2one" and "many2one".`);
|
||||
}
|
||||
}
|
||||
// 3. Computed field.
|
||||
if (field.compute && !(typeof field.compute === 'string')) {
|
||||
@@ -708,6 +713,16 @@ class ModelManager {
|
||||
for (const record of this._updatedRecords) {
|
||||
record.__state++;
|
||||
}
|
||||
|
||||
// handle required field.
|
||||
for (const record of this._updatedRecords) {
|
||||
for (const required of record.constructor.__requiredFieldsList) {
|
||||
if (record[required.fieldName] === undefined) {
|
||||
throw Error(`Field ${required.fieldName} of ${record.localId} is required.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._updatedRecords.clear();
|
||||
|
||||
// Trigger at most one useStore call per update cycle
|
||||
@@ -977,6 +992,9 @@ class ModelManager {
|
||||
Model.__fieldMap = Model.__combinedFields;
|
||||
// List of all fields, for iterating.
|
||||
Model.__fieldList = Object.values(Model.__fieldMap);
|
||||
Model.__requiredFieldsList = Model.__fieldList.filter(
|
||||
field => field.required
|
||||
);
|
||||
// Add field accessors.
|
||||
for (const field of Model.__fieldList) {
|
||||
Object.defineProperty(Model.prototype, field.fieldName, {
|
||||
|
||||
@@ -283,7 +283,9 @@ function factory(dependencies) {
|
||||
default: false,
|
||||
}),
|
||||
icon: attr(),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
isCurrentPartnerAssignee: attr({
|
||||
compute: '_computeIsCurrentPartnerAssignee',
|
||||
default: false,
|
||||
|
||||
@@ -26,7 +26,9 @@ function factory(dependencies) {
|
||||
inverse: 'type',
|
||||
}),
|
||||
displayName: attr(),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
ActivityType.modelName = 'mail.activity_type';
|
||||
|
||||
@@ -321,7 +321,9 @@ function factory(dependencies) {
|
||||
'url',
|
||||
],
|
||||
}),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
isLinkedToComposer: attr({
|
||||
compute: '_computeIsLinkedToComposer',
|
||||
dependencies: ['composers'],
|
||||
|
||||
@@ -41,7 +41,9 @@ function factory(dependencies) {
|
||||
'code',
|
||||
],
|
||||
}),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
name: attr(),
|
||||
};
|
||||
|
||||
|
||||
@@ -239,7 +239,9 @@ function factory(dependencies) {
|
||||
followedThread: many2one('mail.thread', {
|
||||
inverse: 'followers',
|
||||
}),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
isActive: attr({
|
||||
default: true,
|
||||
}),
|
||||
|
||||
@@ -57,7 +57,9 @@ function factory(dependencies) {
|
||||
}
|
||||
|
||||
FollowerSubtype.fields = {
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
isDefault: attr({
|
||||
default: false,
|
||||
}),
|
||||
|
||||
@@ -69,7 +69,9 @@ function factory(dependencies) {
|
||||
activities: many2many('mail.activity', {
|
||||
inverse: 'mailTemplates',
|
||||
}),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
name: attr(),
|
||||
};
|
||||
|
||||
|
||||
@@ -553,7 +553,9 @@ function factory(dependencies) {
|
||||
default: false,
|
||||
dependencies: ['isModeratedByCurrentPartner'],
|
||||
}),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
isCurrentPartnerAuthor: attr({
|
||||
compute: '_computeIsCurrentPartnerAuthor',
|
||||
default: false,
|
||||
|
||||
@@ -262,7 +262,9 @@ function factory(dependencies) {
|
||||
* (required fields) should improve and let us just use the relational
|
||||
* fields.
|
||||
*/
|
||||
channelId: attr(),
|
||||
channelId: attr({
|
||||
required: true,
|
||||
}),
|
||||
hasEveryoneFetched: attr({
|
||||
compute: '_computeHasEveryoneFetched',
|
||||
default: false,
|
||||
@@ -321,7 +323,9 @@ function factory(dependencies) {
|
||||
* (required fields) should improve and let us just use the relational
|
||||
* fields.
|
||||
*/
|
||||
messageId: attr(),
|
||||
messageId: attr({
|
||||
required: true,
|
||||
}),
|
||||
partnersThatHaveFetched: many2many('mail.partner', {
|
||||
compute: '_computePartnersThatHaveFetched',
|
||||
dependencies: ['messageAuthor', 'messageId', 'threadPartnerSeenInfos'],
|
||||
|
||||
@@ -61,7 +61,9 @@ function factory(dependencies) {
|
||||
|
||||
Notification.fields = {
|
||||
failure_type: attr(),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
message: many2one('mail.message', {
|
||||
inverse: 'notifications',
|
||||
}),
|
||||
|
||||
@@ -98,7 +98,9 @@ function factory(dependencies) {
|
||||
|
||||
NotificationGroup.fields = {
|
||||
date: attr(),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
notification_type: attr(),
|
||||
notifications: one2many('mail.notification'),
|
||||
res_id: attr(),
|
||||
|
||||
@@ -311,7 +311,9 @@ function factory(dependencies) {
|
||||
hasCheckedUser: attr({
|
||||
default: false,
|
||||
}),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
im_status: attr(),
|
||||
memberThreads: many2many('mail.thread', {
|
||||
inverse: 'members',
|
||||
|
||||
@@ -1653,7 +1653,9 @@ function factory(dependencies) {
|
||||
'model',
|
||||
],
|
||||
}),
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* States whether this thread is a `mail.channel` qualified as chat.
|
||||
*
|
||||
@@ -1834,7 +1836,9 @@ function factory(dependencies) {
|
||||
messagingCurrentPartner: many2one('mail.partner', {
|
||||
related: 'messaging.currentPartner',
|
||||
}),
|
||||
model: attr(),
|
||||
model: attr({
|
||||
required: true,
|
||||
}),
|
||||
model_name: attr(),
|
||||
moderation: attr({
|
||||
default: false,
|
||||
|
||||
@@ -59,7 +59,9 @@ function factory(dependencies) {
|
||||
* (required fields) should improve and let us just use the relational
|
||||
* fields.
|
||||
*/
|
||||
channelId: attr(),
|
||||
channelId: attr({
|
||||
required: true,
|
||||
}),
|
||||
lastFetchedMessage: many2one('mail.message'),
|
||||
lastSeenMessage: many2one('mail.message'),
|
||||
/**
|
||||
@@ -87,7 +89,9 @@ function factory(dependencies) {
|
||||
* (required fields) should improve and let us just use the relational
|
||||
* fields.
|
||||
*/
|
||||
partnerId: attr(),
|
||||
partnerId: attr({
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Thread (channel) that this seen info is related to.
|
||||
*
|
||||
|
||||
@@ -203,7 +203,9 @@ function factory(dependencies) {
|
||||
}
|
||||
|
||||
User.fields = {
|
||||
id: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
}),
|
||||
display_name: attr({
|
||||
compute: '_computeDisplayName',
|
||||
dependencies: [
|
||||
|
||||
Reference in New Issue
Block a user