Skip to content

Commit 0cccdc3

Browse files
dalonsodkittiu
authored andcommitted
[MIG] base_custom_info: Migration to 13.0 - added OCA#1844 improvements from v12
1 parent 3558237 commit 0cccdc3

22 files changed

+501
-35
lines changed

base_custom_info/__manifest__.py

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"version": "13.0.1.0.0",
1111
"depends": ["base_setup"],
1212
"data": [
13+
"views/webclient_templates.xml",
1314
"security/ir.model.access.csv",
1415
"security/res_groups_security.xml",
1516
"views/custom_info_category_view.xml",
@@ -21,6 +22,7 @@
2122
"views/res_partner_view.xml",
2223
"wizard/res_config_settings_view.xml",
2324
],
25+
"qweb": ["static/src/xml/custom_info_item.xml"],
2426
"demo": [
2527
"demo/custom.info.category.csv",
2628
"demo/custom.info.template.csv",
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
id,name,template_id:id,field_type,required,minimum,maximum,category_id:id,sequence
2-
prop_teacher,Name of his/her teacher,tpl_smart,str,,1,30,,100
3-
prop_haters,Amount of people that hates him/her for being so smart,tpl_smart,int,,0,99999,cat_statics,200
1+
id,name,template_id:id,widget,required,minimum,maximum,category_id:id,sequence
2+
prop_teacher,Name of his/her teacher,tpl_smart,char,,1,30,,100
3+
prop_haters,Amount of people that hates him/her for being so smart,tpl_smart,integer,,0,99999,cat_statics,200
44
prop_avg_note,Average note on all subjects,tpl_smart,float,True,0,10,cat_statics,300
5-
prop_smartypants,Does he/she believe he/she is the smartest person on earth?,tpl_smart,bool,,0,-1,,400
6-
prop_weaknesses,What weaknesses does he/she have?,tpl_smart,id,,0,-1,,500
7-
prop_fav_genre,Favourite videogames genre,tpl_gamer,id,,0,-1,cat_gaming,600
8-
prop_fav_game,Favourite videogame,tpl_gamer,str,,0,-1,cat_gaming,700
5+
prop_smartypants,Does he/she believe he/she is the smartest person on earth?,tpl_smart,boolean,,0,-1,,400
6+
prop_weaknesses,What weaknesses does he/she have?,tpl_smart,many2one,,0,-1,,500
7+
prop_fav_genre,Favourite videogames genre,tpl_gamer,many2one,,0,-1,cat_gaming,600
8+
prop_fav_game,Favourite videogame,tpl_gamer,char,,0,-1,cat_gaming,700
9+
prop_buy_fav_game,When Favourite videogame was bought?,tpl_gamer,date,,0,-1,cat_gaming,700

base_custom_info/models/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@
1111
custom_info_value,
1212
custom_info,
1313
res_partner,
14+
ir_actions_act_window_view,
15+
ir_ui_view,
1416
)

base_custom_info/models/custom_info.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,27 @@ def _onchange_custom_info_template_id(self):
5454
values = self.custom_info_ids
5555
values = values.filtered(lambda r: r.property_id not in to_remove)
5656
for prop in to_add.sorted():
57-
newvalue = self.custom_info_ids.new(
58-
{
59-
"property_id": prop.id,
60-
"res_id": self.id,
61-
"value": prop.default_value,
62-
}
63-
)
57+
vals = {
58+
"property_id": prop.id,
59+
"res_id": self.id,
60+
}
61+
if prop.default_value:
62+
if prop.field_type != "id":
63+
vals["value_%s" % prop.field_type] = prop.default_value
64+
else:
65+
vals["value_id"] = (
66+
self.env["custom.info.option"]
67+
.search(
68+
[
69+
("property_ids", "=", prop.id),
70+
("name", "=", prop.default_value),
71+
],
72+
limit=1,
73+
)
74+
.id
75+
)
76+
newvalue = self.custom_info_ids.new(vals)
6477
newvalue._onchange_property_set_default_value()
65-
newvalue._inverse_value()
6678
newvalue._compute_value()
6779
values += newvalue
6880
self.custom_info_ids = values

base_custom_info/models/custom_info_property.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,24 @@ class CustomInfoProperty(models.Model):
6767
("int", "Whole number"),
6868
("float", "Decimal number"),
6969
("bool", "Yes/No"),
70+
("date", "Date"),
7071
("id", "Selection"),
7172
],
72-
default="str",
73+
compute="_compute_field_type",
74+
store=True,
75+
)
76+
widget = fields.Selection(
77+
selection=[
78+
("boolean", "Boolean"),
79+
("float", "Decimal"),
80+
("integer", "Integer"),
81+
("date", "Date"),
82+
("char", "Single line text"),
83+
("text", "Multi line Text"),
84+
("html", "Complex text"),
85+
("many2one", "Choice"),
86+
],
87+
default="char",
7388
required=True,
7489
help="Type of information that can be stored in the property.",
7590
)
@@ -80,6 +95,25 @@ class CustomInfoProperty(models.Model):
8095
"options here.",
8196
)
8297

98+
@api.model
99+
def _get_field_type_map(self):
100+
return {
101+
"boolean": "bool",
102+
"float": "float",
103+
"integer": "int",
104+
"date": "date",
105+
"char": "str",
106+
"text": "str",
107+
"html": "str",
108+
"many2one": "id",
109+
}
110+
111+
@api.depends("widget")
112+
def _compute_field_type(self):
113+
field_type_map = self._get_field_type_map()
114+
for record in self:
115+
record.field_type = field_type_map.get(record.widget, "str")
116+
83117
def check_access_rule(self, operation):
84118
"""You access a property if you access its template."""
85119
self.mapped("template_id").check_access_rule(operation)

base_custom_info/models/custom_info_value.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,7 @@ class CustomInfoValue(models.Model):
4040
string="Resource ID", required=True, index=True, ondelete="cascade",
4141
)
4242
property_id = fields.Many2one(
43-
comodel_name="custom.info.property",
44-
required=True,
45-
string="Property",
46-
readonly=True,
43+
comodel_name="custom.info.property", required=True, string="Property",
4744
)
4845
property_sequence = fields.Integer(
4946
related="property_id.sequence", store=True, index=True,
@@ -56,21 +53,22 @@ class CustomInfoValue(models.Model):
5653
category_id = fields.Many2one(related="property_id.category_id", store=True)
5754
name = fields.Char(related="property_id.name")
5855
field_type = fields.Selection(related="property_id.field_type")
56+
widget = fields.Selection(related="property_id.widget", readonly=True,)
5957
field_name = fields.Char(
6058
compute="_compute_field_name",
6159
help="Technical name of the field where the value is stored.",
6260
)
6361
required = fields.Boolean(related="property_id.required")
6462
value = fields.Char(
6563
compute="_compute_value",
66-
inverse="_inverse_value",
6764
search="_search_value",
6865
help="Value, always converted to/from the typed field.",
6966
)
7067
value_str = fields.Char(string="Text value", translate=True, index=True)
7168
value_int = fields.Integer(string="Whole number value", index=True)
7269
value_float = fields.Float(string="Decimal number value", index=True)
7370
value_bool = fields.Boolean(string="Yes/No value", index=True)
71+
value_date = fields.Date(string="Date value", index=True)
7472
value_id = fields.Many2one(
7573
comodel_name="custom.info.option",
7674
string="Selection value",
@@ -170,7 +168,7 @@ def _check_min_max_limits(self):
170168
# This is a job for :meth:`.~_check_required`
171169
continue
172170
if record.field_type == "str":
173-
number = len(self.value_str)
171+
number = len(record.value_str)
174172
message = _(
175173
"Length for %(prop)s is %(val)s, but it should be "
176174
"between %(min)d and %(max)d."
@@ -200,6 +198,24 @@ def _check_min_max_limits(self):
200198
}
201199
)
202200

201+
@api.constrains(
202+
"value_str",
203+
"value_int",
204+
"value_float",
205+
"value_bool",
206+
"value_date",
207+
"value_id",
208+
"property_id",
209+
)
210+
def _check_required(self):
211+
for record in self:
212+
if not record.required:
213+
continue
214+
if not record.value:
215+
raise ValidationError(
216+
_("Some required elements have not been fulfilled")
217+
)
218+
203219
@api.onchange("property_id")
204220
def _onchange_property_set_default_value(self):
205221
"""Load default value for this property."""
@@ -251,6 +267,10 @@ def _transform_value(self, value, format_, properties=None):
251267
"off",
252268
_("No").lower(),
253269
}
270+
elif format_ in {"date"}:
271+
value = fields.Date.from_string(value)
272+
elif format_ in {"datetime"}:
273+
value = fields.Datetime.from_string(value)
254274
elif format_ not in {"str", "id"}:
255275
value = safe_eval("{!s}({!r})".format(format_, value))
256276
return value
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright 2019 Creu Blanca
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo import fields, models
5+
6+
7+
class IrActionsActWindowView(models.Model):
8+
_inherit = "ir.actions.act_window.view"
9+
10+
view_mode = fields.Selection(selection_add=[("custom_info", "Custom Info")])

base_custom_info/models/ir_ui_view.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright 2019 Creu Blanca
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo import fields, models
5+
6+
7+
class IrUiView(models.Model):
8+
_inherit = "ir.ui.view"
9+
10+
type = fields.Selection(selection_add=[("custom_info", "Custom Info")])

base_custom_info/readme/CONTRIBUTORS.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
* Jairo Llopis <jairo.llopis@tecnativa.com>
77
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
88
* Alexandre Díaz <alexandre.diaz@tecnativa.com>
9+
* Creu Blanca:
910

10-
* David Alonso <david.alonso@solvos.es>
11+
* Enric Tobella <etobella@creublanca.es>
12+
* Solvos:
13+
14+
* David Alonso <david.alonso@solvos.es>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
odoo.define("base_custom_info.CustomInfoRenderer", function(require) {
2+
"use strict";
3+
4+
var BasicRenderer = require("web.BasicRenderer");
5+
var field_registry = require("web.field_registry");
6+
var core = require("web.core");
7+
var qweb = core.qweb;
8+
9+
var CustomInfoRenderer = BasicRenderer.extend({
10+
init: function(parent, state, params) {
11+
params = _.defaults({}, params, {
12+
viewType: "custom_info",
13+
});
14+
this._super(parent, state, params);
15+
if (
16+
parent !== undefined &&
17+
parent.mode === "edit" &&
18+
params.mode === undefined
19+
) {
20+
this.mode = "edit";
21+
}
22+
this.recordWidgets = [];
23+
},
24+
_getWidgetOptions: function(data) {
25+
var mode = this.mode;
26+
if (data.data.readonly) {
27+
mode = "readonly";
28+
}
29+
var options = {
30+
attrs: {
31+
options: {},
32+
modifiers: {},
33+
},
34+
mode: mode,
35+
viewType: this.viewType,
36+
};
37+
if (data.data.required) {
38+
options.attrs.modifiers.required = true;
39+
}
40+
if (data.data.widget === "many2one") {
41+
options.attrs.options.no_create_edit = true;
42+
options.attrs.options.no_open = true;
43+
}
44+
return options;
45+
},
46+
_renderView: function() {
47+
var self = this;
48+
var $table = $(qweb.render("base_custom_info.table"));
49+
var $body = $table.find("tbody");
50+
this.$el.empty();
51+
this.recordWidgets = [];
52+
$table.appendTo(this.$el);
53+
_.each(this.state.data, function(data) {
54+
var element = $(
55+
qweb.render("base_custom_info.item", {
56+
widget: self,
57+
data: data,
58+
})
59+
);
60+
var Widget = field_registry.get(data.data.widget);
61+
if (Widget !== undefined) {
62+
self._renderCustomInfoWidget(Widget, element, data);
63+
}
64+
element.appendTo($body);
65+
});
66+
return this._super();
67+
},
68+
_renderCustomInfoWidget: function(Widget, element, data) {
69+
var options = this._getWidgetOptions(data);
70+
var widget = new Widget(
71+
this,
72+
"value_" + data.data.field_type,
73+
data,
74+
options
75+
);
76+
this.recordWidgets.push(widget);
77+
this._registerModifiers(widget, data, element, _.pick(options, "mode"));
78+
var node = element.find(".result_data");
79+
widget.appendTo(node);
80+
},
81+
_onNavigationMove: function(ev) {
82+
var currentIndex = -1;
83+
if (ev.data.direction === "next") {
84+
currentIndex = this.recordWidgets.indexOf(ev.data.target || ev.target);
85+
if (currentIndex + 1 >= (this.recordWidgets || []).length) {
86+
return;
87+
}
88+
ev.stopPropagation();
89+
this._activateNextCustomInfoWidget(currentIndex);
90+
} else if (ev.data.direction === "previous") {
91+
currentIndex = this.recordWidgets.indexOf(ev.data.target);
92+
if (currentIndex <= 0) {
93+
return;
94+
}
95+
ev.stopPropagation();
96+
this._activatePreviousCustomInfoWidget(currentIndex);
97+
}
98+
},
99+
_activateNextCustomInfoWidget: function(currentIndex) {
100+
currentIndex = (currentIndex + 1) % (this.recordWidgets || []).length;
101+
var activatedIndex = this._activateCustomInfoWidget(currentIndex, {inc: 1});
102+
if (activatedIndex === -1) {
103+
// No widget have been activated, we should go to the edit/save buttons
104+
this.trigger_up("focus_control_button");
105+
this.lastActivatedFieldIndex = -1;
106+
} else {
107+
this.lastActivatedFieldIndex = activatedIndex;
108+
}
109+
return this.lastActivatedFieldIndex;
110+
},
111+
_activatePreviousCustomInfoWidget: function(currentIndex) {
112+
currentIndex = currentIndex
113+
? currentIndex - 1
114+
: (this.recordWidgets || []).length - 1;
115+
return this._activateCustomInfoWidget(currentIndex, {inc: -1});
116+
},
117+
_activateCustomInfoWidget: function(currentIndex, options) {
118+
options = options || {};
119+
_.defaults(options, {inc: 1, wrap: false});
120+
currentIndex = Math.max(0, currentIndex); // Do not allow negative currentIndex
121+
122+
for (var i = 0; i < this.recordWidgets.length; i++) {
123+
var activated = this.recordWidgets[currentIndex].activate({
124+
event: options.event,
125+
noAutomaticCreate: options.noAutomaticCreate || false,
126+
});
127+
if (activated) {
128+
return currentIndex;
129+
}
130+
131+
currentIndex += options.inc;
132+
if (currentIndex >= this.recordWidgets.length) {
133+
if (options.wrap) {
134+
currentIndex -= this.recordWidgets.length;
135+
} else {
136+
return -1;
137+
}
138+
} else if (currentIndex < 0) {
139+
if (options.wrap) {
140+
currentIndex += this.recordWidgets.length;
141+
} else {
142+
return -1;
143+
}
144+
}
145+
}
146+
return -1;
147+
},
148+
});
149+
150+
return CustomInfoRenderer;
151+
});

0 commit comments

Comments
 (0)