Enric Tobella
5 years ago
No known key found for this signature in database
GPG Key ID: D76663C0B4023597
25 changed files with 512 additions and 48 deletions
-
3base_custom_info/README.rst
-
4base_custom_info/__manifest__.py
-
15base_custom_info/demo/custom.info.property.csv
-
32base_custom_info/migrations/12.0.2.0.0/pre-migration.py
-
2base_custom_info/models/__init__.py
-
15base_custom_info/models/custom_info.py
-
36base_custom_info/models/custom_info_property.py
-
37base_custom_info/models/custom_info_value.py
-
12base_custom_info/models/ir_actions_act_window_view.py
-
10base_custom_info/models/ir_ui_view.py
-
3base_custom_info/readme/CONTRIBUTORS.rst
-
4base_custom_info/static/description/index.html
-
148base_custom_info/static/src/js/custom_info_renderer.js
-
24base_custom_info/static/src/js/custom_info_view.js
-
58base_custom_info/static/src/js/relational_fields.js
-
9base_custom_info/static/src/scss/custom_info.scss
-
21base_custom_info/static/src/xml/custom_info_item.xml
-
3base_custom_info/tests/__init__.py
-
12base_custom_info/tests/test_partner.py
-
48base_custom_info/tests/test_required.py
-
16base_custom_info/tests/test_value_conversion.py
-
2base_custom_info/views/custom_info_property_view.xml
-
25base_custom_info/views/custom_info_value_view.xml
-
2base_custom_info/views/res_partner_view.xml
-
19base_custom_info/views/webclient_templates.xml
@ -1,8 +1,9 @@ |
|||
id,name,template_id:id,field_type,required,minimum,maximum,category_id:id,sequence |
|||
prop_teacher,Name of his/her teacher,tpl_smart,str,,1,30,,100 |
|||
prop_haters,Amount of people that hates him/her for being so smart,tpl_smart,int,,0,99999,cat_statics,200 |
|||
id,name,template_id:id,widget,required,minimum,maximum,category_id:id,sequence |
|||
prop_teacher,Name of his/her teacher,tpl_smart,char,,1,30,,100 |
|||
prop_haters,Amount of people that hates him/her for being so smart,tpl_smart,integer,,0,99999,cat_statics,200 |
|||
prop_avg_note,Average note on all subjects,tpl_smart,float,True,0,10,cat_statics,300 |
|||
prop_smartypants,Does he/she believe he/she is the smartest person on earth?,tpl_smart,bool,,0,-1,,400 |
|||
prop_weaknesses,What weaknesses does he/she have?,tpl_smart,id,,0,-1,,500 |
|||
prop_fav_genre,Favourite videogames genre,tpl_gamer,id,,0,-1,cat_gaming,600 |
|||
prop_fav_game,Favourite videogame,tpl_gamer,str,,0,-1,cat_gaming,700 |
|||
prop_smartypants,Does he/she believe he/she is the smartest person on earth?,tpl_smart,boolean,,0,-1,,400 |
|||
prop_weaknesses,What weaknesses does he/she have?,tpl_smart,many2one,,0,-1,,500 |
|||
prop_fav_genre,Favourite videogames genre,tpl_gamer,many2one,,0,-1,cat_gaming,600 |
|||
prop_fav_game,Favourite videogame,tpl_gamer,char,,0,-1,cat_gaming,700 |
|||
prop_buy_fav_game,When Favourite videogame was bought?,tpl_gamer,date,,0,-1,cat_gaming,700 |
@ -0,0 +1,32 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from openupgradelib import openupgrade |
|||
|
|||
|
|||
@openupgrade.migrate() |
|||
def migrate(env, version): |
|||
cr = env.cr |
|||
if not openupgrade.column_exists( |
|||
cr, |
|||
'custom_info_property', |
|||
'widget' |
|||
): |
|||
openupgrade.add_fields( |
|||
env, |
|||
[('widget', 'custom.info.property', 'custom_info_property', 'char', |
|||
False, 'base_custom_info')] |
|||
) |
|||
transform_values = [ |
|||
('str', 'char'), |
|||
('int', 'integer'), |
|||
('bool', 'boolean'), |
|||
('float', 'float'), |
|||
('id', 'many2one') |
|||
] |
|||
for field_type, widget in transform_values: |
|||
cr.execute( |
|||
"UPDATE custom_info_property SET widget = %s WHERE " |
|||
"field_type = %s", |
|||
(widget, field_type) |
|||
) |
@ -0,0 +1,12 @@ |
|||
# Copyright 2019 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import fields, models |
|||
|
|||
|
|||
class IrActionsActWindowView(models.Model): |
|||
_inherit = "ir.actions.act_window.view" |
|||
|
|||
view_mode = fields.Selection( |
|||
selection_add=[("custom_info", "Custom Info")] |
|||
) |
@ -0,0 +1,10 @@ |
|||
# Copyright 2019 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import fields, models |
|||
|
|||
|
|||
class IrUiView(models.Model): |
|||
_inherit = "ir.ui.view" |
|||
|
|||
type = fields.Selection(selection_add=[("custom_info", "Custom Info")]) |
@ -0,0 +1,148 @@ |
|||
odoo.define('base_custom_info.CustomInfoRenderer', function (require) { |
|||
"use strict"; |
|||
|
|||
var BasicRenderer = require('web.BasicRenderer'); |
|||
var field_registry = require('web.field_registry'); |
|||
var core = require('web.core'); |
|||
var qweb = core.qweb; |
|||
|
|||
var CustomInfoRenderer = BasicRenderer.extend({ |
|||
init: function (parent, state, params) { |
|||
params = _.defaults({}, params, { |
|||
viewType: "custom_info", |
|||
}); |
|||
this._super(parent, state, params); |
|||
if ( |
|||
parent !== undefined && |
|||
parent.mode === 'edit' && |
|||
params.mode === undefined |
|||
) { |
|||
this.mode = 'edit'; |
|||
} |
|||
this.recordWidgets = []; |
|||
}, |
|||
_getWidgetOptions: function (data) { |
|||
var mode = this.mode; |
|||
if (data.data.readonly) { |
|||
mode = "readonly"; |
|||
} |
|||
var options = { |
|||
attrs: { |
|||
options: {}, |
|||
modifiers: {}, |
|||
}, |
|||
mode: mode, |
|||
viewType: this.viewType, |
|||
}; |
|||
if (data.data.required) { |
|||
options.attrs.modifiers.required = true; |
|||
} |
|||
if (data.data.widget === 'many2one') { |
|||
options.attrs.options.no_create_edit = true |
|||
options.attrs.options.no_open = true |
|||
} |
|||
return options; |
|||
}, |
|||
_renderView: function () { |
|||
var self = this; |
|||
var $table = $(qweb.render('base_custom_info.table')); |
|||
var $body = $table.find('tbody'); |
|||
this.$el.empty(); |
|||
this.recordWidgets = []; |
|||
$table.appendTo(this.$el); |
|||
var fieldInfo = {}; |
|||
_.each(this.state.data, function (data) { |
|||
var element = $(qweb.render( |
|||
'base_custom_info.item', |
|||
{ |
|||
widget: self, |
|||
data: data, |
|||
}, |
|||
)); |
|||
var Widget = field_registry.get(data.data.widget); |
|||
if (Widget !== undefined) { |
|||
self._renderCustomInfoWidget(Widget, element, data) |
|||
} |
|||
element.appendTo($body); |
|||
}); |
|||
return this._super(); |
|||
}, |
|||
_renderCustomInfoWidget: function(Widget, element, data) { |
|||
var options = this._getWidgetOptions(data); |
|||
var widget = new Widget( |
|||
this, "value_" + data.data.field_type, data, options); |
|||
this.recordWidgets.push(widget); |
|||
this._registerModifiers(widget, data, element, _.pick(options, 'mode')); |
|||
var node = element.find(".result_data"); |
|||
widget.appendTo(node); |
|||
}, |
|||
_onNavigationMove: function (ev) { |
|||
var currentIndex; |
|||
if (ev.data.direction === "next") { |
|||
currentIndex = this.recordWidgets.indexOf(ev.data.target || ev.target); |
|||
if ( (currentIndex + 1) >= (this.recordWidgets || []).length) { |
|||
return; |
|||
} |
|||
ev.stopPropagation(); |
|||
this._activateNextCustomInfoWidget(currentIndex); |
|||
} else if (ev.data.direction === "previous") { |
|||
currentIndex = this.recordWidgets.indexOf(ev.data.target); |
|||
if ( currentIndex <= 0) { |
|||
return; |
|||
} |
|||
ev.stopPropagation(); |
|||
this._activatePreviousCustomInfoWidget(currentIndex); |
|||
} |
|||
}, |
|||
_activateNextCustomInfoWidget: function (currentIndex) { |
|||
currentIndex = (currentIndex + 1) % (this.recordWidgets || []).length; |
|||
var activatedIndex = this._activateCustomInfoWidget(currentIndex, {inc: 1}); |
|||
if (activatedIndex === -1 ) { // no widget have been activated, we should go to the edit/save buttons
|
|||
this.trigger_up('focus_control_button'); |
|||
this.lastActivatedFieldIndex = -1; |
|||
} |
|||
else { |
|||
this.lastActivatedFieldIndex = activatedIndex; |
|||
} |
|||
return this.lastActivatedFieldIndex; |
|||
}, |
|||
_activatePreviousCustomInfoWidget: function (currentIndex) { |
|||
currentIndex = currentIndex ? (currentIndex - 1) : ((this.recordWidgets || []).length - 1); |
|||
return this._activateCustomInfoWidget(currentIndex, {inc:-1}); |
|||
}, |
|||
_activateCustomInfoWidget: function (currentIndex, options) { |
|||
options = options || {}; |
|||
_.defaults(options, {inc: 1, wrap: false}); |
|||
currentIndex = Math.max(0,currentIndex); // do not allow negative currentIndex
|
|||
|
|||
for (var i = 0 ; i < this.recordWidgets.length ; i++) { |
|||
var activated = this.recordWidgets[currentIndex].activate( |
|||
{ |
|||
event: options.event, |
|||
noAutomaticCreate: options.noAutomaticCreate || false |
|||
}); |
|||
if (activated) { |
|||
return currentIndex; |
|||
} |
|||
|
|||
currentIndex += options.inc; |
|||
if (currentIndex >= this.recordWidgets.length) { |
|||
if (options.wrap) { |
|||
currentIndex -= this.recordWidgets.length; |
|||
} else { |
|||
return -1; |
|||
} |
|||
} else if (currentIndex < 0) { |
|||
if (options.wrap) { |
|||
currentIndex += this.recordWidgets.length; |
|||
} else { |
|||
return -1; |
|||
} |
|||
} |
|||
} |
|||
return -1; |
|||
}, |
|||
}); |
|||
|
|||
return CustomInfoRenderer; |
|||
}); |
@ -0,0 +1,24 @@ |
|||
odoo.define('base_custom_info.CustomInfoView', function (require) { |
|||
"use strict"; |
|||
|
|||
var BasicView = require('web.BasicView'); |
|||
var CustomInfoRenderer = require('base_custom_info.CustomInfoRenderer'); |
|||
var view_registry = require('web.view_registry'); |
|||
var core = require('web.core'); |
|||
|
|||
var _lt = core._lt; |
|||
|
|||
var CustomInfoView = BasicView.extend({ |
|||
display_name: _lt("Custom Info"), |
|||
viewType: 'custom_info', |
|||
config: _.extend({}, BasicView.prototype.config, { |
|||
Renderer: CustomInfoRenderer, |
|||
}), |
|||
multi_record: true, |
|||
searchable: false, |
|||
}); |
|||
|
|||
view_registry.add('custom_info', CustomInfoView); |
|||
|
|||
return CustomInfoView; |
|||
}); |
@ -0,0 +1,58 @@ |
|||
odoo.define('base_custom_info.relational_fields', function (require) { |
|||
"use strict"; |
|||
|
|||
var CustomInfoRenderer = require('base_custom_info.CustomInfoRenderer'); |
|||
var relational_fields = require('web.relational_fields'); |
|||
var fieldUtils = require('web.field_utils'); |
|||
|
|||
relational_fields.FieldOne2Many.include({ |
|||
_getRenderer: function () { |
|||
if (this.view.arch.tag === 'custom_info') { |
|||
return CustomInfoRenderer; |
|||
} |
|||
return this._super.apply(this, arguments); |
|||
}, |
|||
_updateCustomInfoItem : function (data) { |
|||
var result = { |
|||
value_float: data.value_float, |
|||
value_str: data.value_str, |
|||
value_int: data.value_int, |
|||
value_bool: data.value_bool, |
|||
value_date: data.value_date, |
|||
}; |
|||
if (data.value_id.res_id !== undefined) |
|||
result['value_id'] = {id: data.value_id.res_id}; |
|||
return result; |
|||
}, |
|||
_saveCustomInfo: function () { |
|||
var self = this; |
|||
_.each(this.renderer.recordWidgets, function (widget) { |
|||
self._setValue({ |
|||
operation: 'UPDATE', |
|||
id: widget.dataPointID, |
|||
data: self._updateCustomInfoItem(widget.recordData), |
|||
}); |
|||
}); |
|||
}, |
|||
commitChanges: function () { |
|||
if (this.renderer && |
|||
this.renderer.viewType === "custom_info" |
|||
) { |
|||
var self = this; |
|||
this.renderer.commitChanges().then(function () { |
|||
return self._saveCustomInfo(); |
|||
}); |
|||
} |
|||
return this._super.apply(this, arguments); |
|||
}, |
|||
activate: function (options) { |
|||
var result = this._super.apply(this, arguments); |
|||
if (result && this.renderer.viewType === 'custom_info') { |
|||
if (this.renderer.recordWidgets.length > 0) { |
|||
this.renderer.recordWidgets[0].$input.focus(); |
|||
} |
|||
} |
|||
return result; |
|||
}, |
|||
}); |
|||
}); |
@ -0,0 +1,9 @@ |
|||
.o_form_view { |
|||
.o_field_widget, .btn { |
|||
.custom_info_value { |
|||
.o_field_widget { |
|||
margin-bottom: $o-form-spacing-unit; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<template> |
|||
<t t-name="base_custom_info.table"> |
|||
<div class="o_group"> |
|||
<table class="o_group o_inner_group"> |
|||
<tbody> |
|||
|
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</t> |
|||
|
|||
<t t-name="base_custom_info.item"> |
|||
<tr> |
|||
<td class="o_td_label"> |
|||
<label class="o_form_label" t-esc="data.data['name']"/> |
|||
</td> |
|||
<td style="width: 100%" class="result_data custom_info_value"/> |
|||
</tr> |
|||
</t> |
|||
</template> |
@ -1,5 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
|||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
|||
|
|||
from . import test_partner, test_value_conversion |
|||
from . import test_partner, test_required, test_value_conversion |
@ -0,0 +1,48 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
|||
|
|||
from odoo.tests.common import TransactionCase, Form |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
|
|||
class PartnerCase(TransactionCase): |
|||
def setUp(self, *args, **kwargs): |
|||
super(PartnerCase, self).setUp(*args, **kwargs) |
|||
self.agrolait = self.env.ref("base.res_partner_2") |
|||
self.template = self.env['custom.info.template'].create({ |
|||
'name': 'TEST Template', |
|||
'model_id': self.env.ref('base.model_res_partner').id, |
|||
'property_ids': [(0, 0, { |
|||
'name': 'Property', |
|||
'widget': 'char', |
|||
'required': True, |
|||
})] |
|||
}) |
|||
|
|||
def test_required_form_failure(self): |
|||
with Form(self.agrolait) as f: |
|||
self.assertFalse(f.custom_info_template_id) |
|||
self.assertFalse(f.custom_info_ids) |
|||
f.custom_info_template_id = self.template |
|||
self.assertTrue(f.custom_info_ids) |
|||
with self.assertRaises(AssertionError): |
|||
f.save() |
|||
f.custom_info_template_id = self.env['custom.info.template'] |
|||
self.assertFalse(f.custom_info_ids) |
|||
|
|||
def test_required_failure(self): |
|||
self.assertFalse(self.agrolait.custom_info_template_id) |
|||
self.assertFalse(self.agrolait.custom_info_ids) |
|||
self.agrolait.custom_info_template_id = self.template |
|||
with self.assertRaises(ValidationError): |
|||
self.agrolait._onchange_custom_info_template_id() |
|||
|
|||
def test_required(self): |
|||
with Form(self.agrolait) as f: |
|||
self.assertFalse(f.custom_info_template_id) |
|||
self.assertFalse(f.custom_info_ids) |
|||
f.custom_info_template_id = self.template |
|||
self.assertEqual(1, len(f.custom_info_ids)) |
|||
with f.custom_info_ids.edit(0) as info: |
|||
info.value_str = 'HELLO' |
|||
self.assertTrue(self.agrolait.custom_info_ids.value) |
@ -0,0 +1,19 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
|
|||
<template id="assets_backend" |
|||
name="Backend Assets (used in backend interface)" |
|||
inherit_id="web.assets_backend"> |
|||
<xpath expr="." position="inside"> |
|||
<script type="text/javascript" |
|||
src="/base_custom_info/static/src/js/custom_info_renderer.js"/> |
|||
<script type="text/javascript" |
|||
src="/base_custom_info/static/src/js/custom_info_view.js"/> |
|||
<script type="text/javascript" |
|||
src="/base_custom_info/static/src/js/relational_fields.js"/> |
|||
<link rel="stylesheet" type="text/scss" |
|||
href="/base_custom_info/static/src/scss/custom_info.scss"/> |
|||
</xpath> |
|||
</template> |
|||
|
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue