-
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 -*- |
|||
# 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) |
|||
# 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' |
|||
|
|||
# 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 |
|||
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), |
|||
('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 |
|||
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 |
|||
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 |
|||
vals['ref_ir_act_window'] = action_obj.create(cr, uid, { |
|||
config.ref_ir_act_window = action_obj.create({ |
|||
'name': button_name, |
|||
'type': 'ir.actions.act_window', |
|||
'res_model': 'mass.sort.wizard', |
|||
'src_model': src_obj, |
|||
'src_model': config.model_id.model, |
|||
'view_type': 'form', |
|||
'context': "{'mass_sort_config_id' : %d}" % (config.id), |
|||
'view_mode': 'form,tree', |
|||
'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, |
|||
'model': src_obj, |
|||
'model': config.model_id.model, |
|||
'key2': 'client_action_multi', |
|||
'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: |
|||
action_obj.unlink( |
|||
cr, uid, config.ref_ir_act_window.id, context=context) |
|||
config.ref_ir_act_window.unlink() |
|||
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 |