diff --git a/addons/mail/models/res_users.py b/addons/mail/models/res_users.py index 00914aa494c..4e4a6ab890b 100644 --- a/addons/mail/models/res_users.py +++ b/addons/mail/models/res_users.py @@ -24,9 +24,6 @@ class Users(models.Model): help="Policy on how to handle Chatter notifications:\n" "- Handle by Emails: notifications are sent to your email address\n" "- Handle in Odoo: notifications appear in your Odoo Inbox") - res_users_settings_ids = fields.One2many('res.users.settings', 'user_id') - # Provide a target for relateds that is not a x2Many field. - res_users_settings_id = fields.Many2one('res.users.settings', string="Settings", compute='_compute_res_users_settings_id', search='_search_res_users_settings_id') _sql_constraints = [( "notification_type", @@ -62,15 +59,6 @@ class Users(models.Model): else: user.groups_id -= inbox_group - @api.depends('res_users_settings_ids') - def _compute_res_users_settings_id(self): - for user in self: - user.res_users_settings_id = user.res_users_settings_ids and user.res_users_settings_ids[0] - - @api.model - def _search_res_users_settings_id(self, operator, operand): - return [('res_users_settings_ids', operator, operand)] - # ------------------------------------------------------------ # CRUD # ------------------------------------------------------------ diff --git a/addons/mail/models/res_users_settings.py b/addons/mail/models/res_users_settings.py index 5fd719bee3a..7eb92b0c66d 100644 --- a/addons/mail/models/res_users_settings.py +++ b/addons/mail/models/res_users_settings.py @@ -5,11 +5,8 @@ from odoo import api, fields, models class ResUsersSettings(models.Model): - _name = 'res.users.settings' - _description = 'User Settings' - _rec_name = 'user_id' + _inherit = 'res.users.settings' - user_id = fields.Many2one('res.users', string="User", required=True, readonly=True, ondelete='cascade') is_discuss_sidebar_category_channel_open = fields.Boolean(string="Is discuss sidebar category channel open?", default=True) is_discuss_sidebar_category_chat_open = fields.Boolean(string="Is discuss sidebar category chat open?", default=True) @@ -19,37 +16,18 @@ class ResUsersSettings(models.Model): voice_active_duration = fields.Integer(string="Duration of voice activity in ms", help="How long the audio broadcast will remain active after passing the volume threshold") volume_settings_ids = fields.One2many('res.users.settings.volumes', 'user_setting_id', string="Volumes of other partners") - _sql_constraints = [ - ('unique_user_id', 'UNIQUE(user_id)', 'One user should only have one mail user settings.') - ] - @api.model - def _find_or_create_for_user(self, user): - settings = user.sudo().res_users_settings_ids - if not settings: - settings = self.sudo().create({'user_id': user.id}) - return settings - - def _res_users_settings_format(self, fields_to_format=None): - self.ensure_one() - if not fields_to_format: - fields_to_format = [name for name, field in self._fields.items() if name == 'id' or not field.automatic] - res = self._read_format(fnames=fields_to_format)[0] - if 'user_id' in fields_to_format: - res['user_id'] = {'id': self.user_id.id} + def _format_settings(self, fields_to_format): + res = super()._format_settings(fields_to_format) if 'volume_settings_ids' in fields_to_format: volume_settings = self.volume_settings_ids._discuss_users_settings_volume_format() res['volume_settings_ids'] = [('insert', volume_settings)] return res def set_res_users_settings(self, new_settings): - self.ensure_one() - changed_settings = {} - for setting in new_settings.keys(): - if setting in self._fields and new_settings[setting] != self[setting]: - changed_settings[setting] = new_settings[setting] - self.write(changed_settings) - self.env['bus.bus']._sendone(self.user_id.partner_id, 'mail.record/insert', {'res.users.settings': self._res_users_settings_format([*changed_settings.keys(), 'id'])}) + formated = super().set_res_users_settings(new_settings) + self.env['bus.bus']._sendone(self.user_id.partner_id, 'mail.record/insert', {'res.users.settings': formated}) + return formated def set_volume_setting(self, partner_id, volume, guest_id=None): """ diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 29c55416c9d..3123352f48f 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -59,8 +59,6 @@ access_mail_guest_user,mail.guest,model_mail_guest,base.group_user,1,0,0,0 access_mail_guest_system,mail.guest,model_mail_guest,base.group_system,1,1,1,1 access_mail_ice_server_all,mail.ice.server.all,model_mail_ice_server,,0,0,0,0 access_mail_ice_server_system,mail.ice.server.system,model_mail_ice_server,base.group_system,1,1,1,1 -access_res_users_settings_all,res.users.settings,model_res_users_settings,,0,0,0,0 -access_res_users_settings_user,res.users.settings,model_res_users_settings,base.group_user,1,1,1,1 access_res_users_settings_volumes_all,res.users.settings.volumes,model_res_users_settings_volumes,,0,0,0,0 access_res_users_settings_volumes_user,res.users.settings.volumes,model_res_users_settings_volumes,base.group_user,1,1,1,1 access_mail_template_reset,access.mail.template.reset,model_mail_template_reset,mail.group_mail_template_editor,1,1,1,1 diff --git a/addons/web/models/ir_http.py b/addons/web/models/ir_http.py index 373d4817b57..8ffee516815 100644 --- a/addons/web/models/ir_http.py +++ b/addons/web/models/ir_http.py @@ -95,6 +95,7 @@ class Http(models.AbstractModel): "is_admin": user._is_admin() if session_uid else False, "user_context": user_context, "db": self.env.cr.dbname, + "user_settings": self.env['res.users.settings']._find_or_create_for_user(user)._res_users_settings_format(), "server_version": version_info.get('server_version'), "server_version_info": version_info.get('server_version_info'), "support_url": "https://www.odoo.com/buy", diff --git a/addons/web/static/src/core/user_service.js b/addons/web/static/src/core/user_service.js index 82f4ef3026f..08f175d1117 100644 --- a/addons/web/static/src/core/user_service.js +++ b/addons/web/static/src/core/user_service.js @@ -33,6 +33,8 @@ export const userService = { ...session.user_context, uid: session.uid, }; + let settings = session.user_settings; + delete session.user_settings; return { get context() { return Object.assign({}, context); @@ -49,7 +51,21 @@ export const userService = { async checkAccessRight(model, operation) { return accessRightCache.read(model, operation); }, - + get settings() { + return settings; + }, + async setUserSettings(key, value) { + settings = await env.services.orm.call( + "res.users.settings", + "set_res_users_settings", + [[this.settings.id]], + { + new_settings: { + [key]: value, + }, + } + ); + }, name: session.name, userName: session.username, isAdmin: session.is_admin, diff --git a/addons/web/static/src/webclient/menus/menu_helpers.js b/addons/web/static/src/webclient/menus/menu_helpers.js index a73a311c5b8..e086dcb3aa8 100644 --- a/addons/web/static/src/webclient/menus/menu_helpers.js +++ b/addons/web/static/src/webclient/menus/menu_helpers.js @@ -61,3 +61,26 @@ export function computeAppsAndMenuItems(menuTree) { }); return { apps, menuItems }; } + +/** + * @param {Array} order + * Sorts the apps in the homescreen menu according to the given order as an array of xmlid strings + */ +export function reorderApps(apps, order) { + apps.sort((a, b) => { + const aIndex = order.indexOf(a.xmlid); + const bIndex = order.indexOf(b.xmlid); + if (aIndex === -1 && bIndex === -1) { + // if both items are not present, sort by original order + return apps.indexOf(a) - apps.indexOf(b); + } + // not found items always before found ones + if (aIndex === -1) { + return -1; + } + if (bIndex === -1) { + return 1; + } + return aIndex - bIndex; // sort by order array + }); +} diff --git a/odoo/addons/base/models/__init__.py b/odoo/addons/base/models/__init__.py index 86fe94b5ba0..673a8fa0643 100644 --- a/odoo/addons/base/models/__init__.py +++ b/odoo/addons/base/models/__init__.py @@ -43,6 +43,7 @@ from . import res_config from . import res_currency from . import res_company from . import res_users +from . import res_users_settings from . import res_users_deletion from . import decimal_precision diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index c4e71badf71..5c78215311c 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -326,6 +326,9 @@ class Users(models.Model): help="External user with limited access, created only for the purpose of sharing data.") companies_count = fields.Integer(compute='_compute_companies_count', string="Number of Companies") tz_offset = fields.Char(compute='_compute_tz_offset', string='Timezone offset', invisible=True) + res_users_settings_ids = fields.One2many('res.users.settings', 'user_id') + # Provide a target for relateds that is not a x2Many field. + res_users_settings_id = fields.Many2one('res.users.settings', string="Settings", compute='_compute_res_users_settings_id', search='_search_res_users_settings_id') # Special behavior for this field: res.company.search() will only return the companies # available to the current user (should be the user's companies?), when the user_preference @@ -459,6 +462,15 @@ class Users(models.Model): user.rules_count = len(groups.rule_groups) user.groups_count = len(groups) + @api.depends('res_users_settings_ids') + def _compute_res_users_settings_id(self): + for user in self: + user.res_users_settings_id = user.res_users_settings_ids and user.res_users_settings_ids[0] + + @api.model + def _search_res_users_settings_id(self, operator, operand): + return [('res_users_settings_ids', operator, operand)] + @api.onchange('login') def on_change_login(self): if self.login and tools.single_email_re.match(self.login): diff --git a/odoo/addons/base/models/res_users_settings.py b/odoo/addons/base/models/res_users_settings.py new file mode 100644 index 00000000000..0e40edbbed6 --- /dev/null +++ b/odoo/addons/base/models/res_users_settings.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class ResUsersSettings(models.Model): + _name = 'res.users.settings' + _description = 'User Settings' + _rec_name = 'user_id' + + user_id = fields.Many2one('res.users', string="User", required=True, readonly=True, ondelete='cascade') + + _sql_constraints = [ + ('unique_user_id', 'UNIQUE(user_id)', 'One user should only have one user settings.') + ] + + @api.model + def _find_or_create_for_user(self, user): + settings = user.sudo().res_users_settings_ids + if not settings: + settings = self.sudo().create({'user_id': user.id}) + return settings + + def _res_users_settings_format(self, fields_to_format=None): + self.ensure_one() + if not fields_to_format: + fields_to_format = [name for name, field in self._fields.items() if name == 'id' or not field.automatic] + res = self._format_settings(fields_to_format) + return res + + def _format_settings(self, fields_to_format): + res = self._read_format(fnames=fields_to_format)[0] + if 'user_id' in fields_to_format: + res = self._read_format(fnames=fields_to_format)[0] + res['user_id'] = {'id': self.user_id.id} + return res + + def set_res_users_settings(self, new_settings): + self.ensure_one() + changed_settings = {} + for setting in new_settings.keys(): + if setting in self._fields and new_settings[setting] != self[setting]: + changed_settings[setting] = new_settings[setting] + self.write(changed_settings) + formated = self._res_users_settings_format([*changed_settings.keys(), 'id']) + return formated diff --git a/odoo/addons/base/security/ir.model.access.csv b/odoo/addons/base/security/ir.model.access.csv index 5ef2ab83234..a0662bcb692 100644 --- a/odoo/addons/base/security/ir.model.access.csv +++ b/odoo/addons/base/security/ir.model.access.csv @@ -78,6 +78,8 @@ "access_res_users_apikeys_description_employee","API key wizard employees","model_res_users_apikeys_description","group_user",1,0,1,0 "access_res_users_apikeys_description_portal","API key wizard","model_res_users_apikeys_description","group_portal",1,0,1,0 "access_res_users_apikeys_show_employee","API key result employees","model_res_users_apikeys_show","group_user",1,0,1,0 +access_res_users_settings_all,res.users.settings,model_res_users_settings,,0,0,0,0 +access_res_users_settings_user,res.users.settings,model_res_users_settings,group_user,1,1,1,1 "access_ir_asset_group_system","ir_asset_group_system","model_ir_asset","group_system",1,1,1,1 "access_ir_actions_group_system","ir_actions_group_system","model_ir_actions_actions","group_system",1,1,1,1 "access_ir_actions_act_window_system","ir_actions_act_window_system","model_ir_actions_act_window","group_system",1,1,1,1