Browse Source

[ADD] server_action_navigate

14.0
Sylvain LE GAL 4 years ago
committed by Ashish Hirpara
parent
commit
b5e9abc711
  1. 130
      server_action_navigate/README.rst
  2. 1
      server_action_navigate/__init__.py
  3. 20
      server_action_navigate/__manifest__.py
  4. 32
      server_action_navigate/demo/ir_actions_server.xml
  5. 219
      server_action_navigate/i18n/fr.po
  6. 196
      server_action_navigate/i18n/server_action_navigate.pot
  7. 2
      server_action_navigate/models/__init__.py
  8. 89
      server_action_navigate/models/ir_actions_server.py
  9. 44
      server_action_navigate/models/ir_actions_server_navigate_line.py
  10. 27
      server_action_navigate/readme/CONFIGURE.rst
  11. 1
      server_action_navigate/readme/CONTRIBUTORS.rst
  12. 5
      server_action_navigate/readme/DESCRIPTION.rst
  13. 10
      server_action_navigate/readme/USAGE.rst
  14. 3
      server_action_navigate/security/ir.model.access.csv
  15. BIN
      server_action_navigate/static/description/icon.png
  16. 468
      server_action_navigate/static/description/index.html
  17. BIN
      server_action_navigate/static/description/ir_actions_server_form.png
  18. BIN
      server_action_navigate/static/description/product_product_tree.png
  19. BIN
      server_action_navigate/static/description/sale_order_tree.png
  20. 1
      server_action_navigate/tests/__init__.py
  21. 52
      server_action_navigate/tests/test_module.py
  22. 40
      server_action_navigate/views/view_ir_actions_server.xml

130
server_action_navigate/README.rst

@ -0,0 +1,130 @@
=========================
Server Actions - Navigate
=========================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--backend-lightgray.png?logo=github
:target: https://github.com/OCA/server-backend/tree/12.0/server_action_navigate
:alt: OCA/server-backend
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-backend-12-0/server-backend-12-0-server_action_navigate
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/253/12.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module provides a generic tool to have the possibility to see a list of
records associated to a given selection of records. This is basically the UI version of ``recordset.mapped('field1.field2')``.
For example, if you use the sale module, you can configure an action to see
all the products that have been sold for a given selection of sale orders.
**Table of contents**
.. contents::
:local:
Configuration
=============
* Go to 'Setting / Technical / Actions / Server Actions'
* Create a new item
.. figure:: https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/ir_actions_server_form.png
* Set a name that will be used for the Contextual Action
* Choose a model
* Select 'Navigate' option in the field 'Action To Do'
* Then, select a field of this model. It can be a ``one2many``, ``many2many``,
``many2one`` field.
* You can then optionally select another fields (#2) that belong to the previous
selected field (#1) relation model
* Once done, click on the 'Create Contextual Action' to generate a new 'More options'
button.
**Extra options**
* You can define groups whose members will have access to that option.
* You can select an action that will be used to display the target field list.
Usage
=====
* Go to a tree view, for a model for which you have defined a navigation action.
* click on 'Action' Button
.. figure:: https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/sale_order_tree.png
* then click on the name of the configured Action and see the results
.. figure:: https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/product_product_tree.png
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-backend/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-backend/issues/new?body=module:%20server_action_navigate%0Aversion:%2012.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
~~~~~~~
* GRAP
Contributors
~~~~~~~~~~~~
* Sylvain LE GAL (https://www.twitter.com/legalsylvain)
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.
.. |maintainer-legalsylvain| image:: https://github.com/legalsylvain.png?size=40px
:target: https://github.com/legalsylvain
:alt: legalsylvain
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-legalsylvain|
This module is part of the `OCA/server-backend <https://github.com/OCA/server-backend/tree/12.0/server_action_navigate>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

1
server_action_navigate/__init__.py

@ -0,0 +1 @@
from . import models

20
server_action_navigate/__manifest__.py

@ -0,0 +1,20 @@
# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Server Actions - Navigate",
"version": "12.0.1.0.0",
"author": "GRAP, " "Odoo Community Association (OCA)",
"summary": "Navigate between any items of any Odoo Models",
"category": "Tools",
"website": "https://github.com/OCA/server-ux",
"license": "AGPL-3",
"depends": ["base"],
"maintainers": ["legalsylvain"],
"data": [
"security/ir.model.access.csv",
"views/view_ir_actions_server.xml",
],
"demo": ["demo/ir_actions_server.xml"],
}

32
server_action_navigate/demo/ir_actions_server.xml

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2020 - Today: GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="navigate_partner_2_tags" model="ir.actions.server">
<field name="name">See tags of Partners</field>
<field name="state">navigate</field>
<field name="model_id" ref="base.model_res_users"/>
</record>
<record id="navigate_line_1" model="ir.actions.server.navigate.line">
<field name="action_id" ref="navigate_partner_2_tags"/>
<field name="sequence">1</field>
<field name="field_id" ref="base.field_res_users__partner_id"/>
</record>
<record id="navigate_line_2" model="ir.actions.server.navigate.line">
<field name="action_id" ref="navigate_partner_2_tags"/>
<field name="sequence">2</field>
<field name="field_id" ref="base.field_res_partner__category_id"/>
</record>
<function model="ir.actions.server" name="create_action">
<value eval="[ref('navigate_partner_2_tags')]"/>
</function>
</odoo>

219
server_action_navigate/i18n/fr.po

@ -0,0 +1,219 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * server_action_navigate
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-02-25 14:25+0000\n"
"PO-Revision-Date: 2020-02-25 14:25+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__action_id
msgid "Action"
msgstr "Action"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__state
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__state
msgid "Action To Do"
msgstr "Action à effectuer"
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Add Followers"
msgstr "Ajouter des abonnés"
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Create Next Activity"
msgstr "Créer une activité "
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Create a new Record"
msgstr "Créer un nouvel enregistrement"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__create_date
msgid "Created on"
msgstr "Créé le"
#. module: server_action_navigate
#: model:ir.model.fields,help:server_action_navigate.field_ir_actions_server__navigate_action_id
#: model:ir.model.fields,help:server_action_navigate.field_ir_cron__navigate_action_id
msgid ""
"Define here the action used when the navigation will be executed if empty, a "
"generic action will be used."
msgstr ""
"Définir ici l'action qui sera utilisé lors que la navigation sera exécutée. "
"Si le champs est vide, une action générique sera utilisée."
#. module: server_action_navigate
#: model_terms:ir.ui.view,arch_db:server_action_navigate.view_ir_actions_server_form
msgid "Delete Last Line"
msgstr "Supprimer la dernière ligne"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Execute Python Code"
msgstr "Exécuter le code Python"
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Execute several actions"
msgstr "Exécuter plusieurs actions"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__field_id
msgid "Field"
msgstr "Champ"
#. module: server_action_navigate
#: model:ir.model.fields,help:server_action_navigate.field_ir_actions_server_navigate_line__field_model
msgid "For relationship fields, the technical name of the target model"
msgstr "Pour les champs de relation, le nom technique du modèle cible"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__id
msgid "ID"
msgstr "ID"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__max_navigate_line_model
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__max_navigate_line_model
msgid "Max Navigate Model in lines"
msgstr "Modèle maximum des lignes"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__max_navigate_line_sequence
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__max_navigate_line_sequence
msgid "Max Navigate sequence in lines"
msgstr "Sequence maximum des lignes"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__field_model
msgid "Model"
msgstr "Modèle"
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Navigate"
msgstr "Naviguer"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__navigate_line_ids
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__navigate_line_ids
msgid "Navigate Lines"
msgstr "Lignes de navigation"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__navigate_action_id
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__navigate_action_id
msgid "Navigation Action"
msgstr "Action de navigation"
#. module: server_action_navigate
#: model_terms:ir.ui.view,arch_db:server_action_navigate.view_ir_actions_server_form
msgid "Navigation Options"
msgstr "Options de navigation"
#. module: server_action_navigate
#: model:ir.actions.server,name:server_action_navigate.navigate_partner_2_tags
msgid "See tags of Partners"
msgstr "Voir les catégories des partenaires"
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Send Email"
msgstr "Envoyer par courriel"
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__sequence
msgid "Sequence"
msgstr "Séquence"
#. module: server_action_navigate
#: model:ir.model,name:server_action_navigate.model_ir_actions_server
msgid "Server Action"
msgstr "Action du serveur"
#. module: server_action_navigate
#: model:ir.model,name:server_action_navigate.model_ir_actions_server_navigate_line
msgid "Server Actions Navigation Lines"
msgstr "Lignes de navigation de l'action-serveur"
#. module: server_action_navigate
#: code:addons/server_action_navigate/models/ir_actions_server.py:62
#, fuzzy, python-format
msgid ""
"The Action Server %s is not correctly set\n"
" : No fields defined"
msgstr ""
"L'action-serveur '%d' n'est pas correctement paramétré : Aucun champ défini."
#. module: server_action_navigate
#: model:ir.model.fields,help:server_action_navigate.field_ir_actions_server__state
#: model:ir.model.fields,help:server_action_navigate.field_ir_cron__state
msgid ""
"Type of server action. The following values are available:\n"
"- 'Execute Python Code': a block of python code that will be executed\n"
"- 'Create': create a new record with new values\n"
"- 'Update a Record': update the values of a record\n"
"- 'Execute several actions': define an action that triggers several other "
"server actions\n"
"- 'Send Email': automatically send an email (Discuss)\n"
"- 'Add Followers': add followers to a record (Discuss)\n"
"- 'Create Next Activity': create an activity (Discuss)"
msgstr ""
"Type d'action serveur. Les valeurs suivantes sont disponibles:\n"
"- 'Executer du code Python': un bloc de code python qui sera exécuté\n"
"- 'Créer': créer un nouvel enregistrement avec de nouvelles valeurs\n"
"- 'Mettre à jour un enregistrement': mettre à jour les valeurs d'un "
"enregistrement\n"
"- 'Executer plusieurs actions': définir une action qui déclenche plusieurs "
"autres actions serveur\n"
"- 'Envoyer E-Mail': envoyer un e-mail automatiquement (Discutez)\n"
"- 'Ajouter Abonnés': ajouter des abonnés à un enregistrement (Discutez)\n"
"- 'Créer une Nouvelle Activité': créer une activité (Discutez)"
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Update the Record"
msgstr "Mettre à jour l'enregistrement"
#~ msgid "Line Number"
#~ msgstr "Numéro de ligne"

196
server_action_navigate/i18n/server_action_navigate.pot

@ -0,0 +1,196 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * server_action_navigate
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \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: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__action_id
msgid "Action"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__state
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__state
msgid "Action To Do"
msgstr ""
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Add Followers"
msgstr ""
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Create Next Activity"
msgstr ""
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Create a new Record"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__create_uid
msgid "Created by"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__create_date
msgid "Created on"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,help:server_action_navigate.field_ir_actions_server__navigate_action_id
#: model:ir.model.fields,help:server_action_navigate.field_ir_cron__navigate_action_id
msgid "Define here the action used when the navigation will be executed if empty, a generic action will be used."
msgstr ""
#. module: server_action_navigate
#: model_terms:ir.ui.view,arch_db:server_action_navigate.view_ir_actions_server_form
msgid "Delete Last Line"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__display_name
msgid "Display Name"
msgstr ""
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Execute Python Code"
msgstr ""
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Execute several actions"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__field_id
msgid "Field"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,help:server_action_navigate.field_ir_actions_server_navigate_line__field_model
msgid "For relationship fields, the technical name of the target model"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__id
msgid "ID"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line____last_update
msgid "Last Modified on"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__write_uid
msgid "Last Updated by"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__write_date
msgid "Last Updated on"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__max_navigate_line_model
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__max_navigate_line_model
msgid "Max Navigate Model in lines"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__max_navigate_line_sequence
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__max_navigate_line_sequence
msgid "Max Navigate sequence in lines"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__field_model
msgid "Model"
msgstr ""
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Navigate"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__navigate_line_ids
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__navigate_line_ids
msgid "Navigate Lines"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server__navigate_action_id
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_cron__navigate_action_id
msgid "Navigation Action"
msgstr ""
#. module: server_action_navigate
#: model_terms:ir.ui.view,arch_db:server_action_navigate.view_ir_actions_server_form
msgid "Navigation Options"
msgstr ""
#. module: server_action_navigate
#: model:ir.actions.server,name:server_action_navigate.navigate_partner_2_tags
msgid "See tags of Partners"
msgstr ""
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Send Email"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,field_description:server_action_navigate.field_ir_actions_server_navigate_line__sequence
msgid "Sequence"
msgstr ""
#. module: server_action_navigate
#: model:ir.model,name:server_action_navigate.model_ir_actions_server
msgid "Server Action"
msgstr ""
#. module: server_action_navigate
#: model:ir.model,name:server_action_navigate.model_ir_actions_server_navigate_line
msgid "Server Actions Navigation Lines"
msgstr ""
#. module: server_action_navigate
#: code:addons/server_action_navigate/models/ir_actions_server.py:62
#, python-format
msgid "The Action Server %s is not correctly set\n"
" : No fields defined"
msgstr ""
#. module: server_action_navigate
#: model:ir.model.fields,help:server_action_navigate.field_ir_actions_server__state
#: model:ir.model.fields,help:server_action_navigate.field_ir_cron__state
msgid "Type of server action. The following values are available:\n"
"- 'Execute Python Code': a block of python code that will be executed\n"
"- 'Create': create a new record with new values\n"
"- 'Update a Record': update the values of a record\n"
"- 'Execute several actions': define an action that triggers several other server actions\n"
"- 'Send Email': automatically send an email (Discuss)\n"
"- 'Add Followers': add followers to a record (Discuss)\n"
"- 'Create Next Activity': create an activity (Discuss)"
msgstr ""
#. module: server_action_navigate
#: selection:ir.actions.server,state:0
msgid "Update the Record"
msgstr ""

2
server_action_navigate/models/__init__.py

@ -0,0 +1,2 @@
from . import ir_actions_server
from . import ir_actions_server_navigate_line

89
server_action_navigate/models/ir_actions_server.py

@ -0,0 +1,89 @@
# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class IrActionsServer(models.Model):
_inherit = "ir.actions.server"
state = fields.Selection(selection_add=[("navigate", "Navigate")])
navigate_action_id = fields.Many2one(
string="Navigation Action",
comodel_name='ir.actions.act_window',
domain="[('res_model', '=', max_navigate_line_model)]",
help="Define here the action used when the navigation will be executed"
" if empty, a generic action will be used.")
navigate_line_ids = fields.One2many(
comodel_name="ir.actions.server.navigate.line",
string="Navigate Lines", inverse_name="action_id")
max_navigate_line_sequence = fields.Integer(
string='Max Navigate sequence in lines',
compute='_compute_max_navigate_line',
store=True
)
max_navigate_line_model = fields.Char(
string="Max Navigate Model in lines",
compute="_compute_max_navigate_line",
store=True)
@api.depends("navigate_line_ids", "model_id")
def _compute_max_navigate_line(self):
"""Allow to know the highest sequence entered in navigate lines.
Then we add 1 to this value for the next sequence.
This value is given to the context of the o2m field in the view.
So when we create new navigate line, the sequence is automatically
added as : max_sequence + 1
"""
for action in self:
action.max_navigate_line_sequence = (
max(action.mapped('navigate_line_ids.sequence') or [0]) + 1)
action.max_navigate_line_model =\
action.navigate_line_ids\
and action.navigate_line_ids[-1].field_model\
or action.model_id.model
def delete_last_line(self):
self.ensure_one()
self.navigate_line_ids[-1].unlink()
self.navigate_action_id = False
@api.model
def run_action_navigate_multi(self, action, eval_context=None):
IrModel = self.env['ir.model']
lines = action.navigate_line_ids
if not lines:
raise UserError(_(
"The Action Server %s is not correctly set\n"
" : No fields defined"))
mapped_field_value = ".".join(lines.mapped("field_id.name"))
item_ids = eval_context['records'].mapped(mapped_field_value).ids
domain = "[('id','in',[" + ','.join(map(str, item_ids)) + "])]"
# Use Defined action if defined
if action.navigate_action_id:
return_action = action.navigate_action_id
result = return_action.read()[0]
result['domain'] = domain
else:
# Otherwise, return a default action
model_name = action.max_navigate_line_model
model = IrModel.search([('model', '=', model_name)])
view_mode = 'tree,form'
result = {
'name': model.name,
'domain': domain,
'res_model': model_name,
'target': 'current',
'type': 'ir.actions.act_window',
'view_mode': view_mode,
}
return result

44
server_action_navigate/models/ir_actions_server_navigate_line.py

@ -0,0 +1,44 @@
# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
class IrActionsServerNavigateLine(models.Model):
_name = "ir.actions.server.navigate.line"
_description = "Server Actions Navigation Lines"
_order = "sequence"
sequence = fields.Integer(string="Sequence", default=1)
field_model = fields.Char(
string="Model", related="field_id.relation", store=True)
action_id = fields.Many2one(
comodel_name="ir.actions.server", string="Action",
required=True, ondelete="cascade")
field_id = fields.Many2one(
comodel_name="ir.model.fields", string="Field",
required=True)
# when adding a record, onchange is called for every field on the
# form, also in editable list views
@api.onchange('field_id')
def _onchange_field_id(self):
# check out the docstring of this in odoo/models.py
lines = self.action_id.resolve_2many_commands(
'navigate_line_ids',
self.env.context.get('navigate_line_ids', []),
)
lines = sum(map(self.new, lines), self.browse([]))
model = lines[-1:].field_id.relation or self.action_id.model_id.model
return {
'domain': {
'field_id': [
('ttype', 'in', ['many2one', 'one2many', 'many2many']),
('model', '=', model),
],
}
}

27
server_action_navigate/readme/CONFIGURE.rst

@ -0,0 +1,27 @@
* Go to 'Setting / Technical / Actions / Server Actions'
* Create a new item
.. figure:: ../static/description/ir_actions_server_form.png
* Set a name that will be used for the Contextual Action
* Choose a model
* Select 'Navigate' option in the field 'Action To Do'
* Then, select a field of this model. It can be a ``one2many``, ``many2many``,
``many2one`` field.
* You can then optionally select another fields (#2) that belong to the previous
selected field (#1) relation model
* Once done, click on the 'Create Contextual Action' to generate a new 'More options'
button.
**Extra options**
* You can define groups whose members will have access to that option.
* You can select an action that will be used to display the target field list.

1
server_action_navigate/readme/CONTRIBUTORS.rst

@ -0,0 +1 @@
* Sylvain LE GAL (https://www.twitter.com/legalsylvain)

5
server_action_navigate/readme/DESCRIPTION.rst

@ -0,0 +1,5 @@
This module provides a generic tool to have the possibility to see a list of
records associated to a given selection of records. This is basically the UI version of ``recordset.mapped('field1.field2')``.
For example, if you use the sale module, you can configure an action to see
all the products that have been sold for a given selection of sale orders.

10
server_action_navigate/readme/USAGE.rst

@ -0,0 +1,10 @@
* Go to a tree view, for a model for which you have defined a navigation action.
* click on 'Action' Button
.. figure:: ../static/description/sale_order_tree.png
* then click on the name of the configured Action and see the results
.. figure:: ../static/description/product_product_tree.png

3
server_action_navigate/security/ir.model.access.csv

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
ir_actions_server_navigate_line_all,ir_actions_server_navigate_line_all,model_ir_actions_server_navigate_line,,1,0,0,0
ir_actions_server_navigate_line_group_system,ir_actions_server_navigate_line_group_system,model_ir_actions_server_navigate_line,base.group_system,1,1,1,1

BIN
server_action_navigate/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

468
server_action_navigate/static/description/index.html

@ -0,0 +1,468 @@
<?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 0.15.1: http://docutils.sourceforge.net/" />
<title>Server Actions - Navigate</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/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="server-actions-navigate">
<h1 class="title">Server Actions - Navigate</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" 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" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/server-backend/tree/12.0/server_action_navigate"><img alt="OCA/server-backend" src="https://img.shields.io/badge/github-OCA%2Fserver--backend-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/server-backend-12-0/server-backend-12-0-server_action_navigate"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/253/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module provides a generic tool to have the possibility to see a list of
records associated to a given selection of records. This is basically the UI version of <tt class="docutils literal"><span class="pre">recordset.mapped('field1.field2')</span></tt>.</p>
<p>For example, if you use the sale module, you can configure an action to see
all the products that have been sold for a given selection of sale orders.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<ul class="simple">
<li>Go to ‘Setting / Technical / Actions / Server Actions’</li>
<li>Create a new item</li>
</ul>
<div class="figure">
<img alt="https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/ir_actions_server_form.png" src="https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/ir_actions_server_form.png" />
</div>
<ul class="simple">
<li>Set a name that will be used for the Contextual Action</li>
<li>Choose a model</li>
<li>Select ‘Navigate’ option in the field ‘Action To Do’</li>
<li>Then, select a field of this model. It can be a <tt class="docutils literal">one2many</tt>, <tt class="docutils literal">many2many</tt>,
<tt class="docutils literal">many2one</tt> field.</li>
<li>You can then optionally select another fields (#2) that belong to the previous
selected field (#1) relation model</li>
<li>Once done, click on the ‘Create Contextual Action’ to generate a new ‘More options’
button.</li>
</ul>
<p><strong>Extra options</strong></p>
<ul class="simple">
<li>You can define groups whose members will have access to that option.</li>
<li>You can select an action that will be used to display the target field list.</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<ul class="simple">
<li>Go to a tree view, for a model for which you have defined a navigation action.</li>
<li>click on ‘Action’ Button</li>
</ul>
<div class="figure">
<img alt="https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/sale_order_tree.png" src="https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/sale_order_tree.png" />
</div>
<ul class="simple">
<li>then click on the name of the configured Action and see the results</li>
</ul>
<div class="figure">
<img alt="https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/product_product_tree.png" src="https://raw.githubusercontent.com/OCA/server-backend/12.0/server_action_navigate/static/description/product_product_tree.png" />
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-backend/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 smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/server-backend/issues/new?body=module:%20server_action_navigate%0Aversion:%2012.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="#id4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
<ul class="simple">
<li>GRAP</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id6">Contributors</a></h2>
<ul class="simple">
<li>Sylvain LE GAL (<a class="reference external" href="https://www.twitter.com/legalsylvain">https://www.twitter.com/legalsylvain</a>)</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id7">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>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/legalsylvain"><img alt="legalsylvain" src="https://github.com/legalsylvain.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-backend/tree/12.0/server_action_navigate">OCA/server-backend</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>

BIN
server_action_navigate/static/description/ir_actions_server_form.png

After

Width: 1014  |  Height: 529  |  Size: 33 KiB

BIN
server_action_navigate/static/description/product_product_tree.png

After

Width: 1258  |  Height: 360  |  Size: 53 KiB

BIN
server_action_navigate/static/description/sale_order_tree.png

After

Width: 1258  |  Height: 310  |  Size: 76 KiB

1
server_action_navigate/tests/__init__.py

@ -0,0 +1 @@
from . import test_module

52
server_action_navigate/tests/test_module.py

@ -0,0 +1,52 @@
# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.tests.common import TransactionCase
from odoo.tools.safe_eval import safe_eval
class TestModule(TransactionCase):
def setUp(self):
super().setUp()
self.action_server = self.env.ref(
"server_action_navigate.navigate_partner_2_tags")
self.users = self.env["res.users"].search([])
def test_action_result(self):
result = self.action_server.with_context(
active_model="res.users",
active_ids=self.users.ids).run()
self.assertEqual(result.get("id", False), False)
self.assertEqual(
result.get('res_model', False), 'res.partner.category')
self.assertEqual(
safe_eval(result.get('domain', [])),
[("id", "in", self.users.mapped("partner_id.category_id").ids)]
)
def test_delete_last_line(self):
line_qty = len(self.action_server.navigate_line_ids)
self.action_server.delete_last_line()
self.assertEqual(
line_qty - 1,
len(self.action_server.navigate_line_ids)
)
def test_action_navigate_with_action(self):
self.action_server.navigate_action_id = self.env.ref(
"base.action_partner_category_form")
result = self.action_server.with_context(
active_model="res.users",
active_ids=self.users.ids).run()
self.assertEqual(
result.get("id", False),
self.env.ref("base.action_partner_category_form").id,
)

40
server_action_navigate/views/view_ir_actions_server.xml

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2020 - Today: GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="view_ir_actions_server_form" model="ir.ui.view">
<field name="model">ir.actions.server</field>
<field name="inherit_id" ref="base.view_server_action_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='code']" position="after">
<page string="Navigation Options" name='page_navigate' autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'navigate')]}">
<group>
<field name="max_navigate_line_sequence" invisible="1"/>
<field name="max_navigate_line_model" invisible="1"/>
<!-- we can check emptyness of a 2many like this since v10 i think /-->
<button name="delete_last_line" string="Delete Last Line" type="object"
attrs="{'invisible': [('navigate_line_ids', '=', [])]}"/>
<field name="navigate_line_ids" context="{'navigate_line_ids': navigate_line_ids}" nolabel="1" colspan="4">
<tree editable="bottom" delete="false">
<!-- the handle widget takes care of the sequence /-->
<field name="sequence" widget="handle" invisible="True" />
<field name="field_id" />
<field name="field_model"/>
</tree>
</field>
<field name="navigate_action_id"
attrs="{'invisible': [('navigate_line_ids', '=', [])]}"/>
</group>
</page>
</xpath>
</field>
</record>
</odoo>
Loading…
Cancel
Save