diff --git a/.travis.yml b/.travis.yml index 4d9962c..3fa2e72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,38 +1,41 @@ -language: python sudo: false cache: pip -python: - - "2.7" - addons: postgresql: "9.2" # minimal postgresql version for the daterange method apt: - packages: + packages: - expect-dev # provides unbuffer utility - python-lxml # because pip installation is slow + +language: python + +python: + - "3.5" + env: global: - - VERSION="10.0" TESTS="0" LINT_CHECK="0" TRANSIFEX="0" + - VERSION="11.0" TESTS="0" LINT_CHECK="0" TRANSIFEX="0" - TRANSIFEX_USER='transbot@odoo-community.org' - - secure: Z06mZCN+Hm3myqHSOZpOOk1pd4oq1epAWZv6m9OX2bTNHbhyOVOGK6JWWsnDm/3DUCN1ZeLtSGOl9bvQfMa8ahQHA80MkLL16YlTvQV59Lh+L2gAYmxX+ogJCJgeQSVAXlGLscgkADCu/HzDlmatrDeROMtULn5i23j2qcyUNyM= - + matrix: - LINT_CHECK="1" - TRANSIFEX="1" - - TESTS="1" ODOO_REPO="odoo/odoo" - TESTS="1" ODOO_REPO="OCA/OCB" + - TESTS="1" ODOO_REPO="odoo/odoo" + -virtualenv: - system_site_packages: true +before_install: + - "export DISPLAY=:911.0" + - "sh -e /etc/init.d/xvfb start" install: - git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} + - export WKHTMLTOPDF_VERSION=0.12.4 - travis_install_nightly - - printf '[options]\n\nrunning_env = dev\n' > ${HOME}/.openerp_serverrc - - ln -s ${TRAVIS_BUILD_DIR}/server_environment_files_sample ${TRAVIS_BUILD_DIR}/server_environment_files + script: - travis_run_tests diff --git a/README.md b/README.md index 448c537..7bcc4b4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/JayVora-SerpentCS/MassEditing.svg?branch=10.0)](https://travis-ci.org/JayVora-SerpentCS/MassEditing) +[![Build Status](https://travis-ci.org/JayVora-SerpentCS/MassEditing.svg?branch=11.0)](https://travis-ci.org/JayVora-SerpentCS/MassEditing) ============ Mass Editing diff --git a/mass_editing/__init__.py b/mass_editing/__init__.py index f232425..cae1047 100644 --- a/mass_editing/__init__.py +++ b/mass_editing/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/mass_editing/__manifest__.py b/mass_editing/__manifest__.py index 17f8daf..4dc7e47 100644 --- a/mass_editing/__manifest__.py +++ b/mass_editing/__manifest__.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'Mass Editing', - 'version': '10.0.1.0.0', + 'version': '11.0.1.0.0', 'author': 'Serpent Consulting Services Pvt. Ltd., ' 'Odoo Community Association (OCA)', 'contributors': [ @@ -18,10 +17,12 @@ 'uninstall_hook': 'uninstall_hook', 'depends': [ 'base', + 'web', ], 'data': [ 'security/ir.model.access.csv', 'views/mass_editing_view.xml', + 'views/template.xml' ], 'installable': True, 'application': False, diff --git a/mass_editing/hooks.py b/mass_editing/hooks.py index 708c4e4..2517fe6 100644 --- a/mass_editing/hooks.py +++ b/mass_editing/hooks.py @@ -1,12 +1,7 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). def uninstall_hook(cr, registry): - cr.execute("""SELECT id FROM ir_act_window - WHERE res_model = 'mass.editing.wizard'""") - for res in cr.dictfetchall(): - value = 'ir.actions.act_window,%s' % res.get('id') - cr.execute("DELETE FROM ir_values WHERE value = '%s'" % value) - return True + cr.execute("""DELETE FROM ir_act_window WHERE + res_model = 'mass.editing.wizard'""") diff --git a/mass_editing/models/__init__.py b/mass_editing/models/__init__.py index 28f986c..d867b13 100644 --- a/mass_editing/models/__init__.py +++ b/mass_editing/models/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/mass_editing/models/ir_model_fields.py b/mass_editing/models/ir_model_fields.py index d22867e..93eb734 100644 --- a/mass_editing/models/ir_model_fields.py +++ b/mass_editing/models/ir_model_fields.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -11,14 +10,19 @@ class IrModelFields(models.Model): @api.model def search(self, args, offset=0, limit=0, order=None, count=False): model_domain = [] - for domain in args: - if (len(domain) > 2 and domain[0] == 'model_id' and - isinstance(domain[2], basestring) and - list(domain[2][1:-1])): - model_domain += [('model_id', 'in', - map(int, domain[2][1:-1].split(',')))] - else: - model_domain.append(domain) - return super(IrModelFields, self).search(model_domain, offset=offset, - limit=limit, order=order, + if not self.env.context.get('mass_edit', False): + model_domain = args + else: + for domain in args: + if (len(domain) > 2 and domain[0] == 'model_id' and + isinstance(domain[2], str) and + list(domain[2][1:-1])): + model_ids = list(map(int, domain[2][1:-1].split(','))) + model_domain += [('model_id', 'in', model_ids)] + else: + model_domain.append(domain) + return super(IrModelFields, self).search(args=model_domain, + offset=offset, + limit=limit, + order=order, count=count) diff --git a/mass_editing/models/mass_object.py b/mass_editing/models/mass_object.py index 656cfe1..52d3fd2 100644 --- a/mass_editing/models/mass_object.py +++ b/mass_editing/models/mass_object.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -10,7 +9,7 @@ class MassObject(models.Model): _name = "mass.object" _description = "Mass Editing Object" - name = fields.Char('Name', required=True, select=1) + name = fields.Char('Name', required=True, index=True) model_id = fields.Many2one('ir.model', 'Model', required=True, help="Model is used for Selecting Fields. " "This is editable until Sidebar menu " @@ -18,16 +17,12 @@ class MassObject(models.Model): field_ids = fields.Many2many('ir.model.fields', 'mass_field_rel', 'mass_id', 'field_id', 'Fields') ref_ir_act_window_id = fields.Many2one('ir.actions.act_window', - 'Sidebar action', + 'Sidebar Action', readonly=True, help="Sidebar action to make this " "template available on " "records of the related " "document model.") - ref_ir_value_id = fields.Many2one('ir.values', 'Sidebar button', - readonly=True, - help="Sidebar button to open " - "the sidebar action.") model_list = fields.Char('Model List') _sql_constraints = [ @@ -43,8 +38,10 @@ def _onchange_model_id(self): model_list = [self.model_id.id] active_model_obj = self.env[self.model_id.model] if active_model_obj._inherits: - keys = active_model_obj._inherits.keys() - inherits_model_list = model_obj.search([('model', 'in', keys)]) + model_names = active_model_obj._inherits.keys() + inherits_model_list = model_obj.search([('model', + 'in', + list(model_names))]) model_list.extend((inherits_model_list and inherits_model_list.ids or [])) self.model_list = model_list @@ -63,16 +60,9 @@ def create_action(self): 'src_model': src_obj, 'view_type': 'form', 'context': "{'mass_editing_object' : %d}" % (self.id), - 'view_mode': 'form, tree', + 'view_mode': 'form', 'target': 'new', - 'auto_refresh': 1, - }).id - vals['ref_ir_value_id'] = self.env['ir.values'].create({ - 'name': button_name, - 'model': src_obj, - 'key2': 'client_action_multi', - 'value': "ir.actions.act_window," + - str(vals['ref_ir_act_window_id']), + 'binding_model_id': self.model_id.id, }).id self.write(vals) return True @@ -83,8 +73,6 @@ def unlink_action(self): try: if mass.ref_ir_act_window_id: mass.ref_ir_act_window_id.unlink() - if mass.ref_ir_value_id: - mass.ref_ir_value_id.unlink() except: raise UserError(_("Deletion of the action record failed.")) return True diff --git a/mass_editing/static/description/after_edit_note.png b/mass_editing/static/description/after_edit_note.png deleted file mode 100644 index f3757bb..0000000 Binary files a/mass_editing/static/description/after_edit_note.png and /dev/null differ diff --git a/mass_editing/static/description/after_mass_edit.png b/mass_editing/static/description/after_mass_edit.png deleted file mode 100644 index a8a1f70..0000000 Binary files a/mass_editing/static/description/after_mass_edit.png and /dev/null differ diff --git a/mass_editing/static/description/index.html b/mass_editing/static/description/index.html index f2e0dcf..eeee1f0 100644 --- a/mass_editing/static/description/index.html +++ b/mass_editing/static/description/index.html @@ -21,14 +21,14 @@

The user can remove the action by clicking on the "Remove Sidebar Button".

- +
- +

Go to the model/object in which mass editing is configured (ex. Partner).

@@ -39,26 +39,27 @@

- +
-

The user can set or remove the value of the fields which were configured in mass editing configuration.

+

The user can set or remove the value of the fields which were configured in mass editing configuration. + you can also copy the value of the fields from another field.

-
+

You can see the mass editing changes in customer tree view..

- +
-
+
- +

You can see the mass editing changes in selected customer

diff --git a/mass_editing/static/description/mass_edit_copy.png b/mass_editing/static/description/mass_edit_copy.png new file mode 100644 index 0000000..a079aad Binary files /dev/null and b/mass_editing/static/description/mass_edit_copy.png differ diff --git a/mass_editing/static/description/mass_edit_menu.png b/mass_editing/static/description/mass_edit_menu.png new file mode 100644 index 0000000..1b645b7 Binary files /dev/null and b/mass_editing/static/description/mass_edit_menu.png differ diff --git a/mass_editing/static/description/mass_edit_result.png b/mass_editing/static/description/mass_edit_result.png new file mode 100644 index 0000000..e531490 Binary files /dev/null and b/mass_editing/static/description/mass_edit_result.png differ diff --git a/mass_editing/static/description/mass_edit_tree.png b/mass_editing/static/description/mass_edit_tree.png new file mode 100644 index 0000000..fa55b9f Binary files /dev/null and b/mass_editing/static/description/mass_edit_tree.png differ diff --git a/mass_editing/static/description/mass_edit_wizard.png b/mass_editing/static/description/mass_edit_wizard.png deleted file mode 100644 index 0e78b78..0000000 Binary files a/mass_editing/static/description/mass_edit_wizard.png and /dev/null differ diff --git a/mass_editing/static/description/mass_editing-2.png b/mass_editing/static/description/mass_editing-2.png index 830b5b7..a948982 100644 Binary files a/mass_editing/static/description/mass_editing-2.png and b/mass_editing/static/description/mass_editing-2.png differ diff --git a/mass_editing/static/description/mass_editing_menu.png b/mass_editing/static/description/mass_editing_menu.png deleted file mode 100644 index 3f2dcff..0000000 Binary files a/mass_editing/static/description/mass_editing_menu.png and /dev/null differ diff --git a/mass_editing/static/description/remove_button.png b/mass_editing/static/description/remove_button.png deleted file mode 100644 index 403bb25..0000000 Binary files a/mass_editing/static/description/remove_button.png and /dev/null differ diff --git a/mass_editing/static/description/remove_button2.png b/mass_editing/static/description/remove_button2.png new file mode 100644 index 0000000..0fdb954 Binary files /dev/null and b/mass_editing/static/description/remove_button2.png differ diff --git a/mass_editing/static/src/js/mass_editing.js b/mass_editing/static/src/js/mass_editing.js new file mode 100644 index 0000000..d4f187c --- /dev/null +++ b/mass_editing/static/src/js/mass_editing.js @@ -0,0 +1,48 @@ +odoo.define('mass_editing.mass_editing', function (require) { +"use strict"; + + var BasicModel = require("web.BasicModel"); + + BasicModel.include({ + /** + * parse the server values to javascript framwork + * @param {String} fieldNames + * @param {Object} element the dataPoint used as parent for the created + * dataPoints + * @param {Object} data the server data to parse + */ + _parseServerData: function (fieldNames, element, data) { + var self = this; + _.each(fieldNames, function (fieldName) { + var field = element.fields[fieldName]; + var val = data[fieldName]; + if (field.type === 'many2one') { + // process many2one: split [id, nameget] and create corresponding record + // For val = Null + if (val && val !== false) { + // the many2one value is of the form [id, display_name] + var r = self._makeDataPoint({ + modelName: field.relation, + fields: { + display_name: {type: 'char'}, + id: {type: 'integer'}, + }, + data: { + display_name: val[1], + id: val[0], + }, + parentID: element.id, + }); + data[fieldName] = r.id; + } else { + // no value for the many2one + data[fieldName] = false; + } + } else { + data[fieldName] = self._parseServerValue(field, val); + } + }); + }, + }); + +}); diff --git a/mass_editing/tests/__init__.py b/mass_editing/tests/__init__.py index 15099e1..c1f46a4 100644 --- a/mass_editing/tests/__init__.py +++ b/mass_editing/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/mass_editing/tests/test_mass_editing.py b/mass_editing/tests/test_mass_editing.py index 7ec719a..460a660 100644 --- a/mass_editing/tests/test_mass_editing.py +++ b/mass_editing/tests/test_mass_editing.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -6,7 +5,7 @@ from odoo.tests import common from odoo.modules import registry -from odoo.addons.mass_editing.hooks import uninstall_hook +from ..hooks import uninstall_hook class TestMassEditing(common.TransactionCase): @@ -21,11 +20,21 @@ def setUp(self): self.partner_model = model_obj.\ search([('model', '=', 'res.partner')]) self.user_model = model_obj.search([('model', '=', 'res.users')]) + # Calling the Search method without context for + # the Search from the List view of the Fields. self.fields_model = self.env['ir.model.fields'].\ search([('model_id', '=', self.partner_model.id), ('name', 'in', ['email', 'phone', 'category_id', 'comment', 'country_id', 'customer', 'child_ids', 'title'])]) + # Calling the Search method with context for the Search + # model_id field related fields in the fields_ids. + self.fields_model = self.env['ir.model.fields'].\ + with_context({'mass_edit': True}).\ + search([('model_id', '=', self.partner_model.id), + ('name', 'in', ['email', 'phone', 'category_id', 'comment', + 'country_id', 'customer', 'child_ids', + 'title'])]) self.mass = self._create_mass_editing(self.partner_model, self.fields_model) self.copy_mass = self.mass.copy() @@ -142,21 +151,19 @@ def test_mass_edit_copy(self): def test_sidebar_action(self): """Test if Sidebar Action is added / removed to / from give object.""" - action = self.mass.ref_ir_act_window_id and self.mass.ref_ir_value_id + action = self.mass.ref_ir_act_window_id self.assertTrue(action, 'Sidebar action must be exists.') # Remove the sidebar actions self.mass.unlink_action() - action = self.mass.ref_ir_act_window_id and self.mass.ref_ir_value_id + action = self.mass.ref_ir_act_window_id self.assertFalse(action, 'Sidebar action must be removed.') def test_unlink_mass(self): """Test if related actions are removed when mass editing record is unlinked.""" - mass_action_id = "ir.actions.act_window," + str(self.mass.id) self.mass.unlink() - value_cnt = self.env['ir.values'].search([('value', '=', - mass_action_id)], - count=True) + value_cnt = self.env['ir.actions.act_window'].search([ + ('res_model', '=', 'mass.editing.wizard')], count=True) self.assertTrue(value_cnt == 0, "Sidebar action must be removed when mass" " editing is unlinked.") @@ -165,10 +172,8 @@ def test_uninstall_hook(self): """Test if related actions are removed when mass editing record is uninstalled.""" uninstall_hook(self.cr, registry) - mass_action_id = "ir.actions.act_window," + str(self.mass.id) - value_cnt = self.env['ir.values'].search([('value', '=', - mass_action_id)], - count=True) + value_cnt = self.env['ir.actions.act_window'].search([ + ('res_model', '=', 'mass.editing.wizard')], count=True) self.assertTrue(value_cnt == 0, "Sidebar action must be removed when mass" " editing module is uninstalled.") diff --git a/mass_editing/views/mass_editing_view.xml b/mass_editing/views/mass_editing_view.xml index 547c519..87d08ce 100644 --- a/mass_editing/views/mass_editing_view.xml +++ b/mass_editing/views/mass_editing_view.xml @@ -41,13 +41,12 @@
- + - diff --git a/mass_editing/views/template.xml b/mass_editing/views/template.xml new file mode 100644 index 0000000..fadf9ad --- /dev/null +++ b/mass_editing/views/template.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/mass_editing/wizard/__init__.py b/mass_editing/wizard/__init__.py index ecfaa57..47a34f3 100644 --- a/mass_editing/wizard/__init__.py +++ b/mass_editing/wizard/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/mass_editing/wizard/mass_editing_wizard.py b/mass_editing/wizard/mass_editing_wizard.py index 431cdd0..d8bcb89 100644 --- a/mass_editing/wizard/mass_editing_wizard.py +++ b/mass_editing/wizard/mass_editing_wizard.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -15,7 +14,7 @@ class MassEditingWizard(models.TransientModel): @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): - result =\ + result = \ super(MassEditingWizard, self).fields_view_get( view_id=view_id, view_type=view_type, @@ -421,6 +420,8 @@ def create(self, vals): fields_obj = self.env['ir.model.fields'] model_obj = self.env[self._context.get('active_model')] model_rec = model_obj.browse(self._context.get('active_ids')) + model_id = self.env['ir.model'].search([ + ('model', '=', self._context.get('active_model'))]) values = {} for key, val in vals.items(): if key.startswith('selection_'): @@ -430,14 +431,35 @@ def create(self, vals): values.update({split_key: vals.get(split_key, False)}) elif val == 'remove': values.update({split_key: False}) - elif val == 'remove_m2m': + elif val in ['remove_m2m', 'remove_m2m_all']: + m2m_list = [] if vals.get(split_key): - m2m_list = [] for m2m_id in vals.get(split_key)[0][2]: m2m_list.append((3, m2m_id)) + if m2m_list: values.update({split_key: m2m_list}) - elif val in ['remove_o2m', 'remove_m2m_all']: - values.update({split_key: [(5, 0, [])]}) + else: + values.update({split_key: [(5, 0, [])]}) + elif val == 'remove_o2m': + # model_fieds will return the particular model + # in order to get the field of the model + # and its relation. + model_fields = self.env['ir.model.fields'].search( + [('name', '=', split_key), + ('model_id', '=', model_id and model_id.id)]) + # field_model is relation of Object Relation + field_model = tools.ustr(model_fields and + model_fields.relation) + # field relation is relation field of O2m + field_relation = tools.ustr( + model_fields and + model_fields.relation_field) + # remove_ids is return O2m field particular ids + remove_ids = self.env[field_model].search( + [(field_relation, 'in', + self._context.get('active_ids'))]) + o2m_list = [(2, rmv_id.id) for rmv_id in remove_ids] + values.update({split_key: o2m_list}) elif val == 'add': if vals.get(split_key, False): m2m_list = [] @@ -464,33 +486,33 @@ def create(self, vals): if set_val == 'set_fix': tot_val = split_key_data + split_val elif set_val == 'set_per': - tot_val = split_key_data +\ + tot_val = split_key_data + \ (split_key_data * split_val) / 100.0 # Subtraction elif val == 'val_sub': if set_val == 'set_fix': tot_val = split_key_data - split_val elif set_val == 'set_per': - tot_val = split_key_data -\ + tot_val = split_key_data - \ (split_key_data * split_val) / 100.0 # Multiplication elif val == 'val_mul': if set_val == 'set_fix': tot_val = split_key_data * split_val elif set_val == 'set_per': - tot_val = split_key_data *\ + tot_val = split_key_data * \ (split_key_data * split_val) / 100 # Division elif val == 'val_div': if set_val == 'set_fix': tot_val = split_key_data / split_val elif set_val == 'set_per': - tot_val = split_key_data /\ + tot_val = split_key_data / \ (split_key_data * split_val) / 100 data.write({split_key: tot_val}) if values: model_rec.write(values) - return super(MassEditingWizard, self).create({}) + return super(MassEditingWizard, self).create(vals) @api.multi def action_apply(self):