[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:
Didier (did)
2020-11-16 13:41:13 +00:00
parent ea5131c18f
commit 66de13d461
18 changed files with 83 additions and 19 deletions
@@ -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.
*
+3 -1
View File
@@ -203,7 +203,9 @@ function factory(dependencies) {
}
User.fields = {
id: attr(),
id: attr({
required: true,
}),
display_name: attr({
compute: '_computeDisplayName',
dependencies: [