22. Desarrollando con Odoo (17). ORM de atributos many2many y one2many. Arreglando el módulo hide_menu_partner de Cybrosys
0. Manejar atributos many2many y one2many por ORM
Supongamos que tenemos :
- un registro existente llamado "record" con un atributo llamado "m2m_padre" tipo many2many y otro atributo "o2m_padre" de tipo one3many
- otro registro existente llamado "related" con su atributo "id" que es que forma parte de las relaciones anterior, y que tiene el atributo "m2m_hijo" many2many que engancha con "m2m_padre" y "m2o_hijo" que engancha con "o2m_padre"
Veamos como hacemos las operaciones CRUD
1. Vincular el registro related a la relación many2many
record.m2m = [ ( 4, related.id ) ]
2. Vincular el registro related a la relación one2many
La única manera es vincularlo desde el hijo related.
related.m2o_hijo = record
3. Desvincular el registro related de la relación many2many
record.m2m = [ ( 3, related.id ) ]
4. Desvincular el registro related a la relación one2many
La única manera es desvincularlo desde el hijo related.
related.m2o_hijo = another_record
5. Otras opciones que yo no recomiendo
# 1. Valores posibles de "_" # 0 o False si está en medio (2ª posición) # Eliminar si está al final (3ª posición) # 2. No recomendables (0, _ , values) adds a new record created from the provided value dict. (1, id, values) updates an existing record of id id with the values in values. Can not be used in create(). (2, id, _ ) removes the record of id id from the set, then deletes it (from the database). Can not be used in create(). (5, _ , _ ) removes all records from the set, equivalent to using the command 3 on every record explicitly. Can not be used on One2many. Can not be used in create(). (6, _ , ids ) replaces all existing records in the set by the ids list, equivalent to using the command 5 followed by a command 4 for each id in ids. # Hemo visto; (3, id, _ ) removes the record of id id from the set, but does not delete it. Can not be used on One2many. Can not be used in create(). (4, id, _ ) adds an existing record of id id to the set. Can not be used on One2many.
1. Recordatorio de la relación many2many
# TodoTask class: Task <-> Tag relation (long form): tag_ids = fields.Many2many( comodel_name='todo.task.tag', # related model relation='todo_task_tag_rel', # relation table name column1='task_id', # field for "this" record column2='tag_id', # field for "other" record string='Tasks')
#--------------------------------------- # The inverse relation between tasks and tags can be implemented like this: # class Tag(models.Model): # _name = 'todo.task.tag' # Tag class relation to Tasks: #--------------------------------------- task_ids = fields.Many2many( comodel_name='todo.task', # related modelstring='Tasks')
2. Arreglar el módulo hide_menu_partner de Cybrosys
Queremos mostrar:
- Una pestaña en los usarios que se vean los menús que son accessibles en base a los grupos a los que pertenec
- Un campo booleano calculado que diga si está oculto o no dicho menú al usuario.
- Un botón de mostrar / Ocultar menú
Para ello, vamos a:
1. Separar los modelos y las vistas en ficheros individuales
2. Modificar el modelo RestrictMenu:
- Añadir el campo calculado is_user_hidden, de tipo booleano que indicará si el menú está oculto al usuario.
- Añadir la función de cálculo de dicho campo _get_is_user_hidden
- Añadir la función de ocultación de un menú a un usuario _do_hide_this_menu
- Añadir la función de des-ocultación de un menú a un usuario _do_show_this_menu
3. Modificar el modelo HideMenuUser:
- Añadir el campo calculado all_user_menus_ids, de tipo one2many calculado para recoger los menús que son accessibles por el ususario por su pertenencia al grupo
- Añadir la función de cálculo de dicho campo _get_all_user_menus
4. Modificar la vista de HideMenuUser:
- Añadiendo la vista de RestrictMenus en una pestaña indicando si está oculto el menú o no
- Añadir boton de ocultar / mostrar a cada RestrictMenu
- Añadir una pestaña para mostrar los grupos a los que pertenece el usuario (peligrosa)
2.1 Separar vistas y modelos
Ya se vió en apartados anteriores.
Para las vistas, hay que añadir cada fichero al __manifest__.py que cuelga de la carpeta hide_menu_user modificando el apartado data:
'data': [ #'views/res_users.xml', 'views/hide_menu_user.xml', 'views/restrict_menu.xml', 'security/security.xml' ],
Para los modelos, hay que añadir cada fichero al __init__.py que cuelga de la carpeta hide_menu_user/models
#from . import res_user from . import hide_menu_user from . import restrict_menu
2.2 Modificar el modelo RestrictMenu
2.2.1 Crear un campo calculado "is_user_hidden" y su función de calculo leyendo el contexto.
El objetivo es obtener desde el context el usuario que ha llamado al form (variable "current_user"). Si este usuario se encuentra en la colección restrict_user_ids del menu entonces estará oculto.
Para ello nos cremos el campo calculado "is_user_hidden" y una función de cálculo "get_is_user_hidden":
Para ver si el usuario tiene el menú oculto, basta con ver si el "id" del usuario se puede localizar en el campo "id" de todos los usuarios que se encuentran en la relación restrict_user_ids
def _get_is_user_hidden(self): users= self.browse(self._context.get('current_user')) user=False if (users) and len(users)>0: user=users[0] for record in self: isHidden=False; if user: _logger.info(record.name + " " + str(record.restrict_user_ids)) if (record.restrict_user_ids and len(record.restrict_user_ids)>0): if any(x.id == user.id for x in record.restrict_user_ids): isHidden=True _logger.info(" isHidden=True") record.is_user_hidden=isHidden is_user_hidden =fields.Boolean(string="Oculto al Usuario", compute="_get_is_user_hidden", store=False)
2.2.2 Crear las funciones de los botones de ocultar/mostrar menu
Creamos las funciones de dichos botones:
Para ocultar un menú, el usuario del contexto se incluirá en el campo restrict_user_ids solo si NO estaba incluido. Debemos recordar que para vincular el id de usuario a la relación utilizabamos la opción 4
Para mostrar el menú, el usuario del contexto se eliminará del campo restrict_user_ids solo si estaba incluido.Debemos recordar que para desvincular el id de usuario a la relación utilizabamos la opción 3
def do_hide_this_menu(self): users= self.browse(self._context.get('current_user')) ; userName='No User' ; userId=-1000 if (users) and len(users)>0: user=users[0] ; userName=user.name ; userId=user.id for record in self: toHide=False if user: if (not record.restrict_user_ids or len(record.restrict_user_ids)==0): toHide=True else: if not any(x.id == userId for x in record.restrict_user_ids): toHide=True if toHide: record.restrict_user_ids = [(4,user.id)] # 4= Link _logger.info('1. User.id=' + str(userId)+ ' user.name=' + userName + ' in menu:' + record.name + '('+ str(record.id) +')' ) else: _logger.warn('2. User '+userName + '('+ str(userId) +') already hidden in menu:' + record.name + '('+ str(record.id) +')' ) else: _logger.warn('3. User '+userName + '('+ str(userId) +') cannot be hidden in menu:' + record.name + '('+ str(record.id) +')' ) def do_show_this_menu(self): users= self.browse(self._context.get('current_user')) ; userName='No User' ; userId=-1000 if (users) and len(users)>0: user=users[0] ; userName=user.name ; userId=user.id for record in self: toShow=False if user: if (record.restrict_user_ids and len(record.restrict_user_ids)>0): if any(x.id == userId for x in record.restrict_user_ids): toShow=True if toShow: record.restrict_user_ids = [(3,user.id)] # 3 = Unlink _logger.info('2. do_show_this_menu from restrict_menu menu.id='+ str(record.id) + " menu.name="+ record.name + " user id="+ str(user.id) + ' user.name= ' + user.name ) else: _logger.warn('2. User '+userName + '('+ str(userId) +') already showed in menu:' + record.name + '('+ str(record.id) +')' ) else: _logger.warn('4. No user specified to hide menu ' )
Al final el modelo entero RestrictMenu.py quedaría:
from odoo import models, fields, api import logging _logger = logging.getLogger(__name__) # Recoge la información del fichero "odoo.conf" class RestrictMenu(models.Model): _inherit = 'ir.ui.menu' restrict_user_ids = fields.Many2many('res.users') #---- Ximo BEGIN CODE ----- def _get_is_user_hidden(self): users= self.browse(self._context.get('current_user')) user=False if (users) and len(users)>0: user=users[0] for record in self: isHidden=False; if user: _logger.info(record.name + " " + str(record.restrict_user_ids)) if (record.restrict_user_ids and len(record.restrict_user_ids)>0): if any(x.id == user.id for x in record.restrict_user_ids): isHidden=True _logger.info(" isHidden=True") record.is_user_hidden=isHidden is_user_hidden =fields.Boolean(string="Oculto al Usuario", compute="_get_is_user_hidden", store=False) # -- Action forn hide menu botton def do_hide_this_menu(self): users= self.browse(self._context.get('current_user')) ; userName='No User' ; userId=-1000 if (users) and len(users)>0: user=users[0] ; userName=user.name ; userId=user.id for record in self: toHide=False if user: if (not record.restrict_user_ids or len(record.restrict_user_ids)==0): toHide=True else: if not any(x.id == userId for x in record.restrict_user_ids): toHide=True if toHide: record.restrict_user_ids = [(4,user.id)] # 4= Add _logger.info('1. User.id=' + str(userId)+ ' user.name=' + userName + ' in menu:' + record.name + '('+ str(record.id) +')' ) else: _logger.warn('2. User '+userName + '('+ str(userId) +') already hidden in menu:' + record.name + '('+ str(record.id) +')' ) else: _logger.warn('3. User '+userName + '('+ str(userId) +') cannot be hidden in menu:' + record.name + '('+ str(record.id) +')' ) # -- Action for show Menu button def do_show_this_menu(self): users= self.browse(self._context.get('current_user')) ; userName='No User' ; userId=-1000 if (users) and len(users)>0: user=users[0] ; userName=user.name ; userId=user.id for record in self: toShow=False if user: if (record.restrict_user_ids and len(record.restrict_user_ids)>0): if any(x.id == userId for x in record.restrict_user_ids): toShow=True if toShow: record.restrict_user_ids = [(3,user.id)] # 3 = Unlink _logger.info('2. do_show_this_menu from restrict_menu menu.id='+ str(record.id) + " menu.name="+ record.name + " user id="+ str(user.id) + ' user.name= ' + user.name ) else: _logger.warn('2. User '+userName + '('+ str(userId) +') already showed in menu:' + record.name + '('+ str(record.id) +')' ) else: _logger.warn('4. No user specified to hide menu ' ) #---- Ximo END CODE-----
2.3 Modificar el modelo HideMenuUser
2.2.1 Crear un campo calculado "all_user_menus_ids" y su función de cálculo.
Para ello buscamos los grupos a los que pertenece el usuario y después buscamos los menús cuyos grupos tengan alguno de los grupos a los que pertenece el usuario. Para no duplicar menús utilizamos un "set" o conjunto
# INICI: Codi meu @api.depends("groups_id") def _get_all_user_menus(self): for record in self: menuSet=set(()) groups=self.env['res.groups'].search([('id', 'in', record.groups_id.ids)]) for group in groups: menus=self.env["ir.ui.menu"].search([('groups_id', 'in', [group.id])]) for menu in menus: menuSet.add(menu.id) record.all_user_menus_ids = list(menuSet) all_user_menus_ids = fields.One2many(comodel_name='ir.ui.menu', compute="_get_all_user_menus", string='All user menus', store=False) # FI: Codi meu
Al final HideMenuUser.py queda:
from odoo import models, fields, api import logging _logger = logging.getLogger(__name__) # Recoge la información del fichero "odoo.conf" class HideMenuUser(models.Model): _inherit = 'res.users' @api.model_create_multi def create(self, vals_list): """ Else the menu will be still hidden even after removing from the list """ self.clear_caches() return super(HideMenuUser, self).create(vals_list) def write(self, vals): """ Else the menu will be still hidden even after removing from the list """ res = super(HideMenuUser, self).write(vals) for record in self: for menu in record.hide_menu_ids: menu.write({ 'restrict_user_ids': [(4, record.id)] }) self.clear_caches() return res def _get_is_admin(self): """ The Hide specific menu tab will be hidden for the Admin user form. Else once the menu is hidden, it will be difficult to re-enable it. """ for rec in self: rec.is_admin = False if rec.id == self.env.ref('base.user_admin').id: rec.is_admin = True hide_menu_ids = fields.Many2many('ir.ui.menu', string="Menu", store=True, help='Select menu items that needs to be ' 'hidden to this user ') is_admin = fields.Boolean(compute=_get_is_admin, string="Admin") # INICI: Codi meu @api.depends("groups_id") def _get_all_user_menus(self): for record in self: menuSet=set(()) #lstGroupIds=record.groups_id.ids groups=self.env['res.groups'].search([('id', 'in', record.groups_id.ids)]) for group in groups: menus=self.env["ir.ui.menu"].search([('groups_id', 'in', [group.id])]) for menu in menus: menuSet.add(menu.id) record.all_user_menus_ids = list(menuSet) all_user_menus_ids = fields.One2many(comodel_name='ir.ui.menu', compute="_get_all_user_menus", string='All user menus', store=False) # FI: Codi meu
2.4. Modificar la vista de HideMenuUser
El objetivo es pasar el usuario actual al context en el campo "all_user_menus_id". Para ello en el context nos creamos la variable "current_user" que guardará el usuario que se expresa como "active_id". Tambien metemos algunos campos adicionales y los botones de ocultar y mostrar menús, too ello en negrita. Veamos todo el fichero hide_menu_user.xml:
<odoo> <data> <record id="hide_user_menu" model="ir.ui.view"> <field name="name">hide.menu</field> <field name="model">res.users</field> <field name="inherit_id" ref="base.view_users_form"/> <field name="arch" type="xml"> <xpath expr="//notebook" position="inside"> <page string="Hide Specific Menu" attrs="{'invisible': [('is_admin','=', True)]}"> <field name="all_user_menus_ids" context="{'current_user':active_id}" readonly="False" > <tree> <field name= "name"/> <field name= "is_user_hidden" /> <field name= "id" /> <button name="do_hide_this_menu" type="object" icon= "fa-eye-slash" string="Hide this menu" attrs="{'invisible': [('is_user_hidden','=', True)]}"/> <button name="do_show_this_menu" type="object" icon= "fa-eye" string="Show this menu" attrs="{'invisible': [('is_user_hidden','=', False)]}"/> </tree> </field> </page> </xpath> <xpath expr="//notebook" position="inside"> <!-- <page string="My groups" attrs="{'invisible': [('is_admin','=', True)]}"> --> <page string="My groups Ximo DANGEROUS !!!!!!" > <tree> <field name="groups_id"/> <!-- context="{'form_view_ref':'ir_ui_menu_users', 'current_user':active_id}"> --> </tree> </page> </xpath> <field name="name" position="after"> <field name="is_admin" invisible="1"/> </field> </field> </record> </data> </odoo>
2.5. Modificar la vista de RestrictMenuUser
A la vista de menús se le ha añadido los usuarios a los que se ha restringido el uso del menú. Al final el fichero restrict_menu_user.xml es:
<odoo> <data> <record model="ir.ui.view" id="ir_ui_menu_users_list"> <field name="name">restrict.menu</field> <field name="model">ir.ui.menu</field> <field name="inherit_id" ref="base.edit_menu"/> <field name="arch" type="xml"> <xpath expr="//field[@name='complete_name']" position="after"> <field name="is_user_hidden"/> </xpath> </field> </record> <record model="ir.ui.view" id="ir_ui_menu_users_form" > <field name="name">restrict.menu</field> <field name="model">ir.ui.menu</field> <field name="inherit_id" ref="base.edit_menu_access"/> <field name="arch" type="xml"> <xpath expr="//notebook" position="inside"> <page string="Restrict users" name="restrict_users"> <tree> <field name="restrict_user_ids"/> </tree> </page> </xpath> </field> </record> </data> </odoo>
Comentarios
Publicar un comentario