Browse Source
[MIG] [13.0] Migrate base_comment_template from account_invoic_reporting.
myc-14.0-py3o
[MIG] [13.0] Migrate base_comment_template from account_invoic_reporting.
myc-14.0-py3o
Mihai Fekete
4 years ago
committed by
Víctor Martínez
18 changed files with 607 additions and 150 deletions
-
72base_comment_template/README.rst
-
11base_comment_template/__manifest__.py
-
26base_comment_template/migrations/14.0.1.0.0/pre-migration.py
-
4base_comment_template/models/__init__.py
-
207base_comment_template/models/base_comment_template.py
-
48base_comment_template/models/comment.py
-
33base_comment_template/models/ir_model.py
-
15base_comment_template/models/res_partner.py
-
8base_comment_template/readme/CONFIGURE.rst
-
4base_comment_template/readme/CONTRIBUTORS.rst
-
18base_comment_template/readme/DESCRIPTION.rst
-
31base_comment_template/readme/USAGE.rst
-
1base_comment_template/security/ir.model.access.csv
-
5base_comment_template/security/security.xml
-
34base_comment_template/tests/fake_models.py
-
145base_comment_template/tests/test_base_comment_template.py
-
74base_comment_template/views/base_comment_template_view.xml
-
21base_comment_template/views/res_partner_view.xml
@ -0,0 +1,26 @@ |
|||
# Copyright 2020 NextERP Romania SRL |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). |
|||
from openupgradelib import openupgrade |
|||
|
|||
|
|||
@openupgrade.migrate() |
|||
def migrate(env, version): |
|||
# Not tested |
|||
properties = env["ir.property"].search( |
|||
[ |
|||
( |
|||
"fields_id", |
|||
"=", |
|||
env.ref("base.field_res_partner_property_comment_template_id").id, |
|||
) |
|||
] |
|||
) |
|||
if properties: |
|||
for template in properties.mapped("value_reference"): |
|||
template_id = template.value_reference.split(",")[-1] |
|||
if template_id: |
|||
template = env["base.comment.template"].browse(template_id) |
|||
part_prop = properties.filtered(lambda p: p.value_reference == template) |
|||
template.partner_ids = [ |
|||
(prop["res_id"] or "").split(",")[-1] for prop in part_prop |
|||
] |
@ -1,3 +1,3 @@ |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import comment |
|||
from . import base_comment_template |
|||
from . import res_partner |
|||
from . import ir_model |
@ -0,0 +1,207 @@ |
|||
# Copyright 2014 Guewen Baconnier (Camptocamp SA) |
|||
# Copyright 2013-2014 Nicolas Bessi (Camptocamp SA) |
|||
# Copyright 2020 NextERP Romania SRL |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from lxml import etree |
|||
|
|||
from odoo import _, api, fields, models |
|||
from odoo.exceptions import ValidationError |
|||
from odoo.tools.safe_eval import safe_eval |
|||
|
|||
|
|||
class CommentTemplate(models.AbstractModel): |
|||
_name = "comment.template" |
|||
_description = ( |
|||
"base.comment.template to put header and footer " |
|||
"in reports based on created comment templates" |
|||
) |
|||
|
|||
base_comment_template_ids = fields.Many2many( |
|||
comodel_name="base.comment.template", |
|||
string="Comment templates", |
|||
compute="_compute_base_comment_template_ids", |
|||
search="_search_follower_partners", |
|||
help="Templates that can be included in your reports", |
|||
) |
|||
|
|||
def _compute_base_comment_template_ids(self): |
|||
company_id = self.env.company.id |
|||
present_model_id = self.env["ir.model"].search([("name", "=", self._name)]) |
|||
templates = ( |
|||
self.env["base.comment.template"] |
|||
.sudo() |
|||
.search( |
|||
[ |
|||
("model_ids", "in", present_model_id.id), |
|||
"|", |
|||
("company_id", "=", company_id), |
|||
("company_id", "=", False), |
|||
] |
|||
) |
|||
) |
|||
# using read() below is much faster than followers.mapped('res_id') |
|||
model_mako_templates_ids = templates.mapped("id") |
|||
for record in self: |
|||
record.base_comment_template_ids = record.id in model_mako_templates_ids |
|||
|
|||
def get_comment_template( |
|||
self, position="before_lines", company_id=False, partner_id=False |
|||
): |
|||
""" Method that is called from report xml and is returning the |
|||
position template as a html if exists |
|||
""" |
|||
self.ensure_one() |
|||
if not company_id: |
|||
company_id = self.env.company.id |
|||
present_model_id = self.env["ir.model"].search([("model", "=", self._name)]) |
|||
default_dom = [ |
|||
("model_ids", "in", present_model_id.id), |
|||
("position", "=", position), |
|||
] |
|||
lang = False |
|||
if partner_id and "partner_id" in self._fields: |
|||
default_dom += [ |
|||
"|", |
|||
("partner_ids", "=", False), |
|||
("partner_ids", "in", partner_id), |
|||
] |
|||
lang = self.env["res.partner"].browse(partner_id).lang |
|||
if company_id and "company_id" in self._fields: |
|||
if partner_id and "partner_id" in self._fields: |
|||
default_dom.insert(-3, "&") |
|||
default_dom += [ |
|||
"|", |
|||
("company_id", "=", company_id), |
|||
("company_id", "=", False), |
|||
] |
|||
templates = self.env["base.comment.template"].search( |
|||
default_dom, order="priority" |
|||
) |
|||
if lang: |
|||
templates = templates.with_context({"lang": lang}) |
|||
template = False |
|||
if templates: |
|||
for templ in templates: |
|||
if self in self.search(safe_eval(templ.domain or "[]")): |
|||
template = templ |
|||
break |
|||
if not template: |
|||
return "" |
|||
return self.env["mail.template"]._render_template( |
|||
template.text, self._name, self.id, post_process=True |
|||
) |
|||
|
|||
|
|||
class BaseCommentTemplate(models.Model): |
|||
"""Comment templates printed on reports""" |
|||
|
|||
_name = "base.comment.template" |
|||
_description = "Comments Template" |
|||
|
|||
active = fields.Boolean(default=True) |
|||
position = fields.Selection( |
|||
selection=[("before_lines", "Before lines"), ("after_lines", "After lines")], |
|||
required=True, |
|||
default="before_lines", |
|||
help="Position on document", |
|||
) |
|||
name = fields.Char( |
|||
string="Name", |
|||
translate=True, |
|||
required=True, |
|||
help="Name/description of this mako comment template", |
|||
) |
|||
text = fields.Html( |
|||
string="Template", |
|||
translate=True, |
|||
required=True, |
|||
sanitize=False, |
|||
help="This is the mako template that will be inserted into reports.", |
|||
) |
|||
company_id = fields.Many2one( |
|||
"res.company", |
|||
string="Company", |
|||
ondelete="cascade", |
|||
index=True, |
|||
help="If set, it'll only be available for this company" |
|||
"(if the model_id has company_id)", |
|||
) |
|||
partner_ids = fields.Many2many( |
|||
comodel_name="res.partner", |
|||
string="Partner", |
|||
ondelete="cascade", |
|||
help="If set, the comment template will be available only for this " |
|||
"partner (if the model_id has a partner_id field).", |
|||
) |
|||
|
|||
model_ids = fields.Many2many( |
|||
comodel_name="ir.model", |
|||
string="IR Model", |
|||
ondelete="cascade", |
|||
required=True, |
|||
help="This comment template will be available on this models." |
|||
"You can see here only models that have report on them " |
|||
"and have inherited comment.template", |
|||
) |
|||
|
|||
domain = fields.Char( |
|||
"Filter Domain", |
|||
required=True, |
|||
default="[]", |
|||
help="This comment template will be available only for objects " |
|||
"that satisfy the condition", |
|||
) |
|||
|
|||
priority = fields.Integer( |
|||
default=10, copy=False, help="the highest priority = the smallest number", |
|||
) |
|||
|
|||
@api.constrains("domain", "priority", "model_ids", "position") |
|||
def _check_partners_in_company_id(self): |
|||
templates = self.search([]) |
|||
for record in self: |
|||
other_template_same_models_and_priority = templates.filtered( |
|||
lambda t: t.priority == record.priority |
|||
and set(record.model_ids).intersection(record.model_ids) |
|||
and t.domain == record.domain |
|||
and t.position == record.position |
|||
and t.id != record.id |
|||
) |
|||
if other_template_same_models_and_priority: |
|||
raise ValidationError( |
|||
_( |
|||
"The are other records with same models, priority, " |
|||
"domain and position." |
|||
) |
|||
) |
|||
|
|||
@api.model |
|||
def fields_view_get( |
|||
self, view_id=None, view_type="form", toolbar=False, submenu=False |
|||
): |
|||
# modify the form view of base_commnent_template |
|||
# Add domain on model_id to get only models that have a report set |
|||
# and those whom have inherited this model |
|||
res = super().fields_view_get( |
|||
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu |
|||
) |
|||
if view_type == "form": |
|||
doc = etree.XML(res["arch"]) |
|||
for node in doc.xpath("//field[@name='model_ids']"): |
|||
report_models = self.env["ir.actions.report"].search([]).mapped("model") |
|||
model_ids = ( |
|||
self.env["ir.model"] |
|||
.search( |
|||
[ |
|||
("model", "in", report_models), |
|||
("is_comment_template", "=", True), |
|||
"!", |
|||
("name", "=like", "ir.%"), |
|||
] |
|||
) |
|||
.ids |
|||
) |
|||
model_filter = "[('id','in'," + str(model_ids) + ")]" |
|||
node.set("domain", model_filter) |
|||
res["arch"] = etree.tostring(doc, encoding="unicode") |
|||
return res |
@ -1,48 +0,0 @@ |
|||
# Copyright 2014 Guewen Baconnier (Camptocamp SA) |
|||
# Copyright 2013-2014 Nicolas Bessi (Camptocamp SA) |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from odoo import fields, models |
|||
|
|||
|
|||
class BaseCommentTemplate(models.Model): |
|||
_name = "base.comment.template" |
|||
_description = "Base comment template" |
|||
|
|||
active = fields.Boolean(default=True) |
|||
|
|||
name = fields.Char( |
|||
string="Comment summary", |
|||
required=True, |
|||
) |
|||
|
|||
position = fields.Selection( |
|||
selection=[ |
|||
("before_lines", "Before lines"), |
|||
("after_lines", "After lines"), |
|||
], |
|||
required=True, |
|||
default="before_lines", |
|||
help="Position on document", |
|||
) |
|||
|
|||
text = fields.Html( |
|||
string="Comment", |
|||
translate=True, |
|||
required=True, |
|||
) |
|||
|
|||
company_id = fields.Many2one( |
|||
"res.company", |
|||
string="Company", |
|||
help="If set, it'll only be available for this company", |
|||
ondelete="cascade", |
|||
index=True, |
|||
) |
|||
|
|||
def get_value(self, partner_id=False): |
|||
self.ensure_one() |
|||
lang = None |
|||
if partner_id: |
|||
lang = self.env["res.partner"].browse(partner_id).lang |
|||
return self.with_context(lang=lang).text |
@ -0,0 +1,33 @@ |
|||
# Copyright 2020 NextERP Romania SRL |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class IrModel(models.Model): |
|||
_inherit = "ir.model" |
|||
|
|||
is_comment_template = fields.Boolean( |
|||
string="Comment Template", |
|||
default=False, |
|||
help="Whether this model supports in reports to add " "comment templates.", |
|||
) |
|||
|
|||
def _reflect_model_params(self, model): |
|||
vals = super(IrModel, self)._reflect_model_params(model) |
|||
vals["is_comment_template"] = issubclass( |
|||
type(model), self.pool["comment.template"] |
|||
) |
|||
return vals |
|||
|
|||
@api.model |
|||
def _instanciate(self, model_data): |
|||
model_class = super(IrModel, self)._instanciate(model_data) |
|||
if ( |
|||
model_data.get("is_comment_template") |
|||
and model_class._name != "comment.template" |
|||
): |
|||
parents = model_class._inherit or [] |
|||
parents = [parents] if isinstance(parents, str) else parents |
|||
model_class._inherit = parents + ["comment.template"] |
|||
return model_class |
@ -1,19 +1,14 @@ |
|||
# Copyright 2020 NextERP Romania SRL |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from odoo import api, fields, models |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class ResPartner(models.Model): |
|||
_inherit = "res.partner" |
|||
|
|||
property_comment_template_id = fields.Many2one( |
|||
base_comment_template_ids = fields.Many2many( |
|||
comodel_name="base.comment.template", |
|||
string="Conditions template", |
|||
company_dependent=True, |
|||
string="Comment Templates", |
|||
help="Specific partner comments that can be included in reports", |
|||
) |
|||
|
|||
@api.model |
|||
def _commercial_fields(self): |
|||
res = super(ResPartner, self)._commercial_fields() |
|||
res += ["property_comment_template_id"] |
|||
return res |
@ -0,0 +1,8 @@ |
|||
Go to *Settings > Technical > Reporting > Comment Templates* and start designing you comment templates. |
|||
|
|||
This module is the base module for following modules: |
|||
|
|||
* sale_comment_template |
|||
* purchase_comment_template |
|||
* invoice_comment_template |
|||
* stock_picking_comment_template |
@ -0,0 +1,31 @@ |
|||
#. Go to *Settings* and activate the developer mode. |
|||
#. Go to *Settings > Technical > Reporting > Comment Templates*. |
|||
#. Create a new record. |
|||
#. Define the Company the template is linked or leave default for all companies. |
|||
#. Define the Partner the template is linked or leave default for all partners. |
|||
#. Define the Model, Domain the template is linked. |
|||
#. Define the Position where the template will be printed: |
|||
|
|||
* above document lines |
|||
* below document lines |
|||
|
|||
You should have at least one template with Default field set, if you choose a Partner the template is deselected as a Default one. |
|||
If you create a new template with the same configuration (Model, Domain, Position) and set it as Default, the previous one will be deselected as a default one. |
|||
|
|||
The template is a html field which will be rendered just like a mail template, so you can use variables like ${object}, ${user}, ${ctx} to add dynamic content. |
|||
|
|||
Change the report related to the model from configuration and add a statement like: |
|||
|
|||
<p t-if="o.get_comment_template('before_lines', o.company_id.id, o.partner_id and o.partner_id.id or False)"> |
|||
|
|||
<span t-raw="o.get_comment_template('before_lines', o.company_id.id, o.partner_id and o.partner_id.id or False)"/> |
|||
|
|||
</p> |
|||
|
|||
<p t-if="o.get_comment_template('after_lines', o.company_id.id, o.partner_id and o.partner_id.id or False)"> |
|||
|
|||
<span t-raw="o.get_comment_template('after_lines', o.company_id.id, o.partner_id and o.partner_id.id or False)"/> |
|||
|
|||
</p> |
|||
|
|||
You should always use t-if since the method returns False if no template is found. |
@ -1,2 +1,3 @@ |
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
|||
access_base_comment_template_user,access_base_comment_template_user,model_base_comment_template,,1,0,0,0 |
|||
access_base_comment_template,access_base_comment_template no one,model_base_comment_template,base.group_no_one,1,1,1,1 |
@ -0,0 +1,34 @@ |
|||
# Copyright 2017 LasLabs Inc. |
|||
# Copyright 2018 ACSONE |
|||
# Copyright 2018 Camptocamp |
|||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
|||
|
|||
from odoo import models |
|||
|
|||
|
|||
def setup_test_model(env, model_cls): |
|||
"""Pass a test model class and initialize it. |
|||
|
|||
Courtesy of SBidoul from https://github.com/OCA/mis-builder :) |
|||
""" |
|||
model_cls._build_model(env.registry, env.cr) |
|||
env.registry.setup_models(env.cr) |
|||
env.registry.init_models( |
|||
env.cr, [model_cls._name], dict(env.context, update_custom_fields=True) |
|||
) |
|||
|
|||
|
|||
def teardown_test_model(env, model_cls): |
|||
"""Pass a test model class and deinitialize it. |
|||
|
|||
Courtesy of SBidoul from https://github.com/OCA/mis-builder :) |
|||
""" |
|||
if not getattr(model_cls, "_teardown_no_delete", False): |
|||
del env.registry.models[model_cls._name] |
|||
env.registry.setup_models(env.cr) |
|||
|
|||
|
|||
class ResUsers(models.Model): |
|||
_name = "res.users" |
|||
_inherit = ["res.users", "comment.template"] |
|||
_teardown_no_delete = True |
@ -1,37 +1,128 @@ |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). |
|||
from odoo.tests.common import TransactionCase |
|||
# Copyright 2020 NextERP Romania SRL |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from odoo.tests import common |
|||
|
|||
from .fake_models import ResUsers, setup_test_model, teardown_test_model |
|||
|
|||
class TestResPartner(TransactionCase): |
|||
def setUp(self): |
|||
super(TestResPartner, self).setUp() |
|||
self.template_id = self.env["base.comment.template"].create( |
|||
|
|||
class TestCommentTemplate(common.SavepointCase): |
|||
at_install = False |
|||
post_install = True |
|||
|
|||
@classmethod |
|||
def setUpClass(cls): |
|||
super().setUpClass() |
|||
setup_test_model(cls.env, ResUsers) |
|||
cls.user_obj = cls.env["ir.model"].search([("model", "=", "res.users")]) |
|||
|
|||
cls.user = cls.env.ref("base.user_demo") |
|||
cls.user2 = cls.env.ref("base.demo_user0") |
|||
cls.partner_id = cls.env.ref("base.res_partner_12") |
|||
cls.partner2_id = cls.env.ref("base.res_partner_10") |
|||
cls.company = cls.env["res.company"].create({"name": "Test Company 1"}) |
|||
cls.before_template_id = cls.env["base.comment.template"].create( |
|||
{ |
|||
"name": "before_lines", |
|||
"text": "Text before lines", |
|||
"model_ids": [(6, 0, cls.user_obj.ids)], |
|||
"priority": 5, |
|||
} |
|||
) |
|||
cls.after_template_id = cls.env["base.comment.template"].create( |
|||
{ |
|||
"name": "Comment before lines", |
|||
"position": "before_lines", |
|||
"text": "<p>Text before lines</p>", |
|||
"name": "after_lines", |
|||
"position": "after_lines", |
|||
"text": "Text after lines", |
|||
"model_ids": [(6, 0, cls.user_obj.ids)], |
|||
"priority": 6, |
|||
} |
|||
) |
|||
|
|||
def test_commercial_partner_fields(self): |
|||
# Azure Interior |
|||
partner_id = self.env.ref("base.res_partner_12") |
|||
partner_id.property_comment_template_id = self.template_id.id |
|||
# Test childs propagation of commercial partner field |
|||
for child_id in partner_id.child_ids: |
|||
self.assertEqual(child_id.property_comment_template_id, self.template_id) |
|||
@classmethod |
|||
def tearDownClass(cls): |
|||
teardown_test_model(cls.env, ResUsers) |
|||
super(TestCommentTemplate, cls).tearDownClass() |
|||
|
|||
def test_get_value_without_partner(self): |
|||
self.assertEqual(self.template_id.get_value(), "<p>Text before lines</p>") |
|||
def test_general_template(self): |
|||
# Check getting default comment template |
|||
templ = self.user.get_comment_template("before_lines") |
|||
self.assertEqual(templ, "Text before lines") |
|||
templ = self.user.get_comment_template("after_lines") |
|||
self.assertEqual(templ, "Text after lines") |
|||
|
|||
def test_company_general_template(self): |
|||
# Check getting default comment template company |
|||
self.before_template_id.company_id = self.company |
|||
templ = self.user.get_comment_template("before_lines") |
|||
self.assertEqual(templ, "") |
|||
templ = self.user.get_comment_template( |
|||
"before_lines", company_id=self.company.id |
|||
) |
|||
self.assertEqual(templ, "Text before lines") |
|||
templ = self.user.get_comment_template("after_lines") |
|||
self.assertEqual(templ, "Text after lines") |
|||
|
|||
def test_partner_template(self): |
|||
# Check getting the comment template if partner is set |
|||
self.before_template_id.partner_ids = self.partner2_id.ids |
|||
templ = self.user.get_comment_template( |
|||
"before_lines", partner_id=self.partner2_id.id |
|||
) |
|||
self.assertEqual(templ, "Text before lines") |
|||
templ = self.user.get_comment_template( |
|||
"before_lines", partner_id=self.partner_id.id |
|||
) |
|||
self.assertEqual(templ, "") |
|||
templ = self.user.get_comment_template("after_lines") |
|||
self.assertEqual(templ, "Text after lines") |
|||
|
|||
def test_get_value_with_partner(self): |
|||
self.env["res.lang"]._activate_lang("fr_BE") |
|||
partner = self.env.ref("base.res_partner_12") |
|||
partner.write({"lang": "fr_BE"}) |
|||
self.template_id.with_context(lang="fr_BE").write( |
|||
{"text": "<p>Testing translated fr_BE</p>"} |
|||
def test_partner_template_domain(self): |
|||
# Check getting the comment template if domain is set |
|||
self.before_template_id.partner_ids = self.partner2_id.ids |
|||
self.before_template_id.domain = "[('id', 'in', %s)]" % self.user.ids |
|||
templ = self.user.get_comment_template( |
|||
"before_lines", partner_id=self.partner2_id.id |
|||
) |
|||
self.assertEqual( |
|||
self.template_id.get_value(partner_id=partner.id), |
|||
"<p>Testing translated fr_BE</p>", |
|||
self.assertEqual(templ, "Text before lines") |
|||
templ = self.user2.get_comment_template( |
|||
"before_lines", partner_id=self.partner_id.id |
|||
) |
|||
self.assertEqual(templ, "") |
|||
|
|||
def test_company_partner_template_domain(self): |
|||
# Check getting the comment template with company and if domain is set |
|||
self.before_template_id.company_id = self.company |
|||
templ = self.user.get_comment_template("before_lines") |
|||
self.assertEqual(templ, "") |
|||
templ = self.user.get_comment_template( |
|||
"before_lines", company_id=self.company.id |
|||
) |
|||
self.assertEqual(templ, "Text before lines") |
|||
self.before_template_id.partner_ids = self.partner2_id.ids |
|||
self.before_template_id.domain = "[('id', 'in', %s)]" % self.user.ids |
|||
templ = self.user.get_comment_template( |
|||
"before_lines", partner_id=self.partner2_id.id |
|||
) |
|||
self.assertEqual(templ, "") |
|||
self.before_template_id.company_id = self.env.user.company_id |
|||
templ = self.user.get_comment_template( |
|||
"before_lines", partner_id=self.partner2_id.id |
|||
) |
|||
self.assertEqual(templ, "Text before lines") |
|||
templ = self.user2.get_comment_template( |
|||
"before_lines", partner_id=self.partner2_id.id |
|||
) |
|||
self.assertEqual(templ, "") |
|||
|
|||
def test_priority(self): |
|||
# Check setting default template will change previous record default |
|||
new_template = self.env["base.comment.template"].create( |
|||
{ |
|||
"name": "before_lines", |
|||
"text": "Text before lines 1", |
|||
"model_ids": [(6, 0, self.user_obj.ids)], |
|||
"priority": 2, |
|||
} |
|||
) |
|||
|
|||
self.assertEqual(new_template.text, "Text before lines 1") |
@ -0,0 +1,21 @@ |
|||
<odoo> |
|||
<record id="view_partner_form" model="ir.ui.view"> |
|||
<field name="model">res.partner</field> |
|||
<field name="inherit_id" ref="base.view_partner_form" /> |
|||
<field name="arch" type="xml"> |
|||
<page name='internal_notes' position="after"> |
|||
<page name='base_comment_template_ids' string="Comment Templates"> |
|||
<field |
|||
colspan="4" |
|||
mode="tree" |
|||
name="base_comment_template_ids" |
|||
nolabel="1" |
|||
widget="one2many" |
|||
height="300" |
|||
context="{'default_partner_id': active_id}" |
|||
/> |
|||
</page> |
|||
</page> |
|||
</field> |
|||
</record> |
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue