tracking_manager: refactor code, remove tracking_model

- remove tracking_model (less model, less code, less issue)
- add automatic rule for default config (this avoid missing tracked field
after module installation)
pull/2916/head
Sébastien BEAU 2023-01-09 09:57:21 +01:00 committed by Christopher Rogos
parent 56312632a0
commit be22968e91
10 changed files with 217 additions and 236 deletions

View File

@ -10,7 +10,7 @@
"category": "Tools", "category": "Tools",
"website": "https://github.com/OCA/server-tools", "website": "https://github.com/OCA/server-tools",
"author": "Akretion, Odoo Community Association (OCA)", "author": "Akretion, Odoo Community Association (OCA)",
"maintainers": ["Kev-Roche"], "maintainers": ["Kev-Roche", "sebastienbeau"],
"license": "AGPL-3", "license": "AGPL-3",
"application": False, "application": False,
"installable": True, "installable": True,
@ -19,9 +19,8 @@
"mail", "mail",
], ],
"data": [ "data": [
"views/ir_model.xml",
"views/ir_model_fields.xml", "views/ir_model_fields.xml",
"views/ir_model.xml",
"views/message_template.xml", "views/message_template.xml",
"security/ir.model.access.csv",
], ],
} }

View File

@ -1,3 +1,2 @@
from . import mail_thread from . import mail_thread
from . import tracking_model
from . import ir_model from . import ir_model

View File

@ -2,116 +2,129 @@
# @author Kévin Roche <kevin.roche@akretion.com> # @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from ast import literal_eval
from odoo import api, fields, models from odoo import api, fields, models
class IrModel(models.Model): class IrModel(models.Model):
_inherit = "ir.model" _inherit = "ir.model"
o2m_model_ids = fields.One2many( active_custom_tracking = fields.Boolean(default=False)
string="One2many Models", tracked_field_count = fields.Integer(compute="_compute_tracked_field_count")
comodel_name="ir.model", automatic_custom_tracking = fields.Boolean(
compute="_compute_o2m_model_ids", compute="_compute_automatic_custom_tracking",
readonly=True, readonly=False,
)
custom_tracking_field_ids = fields.One2many(
comodel_name="tracking.model.field",
inverse_name="model_id",
string="Custom Tracked Fields",
compute="_compute_custom_tracking_fields",
store=True, store=True,
)
field_count = fields.Integer(compute="_compute_tracked_field_count")
apply_custom_tracking = fields.Boolean(
"Apply custom tracking on fields",
default=False, default=False,
help="""Add tracking on all this model fields if they help=("If tick new field will be automatically tracked " "if the domain match"),
are not readonly True, neither computed.""", )
automatic_custom_tracking_domain = fields.Char(
compute="_compute_automatic_custom_tracking_domain",
store=True,
readonly=False,
) )
@api.depends("field_id", "apply_custom_tracking") @api.depends("active_custom_tracking")
def _compute_custom_tracking_fields(self): def _compute_automatic_custom_tracking(self):
for rec in self: for record in self:
fields = rec.env["ir.model.fields"].search( record.automatic_custom_tracking = False
[("model_id.model", "=", rec.model)]
def _default_automatic_custom_tracking_domain_rules(self):
return {
"product.product": [
"|",
("ttype", "!=", "one2many"),
("name", "in", ["barcode_ids"]),
],
"sale.order": [
"|",
("ttype", "!=", "one2many"),
("name", "in", ["line_ids"]),
],
"account.move": [
"|",
("ttype", "!=", "one2many"),
("name", "in", ["invoice_line_ids"]),
],
"default_automatic_rule": [("ttype", "!=", "one2many")],
}
@api.depends("automatic_custom_tracking")
def _compute_automatic_custom_tracking_domain(self):
rules = self._default_automatic_custom_tracking_domain_rules()
for record in self:
record.automatic_custom_tracking_domain = str(
rules.get(record.model) or rules.get("default_automatic_rule")
) )
fields_ids = []
for field in fields:
values = {}
values["tracking_field_id"] = field.id
values["model_id"] = rec.id
fields_ids.append((0, 0, values))
rec.custom_tracking_field_ids = [(6, 0, [])]
rec.custom_tracking_field_ids = fields_ids
@api.depends("field_id", "apply_custom_tracking") def update_custom_tracking(self):
def _compute_o2m_model_ids(self): for record in self:
for rec in self: fields = record.field_id.filtered("trackable").filtered_domain(
o2m_model_list = [] literal_eval(record.automatic_custom_tracking_domain)
if rec.apply_custom_tracking: )
for field in rec.field_id: fields.write({"custom_tracking": True})
if field.ttype == "one2many": untrack_fields = record.field_id - fields
o2m_relation_id = self.search( untrack_fields.write({"custom_tracking": False})
[("model", "=", field.relation)], limit=1
)
o2m_model_list.append(o2m_relation_id.id)
rec.o2m_model_ids = [(6, 0, o2m_model_list)]
@api.depends( @api.depends("field_id.custom_tracking")
"custom_tracking_field_ids", "custom_tracking_field_ids.custom_tracking"
)
def _compute_tracked_field_count(self): def _compute_tracked_field_count(self):
for rec in self: for rec in self:
rec.field_count = sum( rec.tracked_field_count = len(rec.field_id.filtered("custom_tracking"))
rec.custom_tracking_field_ids.mapped("custom_tracking")
)
def show_custom_tracked_field(self):
return {
"name": "Custom tracked fields",
"type": "ir.actions.act_window",
"res_id": self.id,
"view_mode": "tree",
"res_model": "tracking.model.field",
"view_id": self.env.ref(
"tracking_manager.custom_tracking_field_view_tree"
).id,
"target": "current",
"domain": [("id", "in", self.custom_tracking_field_ids.ids)],
}
def show_o2m_models(self):
return {
"name": "o2m models",
"type": "ir.actions.act_window",
"res_id": self.id,
"view_mode": "tree,form",
"res_model": "ir.model",
"views": [
(self.env.ref("base.view_model_tree").id, "tree"),
(self.env.ref("base.view_model_form").id, "form"),
],
"target": "current",
"domain": [("id", "in", self.o2m_model_ids.ids)],
}
class IrModelFields(models.Model): class IrModelFields(models.Model):
_inherit = "ir.model.fields" _inherit = "ir.model.fields"
custom_tracking_id = fields.Many2one( custom_tracking = fields.Boolean(
comodel_name="tracking.model.field", compute="_compute_custom_tracking",
string="Technical Custom Tracking Field", store=True,
compute="_compute_custom_tracking_field_id", readonly=False,
)
native_tracking = fields.Boolean(
compute="_compute_native_tracking",
store=True,
)
trackable = fields.Boolean(
compute="_compute_trackable",
store=True,
) )
@api.depends("model_id.apply_custom_tracking") @api.depends("native_tracking")
def _compute_custom_tracking_field_id(self): def _compute_custom_tracking(self):
for rec in self: for record in self:
if rec.model_id.apply_custom_tracking: if record.model_id.automatic_custom_tracking:
rec.custom_tracking_id = self.env["tracking.model.field"].search( domain = literal_eval(record.model_id.automatic_custom_tracking_domain)
[("tracking_field_id", "=", rec.id), ("model_id", "=", rec._name)], record.custom_tracking = bool(record.filtered_domain(domain))
limit=1,
)
else: else:
rec.custom_tracking_id = False record.custom_tracking = record.native_tracking
@api.depends("tracking")
def _compute_native_tracking(self):
for record in self:
record.native_tracking = bool(record.tracking)
@api.depends("readonly", "related", "store")
def _compute_trackable(self):
blacklists = [
"activity_ids",
"message_ids",
"message_last_post",
"message_main_attachment",
]
for rec in self:
rec.trackable = (
rec.name not in blacklists
and rec.store
and not rec.readonly
and not rec.related
)
def write(self, vals):
custom_tracking = None
if "custom_tracking" in vals:
self.check_access_rights("write")
custom_tracking = vals.pop("custom_tracking")
self._write({"custom_tracking": custom_tracking})
self.invalidate_cache(["custom_tracking"])
return super().write(vals)

View File

@ -8,6 +8,7 @@ from odoo import models, tools
class MailThread(models.AbstractModel): class MailThread(models.AbstractModel):
_inherit = "mail.thread" _inherit = "mail.thread"
# TODO invalidate ormcache
@tools.ormcache("self.env.uid", "self.env.su") @tools.ormcache("self.env.uid", "self.env.su")
def _get_tracked_fields(self): def _get_tracked_fields(self):
res = super()._get_tracked_fields() res = super()._get_tracked_fields()
@ -21,12 +22,12 @@ class MailThread(models.AbstractModel):
self.env["ir.model"] self.env["ir.model"]
.sudo() .sudo()
.search( .search(
[("model", "=", self._name), ("apply_custom_tracking", "=", True)], [("model", "=", self._name), ("active_custom_tracking", "=", True)],
limit=1, limit=1,
) )
) )
if tracking_model: if tracking_model:
track_fields = tracking_model.custom_tracking_field_ids.filtered( track_fields = tracking_model.field_id.filtered(
lambda f: f.custom_tracking lambda f: f.custom_tracking
).mapped("name") ).mapped("name")
return track_fields and set(self.fields_get(track_fields)) return track_fields and set(self.fields_get(track_fields))
@ -112,8 +113,8 @@ class MailThread(models.AbstractModel):
# tracking (apply_custom_tracking = True), we track # tracking (apply_custom_tracking = True), we track
# the changes only for fields with custom_tracking. # the changes only for fields with custom_tracking.
if ( if (
line_model_id.apply_custom_tracking line_model_id.active_custom_tracking
and not line_field_id.custom_tracking_id.custom_tracking and not line_field_id.custom_tracking
): ):
old = new = False old = new = False
elif line_field_id.ttype in ["one2many", "many2one", "many2many"]: elif line_field_id.ttype in ["one2many", "many2one", "many2many"]:

View File

@ -1,52 +0,0 @@
# Copyright (C) 2022 Akretion (<http://www.akretion.com>).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class TrackingModelField(models.Model):
_name = "tracking.model.field"
_description = "Tracking Model Field"
name = fields.Char(related="tracking_field_id.name")
tracking_field_id = fields.Many2one(
"ir.model.fields",
"Field",
)
model_id = fields.Many2one(
comodel_name="ir.model",
string="Tracking Model Field",
)
custom_tracking = fields.Boolean(
string="Custom Tracking",
compute="_compute_custom_tracking",
readonly=False,
store=True,
)
native_tracking_field = fields.Integer(
string="Native Tracking", related="tracking_field_id.tracking"
)
type_field = fields.Selection(
string="Kind Field", related="tracking_field_id.ttype"
)
@api.depends(
"tracking_field_id",
"tracking_field_id.readonly",
"tracking_field_id.related",
"tracking_field_id.store",
)
def _compute_custom_tracking(self):
# No tracking on compute / readonly fields
# Custom tracking include native tracking attribute.
for rec in self:
field_id = rec.tracking_field_id
if (
field_id.readonly
or field_id.related
or (field_id.compute and field_id.store and not field_id.readonly)
) and not field_id.tracking:
rec.custom_tracking = False
else:
rec.custom_tracking = True

View File

@ -1 +1,2 @@
* Kévin Roche <kevin.roche@akretion.com> * Kévin Roche <kevin.roche@akretion.com>
* Sébastien BEAU <sebastien.beau@akretion.com>

View File

@ -1,2 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_custom_tracking_model_field,tracking.model.field.user,model_tracking_model_field,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_custom_tracking_model_field tracking.model.field.user model_tracking_model_field base.group_user 1 1 1 1

View File

@ -28,35 +28,40 @@ class TestTrackingManager(SavepointCase):
} }
) )
def test_1_ir_model_config(self): def _active_tracking(self, fields_list):
self.partner_model.apply_custom_tracking = True self.partner_model.active_custom_tracking = True
# custom_tracking_field_ids for field in self._get_fields(fields_list):
self.assertEqual( field.custom_tracking = True
len(self.partner_model.field_id),
len(self.partner_model.custom_tracking_field_ids), def _get_fields(self, fields_list):
) return self.env["ir.model.fields"].search(
# o2m_model_ids [
self.assertEqual( ("model_id.model", "=", "res.partner"),
len(self.partner_model.o2m_model_ids), ("name", "in", fields_list),
len(self.partner_model.field_id.filtered(lambda x: x.ttype == "one2many")), ]
) )
def test_2_tracking_model_field_config(self): def test_not_tracked(self):
self.partner_2 = self.env.ref("base.res_partner_2") self.partner_model.active_custom_tracking = True
self.partner_1_w_parent = self.env.ref("base.res_partner_address_1") field = self._get_fields(["category_id"])[0]
self.partner_model.apply_custom_tracking = True self.assertFalse(field.native_tracking)
# Readonly, related are not tracked self.assertFalse(field.custom_tracking)
parent_name = self.partner_model.custom_tracking_field_ids.filtered(
lambda x: x.name == "parent_name"
)
self.assertFalse(parent_name.custom_tracking)
# Other fields are tracked
street = self.partner_model.custom_tracking_field_ids.filtered(
lambda x: x.name == "street"
)
self.assertTrue(street.custom_tracking)
def test_3_m2m_add_line(self): def test_native_tracked(self):
self.partner_model.active_custom_tracking = True
field = self._get_fields(["email"])[0]
self.assertTrue(field.native_tracking)
self.assertTrue(field.custom_tracking)
def test_update_tracked(self):
self.partner_model.active_custom_tracking = True
field = self._get_fields(["category_id"])[0]
self.assertFalse(field.native_tracking)
self.partner_model.automatic_custom_tracking = True
self.partner_model.update_custom_tracking()
self.assertTrue(field.custom_tracking)
def test_m2m_add_line(self):
initial_msg = self.partner_1.message_ids initial_msg = self.partner_1.message_ids
self.partner_1.category_id = [ self.partner_1.category_id = [
(4, self.env.ref("base.res_partner_category_3").id) (4, self.env.ref("base.res_partner_category_3").id)
@ -64,7 +69,7 @@ class TestTrackingManager(SavepointCase):
after_msg = self.partner_1.message_ids after_msg = self.partner_1.message_ids
self.assertEqual(len(initial_msg), len(after_msg)) self.assertEqual(len(initial_msg), len(after_msg))
self.partner_model.apply_custom_tracking = True self._active_tracking(["category_id"])
self.partner_1.category_id = [ self.partner_1.category_id = [
(4, self.env.ref("base.res_partner_category_8").id) (4, self.env.ref("base.res_partner_category_8").id)
] ]
@ -72,21 +77,21 @@ class TestTrackingManager(SavepointCase):
self.assertEqual(len(initial_msg) + 1, len(after_msg)) self.assertEqual(len(initial_msg) + 1, len(after_msg))
self.assertTrue("New" in after_msg[0].body) self.assertTrue("New" in after_msg[0].body)
def test_4_m2m_delete_line(self): def test_m2m_delete_line(self):
initial_msg = self.partner_1.message_ids initial_msg = self.partner_1.message_ids
self.partner_1.category_id = [(3, self.partner_1.category_id[0].id)] self.partner_1.category_id = [(3, self.partner_1.category_id[0].id)]
after_msg = self.partner_1.message_ids after_msg = self.partner_1.message_ids
self.assertEqual(len(initial_msg), len(after_msg)) self.assertEqual(len(initial_msg), len(after_msg))
self.partner_model.apply_custom_tracking = True self._active_tracking(["category_id"])
self.partner_1.category_id = [(3, self.partner_1.category_id[0].id)] self.partner_1.category_id = [(3, self.partner_1.category_id[0].id)]
after_msg = self.partner_1.message_ids after_msg = self.partner_1.message_ids
self.assertEqual(len(initial_msg) + 1, len(after_msg)) self.assertEqual(len(initial_msg) + 1, len(after_msg))
self.assertTrue("Delete" in after_msg[0].body) self.assertTrue("Delete" in after_msg[0].body)
def test_5_m2m_multi_line(self): def test_m2m_multi_line(self):
initial_msg = self.partner_1.message_ids initial_msg = self.partner_1.message_ids
self.partner_model.apply_custom_tracking = True self._active_tracking(["category_id"])
self.partner_1.category_id = [ self.partner_1.category_id = [
(3, self.partner_1.category_id[0].id), (3, self.partner_1.category_id[0].id),
(4, self.env.ref("base.res_partner_category_8").id), (4, self.env.ref("base.res_partner_category_8").id),
@ -97,26 +102,26 @@ class TestTrackingManager(SavepointCase):
self.assertEqual(after_msg[0].body.count("New"), 2) self.assertEqual(after_msg[0].body.count("New"), 2)
self.assertEqual(after_msg[0].body.count("Delete"), 1) self.assertEqual(after_msg[0].body.count("Delete"), 1)
def test_6_o2m_add(self): def test_o2m_add(self):
initial_msg = self.partner_1.message_ids initial_msg = self.partner_1.message_ids
self.partner_model.apply_custom_tracking = True self._active_tracking(["bank_ids"])
self.partner_1.bank_ids = [(4, self.bank_partner_2.id)] self.partner_1.bank_ids = [(4, self.bank_partner_2.id)]
after_msg = self.partner_1.message_ids after_msg = self.partner_1.message_ids
self.assertEqual(len(initial_msg) + 1, len(after_msg)) self.assertEqual(len(initial_msg) + 1, len(after_msg))
self.assertTrue("New" in after_msg[0].body) self.assertTrue("New" in after_msg[0].body)
def test_7_o2m_delete(self): def test_o2m_delete(self):
self.partner_model.apply_custom_tracking = True self._active_tracking(["bank_ids"])
initial_msg = self.partner_1.message_ids initial_msg = self.partner_1.message_ids
self.partner_1.write({"bank_ids": [(3, self.partner_1.bank_ids[0].id)]}) self.partner_1.write({"bank_ids": [(3, self.partner_1.bank_ids[0].id)]})
after_msg = self.partner_1.message_ids after_msg = self.partner_1.message_ids
self.assertEqual(len(initial_msg) + 1, len(after_msg)) self.assertEqual(len(initial_msg) + 1, len(after_msg))
self.assertTrue("Delete" in after_msg[0].body) self.assertTrue("Delete" in after_msg[0].body)
def test_8_o2m_change_in_line(self): def test_o2m_change_in_line(self):
self.partner_1.bank_ids = [(6, 0, self.bank_partner_2.id)] self.partner_1.bank_ids = [(6, 0, self.bank_partner_2.id)]
initial_msg = self.partner_1.message_ids initial_msg = self.partner_1.message_ids
self.partner_model.apply_custom_tracking = True self._active_tracking(["bank_ids"])
self.partner_1.write( self.partner_1.write(
{ {
"bank_ids": [(1, self.partner_1.bank_ids.id, {"acc_number": "123"})], "bank_ids": [(1, self.partner_1.bank_ids.id, {"acc_number": "123"})],
@ -129,10 +134,8 @@ class TestTrackingManager(SavepointCase):
bank_model = self.env["ir.model"].search( bank_model = self.env["ir.model"].search(
[("model", "=", self.bank_partner_2._name)], limit=1 [("model", "=", self.bank_partner_2._name)], limit=1
) )
bank_model.apply_custom_tracking = True bank_model.active_custom_tracking = True
acc_number = bank_model.custom_tracking_field_ids.filtered( acc_number = bank_model.field_id.filtered(lambda x: x.name == "acc_number")
lambda x: x.name == "acc_number"
)
acc_number.custom_tracking = False acc_number.custom_tracking = False
self.partner_1.write( self.partner_1.write(
{ {
@ -142,9 +145,9 @@ class TestTrackingManager(SavepointCase):
after_msg_2 = self.partner_1.message_ids after_msg_2 = self.partner_1.message_ids
self.assertEqual(len(after_msg), len(after_msg_2)) self.assertEqual(len(after_msg), len(after_msg_2))
def test_9_o2m_multi_line(self): def test_o2m_multi_line(self):
initial_msg = self.partner_1.message_ids initial_msg = self.partner_1.message_ids
self.partner_model.apply_custom_tracking = True self._active_tracking(["bank_ids"])
self.partner_1.bank_ids = [ self.partner_1.bank_ids = [
(3, self.partner_1.bank_ids[0].id), (3, self.partner_1.bank_ids[0].id),
(4, self.bank_partner_2.id), (4, self.bank_partner_2.id),

View File

@ -10,40 +10,50 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//sheet/group[1]" position="after"> <xpath expr="//sheet/group[1]" position="after">
<group string="Tracking"> <group string="Custom Tracking">
<group> <group>
<field name="apply_custom_tracking" /> <field name="active_custom_tracking" string="Active" />
<button <field
name="show_custom_tracked_field" name="automatic_custom_tracking"
string="Custom Tracked fields" string="Automatic configuration"
attrs="{'invisible': [('active_custom_tracking', '=', False)]}"
/>
<field
name="automatic_custom_tracking_domain"
string="Domain"
attrs="{
'invisible': [('automatic_custom_tracking', '=', False)],
'required': [('automatic_custom_tracking', '=', True)],
}"
/>
<label
for="update_custom_tracking"
string="Update fields configuration"
attrs="{'invisible': [('automatic_custom_tracking', '=', False)]}"
/>
<button
name="update_custom_tracking"
string="Update"
icon="fa-refresh"
type="object" type="object"
class="btn-secondary" class="btn-secondary"
attrs="{'invisible': [('apply_custom_tracking', '=', False)]}" attrs="{'invisible': [('automatic_custom_tracking', '=', False)]}"
/> />
<button
name="show_o2m_models"
string="One2many related models"
type="object"
class="btn-secondary"
attrs="{'invisible': [('apply_custom_tracking', '=', False)]}"
/>
</group>
</group> </group>
</group>
</xpath> </xpath>
<xpath expr="//sheet/group[1]" position="before"> <xpath expr="//sheet/group[1]" position="before">
<div <div class="oe_button_box" name="button_box">
class="oe_button_box"
name="button_box"
attrs="{'invisible': [('apply_custom_tracking', '=', False)]}"
>
<button <button
name="show_custom_tracked_field" name="%(ir_model_fields_action)d"
type="object" type="action"
class="oe_stat_button" class="oe_stat_button"
icon="fa-server" icon="fa-server"
attrs="{'invisible': [('active_custom_tracking', '=', False)]}"
> >
<field <field
name="field_count" name="tracked_field_count"
widget="statinfo" widget="statinfo"
string="Tracked Fields" string="Tracked Fields"
/> />
@ -53,15 +63,4 @@
</field> </field>
</record> </record>
<record id="custom_tracking_view_tree" model="ir.ui.view">
<field name="model">ir.model</field>
<field name="name">tracking.ir.model.tree</field>
<field name="inherit_id" ref="base.view_model_tree" />
<field name="arch" type="xml">
<xpath expr="//tree/field[@name='transient']" position="after">
<field name="apply_custom_tracking" readonly="True" />
</xpath>
</field>
</record>
</odoo> </odoo>

View File

@ -3,17 +3,37 @@
@author Kévin Roche <kevin.roche@akretion.com> @author Kévin Roche <kevin.roche@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record id="custom_tracking_field_view_tree" model="ir.ui.view">
<field name="model">tracking.model.field</field> <record model="ir.actions.act_window" id="ir_model_fields_action">
<field name="name">tracking.model.field.tree</field> <field name="name">Trackable Fields</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ir.model.fields</field>
<field name="view_mode">tree,form</field>
<field
name="domain"
>[("trackable", "=", True), ("model_id", "=", context['active_id'])]</field>
<field name="context">{}</field>
<field name="target">current</field>
</record>
<record id="ir_model_fields_view_tree_custom_tracking" model="ir.ui.view">
<field name="model">ir.model.fields</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree editable="bottom" create="false"> <tree editable="bottom" create="0" delete="0" duplicate="0">
<field name="name" readonly="True" /> <field name="name" readonly="True" />
<field name="field_description" readonly="True" />
<field name="ttype" readonly="True" />
<field name="native_tracking" readonly="True" />
<field name="custom_tracking" widget="boolean_toggle" /> <field name="custom_tracking" widget="boolean_toggle" />
<field name="native_tracking_field" readonly="True" />
<field name="type_field" readonly="True" />
<field name="model_id" readonly="True" />
</tree> </tree>
</field> </field>
</record> </record>
<record id="ir_model_fields_action_view" model="ir.actions.act_window.view">
<field name="sequence" eval="2" />
<field name="view_mode">tree</field>
<field name="view_id" ref="ir_model_fields_view_tree_custom_tracking" />
<field name="act_window_id" ref="ir_model_fields_action" />
</record>
</odoo> </odoo>