Add new module base_dynamic_list
This commit is contained in:
1
base_dynamic_list/__init__.py
Normal file
1
base_dynamic_list/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
63
base_dynamic_list/__manifest__.py
Normal file
63
base_dynamic_list/__manifest__.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020 Akretion France (http://www.akretion.com)
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Base Dynamic List',
|
||||
'version': '10.0.1.0.0',
|
||||
'category': 'Tools',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Dynamic lists',
|
||||
'description': """
|
||||
Base Dynamic List
|
||||
=================
|
||||
|
||||
Very often during an Odoo implementation, we need to add selection fields on a native objet, and we don't want to have a hard-coded selection list (fields.Selection), but a selection list that can be changed by users (Many2one field). For that, the developper needs to add a new object (with just a 'name' and 'sequence' field) with a form/tree view. The goal of this module is to speed-up this process by defining a dynamic list object that already has all the required views.
|
||||
|
||||
This module provides several ready-to-go objects:
|
||||
|
||||
* simple list : fields *name*, *sequence* and *active*
|
||||
* translatable list : fields *name* with translate=True, *sequence* and *active*
|
||||
* code list : fields *code* (unique), *name*, *sequence* and *active*
|
||||
* translatable code list : fields *code* (unique), *name* with translate=True, *sequence* and *active*
|
||||
|
||||
These objects are readable by the employee group. The system group has full rights on it.
|
||||
|
||||
To use it, you need to do 2 or 3 things :
|
||||
|
||||
1) Add an entry in the domain field and the object you selected:
|
||||
|
||||
domain = fields.Selection(selection_add=[('risk.type', "Risk Type")])
|
||||
|
||||
2) Add the many2one field on your object:
|
||||
|
||||
risk_type_id = fields.Many2one(
|
||||
'dynamic.list', string="Risk Type",
|
||||
ondelete='restrict', domain=[('domain', '=', 'risk.type')])
|
||||
|
||||
|
||||
3) Optionally, you can add a dedicated action and a menu entry (otherwize, you can use the generic menu entry under *Settings > Technical > Dynamic Lists*:
|
||||
|
||||
<record id="dynamic_list_risk_type_action" model="ir.actions.act_window">
|
||||
<field name="name">Risk Type</field>
|
||||
<field name="res_model">dynamic.list</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('domain', '=', 'risk.type')]</field>
|
||||
<field name="context">{'default_domain': 'risk.type'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_risk_type_menu" action="dynamic_list_risk_type_action"
|
||||
parent="parent_menu_xmlid"/>
|
||||
|
||||
Limitation: when you want to have different access rights on these lists depending on the source object, you should prefer to use dedicated objects.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['base'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/dynamic_list.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
1
base_dynamic_list/models/__init__.py
Normal file
1
base_dynamic_list/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import dynamic_list
|
||||
116
base_dynamic_list/models/dynamic_list.py
Normal file
116
base_dynamic_list/models/dynamic_list.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020 Akretion France (http://www.akretion.com)
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class DynamicList(models.Model):
|
||||
_name = 'dynamic.list'
|
||||
_description = 'Dynamic List (non translatable)'
|
||||
_order = 'sequence, id'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
sequence = fields.Integer()
|
||||
active = fields.Boolean(default=True)
|
||||
domain = fields.Selection([], string='Domain', required=True)
|
||||
|
||||
_sql_constraint = [(
|
||||
'domain_name_uniq',
|
||||
'unique(domain, name)',
|
||||
'This entry already exists!'
|
||||
)]
|
||||
|
||||
|
||||
class DynamicListTranslate(models.Model):
|
||||
_name = 'dynamic.list.translate'
|
||||
_description = 'Translatable Dynamic List'
|
||||
_order = 'sequence, id'
|
||||
|
||||
name = fields.Char(translate=True, required=True)
|
||||
sequence = fields.Integer()
|
||||
active = fields.Boolean(default=True)
|
||||
domain = fields.Selection([], string='Domain', required=True)
|
||||
|
||||
_sql_constraint = [(
|
||||
'domain_name_uniq',
|
||||
'unique(domain, name)',
|
||||
'This entry already exists!'
|
||||
)]
|
||||
|
||||
|
||||
class DynamicListCode(models.Model):
|
||||
_name = 'dynamic.list.code'
|
||||
_description = 'Dynamic list with code'
|
||||
_order = 'sequence, id'
|
||||
|
||||
code = fields.Char(required=True)
|
||||
name = fields.Char(translate=True, required=True)
|
||||
sequence = fields.Integer()
|
||||
active = fields.Boolean(default=True)
|
||||
domain = fields.Selection([], string='Domain', required=True)
|
||||
|
||||
_sql_constraint = [(
|
||||
'domain_code_uniq',
|
||||
'unique(domain, code)',
|
||||
'This code already exists!'
|
||||
)]
|
||||
|
||||
@api.depends('code', 'name')
|
||||
def name_get(self):
|
||||
res = []
|
||||
for rec in self:
|
||||
res.append((rec.id, u'[%s] %s' % (rec.code, rec.name)))
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def name_search(
|
||||
self, name='', args=None, operator='ilike', limit=80):
|
||||
if args is None:
|
||||
args = []
|
||||
if name and operator == 'ilike':
|
||||
recs = self.search(
|
||||
[('code', '=', name)] + args, limit=limit)
|
||||
if recs:
|
||||
return recs.name_get()
|
||||
return super(DynamicListCode, self).name_search(
|
||||
name=name, args=args, operator=operator, limit=limit)
|
||||
|
||||
|
||||
class DynamicListCodeTranslate(models.Model):
|
||||
_name = 'dynamic.list.code.translate'
|
||||
_description = 'Translatable dynamic list with code'
|
||||
_order = 'sequence, id'
|
||||
|
||||
code = fields.Char(required=True)
|
||||
name = fields.Char(translate=True, required=True)
|
||||
sequence = fields.Integer()
|
||||
active = fields.Boolean(default=True)
|
||||
domain = fields.Selection([], string='Domain', required=True)
|
||||
|
||||
_sql_constraint = [(
|
||||
'domain_code_uniq',
|
||||
'unique(domain, code)',
|
||||
'This code already exists!'
|
||||
)]
|
||||
|
||||
@api.depends('code', 'name')
|
||||
def name_get(self):
|
||||
res = []
|
||||
for rec in self:
|
||||
res.append((rec.id, u'[%s] %s' % (rec.code, rec.name)))
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def name_search(
|
||||
self, name='', args=None, operator='ilike', limit=80):
|
||||
if args is None:
|
||||
args = []
|
||||
if name and operator == 'ilike':
|
||||
recs = self.search(
|
||||
[('code', '=', name)] + args, limit=limit)
|
||||
if recs:
|
||||
return recs.name_get()
|
||||
return super(DynamicListCodeTranslate, self).name_search(
|
||||
name=name, args=args, operator=operator, limit=limit)
|
||||
9
base_dynamic_list/security/ir.model.access.csv
Normal file
9
base_dynamic_list/security/ir.model.access.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_dynamic_list_read,Read access on dynamic.list to employees,model_dynamic_list,base.group_user,1,0,0,0
|
||||
access_dynamic_list_full,Full access to dynamic.list to System group,model_dynamic_list,base.group_system,1,1,1,1
|
||||
access_dynamic_list_translate_read,Read access on dynamic.list.translate to employees,model_dynamic_list_translate,base.group_user,1,0,0,0
|
||||
access_dynamic_list_translate_full,Full access to dynamic.list.translate to System group,model_dynamic_list_translate,base.group_system,1,1,1,1
|
||||
access_dynamic_list_code_read,Read access on dynamic.list.code to employees,model_dynamic_list_code,base.group_user,1,0,0,0
|
||||
access_dynamic_list_code_full,Full access to dynamic.list.code to System group,model_dynamic_list_code,base.group_system,1,1,1,1
|
||||
access_dynamic_list_code_translate_read,Read access on dynamic.list.code.translate to employees,model_dynamic_list_code_translate,base.group_user,1,0,0,0
|
||||
access_dynamic_list_code_translate_full,Full access to dynamic.list.code.translate to System group,model_dynamic_list_code_translate,base.group_system,1,1,1,1
|
||||
|
232
base_dynamic_list/views/dynamic_list.xml
Normal file
232
base_dynamic_list/views/dynamic_list.xml
Normal file
@@ -0,0 +1,232 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
|
||||
<menuitem id="dynamic_list_root_menu" name="Dynamic Lists" parent="base.menu_custom" sequence="100"/>
|
||||
|
||||
<record id="dynamic_list_form" model="ir.ui.view">
|
||||
<field name="model">dynamic.list</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object"
|
||||
class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button"
|
||||
options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group name="main">
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_main_view')"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_tree" model="ir.ui.view">
|
||||
<field name="model">dynamic.list</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_main_view')"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_search" model="ir.ui.view">
|
||||
<field name="model">dynamic.list</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Simple List</field>
|
||||
<field name="res_model">dynamic.list</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'dynamic_list_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_menu" action="dynamic_list_action" parent="dynamic_list_root_menu" sequence="10"/>
|
||||
|
||||
<record id="dynamic_list_translate_form" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object"
|
||||
class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button"
|
||||
options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group name="main">
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_translate_main_view')"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_translate_tree" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_translate_main_view')"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_translate_search" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_translate_action" model="ir.actions.act_window">
|
||||
<field name="name">Translatable Simple List</field>
|
||||
<field name="res_model">dynamic.list.translate</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'dynamic_list_translate_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_translate_menu" action="dynamic_list_translate_action" parent="dynamic_list_root_menu" sequence="20"/>
|
||||
|
||||
<record id="dynamic_list_code_form" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.code</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object"
|
||||
class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button"
|
||||
options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group name="main">
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_code_main_view')"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_code_tree" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.code</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_code_main_view')"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_code_search" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.code</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" string="Name or Code" filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"/>
|
||||
<field name="code"/>
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_code_action" model="ir.actions.act_window">
|
||||
<field name="name">Code List</field>
|
||||
<field name="res_model">dynamic.list.code</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'dynamic_list_code_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_code_menu" action="dynamic_list_code_action" parent="dynamic_list_root_menu" sequence="30"/>
|
||||
|
||||
<record id="dynamic_list_code_translate_form" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.code.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object"
|
||||
class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button"
|
||||
options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group name="main">
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_code_translate_main_view')"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_code_translate_tree" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.code.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_code_translate_main_view')"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_code_translate_search" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.code.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" string="Name or Code" filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"/>
|
||||
<field name="code"/>
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="dynamic_list_code_translate_action" model="ir.actions.act_window">
|
||||
<field name="name">Translatable Code List</field>
|
||||
<field name="res_model">dynamic.list.code.translate</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'dynamic_list_code_translate_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_code_translate_menu" action="dynamic_list_code_translate_action" parent="dynamic_list_root_menu" sequence="40"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user