OCA-git-bot
5 years ago
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_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> |
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
# 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