Преглед изворни кода

Merge pull request #21 from sudhir-serpentcs/10.0

[IMP] Code improvements and bug fixes #20
Serpent Consulting Services Pvt. Ltd пре 7 година
родитељ
комит
18ce05cd12

+ 10 - 5
mass_editing/README.rst

@@ -16,10 +16,6 @@ This module provides the following features:
 
 
 * The video explaining the features and how-to for OpenERP Version 7 is here : http://www.youtube.com/watch?v=9BH0o74A748&feature=youtu.be
 * The video explaining the features and how-to for OpenERP Version 7 is here : http://www.youtube.com/watch?v=9BH0o74A748&feature=youtu.be
 
 
-* The video explaining the features and how-to for Odoo v9 is here: https://www.youtube.com/watch?v=hTng8BIbrQw
-
-* The video explaining the features and how-to for Odoo v10 is here: https://www.youtube.com/watch?v=W1EHfyvX9WI
-
 * For more details/customization/feedback contact us on contact@serpentcs.com
 * For more details/customization/feedback contact us on contact@serpentcs.com
 
 
 Installation
 Installation
@@ -37,6 +33,11 @@ To configure this module, you need to:
 Usage
 Usage
 =====
 =====
 
 
+This module allows to add, update or remove the values of more than one records on the fly at the same time.
+
+.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
+   :alt: Try me on Runbot
+   :target: https://runbot.odoo-community.org/runbot/149/10.0
 
 
 As shown in figure you have to configure the object and fields for mass editing.
 As shown in figure you have to configure the object and fields for mass editing.
 
 
@@ -71,7 +72,11 @@ Bug Tracker
 Bugs are tracked on `GitHub Issues
 Bugs are tracked on `GitHub Issues
 <https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
 <https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
 check there if your issue has already been reported. If you spotted it first,
 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.
+help us smashing it by providing a detailed and welcomed `feedback
+<https://github.com/OCA/
+server-tools/issues/new?body=module:%20
+server-tools%0Aversion:%20
+10.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
 
 
 Credits
 Credits
 =======
 =======

+ 3 - 1
mass_editing/__manifest__.py

@@ -16,7 +16,9 @@
     'license': 'GPL-3 or any later version',
     'license': 'GPL-3 or any later version',
     'summary': 'Mass Editing',
     'summary': 'Mass Editing',
     'uninstall_hook': 'uninstall_hook',
     'uninstall_hook': 'uninstall_hook',
-    'depends': ['base'],
+    'depends': [
+        'base',
+    ],
     'data': [
     'data': [
         'security/ir.model.access.csv',
         'security/ir.model.access.csv',
         'views/mass_editing_view.xml',
         'views/mass_editing_view.xml',

+ 4 - 7
mass_editing/models/ir_model_fields.py

@@ -2,7 +2,7 @@
 # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com)
 # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
 
 
-from openerp import api, models
+from odoo import api, models
 
 
 
 
 class IrModelFields(models.Model):
 class IrModelFields(models.Model):
@@ -12,16 +12,13 @@ class IrModelFields(models.Model):
     def search(self, args, offset=0, limit=0, order=None, count=False):
     def search(self, args, offset=0, limit=0, order=None, count=False):
         model_domain = []
         model_domain = []
         for domain in args:
         for domain in args:
-            if (len(domain) > 2 and
-                    domain[0] == 'model_id' and
+            if (len(domain) > 2 and domain[0] == 'model_id' and
                     isinstance(domain[2], basestring) and
                     isinstance(domain[2], basestring) and
                     list(domain[2][1:-1])):
                     list(domain[2][1:-1])):
                 model_domain += [('model_id', 'in',
                 model_domain += [('model_id', 'in',
                                   map(int, domain[2][1:-1].split(',')))]
                                   map(int, domain[2][1:-1].split(',')))]
             else:
             else:
                 model_domain.append(domain)
                 model_domain.append(domain)
-        return super(IrModelFields, self).search(model_domain,
-                                                 offset=offset,
-                                                 limit=limit,
-                                                 order=order,
+        return super(IrModelFields, self).search(model_domain, offset=offset,
+                                                 limit=limit, order=order,
                                                  count=count)
                                                  count=count)

+ 3 - 2
mass_editing/models/mass_object.py

@@ -2,8 +2,8 @@
 # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com)
 # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
 
 
-from openerp.exceptions import UserError
-from openerp import api, fields, models, _
+from odoo.exceptions import UserError
+from odoo import api, fields, models, _
 
 
 
 
 class MassObject(models.Model):
 class MassObject(models.Model):
@@ -94,6 +94,7 @@ class MassObject(models.Model):
         self.unlink_action()
         self.unlink_action()
         return super(MassObject, self).unlink()
         return super(MassObject, self).unlink()
 
 
+    @api.multi
     @api.returns('self', lambda value: value.id)
     @api.returns('self', lambda value: value.id)
     def copy(self, default=None):
     def copy(self, default=None):
         if default is None:
         if default is None:

+ 3 - 3
mass_editing/tests/test_mass_editing.py

@@ -4,9 +4,9 @@
 
 
 import ast
 import ast
 
 
-from openerp.tests import common
-from openerp.modules import registry
-from openerp.addons.mass_editing.hooks import uninstall_hook
+from odoo.tests import common
+from odoo.modules import registry
+from odoo.addons.mass_editing.hooks import uninstall_hook
 
 
 
 
 class TestMassEditing(common.TransactionCase):
 class TestMassEditing(common.TransactionCase):

+ 3 - 2
mass_editing/views/mass_editing_view.xml

@@ -14,7 +14,8 @@
                         </h1>
                         </h1>
                         <group>
                         <group>
                             <group>
                             <group>
-                                <field name="model_id" required="1" attrs="{'readonly':[('ref_ir_act_window_id','!=',False)]}"/>
+                                <field name="model_id" required="1"
+                                       attrs="{'readonly':[('ref_ir_act_window_id','!=',False)]}"/>
                             </group>
                             </group>
                             <group>
                             <group>
                                 <field name="model_list" invisible="1"/>
                                 <field name="model_list" invisible="1"/>
@@ -41,7 +42,7 @@
                     <notebook colspan="4">
                     <notebook colspan="4">
                         <page string="Fields">
                         <page string="Fields">
                             <field name="field_ids" colspan="4" nolabel="1"
                             <field name="field_ids" colspan="4" nolabel="1"
-                            domain="[('ttype', 'not in', ['reference', 'function']), ('model_id', 'in', model_list)]"/>
+                            domain="[('ttype', 'not in', ['refenrence', 'function', 'monetary']), ('model_id', 'in', model_list)]"/>
                         </page>
                         </page>
                         <page string="Advanced" attrs="{'invisible':[('ref_ir_act_window_id','=',False)]}">
                         <page string="Advanced" attrs="{'invisible':[('ref_ir_act_window_id','=',False)]}">
                             <group colspan="2" col="2">
                             <group colspan="2" col="2">

+ 145 - 32
mass_editing/wizard/mass_editing_wizard.py

@@ -2,10 +2,11 @@
 # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com)
 # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
 
 
+import json
 from lxml import etree
 from lxml import etree
 
 
-import openerp.tools as tools
-from openerp import api, models
+from odoo import tools
+from odoo import models, api
 
 
 
 
 class MassEditingWizard(models.TransientModel):
 class MassEditingWizard(models.TransientModel):
@@ -31,14 +32,6 @@ class MassEditingWizard(models.TransientModel):
                 'colspan': '6',
                 'colspan': '6',
                 'col': '6',
                 'col': '6',
             })
             })
-            etree.SubElement(xml_group, 'label', {
-                'string': '',
-                'colspan': '2',
-            })
-            xml_group = etree.SubElement(xml_form, 'group', {
-                'colspan': '6',
-                'col': '6',
-            })
             model_obj = self.env[context.get('active_model')]
             model_obj = self.env[context.get('active_model')]
             field_info = model_obj.fields_get()
             field_info = model_obj.fields_get()
             for field in editing_data.field_ids:
             for field in editing_data.field_ids:
@@ -48,7 +41,8 @@ class MassEditingWizard(models.TransientModel):
                         'type': 'selection',
                         'type': 'selection',
                         'string': field_info[field.name]['string'],
                         'string': field_info[field.name]['string'],
                         'selection': [('set', 'Set'),
                         'selection': [('set', 'Set'),
-                                      ('remove_m2m', 'Remove'),
+                                      ('remove_m2m', 'Remove Specific'),
+                                      ('remove_m2m_all', 'Remove All'),
                                       ('add', 'Add')]
                                       ('add', 'Add')]
                     }
                     }
                     xml_group = etree.SubElement(xml_group, 'group', {
                     xml_group = etree.SubElement(xml_group, 'group', {
@@ -68,30 +62,40 @@ class MassEditingWizard(models.TransientModel):
                         'name': field.name,
                         'name': field.name,
                         'colspan': '6',
                         'colspan': '6',
                         'nolabel': '1',
                         'nolabel': '1',
-                        'attrs': ("{'invisible': [('selection__" +
-                                  field.name + "', '=', 'remove_m2m')]}"),
+                        'attrs': "{'invisible': [('selection__" +
+                        field.name + "', '=', 'remove_m2m')]}",
                     })
                     })
                 elif field.ttype == "one2many":
                 elif field.ttype == "one2many":
                     all_fields["selection__" + field.name] = {
                     all_fields["selection__" + field.name] = {
                         'type': 'selection',
                         'type': 'selection',
                         'string': field_info[field.name]['string'],
                         'string': field_info[field.name]['string'],
-                        'selection': [('set', 'Set'), ('remove', 'Remove')],
+                        'selection': [('set', 'Set'),
+                                      ('remove_o2m', 'Remove')],
                     }
                     }
                     all_fields[field.name] = {
                     all_fields[field.name] = {
                         'type': field.ttype,
                         'type': field.ttype,
                         'string': field.field_description,
                         'string': field.field_description,
                         'relation': field.relation,
                         'relation': field.relation,
                     }
                     }
+                    xml_group = etree.SubElement(xml_group, 'group', {
+                        'colspan': '6',
+                        'col': '6',
+                    })
+                    etree.SubElement(xml_group, 'separator', {
+                        'string': field_info[field.name]['string'],
+                        'colspan': '6',
+                    })
                     etree.SubElement(xml_group, 'field', {
                     etree.SubElement(xml_group, 'field', {
                         'name': "selection__" + field.name,
                         'name': "selection__" + field.name,
-                        'colspan': '4',
+                        'colspan': '6',
+                        'nolabel': '1'
                     })
                     })
                     etree.SubElement(xml_group, 'field', {
                     etree.SubElement(xml_group, 'field', {
                         'name': field.name,
                         'name': field.name,
                         'colspan': '6',
                         'colspan': '6',
                         'nolabel': '1',
                         'nolabel': '1',
-                        'attrs': ("{'invisible':[('selection__" +
-                                  field.name + "', '=', 'remove_o2m')]}"),
+                        'attrs': "{'invisible':[('selection__" +
+                        field.name + "', '=', 'remove_o2m')]}",
                     })
                     })
                 elif field.ttype == "many2one":
                 elif field.ttype == "many2one":
                     all_fields["selection__" + field.name] = {
                     all_fields["selection__" + field.name] = {
@@ -112,8 +116,48 @@ class MassEditingWizard(models.TransientModel):
                         'name': field.name,
                         'name': field.name,
                         'nolabel': '1',
                         'nolabel': '1',
                         'colspan': '4',
                         'colspan': '4',
-                        'attrs': ("{'invisible':[('selection__" +
-                                  field.name + "', '=', 'remove')]}"),
+                        'attrs': "{'invisible':[('selection__" +
+                        field.name + "', '=', 'remove')]}",
+                    })
+                elif field.ttype == "float":
+                    all_fields["selection__" + field.name] = {
+                        'type': 'selection',
+                        'string': field_info[field.name]['string'],
+                        'selection': [('set', 'Set'),
+                                      ('val_add', '+'),
+                                      ('val_sub', '-'),
+                                      ('val_mul', '*'),
+                                      ('val_div', '/'),
+                                      ('remove', 'Remove')],
+                    }
+                    all_fields["set_selection_" + field.name] = {
+                        'type': 'selection',
+                        'string': 'Set calculation',
+                        'selection': [('set_fix', 'Fixed'),
+                                      ('set_per', 'Percentage')],
+                    }
+                    all_fields[field.name] = {
+                        'type': field.ttype,
+                        'string': field.field_description,
+                        'relation': field.relation,
+                    }
+                    etree.SubElement(xml_group, 'field', {
+                        'name': "selection__" + field.name,
+                        'colspan': '2',
+                    })
+                    etree.SubElement(xml_group, 'field', {
+                        'name': "set_selection_" + field.name,
+                        'nolabel': '1',
+                        'colspan': '1',
+                        'attrs': "{'invisible': [('selection__" +
+                        field.name + "', 'in', ('remove', 'set')]}",
+                    })
+                    etree.SubElement(xml_group, 'field', {
+                        'name': field.name,
+                        'nolabel': '1',
+                        'colspan': '3',
+                        'attrs': "{'invisible':[('selection__" +
+                        field.name + "', '=', 'remove')]}",
                     })
                     })
                 elif field.ttype == "char":
                 elif field.ttype == "char":
                     all_fields["selection__" + field.name] = {
                     all_fields["selection__" + field.name] = {
@@ -133,8 +177,8 @@ class MassEditingWizard(models.TransientModel):
                     etree.SubElement(xml_group, 'field', {
                     etree.SubElement(xml_group, 'field', {
                         'name': field.name,
                         'name': field.name,
                         'nolabel': '1',
                         'nolabel': '1',
-                        'attrs': ("{'invisible':[('selection__" +
-                                  field.name + "','=','remove')]}"),
+                        'attrs': "{'invisible':[('selection__" +
+                        field.name + "','=','remove')]}",
                         'colspan': '4',
                         'colspan': '4',
                     })
                     })
                 elif field.ttype == 'selection':
                 elif field.ttype == 'selection':
@@ -151,8 +195,8 @@ class MassEditingWizard(models.TransientModel):
                         'name': field.name,
                         'name': field.name,
                         'nolabel': '1',
                         'nolabel': '1',
                         'colspan': '4',
                         'colspan': '4',
-                        'attrs': ("{'invisible':[('selection__" +
-                                  field.name + "', '=', 'remove')]}"),
+                        'attrs': "{'invisible':[('selection__" +
+                        field.name + "', '=', 'remove')]}",
                     })
                     })
                     all_fields[field.name] = {
                     all_fields[field.name] = {
                         'type': field.ttype,
                         'type': field.ttype,
@@ -187,8 +231,8 @@ class MassEditingWizard(models.TransientModel):
                             'name': field.name,
                             'name': field.name,
                             'colspan': '6',
                             'colspan': '6',
                             'nolabel': '1',
                             'nolabel': '1',
-                            'attrs': ("{'invisible':[('selection__" +
-                                      field.name + "','=','remove')]}"),
+                            'attrs': "{'invisible':[('selection__" +
+                            field.name + "','=','remove')]}",
                         })
                         })
                     else:
                     else:
                         all_fields["selection__" + field.name] = {
                         all_fields["selection__" + field.name] = {
@@ -203,8 +247,8 @@ class MassEditingWizard(models.TransientModel):
                         etree.SubElement(xml_group, 'field', {
                         etree.SubElement(xml_group, 'field', {
                             'name': field.name,
                             'name': field.name,
                             'nolabel': '1',
                             'nolabel': '1',
-                            'attrs': ("{'invisible':[('selection__" +
-                                      field.name + "','=','remove')]}"),
+                            'attrs': "{'invisible':[('selection__" +
+                            field.name + "','=','remove')]}",
                             'colspan': '4',
                             'colspan': '4',
                         })
                         })
             etree.SubElement(xml_form, 'separator', {
             etree.SubElement(xml_form, 'separator', {
@@ -227,6 +271,30 @@ class MassEditingWizard(models.TransientModel):
             root = xml_form.getroottree()
             root = xml_form.getroottree()
             result['arch'] = etree.tostring(root)
             result['arch'] = etree.tostring(root)
             result['fields'] = all_fields
             result['fields'] = all_fields
+            doc = etree.XML(result['arch'])
+            for field in editing_data.field_ids:
+                for node in doc.xpath("//field[@name='set_selection_" +
+                                      field.name + "']"):
+                    modifiers = json.loads(node.get("modifiers", '{}'))
+                    modifiers.update({'invisible': [(
+                        "selection__" + field.name, 'in', ('remove', 'set'))],
+                        'required': [("selection__" + field.name, 'in',
+                                      ('val_add', 'val_sub', 'val_mul',
+                                       'val_div'))]}
+                    )
+                    node.set("modifiers", json.dumps(modifiers))
+                for node in doc.xpath("//field[@name='" + field.name + "']"):
+                    modifiers = json.loads(node.get("modifiers", '{}'))
+                    attr_val = 'remove'
+                    if field.ttype == "many2many":
+                        attr_val = 'remove_m2m_all'
+                    elif field.ttype == "one2many":
+                        attr_val = 'remove_o2m'
+                    modifiers.update({'invisible': [
+                        ("selection__" + field.name, '=', attr_val)
+                    ]})
+                    node.set("modifiers", json.dumps(modifiers))
+            result['arch'] = etree.tostring(doc)
         return result
         return result
 
 
     @api.model
     @api.model
@@ -234,23 +302,68 @@ class MassEditingWizard(models.TransientModel):
         if (self._context.get('active_model') and
         if (self._context.get('active_model') and
                 self._context.get('active_ids')):
                 self._context.get('active_ids')):
             model_obj = self.env[self._context.get('active_model')]
             model_obj = self.env[self._context.get('active_model')]
+            model_rec = model_obj.browse(self._context.get('active_ids'))
             values = {}
             values = {}
             for key, val in vals.items():
             for key, val in vals.items():
                 if key.startswith('selection_'):
                 if key.startswith('selection_'):
                     split_key = key.split('__', 1)[1]
                     split_key = key.split('__', 1)[1]
+                    set_val = vals.get('set_selection_' + split_key)
                     if val == 'set':
                     if val == 'set':
                         values.update({split_key: vals.get(split_key, False)})
                         values.update({split_key: vals.get(split_key, False)})
                     elif val == 'remove':
                     elif val == 'remove':
                         values.update({split_key: False})
                         values.update({split_key: False})
                     elif val == 'remove_m2m':
                     elif val == 'remove_m2m':
+                        if vals.get(split_key):
+                            m2m_list = []
+                            for m2m_id in vals.get(split_key)[0][2]:
+                                m2m_list.append((3, m2m_id))
+                            values.update({split_key: m2m_list})
+                    elif val in ['remove_o2m', 'remove_m2m_all']:
                         values.update({split_key: [(5, 0, [])]})
                         values.update({split_key: [(5, 0, [])]})
                     elif val == 'add':
                     elif val == 'add':
-                        m2m_list = []
-                        for m2m_id in vals.get(split_key, False)[0][2]:
-                            m2m_list.append((4, m2m_id))
-                        values.update({split_key: m2m_list})
+                        if vals.get(split_key, False):
+                            m2m_list = []
+                            for m2m_id in vals.get(split_key)[0][2]:
+                                m2m_list.append((4, m2m_id))
+                            values.update({split_key: m2m_list})
+
+                    # Mathematical operations
+                    elif val in ['val_add', 'val_sub', 'val_mul', 'val_div']:
+                        split_val = vals.get(split_key, 0.0)
+                        for data in model_rec:
+                            split_key_data = data[split_key]
+                            tot_val = 0
+                            # Addition
+                            if val == 'val_add':
+                                if set_val == 'set_fix':
+                                    tot_val = split_key_data + split_val
+                                elif set_val == 'set_per':
+                                    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 -\
+                                        (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 *\
+                                        (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 /\
+                                        (split_key_data * split_val) / 100
+                            data.write({split_key: tot_val})
             if values:
             if values:
-                model_obj.browse(self._context.get('active_ids')).write(values)
+                model_rec.write(values)
         return super(MassEditingWizard, self).create({})
         return super(MassEditingWizard, self).create({})
 
 
     @api.multi
     @api.multi