-
106mass_sorting/README.rst
-
24mass_sorting/__manifest__.py
-
12mass_sorting/demo/function.xml
-
16mass_sorting/demo/mass_sort_config.xml
-
20mass_sorting/demo/mass_sort_config_line.xml
-
195mass_sorting/i18n/fr.po
-
202mass_sorting/models/mass_sort_config.py
-
42mass_sorting/models/mass_sort_config_line.py
-
154mass_sorting/models/mass_sort_wizard.py
-
26mass_sorting/models/mass_sort_wizard_line.py
-
BINmass_sorting/static/description/1_mass_sort_config.png
-
BINmass_sorting/static/description/2_button.png
-
BINmass_sorting/static/description/3_mass_sort_wizard.png
-
BINmass_sorting/static/description/3_mass_sort_wizard_custom.png
-
BINmass_sorting/static/description/4_before.png
-
BINmass_sorting/static/description/5_after.png
-
BINmass_sorting/static/description/icon.png
-
6mass_sorting/views/menu.xml
-
52mass_sorting/views/view_mass_sort_config.xml
-
11mass_sorting/views/view_mass_sort_wizard.xml
@ -0,0 +1,106 @@ |
|||||
|
.. 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 |
||||
|
|
||||
|
============ |
||||
|
Mass Sorting |
||||
|
============ |
||||
|
|
||||
|
This module extends the functionality of odoo to allow users to sort an |
||||
|
one2many fields in any model. |
||||
|
|
||||
|
Typically, you can sort sale order lines on a sale order, using any fields. |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
To configure this module, you need to: |
||||
|
|
||||
|
* Go to Settings / Technical / Mass Sorting |
||||
|
|
||||
|
* Create a new item and define: |
||||
|
* a name |
||||
|
* the model you want to sort |
||||
|
* the field of the model, you want to sort |
||||
|
* The lists of the fields, by which the sort will be done |
||||
|
|
||||
|
.. image:: /mass_sorting/static/description/1_mass_sort_config.png |
||||
|
:width: 70% |
||||
|
|
||||
|
(You can allow users to change or not the values, by checking 'Allow custom Setting') |
||||
|
|
||||
|
* Click on the button 'Add sidebar button' |
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
* Go to the form view of the given model, in this sample, a sale order. (or select items in a tree view) |
||||
|
|
||||
|
.. image:: /mass_sorting/static/description/4_before.png |
||||
|
|
||||
|
* click on the button 'Action' and then select the according action |
||||
|
|
||||
|
.. image:: /mass_sorting/static/description/2_button.png |
||||
|
|
||||
|
* On the pop up (depending of the configuration), change the fields and the order |
||||
|
|
||||
|
.. image:: /mass_sorting/static/description/3_mass_sort_wizard_custom.png |
||||
|
|
||||
|
(If changing configuration is not allowed, a simple message is displayed.) |
||||
|
|
||||
|
.. image:: /mass_sorting/static/description/3_mass_sort_wizard.png |
||||
|
|
||||
|
* The items will be reordered. |
||||
|
|
||||
|
.. image:: /mass_sorting/static/description/5_after.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/149/10.0 |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues |
||||
|
<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, |
||||
|
help us smash it by providing detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Images |
||||
|
------ |
||||
|
|
||||
|
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Sylvain LE GAL (https://twitter.com/legalsylvain) |
||||
|
|
||||
|
Funders |
||||
|
------- |
||||
|
|
||||
|
The development of this module has been financially supported by: |
||||
|
|
||||
|
* GRAP (http://www.grap.coop) |
||||
|
|
||||
|
This module is highly inspired by 'mass_editing' module. (by OCA and SerpentCS) |
||||
|
|
||||
|
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,12 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- |
||||
|
Copyright (C) 2016-Today GRAP (http://www.grap.coop) |
||||
|
@author: Sylvain LE GAL (https://twitter.com/legalsylvain) |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
--> |
||||
|
|
||||
|
<odoo noupdate="1"> |
||||
|
|
||||
|
<function model="mass.sort.config" name="create_action" eval="[ref('mass_sort_config_demo')]"/> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- |
||||
|
Copyright (C) 2016-Today GRAP (http://www.grap.coop) |
||||
|
@author: Sylvain LE GAL (https://twitter.com/legalsylvain) |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
--> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record id="mass_sort_config_demo" model="mass.sort.config"> |
||||
|
<field name="name">Self Mass Sort Demo</field> |
||||
|
<field name="model_id" ref="mass_sorting.model_mass_sort_config"/> |
||||
|
<field name="one2many_field_id" ref="mass_sorting.field_mass_sort_config_line_ids"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,20 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- |
||||
|
Copyright (C) 2016-Today GRAP (http://www.grap.coop) |
||||
|
@author: Sylvain LE GAL (https://twitter.com/legalsylvain) |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
--> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record id="mass_sort_config_demo_line_1" model="mass.sort.config.line"> |
||||
|
<field name="config_id" ref="mass_sort_config_demo"/> |
||||
|
<field name="field_id" ref="mass_sorting.field_mass_sort_config_line_field_id"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="mass_sort_config_demo_line_2" model="mass.sort.config.line"> |
||||
|
<field name="config_id" ref="mass_sort_config_demo"/> |
||||
|
<field name="field_id" ref="mass_sorting.field_mass_sort_config_line_desc"/> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -1,144 +1,118 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
# Copyright (C): |
# Copyright (C): |
||||
# * 2012-Today Serpent Consulting Services (<http://www.serpentcs.com>) |
|
||||
# * 2016-Today GRAP (http://www.grap.coop) |
|
||||
|
# * 2012-Today Serpent Consulting Services (<http://www.serpentcs.com>) |
||||
|
# * 2016-Today GRAP (http://www.grap.coop) |
||||
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) |
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) |
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
from openerp.osv import fields |
|
||||
from openerp.osv.orm import Model |
|
||||
from openerp.tools.translate import _ |
|
||||
|
from odoo import _, api, fields, models |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
|
||||
class MassSortConfig(Model): |
|
||||
|
class MassSortConfig(models.Model): |
||||
_name = 'mass.sort.config' |
_name = 'mass.sort.config' |
||||
|
|
||||
# Column Section |
# Column Section |
||||
_columns = { |
|
||||
'name': fields.char( |
|
||||
string='Name', translate=True, required=True), |
|
||||
'model_id': fields.many2one( |
|
||||
'ir.model', string='Model', required=True), |
|
||||
'allow_custom_setting': fields.boolean( |
|
||||
string='Allow Custom Setting', help="If checked, any user could" |
|
||||
" have the possibility to change fields, and use others."), |
|
||||
'one2many_field_id': fields.many2one( |
|
||||
'ir.model.fields', string='Field to Sort', required=True, |
|
||||
domain="[('model_id', '=', model_id)," |
|
||||
"('ttype', '=', 'one2many')]"), |
|
||||
'one2many_model': fields.related( |
|
||||
'one2many_field_id', 'relation', type='char', |
|
||||
string='Model Name of the Field to Sort', readonly=True), |
|
||||
'ref_ir_act_window': fields.many2one( |
|
||||
'ir.actions.act_window', 'Sidebar Action', readonly=True), |
|
||||
'ref_ir_value': fields.many2one( |
|
||||
'ir.values', 'Sidebar Button', readonly=True), |
|
||||
'line_ids': fields.one2many( |
|
||||
'mass.sort.config.line', 'config_id', 'Sorting Criterias'), |
|
||||
} |
|
||||
|
|
||||
_defaults = { |
|
||||
'allow_custom_setting': True, |
|
||||
} |
|
||||
|
name = fields.Char(string='Name', translate=True, required=True) |
||||
|
|
||||
|
model_id = fields.Many2one( |
||||
|
comodel_name='ir.model', string='Model', required=True) |
||||
|
|
||||
|
allow_custom_setting = fields.Boolean( |
||||
|
string='Allow Custom Setting', default=True, help="If checked, any" |
||||
|
" user could have the possibility to change fields, and use others.") |
||||
|
|
||||
|
one2many_field_id = fields.Many2one( |
||||
|
comodel_name='ir.model.fields', string='Field to Sort', required=True, |
||||
|
domain="[('model_id', '=', model_id),('ttype', '=', 'one2many')]") |
||||
|
|
||||
|
one2many_model = fields.Char( |
||||
|
related='one2many_field_id.relation', readonly=True, |
||||
|
string='Model Name of the Field to Sort', hel="Technical field," |
||||
|
"used in the model 'mass.sort.config.line'", store=True) |
||||
|
|
||||
|
ref_ir_act_window = fields.Many2one( |
||||
|
comodel_name='ir.actions.act_window', string='Sidebar Action', |
||||
|
readonly=True, copy=False) |
||||
|
|
||||
|
ref_ir_value = fields.Many2one( |
||||
|
comodel_name='ir.values', string='Sidebar Button', readonly=True, |
||||
|
copy=False) |
||||
|
|
||||
|
line_ids = fields.One2many( |
||||
|
comodel_name='mass.sort.config.line', inverse_name='config_id', |
||||
|
string='Sorting Criterias') |
||||
|
|
||||
# Constraint Section |
# Constraint Section |
||||
def _check_model_sequence(self, cr, uid, ids, context=None): |
|
||||
field_obj = self.pool['ir.model.fields'] |
|
||||
for config in self.browse(cr, uid, ids, context=context): |
|
||||
if len(field_obj.search(cr, uid, [ |
|
||||
|
@api.constrains('one2many_field_id') |
||||
|
def _check_model_sequence(self): |
||||
|
field_obj = self.env['ir.model.fields'] |
||||
|
for config in self: |
||||
|
if len(field_obj.search([ |
||||
('model', '=', config.one2many_field_id.relation), |
('model', '=', config.one2many_field_id.relation), |
||||
('name', '=', 'sequence')], context=context)) != 1: |
|
||||
return False |
|
||||
return True |
|
||||
|
|
||||
def _check_model_field(self, cr, uid, ids, context=None): |
|
||||
for config in self.browse(cr, uid, ids, context=context): |
|
||||
if config.model_id.id != config.one2many_field_id.model_id.id: |
|
||||
return False |
|
||||
return True |
|
||||
|
|
||||
def _check_line_ids(self, cr, uid, ids, context=None): |
|
||||
for config in self.browse(cr, uid, ids, context=context): |
|
||||
if not config.allow_custom_setting and len(config.line_ids) == 0: |
|
||||
return False |
|
||||
return True |
|
||||
|
|
||||
_constraints = [ |
|
||||
(_check_model_sequence, "The selected Field to Sort doesn't not have" |
|
||||
" 'sequence' field defined.", ['one2many_field_id']), |
|
||||
(_check_model_field, "The selected Field to Sort doesn't belong to the" |
|
||||
" selected model.", ['model_id', 'one2many_field_id']), |
|
||||
(_check_line_ids, "You have to define field(s) in 'Sorting Criterias'" |
|
||||
" if you uncheck 'Allow Custom Setting'.", |
|
||||
['line_ids', 'allow_custom_setting']), |
|
||||
] |
|
||||
|
|
||||
# View Section |
|
||||
def on_change_one2many_field_id( |
|
||||
self, cr, uid, ids, one2many_field_id, context=None): |
|
||||
field_obj = self.pool['ir.model.fields'] |
|
||||
if not one2many_field_id: |
|
||||
return {'value': {'one2many_model': False}} |
|
||||
field = field_obj.browse(cr, uid, one2many_field_id, context=context) |
|
||||
return {'value': {'one2many_model': field.relation}} |
|
||||
|
('name', '=', 'sequence')])) != 1: |
||||
|
raise ValidationError( |
||||
|
_("The selected Field to Sort doesn't not have" |
||||
|
" 'sequence' field defined.")) |
||||
|
|
||||
|
@api.constrains('model_id', 'one2many_field_id') |
||||
|
def _check_model_field(self): |
||||
|
for config in self: |
||||
|
if config.model_id != config.one2many_field_id.model_id: |
||||
|
raise ValidationError( |
||||
|
_("The selected Field to Sort '%s' doesn't belong to the" |
||||
|
" selected model '%s'.") % ( |
||||
|
config.one2many_field_id.model_id.name, |
||||
|
config.model_id.name)) |
||||
|
|
||||
|
@api.constrains('allow_custom_setting', 'line_ids') |
||||
|
def _check_line_ids(self): |
||||
|
for config in self: |
||||
|
if not config.allow_custom_setting and not len(config.line_ids): |
||||
|
raise ValidationError(_( |
||||
|
"You have to define field(s) in 'Sorting Criterias' if" |
||||
|
" you uncheck 'Allow Custom Setting'.")) |
||||
|
|
||||
# Overload Section |
# Overload Section |
||||
def unlink(self, cr, uid, ids, context=None): |
|
||||
self.unlink_action(cr, uid, ids, context=context) |
|
||||
return super(MassSortConfig, self).unlink( |
|
||||
cr, uid, ids, context=context) |
|
||||
|
|
||||
def copy(self, cr, uid, id, value=None, context=None): |
|
||||
value = value or {} |
|
||||
config = self.browse(cr, uid, id, context=context) |
|
||||
value.update({ |
|
||||
'name': _('%s (copy)') % config.name, |
|
||||
'ref_ir_act_window': False, |
|
||||
'ref_ir_value': False}) |
|
||||
return super(MassSortConfig, self).copy( |
|
||||
cr, uid, id, value, context=context) |
|
||||
|
def unlink(self): |
||||
|
self.unlink_action() |
||||
|
return super(MassSortConfig, self).unlink() |
||||
|
|
||||
|
def copy(self, default=None): |
||||
|
default = default or {} |
||||
|
default.update({ |
||||
|
'name': _('%s (copy)') % self.name}) |
||||
|
return super(MassSortConfig, self).copy(default=default) |
||||
|
|
||||
# Custom Section |
# Custom Section |
||||
def create_action(self, cr, uid, ids, context=None): |
|
||||
vals = {} |
|
||||
action_obj = self.pool['ir.actions.act_window'] |
|
||||
values_obj = self.pool['ir.values'] |
|
||||
for config in self.browse(cr, uid, ids, context=context): |
|
||||
src_obj = config.model_id.model |
|
||||
|
@api.multi |
||||
|
def create_action(self): |
||||
|
action_obj = self.env['ir.actions.act_window'] |
||||
|
values_obj = self.env['ir.values'] |
||||
|
for config in self: |
||||
button_name = _('Mass Sort (%s)') % config.name |
button_name = _('Mass Sort (%s)') % config.name |
||||
vals['ref_ir_act_window'] = action_obj.create(cr, uid, { |
|
||||
|
config.ref_ir_act_window = action_obj.create({ |
||||
'name': button_name, |
'name': button_name, |
||||
'type': 'ir.actions.act_window', |
'type': 'ir.actions.act_window', |
||||
'res_model': 'mass.sort.wizard', |
'res_model': 'mass.sort.wizard', |
||||
'src_model': src_obj, |
|
||||
|
'src_model': config.model_id.model, |
||||
'view_type': 'form', |
'view_type': 'form', |
||||
'context': "{'mass_sort_config_id' : %d}" % (config.id), |
'context': "{'mass_sort_config_id' : %d}" % (config.id), |
||||
'view_mode': 'form,tree', |
'view_mode': 'form,tree', |
||||
'target': 'new', |
'target': 'new', |
||||
'auto_refresh': 1, |
|
||||
}, context) |
|
||||
vals['ref_ir_value'] = values_obj.create(cr, uid, { |
|
||||
|
}) |
||||
|
config.ref_ir_value = values_obj.create({ |
||||
'name': button_name, |
'name': button_name, |
||||
'model': src_obj, |
|
||||
|
'model': config.model_id.model, |
||||
'key2': 'client_action_multi', |
'key2': 'client_action_multi', |
||||
'value': ( |
'value': ( |
||||
"ir.actions.act_window,%s" % vals['ref_ir_act_window']), |
|
||||
'object': True, |
|
||||
}, context) |
|
||||
self.write(cr, uid, ids, { |
|
||||
'ref_ir_act_window': vals.get('ref_ir_act_window', False), |
|
||||
'ref_ir_value': vals.get('ref_ir_value', False), |
|
||||
}, context) |
|
||||
return True |
|
||||
|
|
||||
def unlink_action(self, cr, uid, ids, context=None): |
|
||||
action_obj = self.pool['ir.actions.act_window'] |
|
||||
values_obj = self.pool['ir.values'] |
|
||||
for config in self.browse(cr, uid, ids, context=context): |
|
||||
|
"ir.actions.act_window,%s" % config.ref_ir_act_window.id), |
||||
|
}) |
||||
|
|
||||
|
@api.multi |
||||
|
def unlink_action(self): |
||||
|
for config in self: |
||||
if config.ref_ir_act_window: |
if config.ref_ir_act_window: |
||||
action_obj.unlink( |
|
||||
cr, uid, config.ref_ir_act_window.id, context=context) |
|
||||
|
config.ref_ir_act_window.unlink() |
||||
if config.ref_ir_value: |
if config.ref_ir_value: |
||||
values_obj.unlink( |
|
||||
cr, uid, config.ref_ir_value.id, context=context) |
|
||||
return True |
|
||||
|
config.ref_ir_value.unlink() |
Before Width: 770 | Height: 283 | Size: 25 KiB After Width: 798 | Height: 386 | Size: 43 KiB |
Before Width: 609 | Height: 153 | Size: 17 KiB After Width: 357 | Height: 123 | Size: 11 KiB |
Before Width: 443 | Height: 189 | Size: 19 KiB After Width: 276 | Height: 177 | Size: 9.5 KiB |
Before Width: 718 | Height: 251 | Size: 15 KiB After Width: 776 | Height: 318 | Size: 17 KiB |
Before Width: 769 | Height: 424 | Size: 37 KiB After Width: 676 | Height: 415 | Size: 51 KiB |
Before Width: 771 | Height: 420 | Size: 37 KiB After Width: 679 | Height: 412 | Size: 50 KiB |
After Width: 256 | Height: 256 | Size: 6.2 KiB |