Merge PR #2707 into 14.0

Signed-off-by legalsylvain
pull/2767/head
OCA-git-bot 2024-02-28 17:15:22 +00:00
commit 0175cae6fd
38 changed files with 1553 additions and 0 deletions

View File

@ -0,0 +1 @@
../../../../web_custom_modifier

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@ -0,0 +1,215 @@
===================
Web Custom Modifier
===================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3cda9a1f864e81bc69284b8cd6c0219a277f33993b92eba42c626b37f033d8cc
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/14.0/web_custom_modifier
:alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-14-0/web-14-0-web_custom_modifier
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=14.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module allows to customize modifiers on form and tree view nodes.
For example, it allows to make a field readonly, invisible or required.
**Table of contents**
.. contents::
:local:
Use Cases / Context
===================
In Odoo, when you want to customize some fields or views such as hiding
a field, changing the number of line of a list view or make a field
mandatory, you need to have technical knowledge.
This module allows functional people to apply some customizations
without having to code.
Usage
=====
Access to the module
--------------------
As system administrator, I go to *Settings / Technical / User Interface
/ Custom Modifiers*. |Custom Modifier Menu|
Requiered Field
---------------
I create a new custom modifier. |New Modifier|
The modifier is configured to make the field ``default_code`` of a
product required.
After refreshing my screen, I go to the form view of a product.
I notice that the field ``default_code`` is required. |Product form|
Hide selection item
-------------------
The module allows to hide an item (option) of a selection field. |Hide
Selction Item Modifier|
The above example hides the type of address ``Other``. |Contact Form
Witout Selection Item|
Beware that if the hidden option is already selected on a record, it
will look as it was never set. |Contact Form Type not Selected|
Therefore, this feature should only be used to hide options that are
never used.
Force Save
----------
A new option ``Force Save`` is available. |Force Save Modifier|
This modifier may be used along with the ``Readonly`` modifier so that
the field value is saved to the server.
Custom Widget
-------------
It is possible to customize the widget used for a given field. |Custom
Widget| |Task Form with Custom Widget|
Number of lines per page (List Views)
-------------------------------------
A new modifier is added to set the number of lines per page in list
view.
In the following example, we set a limit of 20 sale order lines per page
on a sale order form view.
|Number of lines per Page Modifier| |Sale Order with Limited SOL per
Page|
Advanced Usage
--------------
In the field ``Type``, I can select ``Xpath``. This allows to set a
modifier for a specific view node, such as a button. |Button Modifier|
The example above hides the a button in the form view of a product.
Excluded Groups
---------------
A new field ``Excluded Groups`` is available. |Excluded Groups|
If at least one group of users is selected, the modifier is not applied
for users that are member of any of these groups.
This is useful when rendering an element readonly or invisible only for
a subset of users.
Optional
--------
Since the version **14.0.2.0.1**, it is possible to customize the
optional of a given field on a tree view.
The ``Optional`` modifier takes 2 possible keys:
- show : To make the field displayed by default in the tree view.
- hide : To make the field hidden by default in the 3 dots of a tree
view.
.. important::
The field must be present by default on the tree view and displayed.
Example:
As system administrator, I go to
``Settings / Technical / User Interface / Custom Modifiers``.
I add the field name (labelled as ``Number`` in Quotation List View) of
the model ``sale.order``.
I select the modifier ``Optional`` and then set the key ``show``.
|Optional Modifier|
I go to the Quotation List View and notice that the field is now shown
by default and that it is possible to hide it. |Optional Modifier
Applied|
.. |Custom Modifier Menu| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/custom_modifier_menu.png
.. |New Modifier| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/new_custom_modifier.png
.. |Product form| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/product_form.png
.. |Hide Selction Item Modifier| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/hide_selection_item_modifier.png
.. |Contact Form Witout Selection Item| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/contact_form_without_selection_item.png
.. |Contact Form Type not Selected| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/contact_form_type_not_selected.png
.. |Force Save Modifier| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/force_save_modifier.png
.. |Custom Widget| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/custom_widget.png
.. |Task Form with Custom Widget| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/task_form_with_custom_widget.png
.. |Number of lines per Page Modifier| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/number_lines_per_page_modifier.png
.. |Sale Order with Limited SOL per Page| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/sale_order_with_limited_sol_per_page.png
.. |Button Modifier| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/button_modifier.png
.. |Excluded Groups| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/excluded_groups.png
.. |Optional Modifier| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/optional_modifier.png
.. |Optional Modifier Applied| image:: https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/optional_modifier_applied.png
Known issues / Roadmap
======================
- Set a default value on a field for all users.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_custom_modifier%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Numigi
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/web <https://github.com/OCA/web/tree/14.0/web_custom_modifier>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1,4 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import models

View File

@ -0,0 +1,19 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
"name": "Web Custom Modifier",
"version": "14.0.2.0.1",
"author": "Numigi, " "Odoo Community Association (OCA)",
"maintainer": "Numigi",
"website": "https://github.com/OCA/web",
"license": "LGPL-3",
"category": "Project",
"summary": "Enable easily customizing view modifiers.",
"depends": ["base"],
"data": [
"views/web_custom_modifier.xml",
"security/ir.model.access.csv",
],
"installable": True,
}

View File

@ -0,0 +1,178 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_custom_modifier
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-15 15:07+0000\n"
"PO-Revision-Date: 2022-07-15 15:07+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__active
msgid "Active"
msgstr "Actif"
#. module: web_custom_modifier
#: model_terms:ir.ui.view,arch_db:web_custom_modifier.custom_modifier_search
msgid "Archived"
msgstr "Archivé"
#. module: web_custom_modifier
#: model:ir.model,name:web_custom_modifier.model_base
msgid "Base"
msgstr "Base"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__create_date
msgid "Created on"
msgstr "Créé le"
#. module: web_custom_modifier
#: model:ir.actions.act_window,name:web_custom_modifier.custom_modifier_action
#: model:ir.ui.menu,name:web_custom_modifier.custom_modifier_menu
#: model_terms:ir.ui.view,arch_db:web_custom_modifier.custom_modifier_list
#: model_terms:ir.ui.view,arch_db:web_custom_modifier.custom_modifier_search
msgid "Custom Modifiers"
msgstr "Modificateurs personnalisés"
#. module: web_custom_modifier
#: model:ir.model,name:web_custom_modifier.model_web_custom_modifier
msgid "Custom View Modifier"
msgstr "Modificateurs de vue personnalisés"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__excluded_group_ids
msgid "Excluded Groups"
msgstr "Groupes exclus"
#. module: web_custom_modifier
#: model_terms:ir.ui.view,arch_db:web_custom_modifier.custom_modifier_list
#: model_terms:ir.ui.view,arch_db:web_custom_modifier.custom_modifier_search
#: selection:web.custom.modifier,type_:0
msgid "Field"
msgstr "Champ"
#. module: web_custom_modifier
#: selection:web.custom.modifier,modifier:0
msgid "Force Save"
msgstr "Forcer la sauvegarde"
#. module: web_custom_modifier
#: model_terms:ir.ui.view,arch_db:web_custom_modifier.custom_modifier_search
msgid "Group By"
msgstr "Grouper par"
#. module: web_custom_modifier
#: selection:web.custom.modifier,modifier:0
msgid "Hide Selection Item"
msgstr "Cacher un choix de sélection"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__id
msgid "ID"
msgstr "ID"
#. module: web_custom_modifier
#: selection:web.custom.modifier,modifier:0
msgid "Invisible"
msgstr "Invisible"
#. module: web_custom_modifier
#: selection:web.custom.modifier,modifier:0
msgid "Invisible (List Views)"
msgstr "Invisible (vues listes)"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__key
msgid "Key"
msgstr "Clé"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__model_ids
msgid "Model"
msgstr "Modèle"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__modifier
#: model_terms:ir.ui.view,arch_db:web_custom_modifier.custom_modifier_search
msgid "Modifier"
msgstr "Modificateur"
#. module: web_custom_modifier
#: selection:web.custom.modifier,modifier:0
msgid "Number of lines per page (List Views)"
msgstr "Nombre de lignes par page (Vues listes)"
#. module: web_custom_modifier
#: selection:web.custom.modifier,modifier:0
msgid "Readonly"
msgstr "Lecture seule"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__reference
msgid "Reference"
msgstr "Référence"
#. module: web_custom_modifier
#: selection:web.custom.modifier,modifier:0
msgid "Required"
msgstr "Obligatoire"
#. module: web_custom_modifier
#: model:ir.model.fields,field_description:web_custom_modifier.field_web_custom_modifier__type_
#: model_terms:ir.ui.view,arch_db:web_custom_modifier.custom_modifier_search
msgid "Type"
msgstr "Type"
#. module: web_custom_modifier
#: model:ir.model,name:web_custom_modifier.model_ir_ui_view
msgid "View"
msgstr "View"
#. module: web_custom_modifier
#: selection:web.custom.modifier,modifier:0
msgid "Widget"
msgstr "Widget"
#. module: web_custom_modifier
#: selection:web.custom.modifier,type_:0
msgid "Xpath"
msgstr "Xpath"
#. module: web_custom_modifier
#: model:ir.model.fields.selection,name:web_custom_modifier.selection__web_custom_modifier__modifier__optional
msgid "Optional"
msgstr "Optionnel"

View File

@ -0,0 +1,8 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import (
base,
ir_ui_view,
web_custom_modifier,
)

View File

@ -0,0 +1,19 @@
# Copyright 2023 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, models
from ..utils import set_custom_modifiers_on_fields
class Base(models.AbstractModel):
_inherit = "base"
@api.model
def fields_get(self, allfields=None, attributes=None):
"""Add the custom modifiers to the fields metadata."""
fields = super().fields_get(allfields, attributes)
modifiers = self.env["web.custom.modifier"].get(self._name)
set_custom_modifiers_on_fields(modifiers, fields)
return fields

View File

@ -0,0 +1,26 @@
# © 2023 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from typing import List, Mapping
def set_custom_modifiers_on_fields(modifiers: List[dict], fields: Mapping[str, dict]):
_hide_selection_items(modifiers, fields)
def _hide_selection_items(modifiers, fields):
hidden_items = (
m
for m in modifiers
if m["type_"] == "field" and m["modifier"] == "selection_hide"
)
for item in hidden_items:
_hide_single_selection_item(item, fields)
def _hide_single_selection_item(modifier, fields):
field = fields.get(modifier["reference"])
if field and "selection" in field:
field["selection"] = [
(k, v) for k, v in field["selection"] if k != modifier["key"]
]

View File

@ -0,0 +1,24 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import models
from ..utils import add_custom_modifiers_to_view_arch, set_custom_modifiers_on_fields
class ViewWithCustomModifiers(models.Model):
_inherit = "ir.ui.view"
def postprocess(self, node, current_node_path, editable, name_manager):
"""Add custom modifiers to the view xml.
This method is called in Odoo when generating the final xml of a view.
"""
model_name = name_manager.Model._name
modifiers = self.env["web.custom.modifier"].get(model_name)
node_with_custom_modifiers = add_custom_modifiers_to_view_arch(modifiers, node)
set_custom_modifiers_on_fields(modifiers, name_manager.available_fields)
self.clear_caches() # Clear the cache in order to recompute _get_active_rules
return super().postprocess(
node_with_custom_modifiers, current_node_path, editable, name_manager
)

View File

@ -0,0 +1,95 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, fields, models, tools
class WebCustomModifier(models.Model):
_name = "web.custom.modifier"
_description = "Custom View Modifier"
model_ids = fields.Many2many(
"ir.model", "ir_model_custom_modifier", "modifier_id", "model_id", "Model"
)
type_ = fields.Selection(
[
("field", "Field"),
("xpath", "Xpath"),
],
string="Type",
default="field",
required=True,
)
modifier = fields.Selection(
[
("invisible", "Invisible"),
("column_invisible", "Invisible (List Views)"),
("readonly", "Readonly"),
("force_save", "Force Save"),
("required", "Required"),
("selection_hide", "Hide Selection Item"),
("widget", "Widget"),
("limit", "Number of lines per page (List Views)"),
("optional", "Optional"),
],
required=True,
)
reference = fields.Char(required=True)
key = fields.Char()
active = fields.Boolean(default=True)
excluded_group_ids = fields.Many2many(
"res.groups",
"web_custom_modifier_excluded_group_rel",
"modifier_id",
"group_id",
"Excluded Groups",
)
@api.model
def create(self, vals):
new_record = super().create(vals)
self._clear_modifier_cache()
return new_record
def write(self, vals):
super().write(vals)
self._clear_modifier_cache()
return True
def unlink(self):
super().unlink()
self._clear_modifier_cache()
return True
def _clear_modifier_cache(self):
for model in (
self.sudo().env["web.custom.modifier"].search([]).mapped("model_ids.model")
):
self.env[model].clear_caches()
@tools.ormcache()
def _get_cache(self):
return [
el._to_dict() for el in self.sudo().env["web.custom.modifier"].search([])
]
def _to_dict(self):
return {
"models": self.mapped("model_ids.model"),
"key": self.key,
"type_": self.type_,
"modifier": self.modifier,
"reference": self.reference,
"excluded_group_ids": self.excluded_group_ids.ids,
}
def get(self, model):
cache = self._get_cache()
user_group_ids = self.env.user.groups_id.ids
return [
el
for el in cache
if model in el["models"]
and all(id_ not in user_group_ids for id_ in el["excluded_group_ids"])
]

View File

@ -0,0 +1,3 @@
In Odoo, when you want to customize some fields or views such as hiding a field, changing the number of line of a list view or make a field mandatory, you need to have technical knowledge.
This module allows functional people to apply some customizations without having to code.

View File

@ -0,0 +1,5 @@
- David Dufresne david.dufresne@numigi.com (www.numigi.com)
- Majda El Mariouli majda.elmariouli@numigi.com (www.numigi.com)
- Lanto Razafindrabe lanto.razafindrabe@numigi.com (www.numigi.com)
- Julie LeBrun julie.lebrun@numigi.com (www.numigi.com)
- Abdellatif Benzbiria abdellatif.benzbiria@numigi.com (www.numigi.com)

View File

@ -0,0 +1,3 @@
This module allows to customize modifiers on form and tree view nodes.
For example, it allows to make a field readonly, invisible or required.

View File

@ -0,0 +1 @@
- Set a default value on a field for all users.

View File

@ -0,0 +1,91 @@
## Access to the module
As system administrator, I go to *Settings / Technical / User Interface / Custom Modifiers*.
![Custom Modifier Menu](../static/description/custom_modifier_menu.png)
## Requiered Field
I create a new custom modifier.
![New Modifier](../static/description/new_custom_modifier.png)
The modifier is configured to make the field `default_code` of a product required.
After refreshing my screen, I go to the form view of a product.
I notice that the field `default_code` is required.
![Product form](../static/description/product_form.png)
## Hide selection item
The module allows to hide an item (option) of a selection field.
![Hide Selction Item Modifier](../static/description/hide_selection_item_modifier.png)
The above example hides the type of address `Other`.
![Contact Form Witout Selection Item](../static/description/contact_form_without_selection_item.png)
Beware that if the hidden option is already selected on a record, it will look as it was never set.
![Contact Form Type not Selected](../static/description/contact_form_type_not_selected.png)
Therefore, this feature should only be used to hide options that are never used.
## Force Save
A new option `Force Save` is available.
![Force Save Modifier](../static/description/force_save_modifier.png)
This modifier may be used along with the `Readonly` modifier so that the field value is saved to the server.
## Custom Widget
It is possible to customize the widget used for a given field.
![Custom Widget](../static/description/custom_widget.png)
![Task Form with Custom Widget](../static/description/task_form_with_custom_widget.png)
## Number of lines per page (List Views)
A new modifier is added to set the number of lines per page in list view.
In the following example, we set a limit of 20 sale order lines per page on a sale order form view.
![Number of lines per Page Modifier](../static/description/number_lines_per_page_modifier.png)
![Sale Order with Limited SOL per Page](../static/description/sale_order_with_limited_sol_per_page.png)
## Advanced Usage
In the field `Type`, I can select `Xpath`. This allows to set a modifier for a specific view node, such as a button.
![Button Modifier](../static/description/button_modifier.png)
The example above hides the a button in the form view of a product.
## Excluded Groups
A new field `Excluded Groups` is available.
![Excluded Groups](../static/description/excluded_groups.png)
If at least one group of users is selected, the modifier is not applied for users that are member of any of these groups.
This is useful when rendering an element readonly or invisible only for a subset of users.
## Optional
Since the version **14.0.2.0.1**, it is possible to customize the optional of a given field on a tree view.
The `Optional` modifier takes 2 possible keys:
- show : To make the field displayed by default in the tree view.
- hide : To make the field hidden by default in the 3 dots of a tree view.
> [!IMPORTANT]
> The field must be present by default on the tree view and displayed.
Example:
As system administrator, I go to `Settings / Technical / User Interface / Custom Modifiers`.
I add the field name (labelled as `Number` in Quotation List View) of the model `sale.order`.
I select the modifier `Optional` and then set the key `show`.
![Optional Modifier](../static/description/optional_modifier.png)
I go to the Quotation List View and notice that the field is now shown by default and that it is possible to hide it.
![Optional Modifier Applied](../static/description/optional_modifier_applied.png)

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_web_custom_modifier,access_web_custom_modifier,model_web_custom_modifier,base.group_user,1,0,0,0
access_web_custom_modifier_admin,access_web_custom_modifier_admin,model_web_custom_modifier,base.group_erp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_web_custom_modifier access_web_custom_modifier model_web_custom_modifier base.group_user 1 0 0 0
3 access_web_custom_modifier_admin access_web_custom_modifier_admin model_web_custom_modifier base.group_erp_manager 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,524 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Web Custom Modifier</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="web-custom-modifier">
<h1 class="title">Web Custom Modifier</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3cda9a1f864e81bc69284b8cd6c0219a277f33993b92eba42c626b37f033d8cc
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/14.0/web_custom_modifier"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-14-0/web-14-0-web_custom_modifier"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=14.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows to customize modifiers on form and tree view nodes.</p>
<p>For example, it allows to make a field readonly, invisible or required.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#use-cases-context" id="toc-entry-1">Use Cases / Context</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a><ul>
<li><a class="reference internal" href="#access-to-the-module" id="toc-entry-3">Access to the module</a></li>
<li><a class="reference internal" href="#requiered-field" id="toc-entry-4">Requiered Field</a></li>
<li><a class="reference internal" href="#hide-selection-item" id="toc-entry-5">Hide selection item</a></li>
<li><a class="reference internal" href="#force-save" id="toc-entry-6">Force Save</a></li>
<li><a class="reference internal" href="#custom-widget" id="toc-entry-7">Custom Widget</a></li>
<li><a class="reference internal" href="#number-of-lines-per-page-list-views" id="toc-entry-8">Number of lines per page (List Views)</a></li>
<li><a class="reference internal" href="#advanced-usage" id="toc-entry-9">Advanced Usage</a></li>
<li><a class="reference internal" href="#excluded-groups" id="toc-entry-10">Excluded Groups</a></li>
<li><a class="reference internal" href="#optional" id="toc-entry-11">Optional</a></li>
</ul>
</li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-12">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-13">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-14">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-15">Authors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-16">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="use-cases-context">
<h1><a class="toc-backref" href="#toc-entry-1">Use Cases / Context</a></h1>
<p>In Odoo, when you want to customize some fields or views such as hiding
a field, changing the number of line of a list view or make a field
mandatory, you need to have technical knowledge.</p>
<p>This module allows functional people to apply some customizations
without having to code.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-2">Usage</a></h1>
<div class="section" id="access-to-the-module">
<h2><a class="toc-backref" href="#toc-entry-3">Access to the module</a></h2>
<p>As system administrator, I go to <em>Settings / Technical / User Interface
/ Custom Modifiers</em>. <img alt="Custom Modifier Menu" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/custom_modifier_menu.png" /></p>
</div>
<div class="section" id="requiered-field">
<h2><a class="toc-backref" href="#toc-entry-4">Requiered Field</a></h2>
<p>I create a new custom modifier. <img alt="New Modifier" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/new_custom_modifier.png" /></p>
<p>The modifier is configured to make the field <tt class="docutils literal">default_code</tt> of a
product required.</p>
<p>After refreshing my screen, I go to the form view of a product.</p>
<p>I notice that the field <tt class="docutils literal">default_code</tt> is required. <img alt="Product form" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/product_form.png" /></p>
</div>
<div class="section" id="hide-selection-item">
<h2><a class="toc-backref" href="#toc-entry-5">Hide selection item</a></h2>
<p>The module allows to hide an item (option) of a selection field. <img alt="Hide Selction Item Modifier" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/hide_selection_item_modifier.png" /></p>
<p>The above example hides the type of address <tt class="docutils literal">Other</tt>. <img alt="Contact Form Witout Selection Item" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/contact_form_without_selection_item.png" /></p>
<p>Beware that if the hidden option is already selected on a record, it
will look as it was never set. <img alt="Contact Form Type not Selected" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/contact_form_type_not_selected.png" /></p>
<p>Therefore, this feature should only be used to hide options that are
never used.</p>
</div>
<div class="section" id="force-save">
<h2><a class="toc-backref" href="#toc-entry-6">Force Save</a></h2>
<p>A new option <tt class="docutils literal">Force Save</tt> is available. <img alt="Force Save Modifier" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/force_save_modifier.png" /></p>
<p>This modifier may be used along with the <tt class="docutils literal">Readonly</tt> modifier so that
the field value is saved to the server.</p>
</div>
<div class="section" id="custom-widget">
<h2><a class="toc-backref" href="#toc-entry-7">Custom Widget</a></h2>
<p>It is possible to customize the widget used for a given field. <img alt="Custom Widget" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/custom_widget.png" /> <img alt="Task Form with Custom Widget" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/task_form_with_custom_widget.png" /></p>
</div>
<div class="section" id="number-of-lines-per-page-list-views">
<h2><a class="toc-backref" href="#toc-entry-8">Number of lines per page (List Views)</a></h2>
<p>A new modifier is added to set the number of lines per page in list
view.</p>
<p>In the following example, we set a limit of 20 sale order lines per page
on a sale order form view.</p>
<p><img alt="Number of lines per Page Modifier" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/number_lines_per_page_modifier.png" /> <img alt="Sale Order with Limited SOL per Page" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/sale_order_with_limited_sol_per_page.png" /></p>
</div>
<div class="section" id="advanced-usage">
<h2><a class="toc-backref" href="#toc-entry-9">Advanced Usage</a></h2>
<p>In the field <tt class="docutils literal">Type</tt>, I can select <tt class="docutils literal">Xpath</tt>. This allows to set a
modifier for a specific view node, such as a button. <img alt="Button Modifier" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/button_modifier.png" />
The example above hides the a button in the form view of a product.</p>
</div>
<div class="section" id="excluded-groups">
<h2><a class="toc-backref" href="#toc-entry-10">Excluded Groups</a></h2>
<p>A new field <tt class="docutils literal">Excluded Groups</tt> is available. <img alt="Excluded Groups" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/excluded_groups.png" /></p>
<p>If at least one group of users is selected, the modifier is not applied
for users that are member of any of these groups.</p>
<p>This is useful when rendering an element readonly or invisible only for
a subset of users.</p>
</div>
<div class="section" id="optional">
<h2><a class="toc-backref" href="#toc-entry-11">Optional</a></h2>
<p>Since the version <strong>14.0.2.0.1</strong>, it is possible to customize the
optional of a given field on a tree view.</p>
<p>The <tt class="docutils literal">Optional</tt> modifier takes 2 possible keys:</p>
<ul class="simple">
<li>show : To make the field displayed by default in the tree view.</li>
<li>hide : To make the field hidden by default in the 3 dots of a tree
view.</li>
</ul>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">The field must be present by default on the tree view and displayed.</p>
</div>
<p>Example:</p>
<p>As system administrator, I go to
<tt class="docutils literal">Settings / Technical / User Interface / Custom Modifiers</tt>.</p>
<p>I add the field name (labelled as <tt class="docutils literal">Number</tt> in Quotation List View) of
the model <tt class="docutils literal">sale.order</tt>.</p>
<p>I select the modifier <tt class="docutils literal">Optional</tt> and then set the key <tt class="docutils literal">show</tt>.
<img alt="Optional Modifier" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/optional_modifier.png" /></p>
<p>I go to the Quotation List View and notice that the field is now shown
by default and that it is possible to hide it. <img alt="Optional Modifier Applied" src="https://raw.githubusercontent.com/OCA/web/14.0/web_custom_modifier/static/description/optional_modifier_applied.png" /></p>
</div>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-12">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Set a default value on a field for all users.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-13">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_custom_modifier%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-14">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-15">Authors</a></h2>
<ul class="simple">
<li>Numigi</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-16">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/14.0/web_custom_modifier">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -0,0 +1,4 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import test_view_rendering

View File

@ -0,0 +1,159 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import json
from lxml import etree
from odoo.tests import common
def _extract_modifier_value(el, modifier):
return json.loads(el.attrib.get("modifiers") or "{}").get(modifier)
class TestViewRendering(common.SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.view = cls.env.ref("base.view_partner_form")
cls.email_modifier = cls.env["web.custom.modifier"].create(
{
"model_ids": [(4, cls.env.ref("base.model_res_partner").id)],
"type_": "field",
"reference": "email",
"modifier": "invisible",
}
)
cls.xpath = "//field[@name='street']"
cls.street_modifier = cls.env["web.custom.modifier"].create(
{
"model_ids": [(4, cls.env.ref("base.model_res_partner").id)],
"type_": "xpath",
"reference": cls.xpath,
"modifier": "invisible",
}
)
cls.hidden_option = "other"
cls.env["web.custom.modifier"].create(
{
"model_ids": [(4, cls.env.ref("base.model_res_partner").id)],
"type_": "field",
"reference": "type",
"modifier": "selection_hide",
"key": cls.hidden_option,
}
)
cls.env["web.custom.modifier"].create(
{
"model_ids": [(4, cls.env.ref("base.model_res_partner").id)],
"type_": "field",
"reference": "parent_id",
"modifier": "widget",
"key": "custom_widget",
}
)
cls.env["web.custom.modifier"].create(
{
"model_ids": [(4, cls.env.ref("base.model_ir_model").id)],
"type_": "xpath",
"reference": "//field[@name='field_id']//tree",
"modifier": "limit",
"key": "20",
}
)
cls.env["web.custom.modifier"].create(
{
"model_ids": [(4, cls.env.ref("base.model_res_partner").id)],
"type_": "field",
"reference": "name",
"modifier": "optional",
"key": "show",
}
)
def _get_rendered_view_tree(self):
arch = self.env["res.partner"].fields_view_get(view_id=self.view.id)["arch"]
return etree.fromstring(arch)
def test_field_modifier(self, modifier="invisible"):
self.email_modifier.modifier = modifier
tree = self._get_rendered_view_tree()
el = tree.xpath("//field[@name='email']")[0]
self.assertTrue(_extract_modifier_value(el, modifier))
def test_field_force_save(self):
self.email_modifier.modifier = "force_save"
tree = self._get_rendered_view_tree()
el = tree.xpath("//field[@name='email']")[0]
self.assertEqual(el.attrib["force_save"], "1")
def test_two_modifier_same_field(self):
self.email_modifier.modifier = "invisible"
self.email_modifier.copy().modifier = "readonly"
self.email_modifier.copy().modifier = "column_invisible"
tree = self._get_rendered_view_tree()
el = tree.xpath("//field[@name='email']")[0]
self.assertTrue(_extract_modifier_value(el, "column_invisible"))
self.assertTrue(_extract_modifier_value(el, "readonly"))
self.assertTrue(_extract_modifier_value(el, "invisible"))
def test_xpath_modifier(self, modifier="invisible"):
self.street_modifier.modifier = modifier
tree = self._get_rendered_view_tree()
el = tree.xpath("//field[@name='street']")[0]
self.assertTrue(_extract_modifier_value(el, modifier))
def test_user_in_excluded_groups(self):
modifier = "invisible"
group = self.env.ref("base.group_system")
self.street_modifier.modifier = modifier
self.street_modifier.excluded_group_ids = group
self.env.user.groups_id |= group
tree = self._get_rendered_view_tree()
el = tree.xpath("//field[@name='street']")[0]
self.assertFalse(_extract_modifier_value(el, modifier))
def test_user_not_in_excluded_groups(self):
modifier = "invisible"
group = self.env.ref("base.group_system")
self.street_modifier.modifier = modifier
self.street_modifier.excluded_group_ids = group
self.env.user.groups_id -= group
tree = self._get_rendered_view_tree()
el = tree.xpath("//field[@name='street']")[0]
self.assertTrue(_extract_modifier_value(el, modifier))
def test_selection_hide__fields_view_get(self):
fields = self.env["res.partner"].fields_view_get(view_id=self.view.id)["fields"]
options = {i[0]: i[1] for i in fields["type"]["selection"]}
self.assertNotIn(self.hidden_option, options)
def test_selection_hide__fields_get(self):
fields = self.env["res.partner"].fields_get()
options = {i[0]: i[1] for i in fields["type"]["selection"]}
self.assertNotIn(self.hidden_option, options)
def test_widget(self):
tree = self._get_rendered_view_tree()
el = tree.xpath("//field[@name='parent_id']")[0]
self.assertEqual(el.attrib.get("widget"), "custom_widget")
def test_optional(self):
tree = self._get_rendered_view_tree()
el = tree.xpath("//field[@name='name']")[0]
self.assertEqual(el.attrib.get("optional"), "show")
def test_nbr_line_per_page(self):
model_view = self.env.ref("base.view_model_form")
arch = self.env["ir.model"].fields_view_get(view_id=model_view.id)["fields"][
"field_id"
]["views"]["tree"]["arch"]
tree = etree.fromstring(arch)
el = tree.xpath("//tree")[0]
self.assertEqual(el.attrib.get("limit"), "20")

View File

@ -0,0 +1,88 @@
# Copyright 2023 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import json
STANDARD_MODIFIERS = (
"invisible",
"column_invisible",
"readonly",
"required",
)
def set_custom_modifiers_on_fields(modifiers, fields):
"""
:param modifiers: list[dict] of modifiers to apply on the fields
:param fields: dict[str, dict] of model's fields and their attributes.
:return:
"""
_hide_selection_items(modifiers, fields)
def _hide_selection_items(modifiers, fields):
hidden_items = (
m
for m in modifiers
if m["type_"] == "field" and m["modifier"] == "selection_hide"
)
for item in hidden_items:
_hide_single_selection_item(item, fields)
def _hide_single_selection_item(modifier, fields):
field = fields.get(modifier["reference"])
if field and "selection" in field:
field["selection"] = [
(k, v) for k, v in field["selection"] if k != modifier["key"]
]
def add_custom_modifiers_to_view_arch(modifiers, arch):
"""Add custom modifiers to the given view architecture."""
for modifier in modifiers:
_add_custom_modifier_to_view_tree(modifier, arch)
return arch
def _add_custom_modifier_to_view_tree(modifier, tree):
"""Add a custom modifier to the given view architecture."""
xpath_expr = modifier["reference"]
if modifier["type_"] == "field":
xpath_expr = (
"//field[@name='{field_name}'] | //modifier[@for='{field_name}']".format(
field_name=modifier["reference"]
)
)
for node in tree.xpath(xpath_expr):
_add_custom_modifier_to_node(node, modifier)
def _add_custom_modifier_to_node(node, modifier):
key = modifier["modifier"]
if key == "widget":
node.attrib["widget"] = modifier["key"]
if key == "optional":
node.attrib["optional"] = modifier["key"]
elif key == "force_save":
node.attrib["force_save"] = "1"
elif key == "limit":
node.attrib["limit"] = modifier["key"]
elif key in STANDARD_MODIFIERS:
node.set(key, "1")
modifiers = _get_node_modifiers(node)
modifiers[key] = True
_set_node_modifiers(modifiers, node)
def _get_node_modifiers(node):
modifiers = node.get("modifiers")
return json.loads(modifiers) if modifiers else {}
def _set_node_modifiers(modifiers, node):
node.set("modifiers", json.dumps(modifiers))

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="custom_modifier_list" model="ir.ui.view">
<field name="name">Custom Modifier List View</field>
<field name="model">web.custom.modifier</field>
<field name="arch" type="xml">
<tree editable="top">
<field
name="model_ids"
widget="many2many_tags"
options="{'no_create': 1}"
/>
<field name="type_" />
<field name="reference" string="Field" />
<field name="modifier" />
<field
name="key"
attrs="{'readonly': [('modifier', 'not in', ('selection_hide', 'widget', 'limit', 'optional'))],
'required': [('modifier', 'in', ('selection_hide', 'limit', 'optional'))]}"
/>
<field name="excluded_group_ids" widget="many2many_tags" />
<field name="active" />
</tree>
</field>
</record>
<record id="custom_modifier_search" model="ir.ui.view">
<field name="name">Custom Modifier Search View</field>
<field name="model">web.custom.modifier</field>
<field name="arch" type="xml">
<search string="Custom Modifiers">
<field name="model_ids" />
<field name="reference" />
<field name="modifier" />
<separator />
<filter
string="Archived"
name="inactive"
domain="[('active', '=', False)]"
/>
<group expand="0" string="Group By">
<filter
name="group_by_reference"
string="Field"
context="{'group_by': 'reference'}"
/>
<filter
name="group_by_type_"
string="Type"
context="{'group_by': 'type_'}"
/>
<filter
name="group_by_modifier"
string="Modifier"
context="{'group_by': 'modifier'}"
/>
</group>
</search>
</field>
</record>
<record id="custom_modifier_action" model="ir.actions.act_window">
<field name="name">Custom Modifiers</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">web.custom.modifier</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="custom_modifier_list" />
</record>
<menuitem
action="custom_modifier_action"
id="custom_modifier_menu"
parent="base.next_id_2"
sequence="99"
/>
</odoo>