Florent de Labarre
5 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 575 additions and 0 deletions
-
1setup/web_product_variante_selector/odoo/__init__.py
-
1setup/web_product_variante_selector/odoo/addons/__init__.py
-
1setup/web_product_variante_selector/odoo/addons/web_product_variante_selector
-
6setup/web_product_variante_selector/setup.py
-
81web_product_variante_selector/README.rst
-
3web_product_variante_selector/__init__.py
-
17web_product_variante_selector/__manifest__.py
-
BINweb_product_variante_selector/static/description/description/img_1.png
-
BINweb_product_variante_selector/static/description/description/img_2.png
-
BINweb_product_variante_selector/static/description/icon.png
-
207web_product_variante_selector/static/src/js/form_common.js
-
10web_product_variante_selector/views/web_product_variante_selector.xml
-
3web_product_variante_selector/wizard/__init__.py
-
145web_product_variante_selector/wizard/wizard_product_variante_selector.py
-
100web_product_variante_selector/wizard/wizard_product_variante_selector.xml
@ -0,0 +1 @@ |
|||
__import__('pkg_resources').declare_namespace(__name__) |
@ -0,0 +1 @@ |
|||
__import__('pkg_resources').declare_namespace(__name__) |
@ -0,0 +1 @@ |
|||
../../../../web_product_variante_selector |
@ -0,0 +1,6 @@ |
|||
import setuptools |
|||
|
|||
setuptools.setup( |
|||
setup_requires=['setuptools-odoo'], |
|||
odoo_addon=True, |
|||
) |
@ -0,0 +1,81 @@ |
|||
.. 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 |
|||
|
|||
============================= |
|||
Web product Variante Selector |
|||
============================= |
|||
|
|||
This module extends the functionality of search widget to support product with lot of variantes. |
|||
|
|||
.. image:: /web_product_variante_selector/static/description/img_1.png |
|||
:alt: Screenshot |
|||
|
|||
.. image:: /web_product_variante_selector/static/description/img_2.png |
|||
:alt: Screenshot |
|||
|
|||
In each field Many2one with 'product.product' as comodel, under "Search More" and "Create and Edit", you can see "Product Variante Selector". |
|||
You can add 'no_select_variante': True in options attribute in xml view to desactivate this function localy. |
|||
Limitation at 10 attributes per product. |
|||
|
|||
Installation |
|||
============ |
|||
|
|||
To install this module, you need to: |
|||
|
|||
#. Just install, no configuration needed |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
To configure this module, you need to: |
|||
|
|||
#. No configuration needed |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
|||
:alt: Try me on Runbot |
|||
:target: https://runbot.odoo-community.org/runbot/162/10.0 |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
* Inscrease the limitation. |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues |
|||
<https://github.com/OCA/web/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 |
|||
------------ |
|||
|
|||
* Florent de Labarre |
|||
|
|||
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,3 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
from . import wizard |
@ -0,0 +1,17 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
{ |
|||
'name': 'Web product variante selector', |
|||
'version': '10.0.1.0.0', |
|||
'license': 'AGPL-3', |
|||
'author': "Florent de Labarre, Odoo Community Association (OCA)", |
|||
'website': 'https://github.com/fmdl', |
|||
'summary': 'Web product variante selector', |
|||
'category': 'Product', |
|||
'depends': ['product', 'web', ], |
|||
'data': [ |
|||
'views/web_product_variante_selector.xml', |
|||
'wizard/wizard_product_variante_selector.xml', |
|||
], |
|||
'installable': True, |
|||
} |
After Width: 1065 | Height: 510 | Size: 37 KiB |
After Width: 325 | Height: 341 | Size: 18 KiB |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,207 @@ |
|||
odoo.define('web_product_variante_selector.form_common_extended', function(require) { |
|||
"use strict"; |
|||
|
|||
var FormCommon = require('web.form_common'); |
|||
var core = require('web.core'); |
|||
var data = require('web.data'); |
|||
var data_manager = require('web.data_manager'); |
|||
var _t = core._t; |
|||
var form_relational = require('web.form_relational') |
|||
var ViewDialog = FormCommon.ViewDialog; |
|||
|
|||
form_relational.FieldMany2One.include({ |
|||
_search_variante: function(view, ids, context) { |
|||
var self = this; |
|||
var current_product_id = 0; |
|||
if (!(self.current_display === '')) { |
|||
var product_ids = self.display_value; |
|||
for (var product_id in product_ids) { |
|||
current_product_id = parseInt(product_id); |
|||
break; |
|||
} |
|||
} |
|||
new SelectProductVariante(this, _.extend({}, (this.options || {}), { |
|||
res_model: "web.product.variante.selector", |
|||
domain: self.build_domain(), |
|||
target: "new", |
|||
context: { |
|||
'select_variante_domain': self.build_domain().eval(), |
|||
'current_product_id': current_product_id |
|||
}, |
|||
title: _t("Product Variante Selector"), |
|||
initial_ids: ids ? _.map(ids, function(x) { |
|||
return x[0]; |
|||
}) : undefined, |
|||
initial_view: "form", |
|||
on_selected: function(element_ids) { |
|||
self.add_id(element_ids[0]); |
|||
self.focus(); |
|||
} |
|||
})).open(); |
|||
}, |
|||
|
|||
get_search_result: function(search_val) { |
|||
var self = this; |
|||
|
|||
var dataset = new data.DataSet(this, this.field.relation, self.build_context()); |
|||
this.last_query = search_val; |
|||
var exclusion_domain = [], |
|||
ids_blacklist = this.get_search_blacklist(); |
|||
if (!_(ids_blacklist).isEmpty()) { |
|||
exclusion_domain.push(['id', 'not in', ids_blacklist]); |
|||
} |
|||
|
|||
return this.orderer.add(dataset.name_search( |
|||
search_val, new data.CompoundDomain(self.build_domain(), exclusion_domain), |
|||
'ilike', this.limit + 1, self.build_context())).then(function(_data) { |
|||
self.last_search = _data; |
|||
// possible selections for the m2o
|
|||
var values = _.map(_data, function(x) { |
|||
x[1] = x[1].split("\n")[0]; |
|||
return { |
|||
label: _.str.escapeHTML(x[1].trim()) || data.noDisplayContent, |
|||
value: x[1], |
|||
name: x[1], |
|||
id: x[0], |
|||
}; |
|||
}); |
|||
|
|||
// search more... if more results that max
|
|||
if (values.length > self.limit) { |
|||
values = values.slice(0, self.limit); |
|||
values.push({ |
|||
label: _t("Search More..."), |
|||
action: function() { |
|||
dataset.name_search(search_val, self.build_domain(), 'ilike', 160).done(function(_data) { |
|||
self._search_create_popup("search", _data); |
|||
}); |
|||
}, |
|||
classname: 'o_m2o_dropdown_option' |
|||
}); |
|||
} |
|||
// quick create
|
|||
var raw_result = _(_data.result).map(function(x) { |
|||
return x[1]; |
|||
}); |
|||
if (search_val.length > 0 && !_.include(raw_result, search_val) && |
|||
!(self.options && (self.options.no_create || self.options.no_quick_create))) { |
|||
self.can_create && values.push({ |
|||
label: _.str.sprintf(_t('Create "<strong>%s</strong>"'), |
|||
$('<span />').text(search_val).html()), |
|||
action: function() { |
|||
self._quick_create(search_val); |
|||
}, |
|||
classname: 'o_m2o_dropdown_option' |
|||
}); |
|||
} |
|||
// create...
|
|||
if (!(self.options && (self.options.no_create || self.options.no_create_edit)) && self.can_create) { |
|||
values.push({ |
|||
label: _t("Create and Edit..."), |
|||
action: function() { |
|||
self._search_create_popup("form", undefined, self._create_context(search_val)); |
|||
}, |
|||
classname: 'o_m2o_dropdown_option' |
|||
}); |
|||
} else if (values.length === 0) { |
|||
values.push({ |
|||
label: _t("No results to show..."), |
|||
action: function() {}, |
|||
classname: 'o_m2o_dropdown_option' |
|||
}); |
|||
} |
|||
|
|||
// select variante
|
|||
if (self.field.relation === "product.product" && !((self.options && self.options.no_select_variante))) { |
|||
values.push({ |
|||
label: _t("Variante Selector..."), |
|||
action: function() { |
|||
self._search_variante("form", _data); |
|||
}, |
|||
classname: 'o_m2o_dropdown_option' |
|||
}); |
|||
} |
|||
return values; |
|||
}); |
|||
}, |
|||
}); |
|||
/** |
|||
* Variante Selector (displays a form view record and leave once saved) |
|||
*/ |
|||
var SelectProductVariante = ViewDialog.extend({ |
|||
init: function(parent, options) { |
|||
var self = this; |
|||
|
|||
if (!options || !options.buttons) { |
|||
options = options || {}; |
|||
options.buttons = [{ |
|||
text: _t("Discard"), |
|||
classes: "btn-default o_form_button_cancel", |
|||
close: true, |
|||
click: function() { |
|||
self.view_form.trigger('on_button_cancel'); |
|||
} |
|||
}]; |
|||
|
|||
options.buttons.splice(0, 0, { |
|||
text: _t("Select"), |
|||
classes: "btn-primary", |
|||
click: function() { |
|||
var product_ids = self.view_form.fields.product_id.display_value |
|||
for (var product_id in product_ids) { |
|||
self.on_selected([parseInt(product_id)]); |
|||
self.close(); |
|||
break; |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
this._super(parent, options); |
|||
}, |
|||
|
|||
open: function() { |
|||
var self = this; |
|||
var _super = this._super.bind(this); |
|||
this.init_dataset(); |
|||
|
|||
if (this.res_id) { |
|||
this.dataset.ids = [this.res_id]; |
|||
this.dataset.index = 0; |
|||
} else { |
|||
this.dataset.index = null; |
|||
} |
|||
var options = _.clone(this.options.form_view_options) || {}; |
|||
if (this.res_id !== null) { |
|||
options.initial_mode = this.options.readonly ? "view" : "edit"; |
|||
} |
|||
_.extend(options, { |
|||
$buttons: this.$buttons, |
|||
}); |
|||
var FormView = core.view_registry.get('form'); |
|||
var fields_view_def; |
|||
if (this.options.alternative_form_view) { |
|||
fields_view_def = $.when(this.options.alternative_form_view); |
|||
} else { |
|||
fields_view_def = data_manager.load_fields_view(this.dataset, this.options.view_id, 'form', false); |
|||
} |
|||
fields_view_def.then(function(fields_view) { |
|||
self.view_form = new FormView(self, self.dataset, fields_view, options); |
|||
var fragment = document.createDocumentFragment(); |
|||
self.view_form.appendTo(fragment).then(function() { |
|||
self.view_form.do_show().then(function() { |
|||
_super().$el.append(fragment); |
|||
self.view_form.autofocus(); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
return this; |
|||
}, |
|||
}); |
|||
|
|||
return { |
|||
SelectProductVariante: SelectProductVariante, |
|||
}; |
|||
|
|||
}); |
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<data> |
|||
<template id="assets_backend" name="web_product_variante_selector" inherit_id="web.assets_backend"> |
|||
<xpath expr="." position="inside"> |
|||
<script type="text/javascript" src="/web_product_variante_selector/static/src/js/form_common.js"></script> |
|||
</xpath> |
|||
</template> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,3 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
from . import wizard_product_variante_selector |
@ -0,0 +1,145 @@ |
|||
# -*- coding: utf-8 -*- |
|||
from odoo import models, api, fields |
|||
|
|||
|
|||
class WizardSelectVariante(models.TransientModel): |
|||
_name = 'web.product.variante.selector' |
|||
_description = 'Select Product Variante' |
|||
|
|||
product_tmpl_id = fields.Many2one('product.template', 'Product', ) |
|||
product_id = fields.Many2one('product.product', 'Product Variante', |
|||
default=lambda self: |
|||
self.env['product.product'].browse( |
|||
self.env.context.get( |
|||
'current_product_id', False)).id) |
|||
stop_update = fields.Boolean('Stop_update', |
|||
default=lambda self: |
|||
False if self.env.context.get( |
|||
'current_product_id', False) |
|||
else True, readonly=True) |
|||
|
|||
attr_0 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_0 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_1 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_1 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_2 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_2 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_3 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_3 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_4 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_4 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_5 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_5 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_6 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_6 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_7 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_7 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_8 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_8 = fields.Many2one('product.attribute.value', string='Value') |
|||
attr_9 = fields.Many2one( |
|||
'product.attribute', string='Attribute', readonly=1) |
|||
value_9 = fields.Many2one('product.attribute.value', string='Value') |
|||
|
|||
@api.onchange('product_id') |
|||
def on_change_product_id(self): |
|||
res = {} |
|||
dom = {} |
|||
vals = {} |
|||
if self.product_id and not self.stop_update: |
|||
vals.update( |
|||
{'product_tmpl_id': self.product_id.product_tmpl_id.id, |
|||
'stop_update': True}) |
|||
dom.update({'product_id': self.env.context.get( |
|||
'select_variante_domain', [])}) |
|||
attributes = self.product_id.product_tmpl_id.attribute_line_ids |
|||
for i in range(0, 10): |
|||
if i < len(attributes): |
|||
current_value = self.product_id.attribute_value_ids & \ |
|||
attributes[i].value_ids |
|||
vals.update({'attr_%s' % i: |
|||
attributes[i].attribute_id.id, |
|||
'value_%s' % i: |
|||
current_value[0].id |
|||
if current_value else False}) |
|||
dom.update( |
|||
{'value_%s' % i: |
|||
[('id', 'in', attributes[i].value_ids.ids)]}) |
|||
else: |
|||
vals.update({'attr_%s' % i: False}) |
|||
vals.update({'value_%s' % i: False}) |
|||
res.update({'value': vals, 'domain': dom, }) |
|||
return res |
|||
|
|||
@api.onchange('product_tmpl_id') |
|||
def on_change_product_tmpl_id(self): |
|||
res = {} |
|||
dom = {} |
|||
vals = {} |
|||
domain = [] |
|||
domain.extend(self.env.context.get('select_variante_domain', [])) |
|||
if self.stop_update or (not self.stop_update and not self.product_id): |
|||
if self.product_tmpl_id: |
|||
domain.append( |
|||
('product_tmpl_id', '=', self.product_tmpl_id.id)) |
|||
vals.update( |
|||
{'product_id': self.product_id.search(domain, limit=1).id}) |
|||
dom.update({'product_id': domain}) |
|||
|
|||
attributes = self.product_tmpl_id.attribute_line_ids |
|||
for i in range(0, 10): |
|||
vals.update({'value_%s' % i: False}) |
|||
if i < len(attributes): |
|||
vals.update({'attr_%s' % i: |
|||
attributes[i].attribute_id.id}) |
|||
dom.update( |
|||
{'value_%s' % i: |
|||
[('id', 'in', attributes[i].value_ids.ids)]}) |
|||
else: |
|||
vals.update({'attr_%s' % i: False}) |
|||
else: |
|||
vals.update({'product_id': False, }) |
|||
for i in range(0, 10): |
|||
vals.update({'attr_%s' % i: False, 'value_%s' % i: False}) |
|||
|
|||
res.update({'value': vals, 'domain': dom, }) |
|||
return res |
|||
|
|||
@api.onchange('value_0', |
|||
'value_1', |
|||
'value_2', |
|||
'value_3', |
|||
'value_4', |
|||
'value_5', |
|||
'value_6', |
|||
'value_7', |
|||
'value_8', |
|||
'value_9') |
|||
def onchange_value(self): |
|||
res = {} |
|||
values = [] |
|||
domain = self.env.context.get('select_variante_domain', []) |
|||
for i in range(0, 10): |
|||
value = self['value_%s' % i] |
|||
if value: |
|||
values.extend((value.attribute_id.value_ids - value).ids) |
|||
|
|||
dom = [] |
|||
dom.extend(domain) |
|||
dom.append(('product_tmpl_id', '=', self.product_tmpl_id.id)) |
|||
if values: |
|||
dom.append(('attribute_value_ids', 'not in', values)) |
|||
|
|||
res_ids = self.product_id.search(dom).ids |
|||
res.update({'value': {'product_id': res_ids[0] if res_ids else False}}) |
|||
domain.append(('id', 'in', res_ids)) |
|||
res.update({'domain': {'product_id': domain}}) |
|||
return res |
@ -0,0 +1,100 @@ |
|||
<?xml version="1.0"?> |
|||
<odoo> |
|||
<data> |
|||
<record id="wizard_select_variante_form" model="ir.ui.view"> |
|||
<field name="name">web.product.variante.selector.form</field> |
|||
<field name="model">web.product.variante.selector</field> |
|||
<field eval="20" name="priority"/> |
|||
<field name="arch" type="xml"> |
|||
<form string="Wizard"> |
|||
<group> |
|||
<field name="product_tmpl_id" options="{'no_create': True}" string="Select a product"/> |
|||
<field name="product_id" options="{'no_create': True, 'no_select_variante': True}" attrs="{'invisible':[('product_tmpl_id', '=', False), ('product_id', '=', False)]}"/> |
|||
<field name="stop_update" invisible="1"/> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_0', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_0"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_0" domain="[('attribute_id', '=', attr_0)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_1', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_1"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_1" domain="[('attribute_id', '=', attr_1)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_2', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_2"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_2" domain="[('attribute_id', '=', attr_2)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_3', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_3"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_3" domain="[('attribute_id', '=', attr_3)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_4', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_4"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_4" domain="[('attribute_id', '=', attr_4)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_5', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_5"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_5" domain="[('attribute_id', '=', attr_5)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_6', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_6"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_6" domain="[('attribute_id', '=', attr_6)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_7', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_7"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_7" domain="[('attribute_id', '=', attr_7)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_8', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_8"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_8" domain="[('attribute_id', '=', attr_8)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
<group attrs="{'invisible':[('attr_9', '=', False)]}" col="2"> |
|||
<group> |
|||
<field name="attr_9"/> |
|||
</group> |
|||
<group> |
|||
<field name="value_9" domain="[('attribute_id', '=', attr_9)]" options="{'no_create': True}"/> |
|||
</group> |
|||
</group> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
</data> |
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue