Browse Source

Merge PR #1844 into 12.0

Signed-off-by LoisRForgeFlow
12.0-mig-module_prototyper_last
OCA-git-bot 4 years ago
parent
commit
1d75400278
  1. 3
      base_custom_info/README.rst
  2. 4
      base_custom_info/__manifest__.py
  3. 15
      base_custom_info/demo/custom.info.property.csv
  4. 32
      base_custom_info/migrations/12.0.2.0.0/pre-migration.py
  5. 2
      base_custom_info/models/__init__.py
  6. 15
      base_custom_info/models/custom_info.py
  7. 36
      base_custom_info/models/custom_info_property.py
  8. 37
      base_custom_info/models/custom_info_value.py
  9. 12
      base_custom_info/models/ir_actions_act_window_view.py
  10. 10
      base_custom_info/models/ir_ui_view.py
  11. 3
      base_custom_info/readme/CONTRIBUTORS.rst
  12. 4
      base_custom_info/static/description/index.html
  13. 148
      base_custom_info/static/src/js/custom_info_renderer.js
  14. 24
      base_custom_info/static/src/js/custom_info_view.js
  15. 58
      base_custom_info/static/src/js/relational_fields.js
  16. 9
      base_custom_info/static/src/scss/custom_info.scss
  17. 21
      base_custom_info/static/src/xml/custom_info_item.xml
  18. 3
      base_custom_info/tests/__init__.py
  19. 12
      base_custom_info/tests/test_partner.py
  20. 48
      base_custom_info/tests/test_required.py
  21. 16
      base_custom_info/tests/test_value_conversion.py
  22. 2
      base_custom_info/views/custom_info_property_view.xml
  23. 25
      base_custom_info/views/custom_info_value_view.xml
  24. 2
      base_custom_info/views/res_partner_view.xml
  25. 19
      base_custom_info/views/webclient_templates.xml

3
base_custom_info/README.rst

@ -260,6 +260,9 @@ Contributors
* Jairo Llopis <jairo.llopis@tecnativa.com>
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
* Alexandre Díaz <alexandre.diaz@tecnativa.com>
* Creu Blanca:
* Enric Tobella <etobella@creublanca.es>
Maintainers
~~~~~~~~~~~

4
base_custom_info/__manifest__.py

@ -7,11 +7,12 @@
'name': "Base Custom Info",
'summary': "Add custom field in models",
'category': 'Tools',
'version': '12.0.1.0.2',
'version': '12.0.2.0.0',
'depends': [
'base_setup',
],
'data': [
'views/webclient_templates.xml',
'security/ir.model.access.csv',
'security/res_groups_security.xml',
'views/custom_info_category_view.xml',
@ -23,6 +24,7 @@
'views/res_partner_view.xml',
'wizard/res_config_settings_view.xml',
],
'qweb': ['static/src/xml/custom_info_item.xml'],
'demo': [
'demo/custom.info.category.csv',
'demo/custom.info.template.csv',

15
base_custom_info/demo/custom.info.property.csv

@ -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

32
base_custom_info/migrations/12.0.2.0.0/pre-migration.py

@ -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)
)

2
base_custom_info/models/__init__.py

@ -11,4 +11,6 @@ from . import (
custom_info_value,
custom_info,
res_partner,
ir_actions_act_window_view,
ir_ui_view
)

15
base_custom_info/models/custom_info.py

@ -54,13 +54,20 @@ class CustomInfo(models.AbstractModel):
values = self.custom_info_ids
values = values.filtered(lambda r: r.property_id not in to_remove)
for prop in to_add.sorted():
newvalue = self.custom_info_ids.new({
vals = {
"property_id": prop.id,
"res_id": self.id,
"value": prop.default_value,
})
}
if prop.default_value:
if prop.field_type != 'id':
vals["value_%s" % prop.field_type] = prop.default_value
else:
vals["value_id"] = self.env['custom.info.option'].search([
('property_ids', '=', prop.id),
('name', '=', prop.default_value)
], limit=1).id
newvalue = self.custom_info_ids.new(vals)
newvalue._onchange_property_set_default_value()
newvalue._inverse_value()
newvalue._compute_value()
values += newvalue
self.custom_info_ids = values

36
base_custom_info/models/custom_info_property.py

@ -64,9 +64,24 @@ class CustomInfoProperty(models.Model):
("int", "Whole number"),
("float", "Decimal number"),
("bool", "Yes/No"),
("date", "Date"),
("id", "Selection"),
],
default="str",
compute="_compute_field_type",
store=True,
)
widget = fields.Selection(
selection=[
("boolean", "Boolean"),
("float", "Decimal"),
("integer", "Integer"),
("date", "Date"),
("char", "Single line text"),
("text", "Multi line Text"),
("html", "Complex text"),
("many2one", "Choice"),
],
default="char",
required=True,
help="Type of information that can be stored in the property.",
)
@ -77,6 +92,25 @@ class CustomInfoProperty(models.Model):
"options here.",
)
@api.model
def _get_field_type_map(self):
return {
"boolean": "bool",
"float": "float",
"integer": "int",
"date": "date",
"char": "str",
"text": "str",
"html": "str",
"many2one": "id",
}
@api.depends('widget')
def _compute_field_type(self):
field_type_map = self._get_field_type_map()
for record in self:
record.field_type = field_type_map.get(record.widget, 'str')
@api.multi
def check_access_rule(self, operation):
"""You access a property if you access its template."""

37
base_custom_info/models/custom_info_value.py

@ -34,7 +34,6 @@ class CustomInfoValue(models.Model):
)
property_id = fields.Many2one(
comodel_name='custom.info.property', required=True, string='Property',
readonly=True,
)
property_sequence = fields.Integer(
related="property_id.sequence", store=True, index=True, readonly=True,
@ -50,13 +49,16 @@ class CustomInfoValue(models.Model):
field_type = fields.Selection(
related="property_id.field_type", readonly=True,
)
widget = fields.Selection(
related="property_id.widget", readonly=True,
)
field_name = fields.Char(
compute="_compute_field_name",
help="Technical name of the field where the value is stored.",
)
required = fields.Boolean(related="property_id.required", readonly=True)
value = fields.Char(
compute="_compute_value", inverse="_inverse_value",
compute="_compute_value",
search="_search_value",
help="Value, always converted to/from the typed field.",
)
@ -64,6 +66,7 @@ class CustomInfoValue(models.Model):
value_int = fields.Integer(string="Whole number value", index=True)
value_float = fields.Float(string="Decimal number value", index=True)
value_bool = fields.Boolean(string="Yes/No value", index=True)
value_date = fields.Date(string="Date value", index=True)
value_id = fields.Many2one(
comodel_name="custom.info.option", string="Selection value",
ondelete="cascade", domain="[('property_ids', '=', property_id)]",
@ -125,18 +128,6 @@ class CustomInfoValue(models.Model):
else:
s.value = getattr(s, s.field_name, False)
@api.multi
def _inverse_value(self):
"""Write the value correctly converted in the typed field."""
for record in self:
if (record.field_type == "id"
and record.value == record.value_id.display_name):
# Avoid another search that can return a different value
continue
record[record.field_name] = self._transform_value(
record.value, record.field_type, record.property_id,
)
@api.one
@api.constrains("property_id", "value_str", "value_int", "value_float")
def _check_min_max_limits(self):
@ -172,7 +163,17 @@ class CustomInfoValue(models.Model):
"max": maximum,
})
@api.multi
@api.constrains('value_str', 'value_int', 'value_float', 'value_bool',
'value_date', 'value_id', 'property_id')
def _check_required(self):
for record in self:
if not record.required:
continue
if not record.value:
raise ValidationError(_(
'Some required elements have not been fulfilled'
))
@api.onchange("property_id")
def _onchange_property_set_default_value(self):
"""Load default value for this property."""
@ -182,12 +183,6 @@ class CustomInfoValue(models.Model):
if not record.field_type and record.property_id.field_type:
record.field_type = record.property_id.field_type
@api.onchange('value')
def _onchange_value(self):
"""Inverse function is not launched after writing, so we need to
trigger it right now."""
self._inverse_value()
@api.model
def _transform_value(self, value, format_, properties=None):
"""Transforms a text value to the expected format.

12
base_custom_info/models/ir_actions_act_window_view.py

@ -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")]
)

10
base_custom_info/models/ir_ui_view.py

@ -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")])

3
base_custom_info/readme/CONTRIBUTORS.rst

@ -6,3 +6,6 @@
* Jairo Llopis <jairo.llopis@tecnativa.com>
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
* Alexandre Díaz <alexandre.diaz@tecnativa.com>
* Creu Blanca:
* Enric Tobella <etobella@creublanca.es>

4
base_custom_info/static/description/index.html

@ -581,6 +581,10 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<li>Alexandre Díaz &lt;<a class="reference external" href="mailto:alexandre.diaz&#64;tecnativa.com">alexandre.diaz&#64;tecnativa.com</a>&gt;</li>
</ul>
</li>
<li>Creu Blanca:<ul>
<li>Enric Tobella &lt;<a class="reference external" href="mailto:etobella&#64;creublanca.es">etobella&#64;creublanca.es</a>&gt;</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">

148
base_custom_info/static/src/js/custom_info_renderer.js

@ -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;
});

24
base_custom_info/static/src/js/custom_info_view.js

@ -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;
});

58
base_custom_info/static/src/js/relational_fields.js

@ -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;
},
});
});

9
base_custom_info/static/src/scss/custom_info.scss

@ -0,0 +1,9 @@
.o_form_view {
.o_field_widget, .btn {
.custom_info_value {
.o_field_widget {
margin-bottom: $o-form-spacing-unit;
}
}
}
}

21
base_custom_info/static/src/xml/custom_info_item.xml

@ -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>

3
base_custom_info/tests/__init__.py

@ -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

12
base_custom_info/tests/test_partner.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
@ -135,7 +134,8 @@ class PartnerCase(TransactionCase):
self.set_custom_info_for_agrolait()
prop_weaknesses = self.env.ref("base_custom_info.prop_weaknesses")
val_weaknesses = self.agrolait.get_custom_info_value(prop_weaknesses)
val_weaknesses.value = "Needs videogames"
val_weaknesses.value_id = self.env.ref(
"base_custom_info.opt_videogames")
tpl_gamer = self.env.ref("base_custom_info.tpl_gamer")
self.agrolait.invalidate_cache()
self.assertIn(tpl_gamer, self.agrolait.all_custom_info_templates())
@ -153,8 +153,8 @@ class PartnerCase(TransactionCase):
val = self.agrolait.get_custom_info_value(
self.env.ref("base_custom_info.prop_teacher"))
with self.assertRaises(ValidationError):
val.value = (u"Don Walter Antonio José de la Cruz Hëisenberg de "
u"Borbón Westley Jordy López Manuélez")
val.value_str = (u"Don Walter Antonio José de la Cruz Hëisenberg "
u"de Borbón Westley Jordy López Manuélez")
def test_low_average_note(self):
"""Come on, you are supposed to be smart!"""
@ -162,7 +162,7 @@ class PartnerCase(TransactionCase):
val = self.agrolait.get_custom_info_value(
self.env.ref("base_custom_info.prop_avg_note"))
with self.assertRaises(ValidationError):
val.value = "-1"
val.value_float = -1
def test_high_average_note(self):
"""Too smart!"""
@ -170,4 +170,4 @@ class PartnerCase(TransactionCase):
val = self.agrolait.get_custom_info_value(
self.env.ref("base_custom_info.prop_avg_note"))
with self.assertRaises(ValidationError):
val.value = "11"
val.value_float = 11

48
base_custom_info/tests/test_required.py

@ -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)

16
base_custom_info/tests/test_value_conversion.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
@ -26,7 +25,13 @@ class ValueConversionCase(TransactionCase):
self.agrolait.custom_info_template_id = self.tpl
self.agrolait._onchange_custom_info_template_id()
if field == "value":
value = str(value)
field = "value_%s" % prop.field_type
if prop.field_type == "id" and isinstance(value, str):
value = self.env["custom.info.option"].search([
('property_ids', '=', prop.id),
('name', '=', value)
], limit=1).id
self.assertTrue(value)
self.value = self.agrolait.get_custom_info_value(prop)
self.value[field] = value
@ -35,6 +40,7 @@ class ValueConversionCase(TransactionCase):
prop = self.value.property_id
_logger.info(
"Searching. prop: %s; value: %s", prop, value)
self.assertEqual(
self.value.search([
("property_id", "=", prop.id),
@ -89,21 +95,21 @@ class ValueConversionCase(TransactionCase):
def test_to_bool_true(self):
"""Conversion to yes."""
self.fill_value(self.prop_bool, "True")
self.fill_value(self.prop_bool, True)
self.creation_found("True")
self.assertEqual(self.value.with_context(lang="en_US").value, "Yes")
self.assertIs(self.value.value_bool, True)
def test_from_bool_true(self):
"""Conversion from yes."""
self.fill_value(self.prop_bool, "True", "value_bool")
self.fill_value(self.prop_bool, True, "value_bool")
self.creation_found("True")
self.assertEqual(self.value.with_context(lang="en_US").value, "Yes")
self.assertIs(self.value.value_bool, True)
def test_to_bool_false(self):
"""Conversion to no."""
self.fill_value(self.prop_bool, "False")
self.fill_value(self.prop_bool, False)
self.assertEqual(self.value.with_context(lang="en_US").value, "No")
self.assertIs(self.value.value_bool, False)

2
base_custom_info/views/custom_info_property_view.xml

@ -38,6 +38,7 @@
<sheet>
<group>
<field name="name"/>
<field name="widget"/>
<field name="field_type"/>
<field name="category_id"/>
<field name="required"/>
@ -78,6 +79,7 @@
<search>
<field name="name"/>
<field name="template_id"/>
<field name="widget"/>
<field name="field_type"/>
<field name="category_id"/>
<field name="required"/>

25
base_custom_info/views/custom_info_value_view.xml

@ -25,6 +25,28 @@
</field>
</record>
<record id="custom_info_value_editable" model="ir.ui.view">
<field name="model">custom.info.value</field>
<field name="arch" type="xml">
<custom_info string="Custom Property Values">
<field name="name"/>
<field name="owner_id"/>
<field name="property_id" force_save="1" readonly="0"/>
<field name="category_id"/>
<field name="required"/>
<field name="widget"/>
<field name="field_type"/>
<field name="value_str"/>
<field name="value_bool"/>
<field name="value_int"/>
<field name="value_float"/>
<field name="value_date"/>
<field name="value"/>
<field name="value_id"/>
</custom_info>
</field>
</record>
<record id="custom_info_value_tree_editable" model="ir.ui.view">
<field name="model">custom.info.value</field>
<field name="priority" eval="999"/>
@ -63,6 +85,9 @@
<field name="value_bool"
attrs="{'invisible': [('field_type', '!=', 'bool')], 'required': [('required', '=', True), ('field_type', '=', 'bool')]}"
/>
<field name="value_date"
attrs="{'invisible': [('field_type', '!=', 'date')], 'required': [('required', '=', True), ('field_type', '=', 'date')]}"
/>
<field name="value_id"
widget="selection"
attrs="{'invisible': [('field_type', '!=', 'id')], 'required': [('required', '=', True), ('field_type', '=', 'id')]}"

2
base_custom_info/views/res_partner_view.xml

@ -21,7 +21,7 @@
<field name="custom_info_ids"
colspan="4"
nolabel="1"
context="{'embed': True, 'tree_view_ref': 'base_custom_info.custom_info_value_tree_editable'}"
mode="custom_info"
/>
</group>
</page>

19
base_custom_info/views/webclient_templates.xml

@ -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>
Loading…
Cancel
Save