Browse Source
Rename addon according to the new term: partner_changeset
Rename addon according to the new term: partner_changeset
Add translations Add test coverage for caching of changeset rules Add unique constraint (model_id, field_id) on rules Add rules in demo data Put the security groups links in noupdate Put keep the groups in a noupdate=0 section so the records in ir.model.data will still be modifiable by other modules. Store the source of a changeset Differentiate rules according to their origin Rules are applied also for manual edition Action is required Do not keep recordsets in ormcache Because they would be unreadable as soon as the cursor is closed. Instead, we keep only the id and the record is browsed for every new environment. Remove useless 'model_id' on changeset rules model_id has been removed Remove reference to the model_name on rules Because the model has been removed earlier (dead code) Fix issue when applying empty many2one Do not create changesets on moved contacts As we just created the contact with a 'copy' we don't want to have a changeset for the initialization values Update translations Use a selection widget on source model Adapt for inclusion in OCA Rename 'Pending Changesets' to 'Changes' It's shorter Add screenshots Do not create a changeset when both sides are empty But have a different type (e.g. False and '')pull/380/head
Guewen Baconnier
9 years ago
committed by
Damien Crier
25 changed files with 2691 additions and 0 deletions
-
171partner_changeset/README.rst
-
4partner_changeset/__init__.py
-
23partner_changeset/__openerp__.py
-
51partner_changeset/demo/changeset_field_rule.xml
-
313partner_changeset/i18n/fr.po
-
289partner_changeset/i18n/partner_changeset.pot
-
7partner_changeset/models/__init__.py
-
144partner_changeset/models/changeset_field_rule.py
-
50partner_changeset/models/res_partner.py
-
514partner_changeset/models/res_partner_changeset.py
-
10partner_changeset/security/ir.model.access.csv
-
29partner_changeset/security/security.xml
-
BINpartner_changeset/static/description/icon.png
-
BINpartner_changeset/static/src/img/changeset.png
-
BINpartner_changeset/static/src/img/rules.png
-
6partner_changeset/tests/__init__.py
-
77partner_changeset/tests/common.py
-
95partner_changeset/tests/test_changeset_field_rule.py
-
279partner_changeset/tests/test_changeset_field_type.py
-
246partner_changeset/tests/test_changeset_flow.py
-
110partner_changeset/tests/test_changeset_origin.py
-
63partner_changeset/views/changeset_field_rule_views.xml
-
10partner_changeset/views/menu.xml
-
159partner_changeset/views/res_partner_changeset_views.xml
-
41partner_changeset/views/res_partner_views.xml
@ -0,0 +1,171 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
================== |
|||
Partner Changesets |
|||
================== |
|||
|
|||
This module extends the functionality of partners. It allows to create |
|||
changesets that must be validated when a partner is modified instead of direct |
|||
modifications. Rules allow to configure which field must be validated. |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
Access Rights |
|||
------------- |
|||
|
|||
The changesets rules must be edited by users with the group ``Changesets |
|||
Configuration``. The changesets can be applied or canceled only by users |
|||
with the group ``Changesets Validations`` |
|||
|
|||
Changesets Rules |
|||
---------------- |
|||
|
|||
The changesets rules can be configured in ``Sales > Configuration > |
|||
Partner Changesets > Fields Rules``. For each partner field, an |
|||
action can be defined: |
|||
|
|||
* Auto: the changes made on this field are always applied |
|||
* Validate: the changes made on this field must be manually confirmed by |
|||
a 'Changesets User' user |
|||
* Never: the changes made on this field are always refused |
|||
|
|||
In any case, all the changes made by the users are always applied |
|||
directly on the users, but a 'validated' changeset is created for the |
|||
history. |
|||
|
|||
The supported fields are: |
|||
|
|||
* Char |
|||
* Text |
|||
* Date |
|||
* Datetime |
|||
* Integer |
|||
* Float |
|||
* Boolean |
|||
* Many2one |
|||
|
|||
Rules can be global (no source model) or configured by source model. |
|||
Rules by source model have the priority. If a field is not configured |
|||
for the source model, it will use the global rule (if existing). |
|||
|
|||
If a field has no rule, it is written to the partner without changeset. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
General case |
|||
------------ |
|||
|
|||
The first step is to create the changeset rules, once that done, writes on |
|||
partners will be created as changesets. |
|||
|
|||
Finding changesets |
|||
------------------ |
|||
|
|||
A menu lists all the changesets in ``Sales > Configuration > Partner |
|||
Changesets > Changesets``. |
|||
|
|||
However, it is more convenient to access them directly from the |
|||
partners. Pending changesets can be accessed directly from the top right |
|||
of the partners' view. A new filter on the partners shows the partners |
|||
having at least one pending changeset. |
|||
|
|||
Handling changesets |
|||
------------------- |
|||
|
|||
A changeset shows the list of the changes made on a partner. Some of the |
|||
changes may be 'Pending', some 'Accepted' or 'Rejected' according to the |
|||
changeset rules. The only changes that need an action from the user are |
|||
'Pending' changes. When a change is accepted, the value is written on |
|||
the user. |
|||
|
|||
The changes view shows the name of the partner's field, the Origin value |
|||
and the New value alongside the state of the change. By clicking on the |
|||
change in some cases a more detailed view is displayed, for instance, |
|||
links for relations can be clicked on. |
|||
|
|||
A button on a changeset allows to apply or reject all the changes at |
|||
once. |
|||
|
|||
Custom source rules in your addon |
|||
--------------------------------- |
|||
|
|||
Addons wanting to create changeset with their own rules should pass the |
|||
following keys in the context when they write on the partner: |
|||
|
|||
* ``__changeset_rules_source_model``: name of the model which asks for |
|||
the change |
|||
* ``__changeset_rules_source_id``: id of the record which asks for the |
|||
change |
|||
|
|||
Also, they should extend the selection in |
|||
``ChangesetFieldRule._domain_source_models`` to add their model (the |
|||
same that is passed in ``__changeset_rules_source_model``). |
|||
|
|||
The source is used for the application of the rules, allowing to have a |
|||
different rule for a different source. It is also stored on the changeset for |
|||
information. |
|||
|
|||
Screenshot: |
|||
----------- |
|||
|
|||
* Configuration of rules |
|||
|
|||
.. image:: partner_changeset/static/src/img/rules.png |
|||
|
|||
* Changeset waiting for validation |
|||
|
|||
.. image:: partner_changeset/static/src/img/changeset.png |
|||
|
|||
|
|||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
|||
:alt: Try me on Runbot |
|||
:target: https://runbot.odoo-community.org/runbot/134/8.0 |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
* Only a subset of the type of fields is actually supported |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues |
|||
<https://github.com/OCA/partner-contact/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/ |
|||
partner-contact/issues/new?body=module:%20 |
|||
partner_changeset%0Aversion:%20 |
|||
8.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Images |
|||
------ |
|||
|
|||
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Guewen Baconnier <guewen.baconnier@camptocamp.com> |
|||
|
|||
Maintainer |
|||
---------- |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
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. |
|||
|
|||
To contribute to this module, please visit https://odoo-community.org. |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from . import models |
@ -0,0 +1,23 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
{'name': 'Partner Changesets', |
|||
'version': '8.0.1.0.0', |
|||
'author': 'Camptocamp, Odoo Community Association (OCA)', |
|||
'license': 'AGPL-3', |
|||
'category': 'Sales Management', |
|||
'depends': ['base', |
|||
], |
|||
'website': 'http://www.camptocamp.com', |
|||
'data': ['security/security.xml', |
|||
'security/ir.model.access.csv', |
|||
'views/menu.xml', |
|||
'views/res_partner_changeset_views.xml', |
|||
'views/changeset_field_rule_views.xml', |
|||
'views/res_partner_views.xml', |
|||
], |
|||
'demo': ['demo/changeset_field_rule.xml', |
|||
], |
|||
'installable': True, |
|||
} |
@ -0,0 +1,51 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data noupdate="1"> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_name"> |
|||
<field name="field_id" ref="base.field_res_partner_name"/> |
|||
<field name="action">auto</field> |
|||
</record> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_street"> |
|||
<field name="field_id" ref="base.field_res_partner_street"/> |
|||
<field name="action">validate</field> |
|||
</record> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_street2"> |
|||
<field name="field_id" ref="base.field_res_partner_street2"/> |
|||
<field name="action">validate</field> |
|||
</record> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_zip"> |
|||
<field name="field_id" ref="base.field_res_partner_zip"/> |
|||
<field name="action">validate</field> |
|||
</record> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_city"> |
|||
<field name="field_id" ref="base.field_res_partner_city"/> |
|||
<field name="action">validate</field> |
|||
</record> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_email"> |
|||
<field name="field_id" ref="base.field_res_partner_email"/> |
|||
<field name="action">never</field> |
|||
</record> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_ref"> |
|||
<field name="field_id" ref="base.field_res_partner_ref"/> |
|||
<field name="action">validate</field> |
|||
</record> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_country_id"> |
|||
<field name="field_id" ref="base.field_res_partner_country_id"/> |
|||
<field name="action">validate</field> |
|||
</record> |
|||
|
|||
<record model="changeset.field.rule" id="changeset_field_rule_credit_limit"> |
|||
<field name="field_id" ref="base.field_res_partner_credit_limit"/> |
|||
<field name="action">validate</field> |
|||
</record> |
|||
|
|||
</data> |
|||
</openerp> |
@ -0,0 +1,313 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * partner_changeset |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 8.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2015-11-25 08:31+0000\n" |
|||
"PO-Revision-Date: 2015-09-18 14:48+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: partner_changeset |
|||
#: sql_constraint:changeset.field.rule:0 |
|||
msgid "A rule already exists for this field." |
|||
msgstr "Une règle existe déjà pour ce champ." |
|||
|
|||
#. module: partner_changeset |
|||
#: selection:res.partner.changeset.change,state:0 |
|||
msgid "Accepted" |
|||
msgstr "Accepté" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,action:0 |
|||
msgid "Action" |
|||
msgstr "Action" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner.changeset.change:partner_changeset.view_res_partner_changeset_change_form |
|||
msgid "Apply" |
|||
msgstr "Appliquer" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
msgid "Apply pending changes" |
|||
msgstr "Appliquer les modifications en attente" |
|||
|
|||
#. module: partner_changeset |
|||
#: help:changeset.field.rule,action:0 |
|||
msgid "" |
|||
"Auto: always apply a change.\n" |
|||
"Validate: manually applied by an administrator.\n" |
|||
"Never: change never applied." |
|||
msgstr "" |
|||
"Auto: toujours appliquer une modification.\n" |
|||
"Validation: modification manuellement appliquée par un administrateur.\n" |
|||
"Jamais: modification jamais appliquée." |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner:partner_changeset.res_partner_view_buttons |
|||
#: field:res.partner.changeset,change_ids:0 |
|||
msgid "Changes" |
|||
msgstr "Modifications" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset.change,changeset_id:0 |
|||
msgid "Changeset" |
|||
msgstr "Jeu de modifications" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:res.groups,name:partner_changeset.group_changeset_manager |
|||
msgid "Changeset Configuration" |
|||
msgstr "Configuration des modifications" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.model,name:partner_changeset.model_changeset_field_rule |
|||
msgid "Changeset Field Rules" |
|||
msgstr "Règles de modifications par champ" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:changeset.field.rule:partner_changeset.view_changeset_field_rule_form |
|||
#: view:changeset.field.rule:partner_changeset.view_changeset_field_rule_search |
|||
#: view:changeset.field.rule:partner_changeset.view_changeset_field_rule_tree |
|||
#: model:ir.actions.act_window,name:partner_changeset.action_changeset_field_rule_view |
|||
msgid "Changeset Fields Rules" |
|||
msgstr "Règles de modifications par champ" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.ui.menu,name:partner_changeset.menu_res_partner_changeset |
|||
#: field:res.partner,changeset_ids:0 |
|||
msgid "Changesets" |
|||
msgstr "Modifications" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:res.groups,name:partner_changeset.group_changeset_user |
|||
msgid "Changesets Validations " |
|||
msgstr "Validations des modifications" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,create_uid:0 |
|||
#: field:res.partner.changeset,create_uid:0 |
|||
#: field:res.partner.changeset.change,create_uid:0 |
|||
msgid "Created by" |
|||
msgstr "Créé par" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,create_date:0 |
|||
#: field:res.partner.changeset,create_date:0 |
|||
#: field:res.partner.changeset.change,create_date:0 |
|||
msgid "Created on" |
|||
msgstr "Créé le" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset,date:0 |
|||
msgid "Date" |
|||
msgstr "Date" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: selection:res.partner.changeset,state:0 |
|||
msgid "Done" |
|||
msgstr "Fait" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,field_id:0 |
|||
#: field:res.partner.changeset.change,field_id:0 |
|||
msgid "Field" |
|||
msgstr "Champ" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.ui.menu,name:partner_changeset.menu_changeset_field_rule |
|||
msgid "Field Rules" |
|||
msgstr "Règle par champ" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
msgid "Group By" |
|||
msgstr "Grouper par" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,id:0 field:res.partner.changeset,id:0 |
|||
#: field:res.partner.changeset.change,id:0 |
|||
msgid "ID" |
|||
msgstr "Id." |
|||
|
|||
#. module: partner_changeset |
|||
#: help:changeset.field.rule,source_model_id:0 |
|||
msgid "" |
|||
"If a source model is defined, the rule will be applied only when the change " |
|||
"is made from this origin. Rules without source model are global and applies " |
|||
"to all backends.\n" |
|||
"Rules with a source model have precedence over global rules, but if a field " |
|||
"has no rule with a source model, the global rule is used." |
|||
msgstr "" |
|||
"Si un modèle source est défini, la règle ne sera appliquée que lorsque le " |
|||
"changement vient de cette source. Les règles sans modèle source sont " |
|||
"globales et sont appliquées à toutes les sources.\n " |
|||
"Les règles avec une souce ont la précédence sur les règles globales. Si un " |
|||
"champ n'a pas de règle avec source, la règle globale est utilisée." |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,write_uid:0 |
|||
#: field:res.partner.changeset,write_uid:0 |
|||
#: field:res.partner.changeset.change,write_uid:0 |
|||
msgid "Last Updated by" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,write_date:0 |
|||
#: field:res.partner.changeset,write_date:0 |
|||
#: field:res.partner.changeset.change,write_date:0 |
|||
msgid "Last Updated on" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset.change,new_value_boolean:0 |
|||
#: field:res.partner.changeset.change,new_value_char:0 |
|||
#: field:res.partner.changeset.change,new_value_date:0 |
|||
#: field:res.partner.changeset.change,new_value_datetime:0 |
|||
#: field:res.partner.changeset.change,new_value_float:0 |
|||
#: field:res.partner.changeset.change,new_value_integer:0 |
|||
#: field:res.partner.changeset.change,new_value_reference:0 |
|||
#: field:res.partner.changeset.change,new_value_text:0 |
|||
msgid "New" |
|||
msgstr "Nouveau" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset,note:0 |
|||
msgid "Note" |
|||
msgstr "Note" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset.change,old_value_boolean:0 |
|||
#: field:res.partner.changeset.change,old_value_char:0 |
|||
#: field:res.partner.changeset.change,old_value_date:0 |
|||
#: field:res.partner.changeset.change,old_value_datetime:0 |
|||
#: field:res.partner.changeset.change,old_value_float:0 |
|||
#: field:res.partner.changeset.change,old_value_integer:0 |
|||
#: field:res.partner.changeset.change,old_value_reference:0 |
|||
#: field:res.partner.changeset.change,old_value_text:0 |
|||
msgid "Old" |
|||
msgstr "Ancien" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.model,name:partner_changeset.model_res_partner |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: field:res.partner.changeset,partner_id:0 |
|||
msgid "Partner" |
|||
msgstr "Partenaire" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.actions.act_window,name:partner_changeset.action_res_partner_changeset_view |
|||
#: model:ir.model,name:partner_changeset.model_res_partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_tree |
|||
msgid "Partner Changeset" |
|||
msgstr "Modifications de partenaire" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.model,name:partner_changeset.model_res_partner_changeset_change |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner.changeset.change:partner_changeset.view_res_partner_changeset_change_form |
|||
msgid "Partner Changeset Change" |
|||
msgstr "Modification de partenaire" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.ui.menu,name:partner_changeset.menu_changeset |
|||
msgid "Partner Changesets" |
|||
msgstr "Modifications de partenaire" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: selection:res.partner.changeset,state:0 |
|||
#: selection:res.partner.changeset.change,state:0 |
|||
msgid "Pending" |
|||
msgstr "En attente" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner:partner_changeset.view_res_partner_filter |
|||
msgid "Pending Changesets" |
|||
msgstr "Modifications en attente" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
msgid "Previous" |
|||
msgstr "Précédent" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner.changeset.change:partner_changeset.view_res_partner_changeset_change_form |
|||
msgid "Reject" |
|||
msgstr "Rejeter" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
msgid "Reject pending changes" |
|||
msgstr "Rejeter les modifications en attente" |
|||
|
|||
#. module: partner_changeset |
|||
#: selection:res.partner.changeset.change,state:0 |
|||
msgid "Rejected" |
|||
msgstr "Rejeté" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,source_model_id:0 |
|||
msgid "Source Model" |
|||
msgstr "Modèle source" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset,source:0 |
|||
msgid "Source of the change" |
|||
msgstr "Source du changement" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: field:res.partner.changeset,state:0 |
|||
#: field:res.partner.changeset.change,state:0 |
|||
msgid "State" |
|||
msgstr "État" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:res.groups,comment:partner_changeset.group_changeset_user |
|||
msgid "The user will be able to apply or reject changesets." |
|||
msgstr "L'utilisateur pourra appliquer ou rejeter les jeux de modifications." |
|||
|
|||
#. module: partner_changeset |
|||
#: model:res.groups,comment:partner_changeset.group_changeset_manager |
|||
msgid "" |
|||
"The user will have an access to the configuration of the changeset rules." |
|||
msgstr "" |
|||
"L'utilisateur aura accès à la configuration des règles de jeux de " |
|||
"modifications." |
|||
|
|||
#. module: partner_changeset |
|||
#: code:addons/partner_changeset/models/res_partner_changeset.py:418 |
|||
#, python-format |
|||
msgid "" |
|||
"This change cannot be applied because a previous changeset for the same " |
|||
"partner is pending.\n" |
|||
"Apply all the anterior changesets before applying this one." |
|||
msgstr "" |
|||
"The changement ne peux pas être appliqué car un précédent jeu de " |
|||
"modification pour le même partenaire est toujours en attente.\n" |
|||
"Il est nécessaire d'appliquer tous les précédents jeux de modification avant " |
|||
"d'applique celui-ci." |
|||
|
|||
#. module: partner_changeset |
|||
#: code:addons/partner_changeset/models/res_partner_changeset.py:433 |
|||
#, python-format |
|||
msgid "This change has already be applied." |
|||
msgstr "The changement est déjà appliqué." |
|||
|
|||
#~ msgid "Model" |
|||
#~ msgstr "Modèle" |
@ -0,0 +1,289 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * partner_changeset |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 8.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2015-11-25 08:31+0000\n" |
|||
"PO-Revision-Date: 2015-11-25 08:31+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: partner_changeset |
|||
#: sql_constraint:changeset.field.rule:0 |
|||
msgid "A rule already exists for this field." |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: selection:res.partner.changeset.change,state:0 |
|||
msgid "Accepted" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,action:0 |
|||
msgid "Action" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner.changeset.change:partner_changeset.view_res_partner_changeset_change_form |
|||
msgid "Apply" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
msgid "Apply pending changes" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: help:changeset.field.rule,action:0 |
|||
msgid "Auto: always apply a change.\n" |
|||
"Validate: manually applied by an administrator.\n" |
|||
"Never: change never applied." |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner:partner_changeset.res_partner_view_buttons |
|||
#: field:res.partner.changeset,change_ids:0 |
|||
msgid "Changes" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset.change,changeset_id:0 |
|||
msgid "Changeset" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:res.groups,name:partner_changeset.group_changeset_manager |
|||
msgid "Changeset Configuration" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.model,name:partner_changeset.model_changeset_field_rule |
|||
msgid "Changeset Field Rules" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:changeset.field.rule:partner_changeset.view_changeset_field_rule_form |
|||
#: view:changeset.field.rule:partner_changeset.view_changeset_field_rule_search |
|||
#: view:changeset.field.rule:partner_changeset.view_changeset_field_rule_tree |
|||
#: model:ir.actions.act_window,name:partner_changeset.action_changeset_field_rule_view |
|||
msgid "Changeset Fields Rules" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.ui.menu,name:partner_changeset.menu_res_partner_changeset |
|||
#: field:res.partner,changeset_ids:0 |
|||
msgid "Changesets" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:res.groups,name:partner_changeset.group_changeset_user |
|||
msgid "Changesets Validations " |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,create_uid:0 |
|||
#: field:res.partner.changeset,create_uid:0 |
|||
#: field:res.partner.changeset.change,create_uid:0 |
|||
msgid "Created by" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,create_date:0 |
|||
#: field:res.partner.changeset,create_date:0 |
|||
#: field:res.partner.changeset.change,create_date:0 |
|||
msgid "Created on" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset,date:0 |
|||
msgid "Date" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: selection:res.partner.changeset,state:0 |
|||
msgid "Done" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,field_id:0 |
|||
#: field:res.partner.changeset.change,field_id:0 |
|||
msgid "Field" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.ui.menu,name:partner_changeset.menu_changeset_field_rule |
|||
msgid "Field Rules" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
msgid "Group By" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,id:0 |
|||
#: field:res.partner.changeset,id:0 |
|||
#: field:res.partner.changeset.change,id:0 |
|||
msgid "ID" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: help:changeset.field.rule,source_model_id:0 |
|||
msgid "If a source model is defined, the rule will be applied only when the change is made from this origin. Rules without source model are global and applies to all backends.\n" |
|||
"Rules with a source model have precedence over global rules, but if a field has no rule with a source model, the global rule is used." |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,write_uid:0 |
|||
#: field:res.partner.changeset,write_uid:0 |
|||
#: field:res.partner.changeset.change,write_uid:0 |
|||
msgid "Last Updated by" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,write_date:0 |
|||
#: field:res.partner.changeset,write_date:0 |
|||
#: field:res.partner.changeset.change,write_date:0 |
|||
msgid "Last Updated on" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset.change,new_value_boolean:0 |
|||
#: field:res.partner.changeset.change,new_value_char:0 |
|||
#: field:res.partner.changeset.change,new_value_date:0 |
|||
#: field:res.partner.changeset.change,new_value_datetime:0 |
|||
#: field:res.partner.changeset.change,new_value_float:0 |
|||
#: field:res.partner.changeset.change,new_value_integer:0 |
|||
#: field:res.partner.changeset.change,new_value_reference:0 |
|||
#: field:res.partner.changeset.change,new_value_text:0 |
|||
msgid "New" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset,note:0 |
|||
msgid "Note" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset.change,old_value_boolean:0 |
|||
#: field:res.partner.changeset.change,old_value_char:0 |
|||
#: field:res.partner.changeset.change,old_value_date:0 |
|||
#: field:res.partner.changeset.change,old_value_datetime:0 |
|||
#: field:res.partner.changeset.change,old_value_float:0 |
|||
#: field:res.partner.changeset.change,old_value_integer:0 |
|||
#: field:res.partner.changeset.change,old_value_reference:0 |
|||
#: field:res.partner.changeset.change,old_value_text:0 |
|||
msgid "Old" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.model,name:partner_changeset.model_res_partner |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: field:res.partner.changeset,partner_id:0 |
|||
msgid "Partner" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.actions.act_window,name:partner_changeset.action_res_partner_changeset_view |
|||
#: model:ir.model,name:partner_changeset.model_res_partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_tree |
|||
msgid "Partner Changeset" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.model,name:partner_changeset.model_res_partner_changeset_change |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner.changeset.change:partner_changeset.view_res_partner_changeset_change_form |
|||
msgid "Partner Changeset Change" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:ir.ui.menu,name:partner_changeset.menu_changeset |
|||
msgid "Partner Changesets" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: selection:res.partner.changeset,state:0 |
|||
#: selection:res.partner.changeset.change,state:0 |
|||
msgid "Pending" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner:partner_changeset.view_res_partner_filter |
|||
msgid "Pending Changesets" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
msgid "Previous" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
#: view:res.partner.changeset.change:partner_changeset.view_res_partner_changeset_change_form |
|||
msgid "Reject" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_form |
|||
msgid "Reject pending changes" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: selection:res.partner.changeset.change,state:0 |
|||
msgid "Rejected" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:changeset.field.rule,source_model_id:0 |
|||
msgid "Source Model" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: field:res.partner.changeset,source:0 |
|||
msgid "Source of the change" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: view:res.partner.changeset:partner_changeset.view_res_partner_changeset_search |
|||
#: field:res.partner.changeset,state:0 |
|||
#: field:res.partner.changeset.change,state:0 |
|||
msgid "State" |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:res.groups,comment:partner_changeset.group_changeset_user |
|||
msgid "The user will be able to apply or reject changesets." |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: model:res.groups,comment:partner_changeset.group_changeset_manager |
|||
msgid "The user will have an access to the configuration of the changeset rules." |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: code:addons/partner_changeset/models/res_partner_changeset.py:418 |
|||
#, python-format |
|||
msgid "This change cannot be applied because a previous changeset for the same partner is pending.\n" |
|||
"Apply all the anterior changesets before applying this one." |
|||
msgstr "" |
|||
|
|||
#. module: partner_changeset |
|||
#: code:addons/partner_changeset/models/res_partner_changeset.py:433 |
|||
#, python-format |
|||
msgid "This change has already be applied." |
|||
msgstr "" |
|||
|
@ -0,0 +1,7 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from . import res_partner |
|||
from . import res_partner_changeset |
|||
from . import changeset_field_rule |
@ -0,0 +1,144 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from openerp import models, fields, api |
|||
from openerp.tools.cache import ormcache |
|||
|
|||
|
|||
class ChangesetFieldRule(models.Model): |
|||
_name = 'changeset.field.rule' |
|||
_description = 'Changeset Field Rules' |
|||
_rec_name = 'field_id' |
|||
|
|||
field_id = fields.Many2one( |
|||
comodel_name='ir.model.fields', |
|||
string='Field', |
|||
domain="[('model_id.model', '=', 'res.partner'), " |
|||
" ('ttype', 'in', ('char', 'selection', 'date', 'datetime', " |
|||
" 'float', 'integer', 'text', 'boolean', " |
|||
" 'many2one')), " |
|||
" ('readonly', '=', False)]", |
|||
ondelete='cascade', |
|||
required=True, |
|||
) |
|||
action = fields.Selection( |
|||
selection='_selection_action', |
|||
string='Action', |
|||
required=True, |
|||
help="Auto: always apply a change.\n" |
|||
"Validate: manually applied by an administrator.\n" |
|||
"Never: change never applied.", |
|||
) |
|||
source_model_id = fields.Many2one( |
|||
comodel_name='ir.model', |
|||
string='Source Model', |
|||
ondelete='cascade', |
|||
domain=lambda self: [('id', 'in', self._domain_source_models().ids)], |
|||
help="If a source model is defined, the rule will be applied only " |
|||
"when the change is made from this origin. " |
|||
"Rules without source model are global and applies to all " |
|||
"backends.\n" |
|||
"Rules with a source model have precedence over global rules, " |
|||
"but if a field has no rule with a source model, the global rule " |
|||
"is used." |
|||
) |
|||
|
|||
_sql_constraints = [ |
|||
('model_field_uniq', |
|||
'unique (source_model_id, field_id)', |
|||
'A rule already exists for this field.'), |
|||
] |
|||
|
|||
@api.model |
|||
def _domain_source_models(self): |
|||
""" Returns the models for which we can define rules. |
|||
|
|||
Example for submodules (replace by the xmlid of the model): |
|||
|
|||
:: |
|||
models = super(ChangesetFieldRule, self)._domain_source_models() |
|||
return models | self.env.ref('base.model_res_users') |
|||
|
|||
Rules without model are global and apply for all models. |
|||
|
|||
""" |
|||
return self.env.ref('base.model_res_users') |
|||
|
|||
@api.model |
|||
def _selection_action(self): |
|||
return [('auto', 'Auto'), |
|||
('validate', 'Validate'), |
|||
('never', 'Never'), |
|||
] |
|||
|
|||
@ormcache(skiparg=1) |
|||
@api.model |
|||
def _get_rules(self, source_model_name): |
|||
""" Cache rules |
|||
|
|||
Keep only the id of the rules, because if we keep the recordsets |
|||
in the ormcache, we won't be able to browse them once their |
|||
cursor is closed. |
|||
|
|||
The public method ``get_rules`` return the rules with the recordsets |
|||
when called. |
|||
|
|||
""" |
|||
model_rules = self.search( |
|||
['|', ('source_model_id.model', '=', source_model_name), |
|||
('source_model_id', '=', False)], |
|||
# using 'ASC' means that 'NULLS LAST' is the default |
|||
order='source_model_id ASC', |
|||
) |
|||
# model's rules have precedence over global ones so we iterate |
|||
# over rules which have a source model first, then we complete |
|||
# them with the global rules |
|||
result = {} |
|||
for rule in model_rules: |
|||
# we already have a model's rule |
|||
if result.get(rule.field_id.name): |
|||
continue |
|||
result[rule.field_id.name] = rule.id |
|||
return result |
|||
|
|||
@api.model |
|||
def get_rules(self, source_model_name): |
|||
""" Return the rules for a model |
|||
|
|||
When a model is specified, it will return the rules for this |
|||
model. Fields that have no rule for this model will use the |
|||
global rules (those without source). |
|||
|
|||
The source model is the model which ask for a change, it will be |
|||
for instance ``res.users``, ``lefac.backend`` or |
|||
``magellan.backend``. |
|||
|
|||
The second argument (``source_model_name``) is optional but |
|||
cannot be an optional keyword argument otherwise it would not be |
|||
in the key for the cache. The callers have to pass ``None`` if |
|||
they want only global rules. |
|||
""" |
|||
rules = {} |
|||
cached_rules = self._get_rules(source_model_name) |
|||
for field, rule_id in cached_rules.iteritems(): |
|||
rules[field] = self.browse(rule_id) |
|||
return rules |
|||
|
|||
@api.model |
|||
def create(self, vals): |
|||
record = super(ChangesetFieldRule, self).create(vals) |
|||
self.clear_caches() |
|||
return record |
|||
|
|||
@api.multi |
|||
def write(self, vals): |
|||
result = super(ChangesetFieldRule, self).write(vals) |
|||
self.clear_caches() |
|||
return result |
|||
|
|||
@api.multi |
|||
def unlink(self): |
|||
result = super(ChangesetFieldRule, self).unlink() |
|||
self.clear_caches() |
|||
return result |
@ -0,0 +1,50 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from openerp import models, fields, api |
|||
|
|||
|
|||
class ResPartner(models.Model): |
|||
_inherit = 'res.partner' |
|||
|
|||
changeset_ids = fields.One2many(comodel_name='res.partner.changeset', |
|||
inverse_name='partner_id', |
|||
string='Changesets', |
|||
readonly=True) |
|||
count_pending_changesets = fields.Integer( |
|||
string='Changes', |
|||
compute='_count_pending_changesets', |
|||
search='_search_count_pending_changesets') |
|||
|
|||
@api.one |
|||
@api.depends('changeset_ids', 'changeset_ids.state') |
|||
def _count_pending_changesets(self): |
|||
changesets = self.changeset_ids.filtered( |
|||
lambda rev: rev.state == 'draft' and rev.partner_id == self |
|||
) |
|||
self.count_pending_changesets = len(changesets) |
|||
|
|||
@api.multi |
|||
def write(self, values): |
|||
if self.env.context.get('__no_changeset'): |
|||
return super(ResPartner, self).write(values) |
|||
else: |
|||
changeset_model = self.env['res.partner.changeset'] |
|||
for record in self: |
|||
local_values = changeset_model.add_changeset(record, values) |
|||
super(ResPartner, record).write(local_values) |
|||
return True |
|||
|
|||
def _search_count_pending_changesets(self, operator, value): |
|||
if operator not in ('=', '!=', '<', '<=', '>', '>=', 'in', 'not in'): |
|||
return [] |
|||
query = ("SELECT p.id " |
|||
"FROM res_partner p " |
|||
"INNER JOIN res_partner_changeset r ON r.partner_id = p.id " |
|||
"WHERE r.state = 'draft' " |
|||
"GROUP BY p.id " |
|||
"HAVING COUNT(r.id) %s %%s ") % operator |
|||
self.env.cr.execute(query, (value,)) |
|||
ids = [row[0] for row in self.env.cr.fetchall()] |
|||
return [('id', 'in', ids)] |
@ -0,0 +1,514 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from itertools import groupby |
|||
from lxml import etree |
|||
from operator import attrgetter |
|||
|
|||
from openerp import models, fields, api, exceptions, _ |
|||
from openerp.osv.orm import setup_modifiers |
|||
|
|||
# sentinel object to be sure that no empty value was passed to |
|||
# ResPartnerChangesetChange._value_for_changeset |
|||
_NO_VALUE = object() |
|||
|
|||
|
|||
class ResPartnerChangeset(models.Model): |
|||
_name = 'res.partner.changeset' |
|||
_description = 'Partner Changeset' |
|||
_order = 'date desc' |
|||
_rec_name = 'date' |
|||
|
|||
partner_id = fields.Many2one(comodel_name='res.partner', |
|||
string='Partner', |
|||
select=True, |
|||
required=True, |
|||
readonly=True, |
|||
ondelete='cascade') |
|||
change_ids = fields.One2many(comodel_name='res.partner.changeset.change', |
|||
inverse_name='changeset_id', |
|||
string='Changes', |
|||
readonly=True) |
|||
date = fields.Datetime(default=fields.Datetime.now, |
|||
select=True, |
|||
readonly=True) |
|||
state = fields.Selection( |
|||
compute='_compute_state', |
|||
selection=[('draft', 'Pending'), |
|||
('done', 'Done')], |
|||
string='State', |
|||
store=True, |
|||
) |
|||
note = fields.Text() |
|||
source = fields.Reference( |
|||
string='Source of the change', |
|||
selection='_reference_models', |
|||
readonly=True, |
|||
) |
|||
|
|||
@api.model |
|||
def _reference_models(self): |
|||
models = self.env['ir.model'].search([]) |
|||
return [(model.model, model.name) for model in models] |
|||
|
|||
@api.one |
|||
@api.depends('change_ids', 'change_ids.state') |
|||
def _compute_state(self): |
|||
if all(change.state in ('done', 'cancel') for change |
|||
in self.mapped('change_ids')): |
|||
self.state = 'done' |
|||
else: |
|||
self.state = 'draft' |
|||
|
|||
@api.multi |
|||
def apply(self): |
|||
self.mapped('change_ids').apply() |
|||
|
|||
@api.multi |
|||
def cancel(self): |
|||
self.mapped('change_ids').cancel() |
|||
|
|||
@api.multi |
|||
def add_changeset(self, record, values): |
|||
""" Add a changeset on a partner |
|||
|
|||
By default, when a partner is modified by a user or by the |
|||
system, the the changeset will follow the rules configured for |
|||
the 'Users' / global rules. |
|||
|
|||
A caller should pass the following keys in the context: |
|||
|
|||
* ``__changeset_rules_source_model``: name of the model which |
|||
asks for the change |
|||
* ``__changeset_rules_source_id``: id of the record which asks |
|||
for the change |
|||
|
|||
When the source model and id are not defined, the current user |
|||
is considered as the origin of the change. |
|||
|
|||
Should be called before the execution of ``write`` on the record |
|||
so we can keep track of the existing value and also because the |
|||
returned values should be used for ``write`` as some of the |
|||
values may have been removed. |
|||
|
|||
:param values: the values being written on the partner |
|||
:type values: dict |
|||
|
|||
:returns: dict of values that should be wrote on the partner |
|||
(fields with a 'Validate' or 'Never' rule are excluded) |
|||
|
|||
""" |
|||
record.ensure_one() |
|||
|
|||
source_model = self.env.context.get('__changeset_rules_source_model') |
|||
source_id = self.env.context.get('__changeset_rules_source_id') |
|||
if not source_model: |
|||
# if the changes source is not defined, log the user who |
|||
# made the change |
|||
source_model = 'res.users' |
|||
if not source_id: |
|||
source_id = self.env.uid |
|||
if source_model and source_id: |
|||
source = '%s,%s' % (source_model, source_id) |
|||
else: |
|||
source = False |
|||
|
|||
change_model = self.env['res.partner.changeset.change'] |
|||
write_values = values.copy() |
|||
changes = [] |
|||
rules = self.env['changeset.field.rule'].get_rules( |
|||
source_model_name=source_model, |
|||
) |
|||
for field in values: |
|||
rule = rules.get(field) |
|||
if not rule: |
|||
continue |
|||
if field in values: |
|||
if not change_model._has_field_changed(record, field, |
|||
values[field]): |
|||
continue |
|||
change, pop_value = change_model._prepare_changeset_change( |
|||
record, rule, field, values[field] |
|||
) |
|||
if pop_value: |
|||
write_values.pop(field) |
|||
changes.append(change) |
|||
if changes: |
|||
self.env['res.partner.changeset'].create({ |
|||
'partner_id': record.id, |
|||
'change_ids': [(0, 0, vals) for vals in changes], |
|||
'date': fields.Datetime.now(), |
|||
'source': source, |
|||
}) |
|||
return write_values |
|||
|
|||
|
|||
class ResPartnerChangesetChange(models.Model): |
|||
""" Store the change of one field for one changeset on one partner |
|||
|
|||
This model is composed of 3 sets of fields: |
|||
|
|||
* 'origin' |
|||
* 'old' |
|||
* 'new' |
|||
|
|||
The 'new' fields contain the value that needs to be validated. |
|||
The 'old' field copies the actual value of the partner when the |
|||
change is either applied either canceled. This field is used as a storage |
|||
place but never shown by itself. |
|||
The 'origin' fields is a related field towards the actual values of |
|||
the partner until the change is either applied either canceled, past |
|||
that it shows the 'old' value. |
|||
The reason behind this is that the values may change on a partner between |
|||
the moment when the changeset is created and when it is applied. |
|||
|
|||
On the views, we show the origin fields which represent the actual |
|||
partner values or the old values and we show the new fields. |
|||
|
|||
The 'origin' and 'new_value_display' are displayed on |
|||
the tree view where we need a unique of field, the other fields are |
|||
displayed on the form view so we benefit from their widgets. |
|||
|
|||
""" |
|||
_name = 'res.partner.changeset.change' |
|||
_description = 'Partner Changeset Change' |
|||
_rec_name = 'field_id' |
|||
|
|||
changeset_id = fields.Many2one(comodel_name='res.partner.changeset', |
|||
required=True, |
|||
string='Changeset', |
|||
ondelete='cascade', |
|||
readonly=True) |
|||
field_id = fields.Many2one(comodel_name='ir.model.fields', |
|||
string='Field', |
|||
required=True, |
|||
readonly=True) |
|||
field_type = fields.Selection(related='field_id.ttype', |
|||
string='Field Type', |
|||
readonly=True) |
|||
|
|||
origin_value_display = fields.Char( |
|||
string='Previous', |
|||
compute='_compute_value_display', |
|||
) |
|||
new_value_display = fields.Char( |
|||
string='New', |
|||
compute='_compute_value_display', |
|||
) |
|||
|
|||
# Fields showing the origin partner's value or the 'old' value if |
|||
# the change is applied or canceled. |
|||
origin_value_char = fields.Char(compute='_compute_origin_values', |
|||
string='Previous', |
|||
readonly=True) |
|||
origin_value_date = fields.Date(compute='_compute_origin_values', |
|||
string='Previous', |
|||
readonly=True) |
|||
origin_value_datetime = fields.Datetime(compute='_compute_origin_values', |
|||
string='Previous', |
|||
readonly=True) |
|||
origin_value_float = fields.Float(compute='_compute_origin_values', |
|||
string='Previous', |
|||
readonly=True) |
|||
origin_value_integer = fields.Integer(compute='_compute_origin_values', |
|||
string='Previous', |
|||
readonly=True) |
|||
origin_value_text = fields.Text(compute='_compute_origin_values', |
|||
string='Previous', |
|||
readonly=True) |
|||
origin_value_boolean = fields.Boolean(compute='_compute_origin_values', |
|||
string='Previous', |
|||
readonly=True) |
|||
origin_value_reference = fields.Reference( |
|||
compute='_compute_origin_values', |
|||
string='Previous', |
|||
selection='_reference_models', |
|||
readonly=True, |
|||
) |
|||
|
|||
# Fields storing the previous partner's values (saved when the |
|||
# changeset is applied) |
|||
old_value_char = fields.Char(string='Old', |
|||
readonly=True) |
|||
old_value_date = fields.Date(string='Old', |
|||
readonly=True) |
|||
old_value_datetime = fields.Datetime(string='Old', |
|||
readonly=True) |
|||
old_value_float = fields.Float(string='Old', |
|||
readonly=True) |
|||
old_value_integer = fields.Integer(string='Old', |
|||
readonly=True) |
|||
old_value_text = fields.Text(string='Old', |
|||
readonly=True) |
|||
old_value_boolean = fields.Boolean(string='Old', |
|||
readonly=True) |
|||
old_value_reference = fields.Reference(string='Old', |
|||
selection='_reference_models', |
|||
readonly=True) |
|||
|
|||
# Fields storing the value applied on the partner |
|||
new_value_char = fields.Char(string='New', |
|||
readonly=True) |
|||
new_value_date = fields.Date(string='New', |
|||
readonly=True) |
|||
new_value_datetime = fields.Datetime(string='New', |
|||
readonly=True) |
|||
new_value_float = fields.Float(string='New', |
|||
readonly=True) |
|||
new_value_integer = fields.Integer(string='New', |
|||
readonly=True) |
|||
new_value_text = fields.Text(string='New', |
|||
readonly=True) |
|||
new_value_boolean = fields.Boolean(string='New', |
|||
readonly=True) |
|||
new_value_reference = fields.Reference(string='New', |
|||
selection='_reference_models', |
|||
readonly=True) |
|||
|
|||
state = fields.Selection( |
|||
selection=[('draft', 'Pending'), |
|||
('done', 'Accepted'), |
|||
('cancel', 'Rejected'), |
|||
], |
|||
required=True, |
|||
default='draft', |
|||
readonly=True, |
|||
) |
|||
|
|||
@api.model |
|||
def _reference_models(self): |
|||
models = self.env['ir.model'].search([]) |
|||
return [(model.model, model.name) for model in models] |
|||
|
|||
_suffix_to_types = { |
|||
'char': ('char', 'selection'), |
|||
'date': ('date',), |
|||
'datetime': ('datetime',), |
|||
'float': ('float',), |
|||
'integer': ('integer',), |
|||
'text': ('text',), |
|||
'boolean': ('boolean',), |
|||
'reference': ('many2one',), |
|||
} |
|||
|
|||
_type_to_suffix = {ftype: suffix |
|||
for suffix, ftypes in _suffix_to_types.iteritems() |
|||
for ftype in ftypes} |
|||
|
|||
_origin_value_fields = ['origin_value_%s' % suffix |
|||
for suffix in _suffix_to_types] |
|||
_old_value_fields = ['old_value_%s' % suffix |
|||
for suffix in _suffix_to_types] |
|||
_new_value_fields = ['new_value_%s' % suffix |
|||
for suffix in _suffix_to_types] |
|||
_value_fields = (_origin_value_fields + |
|||
_old_value_fields + |
|||
_new_value_fields) |
|||
|
|||
@api.one |
|||
@api.depends('changeset_id.partner_id.*') |
|||
def _compute_origin_values(self): |
|||
field_name = self.get_field_for_type(self.field_id, 'origin') |
|||
if self.state == 'draft': |
|||
value = self.changeset_id.partner_id[self.field_id.name] |
|||
else: |
|||
old_field = self.get_field_for_type(self.field_id, 'old') |
|||
value = self[old_field] |
|||
setattr(self, field_name, value) |
|||
|
|||
@api.one |
|||
@api.depends(lambda self: self._value_fields) |
|||
def _compute_value_display(self): |
|||
for prefix in ('origin', 'new'): |
|||
value = getattr(self, 'get_%s_value' % prefix)() |
|||
if self.field_id.ttype == 'many2one' and value: |
|||
value = value.display_name |
|||
setattr(self, '%s_value_display' % prefix, value) |
|||
|
|||
@api.model |
|||
def get_field_for_type(self, field, prefix): |
|||
assert prefix in ('origin', 'old', 'new') |
|||
field_type = self._type_to_suffix.get(field.ttype) |
|||
if not field_type: |
|||
raise NotImplementedError( |
|||
'field type %s is not supported' % field_type |
|||
) |
|||
return '%s_value_%s' % (prefix, field_type) |
|||
|
|||
@api.multi |
|||
def get_origin_value(self): |
|||
self.ensure_one() |
|||
field_name = self.get_field_for_type(self.field_id, 'origin') |
|||
return self[field_name] |
|||
|
|||
@api.multi |
|||
def get_new_value(self): |
|||
self.ensure_one() |
|||
field_name = self.get_field_for_type(self.field_id, 'new') |
|||
return self[field_name] |
|||
|
|||
@api.multi |
|||
def set_old_value(self): |
|||
""" Copy the value of the partner to the 'old' field """ |
|||
for change in self: |
|||
# copy the existing partner's value for the history |
|||
old_value_for_write = self._value_for_changeset( |
|||
change.changeset_id.partner_id, |
|||
change.field_id.name |
|||
) |
|||
old_field_name = self.get_field_for_type(change.field_id, 'old') |
|||
change.write({old_field_name: old_value_for_write}) |
|||
|
|||
@api.multi |
|||
def apply(self): |
|||
""" Apply the change on the changeset's partner |
|||
|
|||
It is optimized thus that it makes only one write on the partner |
|||
per changeset if many changes are applied at once. |
|||
""" |
|||
changes_ok = self.browse() |
|||
key = attrgetter('changeset_id') |
|||
for changeset, changes in groupby(self.sorted(key=key), key=key): |
|||
values = {} |
|||
partner = changeset.partner_id |
|||
for change in changes: |
|||
if change.state in ('cancel', 'done'): |
|||
continue |
|||
|
|||
field = change.field_id |
|||
value_for_write = change._convert_value_for_write( |
|||
change.get_new_value() |
|||
) |
|||
values[field.name] = value_for_write |
|||
|
|||
change.set_old_value() |
|||
|
|||
changes_ok |= change |
|||
|
|||
if not values: |
|||
continue |
|||
|
|||
previous_changesets = self.env['res.partner.changeset'].search( |
|||
[('date', '<', changeset.date), |
|||
('state', '=', 'draft'), |
|||
('partner_id', '=', changeset.partner_id.id), |
|||
], |
|||
limit=1, |
|||
) |
|||
if previous_changesets: |
|||
raise exceptions.Warning( |
|||
_('This change cannot be applied because a previous ' |
|||
'changeset for the same partner is pending.\n' |
|||
'Apply all the anterior changesets before applying ' |
|||
'this one.') |
|||
) |
|||
|
|||
partner.with_context(__no_changeset=True).write(values) |
|||
|
|||
changes_ok.write({'state': 'done'}) |
|||
|
|||
@api.multi |
|||
def cancel(self): |
|||
""" Reject the change """ |
|||
if any(change.state == 'done' for change in self): |
|||
raise exceptions.Warning( |
|||
_('This change has already be applied.') |
|||
) |
|||
self.set_old_value() |
|||
self.write({'state': 'cancel'}) |
|||
|
|||
@api.model |
|||
def _has_field_changed(self, record, field, value): |
|||
field_def = record._fields[field] |
|||
current_value = field_def.convert_to_write(record[field]) |
|||
if not (current_value or value): |
|||
return False |
|||
return current_value != value |
|||
|
|||
@api.multi |
|||
def _convert_value_for_write(self, value): |
|||
if not value: |
|||
return value |
|||
model = self.env[self.field_id.model_id.model] |
|||
model_field_def = model._fields[self.field_id.name] |
|||
return model_field_def.convert_to_write(value) |
|||
|
|||
@api.model |
|||
def _value_for_changeset(self, record, field_name, value=_NO_VALUE): |
|||
""" Return a value from the record ready to write in a changeset field |
|||
|
|||
:param record: modified record |
|||
:param field_name: name of the modified field |
|||
:param value: if no value is given, it is read from the record |
|||
""" |
|||
field_def = record._fields[field_name] |
|||
if value is _NO_VALUE: |
|||
# when the value is read from the record, we need to prepare |
|||
# it for the write (e.g. extract .id from a many2one record) |
|||
value = field_def.convert_to_write(record[field_name]) |
|||
if field_def.type == 'many2one': |
|||
# store as 'reference' |
|||
comodel = field_def.comodel_name |
|||
return "%s,%s" % (comodel, value) if value else False |
|||
else: |
|||
return value |
|||
|
|||
@api.multi |
|||
def _prepare_changeset_change(self, record, rule, field_name, value): |
|||
""" Prepare data for a changeset change |
|||
|
|||
It returns a dict of the values to write on the changeset change |
|||
and a boolean that indicates if the value should be popped out |
|||
of the values to write on the model. |
|||
|
|||
:returns: dict of values, boolean |
|||
""" |
|||
new_field_name = self.get_field_for_type(rule.field_id, 'new') |
|||
new_value = self._value_for_changeset(record, field_name, value=value) |
|||
change = { |
|||
new_field_name: new_value, |
|||
'field_id': rule.field_id.id, |
|||
} |
|||
if rule.action == 'auto': |
|||
change['state'] = 'done' |
|||
pop_value = False |
|||
elif rule.action == 'validate': |
|||
change['state'] = 'draft' |
|||
pop_value = True # change to apply manually |
|||
elif rule.action == 'never': |
|||
change['state'] = 'cancel' |
|||
pop_value = True # change never applied |
|||
|
|||
if change['state'] in ('cancel', 'done'): |
|||
# Normally the 'old' value is set when we use the 'apply' |
|||
# button, but since we short circuit the 'apply', we |
|||
# directly set the 'old' value here |
|||
old_field_name = self.get_field_for_type(rule.field_id, 'old') |
|||
# get values ready to write as expected by the changeset |
|||
# (for instance, a many2one is written in a reference |
|||
# field) |
|||
origin_value = self._value_for_changeset(record, field_name) |
|||
change[old_field_name] = origin_value |
|||
|
|||
return change, pop_value |
|||
|
|||
def fields_view_get(self, *args, **kwargs): |
|||
_super = super(ResPartnerChangesetChange, self) |
|||
result = _super.fields_view_get(*args, **kwargs) |
|||
if result['type'] != 'form': |
|||
return |
|||
doc = etree.XML(result['arch']) |
|||
for suffix, ftypes in self._suffix_to_types.iteritems(): |
|||
for prefix in ('origin', 'old', 'new'): |
|||
field_name = '%s_value_%s' % (prefix, suffix) |
|||
field_nodes = doc.xpath("//field[@name='%s']" % field_name) |
|||
for node in field_nodes: |
|||
node.set( |
|||
'attrs', |
|||
"{'invisible': " |
|||
"[('field_type', 'not in', %s)]}" % (ftypes,) |
|||
) |
|||
setup_modifiers(node) |
|||
result['arch'] = etree.tostring(doc) |
|||
return result |
@ -0,0 +1,10 @@ |
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
|||
access_view_changeset_field_rule_partner_manager,changeset field rules for partner managers,model_changeset_field_rule,base.group_partner_manager,1,0,0,0 |
|||
access_view_changeset_field_rule_user,changeset field rules for changeset users,model_changeset_field_rule,group_changeset_user,1,0,0,0 |
|||
access_view_changeset_field_rule_manager,changeset field rules for changeset managers,model_changeset_field_rule,group_changeset_user,1,1,1,1 |
|||
access_view_res_partner_changeset_partner_manager,changeset for partner managers,model_res_partner_changeset,base.group_partner_manager,1,0,1,0 |
|||
access_view_res_partner_changeset_change_partner_manager,changeset change for partner managers,model_res_partner_changeset_change,base.group_partner_manager,1,0,1,0 |
|||
access_view_res_partner_changeset_user,changeset for changeset users,model_res_partner_changeset,group_changeset_user,1,1,1,0 |
|||
access_view_res_partner_changeset_change_user,changeset change for changeset users,model_res_partner_changeset_change,group_changeset_user,1,1,1,0 |
|||
access_view_res_partner_changeset_manager,changeset for changeset managers,model_res_partner_changeset,group_changeset_manager,1,1,1,1 |
|||
access_view_res_partner_changeset_change_manager,changeset change for changeset managers,model_res_partner_changeset_change,group_changeset_manager,1,1,1,1 |
@ -0,0 +1,29 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data> |
|||
|
|||
<record id="group_changeset_manager" model="res.groups"> |
|||
<field name="name">Changeset Configuration</field> |
|||
<field name="comment">The user will have an access to the configuration of the changeset rules.</field> |
|||
</record> |
|||
|
|||
<record id="group_changeset_user" model="res.groups"> |
|||
<field name="name">Changesets Validations </field> |
|||
<field name="comment">The user will be able to apply or reject changesets.</field> |
|||
</record> |
|||
|
|||
</data> |
|||
|
|||
<data noupdate="1"> |
|||
|
|||
<record id="group_changeset_manager" model="res.groups"> |
|||
<field name="users" eval="[(4, ref('base.user_root'))]"/> |
|||
</record> |
|||
|
|||
<record id="group_changeset_user" model="res.groups"> |
|||
<field name="implied_ids" eval="[(4, ref('group_changeset_manager'))]"/> |
|||
<field name="users" eval="[(4, ref('base.user_root'))]"/> |
|||
</record> |
|||
|
|||
</data> |
|||
</openerp> |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
After Width: 1252 | Height: 577 | Size: 85 KiB |
After Width: 996 | Height: 557 | Size: 60 KiB |
@ -0,0 +1,6 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
from . import test_changeset_flow |
|||
from . import test_changeset_field_type |
|||
from . import test_changeset_origin |
|||
from . import test_changeset_field_rule |
@ -0,0 +1,77 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
|
|||
class ChangesetMixin(object): |
|||
|
|||
def assert_changeset(self, partner, expected_source, expected_changes): |
|||
""" Check if a changeset has been created according to expected values |
|||
|
|||
The partner should have no prior changeset than the one created in the |
|||
test (so it has exactly 1 changeset). |
|||
|
|||
The expected changes are tuples with (field, origin_value, |
|||
new_value, state) |
|||
|
|||
:param partner: record of partner having a changeset |
|||
:param expected_changes: contains tuples with the changes |
|||
:type expected_changes: list(tuple)) |
|||
""" |
|||
changeset = self.env['res.partner.changeset'].search( |
|||
[('partner_id', '=', partner.id)], |
|||
) |
|||
self.assertEqual(len(changeset), 1, |
|||
"1 changeset expected, got %s" % (changeset,)) |
|||
self.assertEqual(changeset.source, expected_source) |
|||
changes = changeset.change_ids |
|||
missing = [] |
|||
for expected_change in expected_changes: |
|||
for change in changes: |
|||
if (change.field_id, |
|||
change.get_origin_value(), |
|||
change.get_new_value(), |
|||
change.state) == expected_change: |
|||
changes -= change |
|||
break |
|||
else: |
|||
missing.append(expected_change) |
|||
message = u'' |
|||
for field, origin_value, new_value, state in missing: |
|||
message += ("- field: '%s', origin_value: '%s', " |
|||
"new_value: '%s', state: '%s'\n" % |
|||
(field.name, origin_value, new_value, state)) |
|||
for change in changes: |
|||
message += ("+ field: '%s', origin_value: '%s', " |
|||
"new_value: '%s', state: '%s'\n" % |
|||
(change.field_id.name, |
|||
change.get_origin_value(), |
|||
change.get_new_value(), |
|||
change.state)) |
|||
if message: |
|||
raise AssertionError('Changes do not match\n\n:%s' % message) |
|||
|
|||
def _create_changeset(self, partner, changes): |
|||
""" Create a changeset and its associated changes |
|||
|
|||
:param partner: 'res.partner' record |
|||
:param changes: list of changes [(field, new value, state)] |
|||
:returns: 'res.partner.changeset' record |
|||
""" |
|||
ChangesetChange = self.env['res.partner.changeset.change'] |
|||
get_field = ChangesetChange.get_field_for_type |
|||
change_values = [] |
|||
for field, value, state in changes: |
|||
change = { |
|||
'field_id': field.id, |
|||
# write in the field of the appropriate type for the |
|||
# origin field (char, many2one, ...) |
|||
get_field(field, 'new'): value, |
|||
'state': state, |
|||
} |
|||
change_values.append((0, 0, change)) |
|||
values = { |
|||
'partner_id': partner.id, |
|||
'change_ids': change_values, |
|||
} |
|||
return self.env['res.partner.changeset'].create(values) |
@ -0,0 +1,95 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# |
|||
# |
|||
# Authors: Guewen Baconnier |
|||
# Copyright 2015 Camptocamp SA |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License as |
|||
# published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Affero General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
# |
|||
|
|||
from openerp.tests import common |
|||
|
|||
|
|||
class TestChangesetFieldRule(common.TransactionCase): |
|||
|
|||
def setUp(self): |
|||
super(TestChangesetFieldRule, self).setUp() |
|||
self.company_model_id = self.env.ref('base.model_res_company').id |
|||
self.field_name = self.env.ref('base.field_res_partner_name') |
|||
self.field_street = self.env.ref('base.field_res_partner_street') |
|||
|
|||
def test_get_rules(self): |
|||
ChangesetFieldRule = self.env['changeset.field.rule'] |
|||
ChangesetFieldRule.search([]).unlink() |
|||
rule1 = ChangesetFieldRule.create({ |
|||
'field_id': self.field_name.id, |
|||
'action': 'validate', |
|||
}) |
|||
rule2 = ChangesetFieldRule.create({ |
|||
'field_id': self.field_street.id, |
|||
'action': 'never', |
|||
}) |
|||
get_rules = ChangesetFieldRule.get_rules(None) |
|||
self.assertEqual(get_rules, {'name': rule1, 'street': rule2}) |
|||
|
|||
def test_get_rules_source(self): |
|||
ChangesetFieldRule = self.env['changeset.field.rule'] |
|||
ChangesetFieldRule.search([]).unlink() |
|||
rule1 = ChangesetFieldRule.create({ |
|||
'field_id': self.field_name.id, |
|||
'action': 'validate', |
|||
}) |
|||
rule2 = ChangesetFieldRule.create({ |
|||
'field_id': self.field_street.id, |
|||
'action': 'never', |
|||
}) |
|||
rule3 = ChangesetFieldRule.create({ |
|||
'source_model_id': self.company_model_id, |
|||
'field_id': self.field_street.id, |
|||
'action': 'never', |
|||
}) |
|||
model = ChangesetFieldRule |
|||
rules = model.get_rules(None) |
|||
self.assertEqual(rules, {u'name': rule1, u'street': rule2}) |
|||
rules = model.get_rules('res.company') |
|||
self.assertEqual(rules, {u'name': rule1, u'street': rule3}) |
|||
|
|||
def test_get_rules_cache(self): |
|||
ChangesetFieldRule = self.env['changeset.field.rule'] |
|||
ChangesetFieldRule.search([]).unlink() |
|||
rule = ChangesetFieldRule.create({ |
|||
'field_id': self.field_name.id, |
|||
'action': 'validate', |
|||
}) |
|||
self.assertEqual( |
|||
ChangesetFieldRule.get_rules(None)['name'].action, |
|||
'validate', |
|||
) |
|||
# Write on cursor to bypass the cache invalidation for the |
|||
# matter of the test |
|||
self.env.cr.execute("UPDATE changeset_field_rule " |
|||
"SET action = 'never' " |
|||
"WHERE id = %s", (rule.id,)) |
|||
self.assertEqual( |
|||
ChangesetFieldRule.get_rules(None)['name'].action, |
|||
'validate', |
|||
) |
|||
rule.action = 'auto' |
|||
self.assertEqual( |
|||
ChangesetFieldRule.get_rules(None)['name'].action, |
|||
'auto', |
|||
) |
|||
rule.unlink() |
|||
self.assertFalse(ChangesetFieldRule.get_rules(None)) |
@ -0,0 +1,279 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from openerp.tests import common |
|||
from .common import ChangesetMixin |
|||
|
|||
|
|||
class TestChangesetFieldType(ChangesetMixin, common.TransactionCase): |
|||
""" Check that changeset changes are stored expectingly to their types """ |
|||
|
|||
def _setup_rules(self): |
|||
ChangesetFieldRule = self.env['changeset.field.rule'] |
|||
ChangesetFieldRule.search([]).unlink() |
|||
fields = (('char', 'ref'), |
|||
('text', 'comment'), |
|||
('boolean', 'customer'), |
|||
('date', 'date'), |
|||
('integer', 'color'), |
|||
('float', 'credit_limit'), |
|||
('selection', 'type'), |
|||
('many2one', 'country_id'), |
|||
('many2many', 'category_id'), |
|||
('one2many', 'user_ids'), |
|||
('binary', 'image'), |
|||
) |
|||
for field_type, field in fields: |
|||
attr_name = 'field_%s' % field_type |
|||
field_record = self.env['ir.model.fields'].search([ |
|||
('model', '=', 'res.partner'), |
|||
('name', '=', field), |
|||
]) |
|||
# set attribute such as 'self.field_char' is a |
|||
# ir.model.fields record of the field res_partner.ref |
|||
setattr(self, attr_name, field_record) |
|||
ChangesetFieldRule.create({ |
|||
'field_id': field_record.id, |
|||
'action': 'validate', |
|||
}) |
|||
|
|||
def setUp(self): |
|||
super(TestChangesetFieldType, self).setUp() |
|||
self._setup_rules() |
|||
self.partner = self.env['res.partner'].create({ |
|||
'name': 'Original Name', |
|||
'street': 'Original Street', |
|||
}) |
|||
|
|||
def test_new_changeset_char(self): |
|||
""" Add a new changeset on a Char field """ |
|||
self.partner.write({ |
|||
self.field_char.name: 'New value', |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_char, self.partner[self.field_char.name], |
|||
'New value', 'draft'), |
|||
] |
|||
) |
|||
|
|||
def test_new_changeset_text(self): |
|||
""" Add a new changeset on a Text field """ |
|||
self.partner.write({ |
|||
self.field_text.name: 'New comment\non 2 lines', |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_text, self.partner[self.field_text.name], |
|||
'New comment\non 2 lines', 'draft'), |
|||
] |
|||
) |
|||
|
|||
def test_new_changeset_boolean(self): |
|||
""" Add a new changeset on a Boolean field """ |
|||
# ensure the changeset has to change the value |
|||
self.partner.with_context(__no_changeset=True).write({ |
|||
self.field_boolean.name: False, |
|||
}) |
|||
|
|||
self.partner.write({ |
|||
self.field_boolean.name: True, |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_boolean, self.partner[self.field_boolean.name], |
|||
True, 'draft'), |
|||
] |
|||
) |
|||
|
|||
def test_new_changeset_date(self): |
|||
""" Add a new changeset on a Date field """ |
|||
self.partner.write({ |
|||
self.field_date.name: '2015-09-15', |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_date, self.partner[self.field_date.name], |
|||
'2015-09-15', 'draft'), |
|||
] |
|||
) |
|||
|
|||
def test_new_changeset_integer(self): |
|||
""" Add a new changeset on a Integer field """ |
|||
self.partner.write({ |
|||
self.field_integer.name: 42, |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_integer, self.partner[self.field_integer.name], |
|||
42, 'draft'), |
|||
] |
|||
) |
|||
|
|||
def test_new_changeset_float(self): |
|||
""" Add a new changeset on a Float field """ |
|||
self.partner.write({ |
|||
self.field_float.name: 3.1415, |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_float, self.partner[self.field_float.name], |
|||
3.1415, 'draft'), |
|||
] |
|||
) |
|||
|
|||
def test_new_changeset_selection(self): |
|||
""" Add a new changeset on a Selection field """ |
|||
self.partner.write({ |
|||
self.field_selection.name: 'delivery', |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_selection, self.partner[self.field_selection.name], |
|||
'delivery', 'draft'), |
|||
] |
|||
) |
|||
|
|||
def test_new_changeset_many2one(self): |
|||
""" Add a new changeset on a Many2one field """ |
|||
self.partner.with_context(__no_changeset=True).write({ |
|||
self.field_many2one.name: self.env.ref('base.fr').id, |
|||
|
|||
}) |
|||
self.partner.write({ |
|||
self.field_many2one.name: self.env.ref('base.ch').id, |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_many2one, self.partner[self.field_many2one.name], |
|||
self.env.ref('base.ch'), 'draft'), |
|||
] |
|||
) |
|||
|
|||
def test_new_changeset_many2many(self): |
|||
""" Add a new changeset on a Many2many field is not supported """ |
|||
with self.assertRaises(NotImplementedError): |
|||
self.partner.write({ |
|||
self.field_many2many.name: [self.env.ref('base.ch').id], |
|||
}) |
|||
|
|||
def test_new_changeset_one2many(self): |
|||
""" Add a new changeset on a One2many field is not supported """ |
|||
with self.assertRaises(NotImplementedError): |
|||
self.partner.write({ |
|||
self.field_one2many.name: [self.env.ref('base.user_root').id], |
|||
}) |
|||
|
|||
def test_new_changeset_binary(self): |
|||
""" Add a new changeset on a Binary field is not supported """ |
|||
with self.assertRaises(NotImplementedError): |
|||
self.partner.write({ |
|||
self.field_binary.name: 'xyz', |
|||
}) |
|||
|
|||
def test_apply_char(self): |
|||
""" Apply a change on a Char field """ |
|||
changes = [(self.field_char, 'New Ref', 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner[self.field_char.name], 'New Ref') |
|||
|
|||
def test_apply_text(self): |
|||
""" Apply a change on a Text field """ |
|||
changes = [(self.field_text, 'New comment\non 2 lines', 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner[self.field_text.name], |
|||
'New comment\non 2 lines') |
|||
|
|||
def test_apply_boolean(self): |
|||
""" Apply a change on a Boolean field """ |
|||
# ensure the changeset has to change the value |
|||
self.partner.write({self.field_boolean.name: False}) |
|||
|
|||
changes = [(self.field_boolean, True, 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner[self.field_boolean.name], True) |
|||
|
|||
changes = [(self.field_boolean, False, 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner[self.field_boolean.name], False) |
|||
|
|||
def test_apply_date(self): |
|||
""" Apply a change on a Date field """ |
|||
changes = [(self.field_date, '2015-09-15', 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertAlmostEqual(self.partner[self.field_date.name], |
|||
'2015-09-15') |
|||
|
|||
def test_apply_integer(self): |
|||
""" Apply a change on a Integer field """ |
|||
changes = [(self.field_integer, 42, 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertAlmostEqual(self.partner[self.field_integer.name], 42) |
|||
|
|||
def test_apply_float(self): |
|||
""" Apply a change on a Float field """ |
|||
changes = [(self.field_float, 52.47, 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertAlmostEqual(self.partner[self.field_float.name], 52.47) |
|||
|
|||
def test_apply_selection(self): |
|||
""" Apply a change on a Selection field """ |
|||
changes = [(self.field_selection, 'delivery', 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertAlmostEqual(self.partner[self.field_selection.name], |
|||
'delivery') |
|||
|
|||
def test_apply_many2one(self): |
|||
""" Apply a change on a Many2one field """ |
|||
self.partner.with_context(__no_changeset=True).write({ |
|||
self.field_many2one.name: self.env.ref('base.fr').id, |
|||
|
|||
}) |
|||
changes = [(self.field_many2one, |
|||
'res.country,%d' % self.env.ref('base.ch').id, |
|||
'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner[self.field_many2one.name], |
|||
self.env.ref('base.ch')) |
|||
|
|||
def test_apply_many2many(self): |
|||
""" Apply a change on a Many2many field is not supported """ |
|||
changes = [(self.field_many2many, |
|||
self.env.ref('base.ch').id, |
|||
'draft')] |
|||
with self.assertRaises(NotImplementedError): |
|||
self._create_changeset(self.partner, changes) |
|||
|
|||
def test_apply_one2many(self): |
|||
""" Apply a change on a One2many field is not supported """ |
|||
changes = [(self.field_one2many, |
|||
[self.env.ref('base.user_root').id, |
|||
self.env.ref('base.user_demo').id, |
|||
], |
|||
'draft')] |
|||
with self.assertRaises(NotImplementedError): |
|||
self._create_changeset(self.partner, changes) |
|||
|
|||
def test_apply_binary(self): |
|||
""" Apply a change on a Binary field is not supported """ |
|||
changes = [(self.field_one2many, '', 'draft')] |
|||
with self.assertRaises(NotImplementedError): |
|||
self._create_changeset(self.partner, changes) |
@ -0,0 +1,246 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from datetime import datetime, timedelta |
|||
|
|||
from openerp import fields, exceptions |
|||
from openerp.tests import common |
|||
from .common import ChangesetMixin |
|||
|
|||
|
|||
class TestChangesetFlow(ChangesetMixin, common.TransactionCase): |
|||
""" Check how changeset are generated and applied based on the rules. |
|||
|
|||
We do not really care about the types of the fields in this test |
|||
suite, so we only use 'char' fields. We have to ensure that the |
|||
general changeset flows work as expected, that is: |
|||
|
|||
* create a changeset when a manual/system write is made on partner |
|||
* create a changeset according to the changeset rules when a source model |
|||
is specified |
|||
* apply a changeset change writes the value on the partner |
|||
* apply a whole changeset writes all the changes' values on the partner |
|||
* changes in state 'cancel' or 'done' do not write on the partner |
|||
* when all the changes are either 'cancel' or 'done', the changeset |
|||
becomes 'done' |
|||
""" |
|||
|
|||
def _setup_rules(self): |
|||
ChangesetFieldRule = self.env['changeset.field.rule'] |
|||
ChangesetFieldRule.search([]).unlink() |
|||
self.field_name = self.env.ref('base.field_res_partner_name') |
|||
self.field_street = self.env.ref('base.field_res_partner_street') |
|||
self.field_street2 = self.env.ref('base.field_res_partner_street2') |
|||
ChangesetFieldRule.create({ |
|||
'field_id': self.field_name.id, |
|||
'action': 'auto', |
|||
}) |
|||
ChangesetFieldRule.create({ |
|||
'field_id': self.field_street.id, |
|||
'action': 'validate', |
|||
}) |
|||
ChangesetFieldRule.create({ |
|||
'field_id': self.field_street2.id, |
|||
'action': 'never', |
|||
}) |
|||
|
|||
def setUp(self): |
|||
super(TestChangesetFlow, self).setUp() |
|||
self._setup_rules() |
|||
self.partner = self.env['res.partner'].create({ |
|||
'name': 'X', |
|||
'street': 'street X', |
|||
'street2': 'street2 X', |
|||
}) |
|||
|
|||
def test_new_changeset(self): |
|||
""" Add a new changeset on a partner |
|||
|
|||
A new changeset is created when we write on a partner |
|||
""" |
|||
self.partner.write({ |
|||
'name': 'Y', |
|||
'street': 'street Y', |
|||
'street2': 'street2 Y', |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_name, 'X', 'Y', 'done'), |
|||
(self.field_street, 'street X', 'street Y', 'draft'), |
|||
(self.field_street2, 'street2 X', 'street2 Y', 'cancel'), |
|||
], |
|||
) |
|||
self.assertEqual(self.partner.name, 'Y') |
|||
self.assertEqual(self.partner.street, 'street X') |
|||
self.assertEqual(self.partner.street2, 'street2 X') |
|||
|
|||
def test_new_changeset_empty_value(self): |
|||
""" Create a changeset change that empty a value """ |
|||
self.partner.write({ |
|||
'street': False, |
|||
}) |
|||
self.assert_changeset( |
|||
self.partner, |
|||
self.env.user, |
|||
[(self.field_street, 'street X', False, 'draft')] |
|||
) |
|||
|
|||
def test_no_changeset_empty_value_both_sides(self): |
|||
""" No changeset created when both sides have an empty value """ |
|||
# we have to ensure that even if we write '' to a False field, we won't |
|||
# write a changeset |
|||
self.partner.with_context(__no_changeset=True).write({ |
|||
'street': False, |
|||
}) |
|||
self.partner.write({ |
|||
'street': '', |
|||
}) |
|||
self.assertFalse(self.partner.changeset_ids) |
|||
|
|||
def test_apply_change(self): |
|||
""" Apply a changeset change on a partner """ |
|||
changes = [ |
|||
(self.field_name, 'Y', 'draft'), |
|||
] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner.name, 'Y') |
|||
self.assertEqual(changeset.change_ids.state, 'done') |
|||
|
|||
def test_apply_done_change(self): |
|||
""" Done changes do not apply (already applied) """ |
|||
changes = [ |
|||
(self.field_name, 'Y', 'done'), |
|||
] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner.name, 'X') |
|||
|
|||
def test_apply_cancel_change(self): |
|||
""" Cancel changes do not apply """ |
|||
changes = [ |
|||
(self.field_name, 'Y', 'cancel'), |
|||
] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner.name, 'X') |
|||
|
|||
def test_apply_empty_value(self): |
|||
""" Apply a change that empty a value """ |
|||
changes = [ |
|||
(self.field_street, False, 'draft'), |
|||
] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertFalse(self.partner.street) |
|||
|
|||
def test_apply_change_loop(self): |
|||
""" Test @api.multi on the changes """ |
|||
changes = [ |
|||
(self.field_name, 'Y', 'draft'), |
|||
(self.field_street, 'street Y', 'draft'), |
|||
(self.field_street2, 'street2 Y', 'draft'), |
|||
] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(self.partner.name, 'Y') |
|||
self.assertEqual(self.partner.street, 'street Y') |
|||
self.assertEqual(self.partner.street2, 'street2 Y') |
|||
|
|||
def test_apply(self): |
|||
""" Apply a full changeset on a partner """ |
|||
changes = [ |
|||
(self.field_name, 'Y', 'draft'), |
|||
(self.field_street, 'street Y', 'draft'), |
|||
(self.field_street2, 'street2 Y', 'draft'), |
|||
] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset.apply() |
|||
self.assertEqual(self.partner.name, 'Y') |
|||
self.assertEqual(self.partner.street, 'street Y') |
|||
self.assertEqual(self.partner.street2, 'street2 Y') |
|||
|
|||
def test_changeset_state_on_done(self): |
|||
""" Check that changeset state becomes done when changes are done """ |
|||
changes = [(self.field_name, 'Y', 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
self.assertEqual(changeset.state, 'draft') |
|||
changeset.change_ids.apply() |
|||
self.assertEqual(changeset.state, 'done') |
|||
|
|||
def test_changeset_state_on_cancel(self): |
|||
""" Check that rev. state becomes done when changes are canceled """ |
|||
changes = [(self.field_name, 'Y', 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
self.assertEqual(changeset.state, 'draft') |
|||
changeset.change_ids.cancel() |
|||
self.assertEqual(changeset.state, 'done') |
|||
|
|||
def test_changeset_state(self): |
|||
""" Check that changeset state becomes done with multiple changes """ |
|||
changes = [ |
|||
(self.field_name, 'Y', 'draft'), |
|||
(self.field_street, 'street Y', 'draft'), |
|||
(self.field_street2, 'street2 Y', 'draft'), |
|||
] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
self.assertEqual(changeset.state, 'draft') |
|||
changeset.apply() |
|||
self.assertEqual(changeset.state, 'done') |
|||
|
|||
def test_apply_changeset_with_other_pending(self): |
|||
""" Error when applying when previous pending changesets exist """ |
|||
changes = [(self.field_name, 'Y', 'draft')] |
|||
old_changeset = self._create_changeset(self.partner, changes) |
|||
# if the date is the same, both changeset can be applied |
|||
to_string = fields.Datetime.to_string |
|||
old_changeset.date = to_string(datetime.now() - timedelta(days=1)) |
|||
changes = [(self.field_name, 'Z', 'draft')] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
with self.assertRaises(exceptions.Warning): |
|||
changeset.change_ids.apply() |
|||
|
|||
def test_apply_different_changesets(self): |
|||
""" Apply different changesets at once """ |
|||
partner2 = self.env['res.partner'].create({'name': 'P2'}) |
|||
changes = [ |
|||
(self.field_name, 'Y', 'draft'), |
|||
(self.field_street, 'street Y', 'draft'), |
|||
(self.field_street2, 'street2 Y', 'draft'), |
|||
] |
|||
changeset = self._create_changeset(self.partner, changes) |
|||
changeset2 = self._create_changeset(partner2, changes) |
|||
self.assertEqual(changeset.state, 'draft') |
|||
self.assertEqual(changeset2.state, 'draft') |
|||
(changeset + changeset2).apply() |
|||
self.assertEqual(self.partner.name, 'Y') |
|||
self.assertEqual(self.partner.street, 'street Y') |
|||
self.assertEqual(self.partner.street2, 'street2 Y') |
|||
self.assertEqual(partner2.name, 'Y') |
|||
self.assertEqual(partner2.street, 'street Y') |
|||
self.assertEqual(partner2.street2, 'street2 Y') |
|||
self.assertEqual(changeset.state, 'done') |
|||
self.assertEqual(changeset2.state, 'done') |
|||
|
|||
def test_new_changeset_source(self): |
|||
""" Source is the user who made the change """ |
|||
self.partner.write({ |
|||
'street': False, |
|||
}) |
|||
changeset = self.partner.changeset_ids |
|||
self.assertEqual(changeset.source, self.env.user) |
|||
|
|||
def test_new_changeset_source_other_model(self): |
|||
""" Define source from another model """ |
|||
company = self.env.ref('base.main_company') |
|||
keys = { |
|||
'__changeset_rules_source_model': 'res.company', |
|||
'__changeset_rules_source_id': company.id, |
|||
} |
|||
self.partner.with_context(**keys).write({ |
|||
'street': False, |
|||
}) |
|||
changeset = self.partner.changeset_ids |
|||
self.assertEqual(changeset.source, company) |
@ -0,0 +1,110 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Camptocamp SA |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from openerp.tests import common |
|||
from .common import ChangesetMixin |
|||
|
|||
|
|||
class TestChangesetOrigin(ChangesetMixin, common.TransactionCase): |
|||
""" Check that origin - old fields are stored as expected. |
|||
|
|||
'origin' fields dynamically read fields from the partner when the state |
|||
of the change is 'draft'. Once a change becomes 'done' or 'cancel', the |
|||
'old' field copies the value from the partner and then the 'origin' field |
|||
displays the 'old' value. |
|||
""" |
|||
|
|||
def _setup_rules(self): |
|||
ChangesetFieldRule = self.env['changeset.field.rule'] |
|||
ChangesetFieldRule.search([]).unlink() |
|||
self.field_name = self.env.ref('base.field_res_partner_name') |
|||
ChangesetFieldRule.create({ |
|||
'field_id': self.field_name.id, |
|||
'action': 'validate', |
|||
}) |
|||
|
|||
def setUp(self): |
|||
super(TestChangesetOrigin, self).setUp() |
|||
self._setup_rules() |
|||
self.partner = self.env['res.partner'].create({ |
|||
'name': 'X', |
|||
}) |
|||
|
|||
def test_origin_value_of_change_with_apply(self): |
|||
""" Origin field is read from the parter or 'old' - with apply |
|||
|
|||
According to the state of the change. |
|||
""" |
|||
self.partner.write({ |
|||
'name': 'Y', |
|||
}) |
|||
changeset = self.partner.changeset_ids |
|||
change = changeset.change_ids |
|||
self.assertEqual(self.partner.name, 'X') |
|||
self.assertEqual(change.origin_value_char, 'X') |
|||
self.assertEqual(change.origin_value_display, 'X') |
|||
self.partner.with_context(__no_changeset=True).write({'name': 'A'}) |
|||
self.assertEqual(change.origin_value_char, 'A') |
|||
self.assertEqual(change.origin_value_display, 'A') |
|||
change.apply() |
|||
self.assertEqual(change.origin_value_char, 'A') |
|||
self.assertEqual(change.origin_value_display, 'A') |
|||
self.partner.with_context(__no_changeset=True).write({'name': 'B'}) |
|||
self.assertEqual(change.origin_value_char, 'A') |
|||
self.assertEqual(change.origin_value_display, 'A') |
|||
|
|||
def test_origin_value_of_change_with_cancel(self): |
|||
""" Origin field is read from the parter or 'old' - with cancel |
|||
|
|||
According to the state of the change. |
|||
""" |
|||
self.partner.write({ |
|||
'name': 'Y', |
|||
}) |
|||
changeset = self.partner.changeset_ids |
|||
change = changeset.change_ids |
|||
self.assertEqual(self.partner.name, 'X') |
|||
self.assertEqual(change.origin_value_char, 'X') |
|||
self.assertEqual(change.origin_value_display, 'X') |
|||
self.partner.with_context(__no_changeset=True).write({'name': 'A'}) |
|||
self.assertEqual(change.origin_value_char, 'A') |
|||
self.assertEqual(change.origin_value_display, 'A') |
|||
change.cancel() |
|||
self.assertEqual(change.origin_value_char, 'A') |
|||
self.assertEqual(change.origin_value_display, 'A') |
|||
self.partner.with_context(__no_changeset=True).write({'name': 'B'}) |
|||
self.assertEqual(change.origin_value_char, 'A') |
|||
self.assertEqual(change.origin_value_display, 'A') |
|||
|
|||
def test_old_field_of_change_with_apply(self): |
|||
""" Old field is stored when the change is applied """ |
|||
self.partner.write({ |
|||
'name': 'Y', |
|||
}) |
|||
changeset = self.partner.changeset_ids |
|||
change = changeset.change_ids |
|||
self.assertEqual(self.partner.name, 'X') |
|||
self.assertFalse(change.old_value_char) |
|||
self.partner.with_context(__no_changeset=True).write({'name': 'A'}) |
|||
self.assertFalse(change.old_value_char) |
|||
change.apply() |
|||
self.assertEqual(change.old_value_char, 'A') |
|||
self.partner.with_context(__no_changeset=True).write({'name': 'B'}) |
|||
self.assertEqual(change.old_value_char, 'A') |
|||
|
|||
def test_old_field_of_change_with_cancel(self): |
|||
""" Old field is stored when the change is canceled """ |
|||
self.partner.write({ |
|||
'name': 'Y', |
|||
}) |
|||
changeset = self.partner.changeset_ids |
|||
change = changeset.change_ids |
|||
self.assertEqual(self.partner.name, 'X') |
|||
self.assertFalse(change.old_value_char) |
|||
self.partner.with_context(__no_changeset=True).write({'name': 'A'}) |
|||
self.assertFalse(change.old_value_char) |
|||
change.cancel() |
|||
self.assertEqual(change.old_value_char, 'A') |
|||
self.partner.with_context(__no_changeset=True).write({'name': 'B'}) |
|||
self.assertEqual(change.old_value_char, 'A') |
@ -0,0 +1,63 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data noupdate="0"> |
|||
<record id="view_changeset_field_rule_tree" model="ir.ui.view"> |
|||
<field name="name">changeset.field.rule.tree</field> |
|||
<field name="model">changeset.field.rule</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Changeset Fields Rules"> |
|||
<field name="field_id"/> |
|||
<field name="source_model_id"/> |
|||
<field name="action"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_changeset_field_rule_form" model="ir.ui.view"> |
|||
<field name="name">changeset.field.rule.form</field> |
|||
<field name="model">changeset.field.rule</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Changeset Fields Rules"> |
|||
<sheet string="Changeset Fields Rules"> |
|||
<group> |
|||
<field name="field_id" |
|||
options="{'no_create_edit': True, 'no_open': True}" |
|||
/> |
|||
<field name="action"/> |
|||
<field name="source_model_id" |
|||
widget="selection" /> |
|||
</group> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_changeset_field_rule_search" model="ir.ui.view"> |
|||
<field name="name">changeset.field.rule.search</field> |
|||
<field name="model">changeset.field.rule</field> |
|||
<field name="arch" type="xml"> |
|||
<search string="Changeset Fields Rules"> |
|||
<field name="field_id"/> |
|||
<field name="source_model_id"/> |
|||
<field name="action"/> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="action_changeset_field_rule_view"> |
|||
<field name="name">Changeset Fields Rules</field> |
|||
<field name="type">ir.actions.act_window</field> |
|||
<field name="res_model">changeset.field.rule</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="search_view_id" ref="view_changeset_field_rule_search"/> |
|||
</record> |
|||
|
|||
<menuitem id="menu_changeset_field_rule" |
|||
parent="menu_changeset" |
|||
name="Field Rules" |
|||
groups="group_changeset_manager" |
|||
sequence="20" |
|||
action="action_changeset_field_rule_view"/> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data noupdate="0"> |
|||
<menuitem id="menu_changeset" |
|||
name="Partner Changesets" |
|||
groups="group_changeset_user" |
|||
parent="base.menu_base_config" |
|||
sequence="20"/> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,159 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data noupdate="0"> |
|||
<record id="view_res_partner_changeset_tree" model="ir.ui.view"> |
|||
<field name="name">res.partner.changeset.tree</field> |
|||
<field name="model">res.partner.changeset</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Partner Changeset" delete="false" create="false"> |
|||
<field name="partner_id"/> |
|||
<field name="date"/> |
|||
<field name="state"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_res_partner_changeset_form" model="ir.ui.view"> |
|||
<field name="name">res.partner.changeset.form</field> |
|||
<field name="model">res.partner.changeset</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Partner Changeset" delete="false" create="false"> |
|||
<header> |
|||
<button name="apply" |
|||
string="Apply pending changes" type="object" |
|||
class="oe_highlight" |
|||
states="draft"/> |
|||
<button name="cancel" |
|||
string="Reject pending changes" type="object" |
|||
class="oe_highlight" |
|||
states="draft"/> |
|||
<field name="state" widget="statusbar" |
|||
statusbar_visible="draft,done"/> |
|||
</header> |
|||
<sheet string="Partner Changeset"> |
|||
<group> |
|||
<field name="partner_id"/> |
|||
<field name="source"/> |
|||
<field name="date"/> |
|||
</group> |
|||
<group string="Changes"> |
|||
<field name="change_ids" nolabel="1"> |
|||
<tree string="Partner Changeset Change"> |
|||
<field name="field_id" context="{'no_open': true}"/> |
|||
<field name="field_type" invisible="1"/> |
|||
|
|||
<field name="origin_value_display" string="Previous"/> |
|||
<field name="new_value_display"/> |
|||
|
|||
<field name="state"/> |
|||
<button name="apply" |
|||
string="Apply" type="object" |
|||
icon="gtk-apply" |
|||
states="draft"/> |
|||
<button name="cancel" |
|||
string="Reject" type="object" |
|||
icon="gtk-close" |
|||
states="draft"/> |
|||
</tree> |
|||
</field> |
|||
</group> |
|||
<group> |
|||
<field name="note"/> |
|||
</group> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_res_partner_changeset_change_form" model="ir.ui.view"> |
|||
<field name="name">res.partner.changeset.change.form</field> |
|||
<field name="model">res.partner.changeset.change</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Partner Changeset Change" delete="false" create="false"> |
|||
<header> |
|||
<button name="apply" |
|||
string="Apply" type="object" |
|||
class="oe_highlight" |
|||
states="draft"/> |
|||
<button name="cancel" |
|||
string="Reject" type="object" |
|||
class="oe_highlight" |
|||
states="draft"/> |
|||
<field name="state" widget="statusbar" |
|||
statusbar_visible="draft,done"/> |
|||
</header> |
|||
<sheet> |
|||
<group> |
|||
<field name="field_id" options="{'no_open': true}"/> |
|||
<field name="field_type" invisible="1"/> |
|||
|
|||
<!-- attrs are added in fields_view_get --> |
|||
<field name="origin_value_char"/> |
|||
<field name="new_value_char"/> |
|||
|
|||
<field name="origin_value_date"/> |
|||
<field name="new_value_date"/> |
|||
|
|||
<field name="origin_value_datetime"/> |
|||
<field name="new_value_datetime"/> |
|||
|
|||
<field name="origin_value_float"/> |
|||
<field name="new_value_float"/> |
|||
|
|||
<field name="origin_value_integer"/> |
|||
<field name="new_value_integer"/> |
|||
|
|||
<field name="origin_value_text"/> |
|||
<field name="new_value_text"/> |
|||
|
|||
<field name="origin_value_boolean"/> |
|||
<field name="new_value_boolean"/> |
|||
|
|||
<field name="origin_value_reference"/> |
|||
<field name="new_value_reference"/> |
|||
|
|||
</group> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_res_partner_changeset_search" model="ir.ui.view"> |
|||
<field name="name">res.partner.changeset.search</field> |
|||
<field name="model">res.partner.changeset</field> |
|||
<field name="arch" type="xml"> |
|||
<search string="Partner Changeset"> |
|||
<field name="partner_id"/> |
|||
<filter string="Pending" name="draft" |
|||
domain="[('state','=','draft')]"/> |
|||
<filter string="Done" name="done" |
|||
domain="[('state','=','done')]"/> |
|||
<group expand="0" string="Group By"> |
|||
<filter string="Partner" |
|||
name="groupby_partner_id" |
|||
context="{'group_by': 'partner_id'}"/> |
|||
<filter string="State" |
|||
name="groupby_state" |
|||
context="{'group_by': 'state'}"/> |
|||
</group> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="action_res_partner_changeset_view"> |
|||
<field name="name">Partner Changeset</field> |
|||
<field name="type">ir.actions.act_window</field> |
|||
<field name="res_model">res.partner.changeset</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="context">{'search_default_draft': 1}</field> |
|||
<field name="search_view_id" ref="view_res_partner_changeset_search"/> |
|||
</record> |
|||
|
|||
<menuitem id="menu_res_partner_changeset" |
|||
parent="menu_changeset" |
|||
sequence="20" |
|||
name="Changesets" |
|||
action="action_res_partner_changeset_view"/> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,41 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data noupdate="0"> |
|||
|
|||
<record id="res_partner_view_buttons" model="ir.ui.view"> |
|||
<field name="name">res.partner.view.buttons</field> |
|||
<field name="model">res.partner</field> |
|||
<field name="inherit_id" ref="base.view_partner_form" /> |
|||
<field name="priority" eval="18"/> |
|||
<field name="groups_id" eval="[(4, ref('partner_changeset.group_changeset_user'))]"/> |
|||
<field name="arch" type="xml"> |
|||
<xpath expr="//div[@name='buttons']" position="inside"> |
|||
<button class="oe_inline oe_stat_button" |
|||
type="action" |
|||
name="%(partner_changeset.action_res_partner_changeset_view)d" |
|||
context="{'search_default_draft': 1, 'search_default_partner_id': active_id}" |
|||
icon="fa-code-fork"> |
|||
<field string="Changes" |
|||
name="count_pending_changesets" |
|||
widget="statinfo"/> |
|||
</button> |
|||
</xpath> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_res_partner_filter" model="ir.ui.view"> |
|||
<field name="name">res.partner.select</field> |
|||
<field name="model">res.partner</field> |
|||
<field name="inherit_id" ref="base.view_res_partner_filter" /> |
|||
<field name="arch" type="xml"> |
|||
<filter name="customer" position="after"> |
|||
<separator/> |
|||
<filter string="Pending Changesets" |
|||
name="pending_changesets" |
|||
domain="[('count_pending_changesets', '>', 0)]"/> |
|||
</filter> |
|||
</field> |
|||
</record> |
|||
|
|||
</data> |
|||
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue