[IMP] tests: common.Form, can't write on invisible fields

In the web client, in a real use case, it's not possible
to write on fields which are invisible,
as it's not possible to write on fields which are readonly.

This is a first step in the goal to change the behavior
of the `groups=` attribute in the back-end views,
to remove them for the view instead of making them invisible.

This is mainly to reduce the diff of the revision that will introduce
the mentioned above behavior change.

As nodes with `groups=` will be removed from the view
when the user doesn't have the group, it's no longer possible
to set a value on a field having a `groups=` the user doesn't have
in the `Form` test class, as the field will no longer be at all in the
view.
However, these unit tests shouldn't have been able to set values
on invisible fields in the first place.
This revision therefore aims to correct the unit tests setting value
on fields which were invisible because the user executing the
test was not part of the required group(s) for these fields
to be visible in the view.

closes odoo/odoo#94337

Related: odoo/enterprise#28936
Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
This commit is contained in:
Denis Ledoux
2022-07-08 14:33:47 +02:00
parent e104937118
commit 5ccc32fcf7
71 changed files with 749 additions and 252 deletions
+8 -1
View File
@@ -385,7 +385,14 @@ class AccountTestInvoicingCommon(TransactionCase):
.with_company(company or cls.env.company) \
.with_context(default_move_type=move_type, account_predictive_bills_disable_prediction=True))
move_form.invoice_date = invoice_date or fields.Date.from_string('2019-01-01')
move_form.date = move_form.invoice_date
# According to the state or type of the invoice, the date field is sometimes visible or not
# Besides, the date field can be put multiple times in the view
# "invisible": "['|', ('state', '!=', 'draft'), ('auto_post', '!=', 'at_date')]"
# "invisible": ['|', '|', ('state', '!=', 'draft'), ('auto_post', '=', 'no'), ('auto_post', '=', 'at_date')]
# "invisible": "['&', ('move_type', 'in', ['out_invoice', 'out_refund', 'out_receipt']), ('quick_edit_mode', '=', False)]"
# :TestAccountMoveOutInvoiceOnchanges, :TestAccountMoveOutRefundOnchanges, .test_00_debit_note_out_invoice, :TestAccountEdi
if not move_form._get_modifier('date', 'invisible'):
move_form.date = move_form.invoice_date
move_form.partner_id = partner or cls.partner_a
for product in (products or []):
@@ -849,6 +849,8 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
})
def test_in_invoice_line_onchange_cash_rounding_1(self):
# Required for `invoice_cash_rounding_id` to be visible in the view
self.env.user.groups_id += self.env.ref('account.group_cash_rounding')
# Test 'add_invoice_line' rounding
move_form = Form(self.invoice)
# Add a cash rounding having 'add_invoice_line'.
@@ -1193,6 +1195,12 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
})
def test_in_invoice_onchange_past_invoice_1(self):
if self.env.ref('purchase.group_purchase_manager', raise_if_not_found=False):
# `purchase` adds a view which makes `invoice_vendor_bill_id` invisible
# for purchase users
# https://github.com/odoo/odoo/blob/385884afd31f25d61e99d139ecd4c574d99a1863/addons/purchase/views/account_move_views.xml#L26
self.env.user.groups_id -= self.env.ref('purchase.group_purchase_manager')
self.env.user.groups_id -= self.env.ref('purchase.group_purchase_user')
copy_invoice = self.invoice.copy()
move_form = Form(self.invoice)
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
# pylint: disable=bad-whitespace
from lxml import etree
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests.common import Form
from odoo.tests import tagged
@@ -533,6 +535,8 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon):
})
def test_in_refund_line_onchange_cash_rounding_1(self):
# Required for `invoice_cash_rounding_id` to be visible in the view
self.env.user.groups_id += self.env.ref('account.group_cash_rounding')
# Test 'add_invoice_line' rounding
move_form = Form(self.invoice)
# Add a cash rounding having 'add_invoice_line'.
@@ -878,6 +882,20 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon):
def test_in_refund_onchange_past_invoice_1(self):
copy_invoice = self.invoice.copy()
if self.env.ref('purchase.group_purchase_manager', raise_if_not_found=False):
# `purchase` adds a view which makes `invoice_vendor_bill_id` invisible
# for purchase users
# https://github.com/odoo/odoo/blob/385884afd31f25d61e99d139ecd4c574d99a1863/addons/purchase/views/account_move_views.xml#L26
self.env.user.groups_id -= self.env.ref('purchase.group_purchase_manager')
self.env.user.groups_id -= self.env.ref('purchase.group_purchase_user')
# 'invisible': ['|', ('state', '!=', 'draft'), ('move_type', '!=', 'in_invoice')]
# This is an in_refund invoice, `invoice_vendor_bill_id` is not supposed to be visible
# and therefore not supposed to be changed.
view = self.env.ref('account.view_move_form')
tree = etree.fromstring(view.arch)
for node in tree.xpath('//field[@name="invoice_vendor_bill_id"]'):
del node.attrib['attrs']
view.arch = etree.tostring(tree)
move_form = Form(self.invoice)
move_form.invoice_line_ids.remove(0)
@@ -1453,6 +1453,8 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
])
def test_out_invoice_line_onchange_cash_rounding_1(self):
# Required for `invoice_cash_rounding_id` to be visible in the view
self.env.user.groups_id += self.env.ref('account.group_cash_rounding')
# Test 'add_invoice_line' rounding
move_form = Form(self.invoice)
# Add a cash rounding having 'add_invoice_line'.
@@ -1977,7 +1979,8 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
a custom reversal date.
'''
move_form = Form(self.invoice)
move_form.date = '2016-01-01'
# `date` is invisible in the view, the date of the invoice should be set using `invoice_date` instead
move_form.invoice_date = '2016-01-01'
move_form.currency_id = self.currency_data['currency']
move_form.save()
@@ -3459,6 +3462,8 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
move_form.partner_id = self.partner_a
# Quick edit total amount not activated yet
# As quick edit total is not yet activated, it's invisible by default in the view
move_form._view['modifiers']['quick_edit_total_amount']['invisible'] = False
move_form.quick_edit_total_amount = 100.0
invoice = move_form.save()
self.assertEqual(invoice.amount_total, 0.0)
@@ -532,6 +532,8 @@ class TestAccountMoveOutRefundOnchanges(AccountTestInvoicingCommon):
})
def test_out_refund_line_onchange_cash_rounding_1(self):
# Required for `invoice_cash_rounding_id` to be visible in the view
self.env.user.groups_id += self.env.ref('account.group_cash_rounding')
# Test 'add_invoice_line' rounding
move_form = Form(self.invoice)
# Add a cash rounding having 'add_invoice_line'.
@@ -3536,6 +3536,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon):
""" Test the CABA entries generated from an invoice with almost
equal lines, different only on analytic accounting
"""
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
# Make the tax account reconcilable
self.tax_account_1.reconcile = True
+11 -3
View File
@@ -200,10 +200,14 @@ class TestAccountPayment(AccountTestInvoicingCommon):
def test_payment_move_sync_onchange(self):
pay_form = Form(self.env['account.payment'].with_context(default_journal_id=self.company_data['default_journal_bank'].id))
pay_form = Form(self.env['account.payment'].with_context(
default_journal_id=self.company_data['default_journal_bank'].id,
# The `partner_type` is set through the window action context in the web client
# the field is otherwise invisible in the form.
default_partner_type='customer',
))
pay_form.amount = 50.0
pay_form.payment_type = 'inbound'
pay_form.partner_type = 'customer'
pay_form.partner_id = self.partner_a
payment = pay_form.save()
@@ -246,8 +250,12 @@ class TestAccountPayment(AccountTestInvoicingCommon):
# ==== Check editing the account.payment ====
# `partner_type` on payment is always invisible. It's supposed to be set through a context `default_` key
# In this case the goal of the test is to take an existing customer payment and change it to a supplier payment,
# which is not supposed to be possible through the web interface.
# So, change the payment partner_type beforehand rather than in the form view.
payment.partner_type = 'supplier'
pay_form = Form(payment)
pay_form.partner_type = 'supplier'
pay_form.currency_id = self.currency_data['currency']
pay_form.partner_id = self.partner_a
payment = pay_form.save()
@@ -73,7 +73,6 @@ class TestAccountInvoiceRounding(AccountTestInvoicingCommon):
with Form(self.invoice) as move_form:
move_form.invoice_payment_term_id = pay_term
move_form.invoice_date = invoice_date
move_form.date = invoice_date
self.assertEqual(
self.invoice.line_ids.filtered(
lambda l: l.account_id == self.company_data['default_account_receivable']
+157 -147
View File
@@ -287,105 +287,110 @@ class TestCRMLead(TestCrmCommon):
phone and email fields. Phone especially has some corner cases due to
automatic formatting (notably with onchange in form view). """
lead, partner = self.lead_1.with_user(self.env.user), self.contact_2
lead_form = Form(lead)
# This is a type == 'lead', not a type == 'opportunity'
# {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]}
# lead.is_partner_visible = bool(lead.type == 'opportunity' or lead.partner_id or is_debug_mode)
# Hence, debug mode required for `partner_id` to be visible
with self.debug_mode():
lead_form = Form(lead)
# reset partner phone to a local number and prepare formatted / sanitized values
partner_phone, partner_mobile = self.test_phone_data[2], self.test_phone_data[1]
partner_phone_formatted = phone_format(partner_phone, 'US', '1')
partner_phone_sanitized = phone_format(partner_phone, 'US', '1', force_format='E164')
partner_mobile_formatted = phone_format(partner_mobile, 'US', '1')
partner_mobile_sanitized = phone_format(partner_mobile, 'US', '1', force_format='E164')
partner_email, partner_email_normalized = self.test_email_data[2], self.test_email_data_normalized[2]
self.assertEqual(partner_phone_formatted, '+1 202-555-0888')
self.assertEqual(partner_phone_sanitized, self.test_phone_data_sanitized[2])
self.assertEqual(partner_mobile_formatted, '+1 202-555-0999')
self.assertEqual(partner_mobile_sanitized, self.test_phone_data_sanitized[1])
# ensure initial data
self.assertEqual(partner.phone, partner_phone)
self.assertEqual(partner.mobile, partner_mobile)
self.assertEqual(partner.email, partner_email)
# reset partner phone to a local number and prepare formatted / sanitized values
partner_phone, partner_mobile = self.test_phone_data[2], self.test_phone_data[1]
partner_phone_formatted = phone_format(partner_phone, 'US', '1')
partner_phone_sanitized = phone_format(partner_phone, 'US', '1', force_format='E164')
partner_mobile_formatted = phone_format(partner_mobile, 'US', '1')
partner_mobile_sanitized = phone_format(partner_mobile, 'US', '1', force_format='E164')
partner_email, partner_email_normalized = self.test_email_data[2], self.test_email_data_normalized[2]
self.assertEqual(partner_phone_formatted, '+1 202-555-0888')
self.assertEqual(partner_phone_sanitized, self.test_phone_data_sanitized[2])
self.assertEqual(partner_mobile_formatted, '+1 202-555-0999')
self.assertEqual(partner_mobile_sanitized, self.test_phone_data_sanitized[1])
# ensure initial data
self.assertEqual(partner.phone, partner_phone)
self.assertEqual(partner.mobile, partner_mobile)
self.assertEqual(partner.email, partner_email)
# LEAD/PARTNER SYNC: email and phone are propagated to lead
# as well as mobile (who does not trigger the reverse sync)
lead_form.partner_id = partner
self.assertEqual(lead_form.email_from, partner_email)
self.assertEqual(lead_form.phone, partner_phone_formatted,
'Lead: form automatically formats numbers')
self.assertEqual(lead_form.mobile, partner_mobile_formatted,
'Lead: form automatically formats numbers')
self.assertFalse(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
# LEAD/PARTNER SYNC: email and phone are propagated to lead
# as well as mobile (who does not trigger the reverse sync)
lead_form.partner_id = partner
self.assertEqual(lead_form.email_from, partner_email)
self.assertEqual(lead_form.phone, partner_phone_formatted,
'Lead: form automatically formats numbers')
self.assertEqual(lead_form.mobile, partner_mobile_formatted,
'Lead: form automatically formats numbers')
self.assertFalse(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
lead_form.save()
self.assertEqual(partner.phone, partner_phone,
'Lead / Partner: partner values sent to lead')
self.assertEqual(lead.email_from, partner_email,
'Lead / Partner: partner values sent to lead')
self.assertEqual(lead.email_normalized, partner_email_normalized,
'Lead / Partner: equal emails should lead to equal normalized emails')
self.assertEqual(lead.phone, partner_phone_formatted,
'Lead / Partner: partner values (formatted) sent to lead')
self.assertEqual(lead.mobile, partner_mobile_formatted,
'Lead / Partner: partner values (formatted) sent to lead')
self.assertEqual(lead.phone_sanitized, partner_mobile_sanitized,
'Lead: phone_sanitized computed field on mobile')
lead_form.save()
self.assertEqual(partner.phone, partner_phone,
'Lead / Partner: partner values sent to lead')
self.assertEqual(lead.email_from, partner_email,
'Lead / Partner: partner values sent to lead')
self.assertEqual(lead.email_normalized, partner_email_normalized,
'Lead / Partner: equal emails should lead to equal normalized emails')
self.assertEqual(lead.phone, partner_phone_formatted,
'Lead / Partner: partner values (formatted) sent to lead')
self.assertEqual(lead.mobile, partner_mobile_formatted,
'Lead / Partner: partner values (formatted) sent to lead')
self.assertEqual(lead.phone_sanitized, partner_mobile_sanitized,
'Lead: phone_sanitized computed field on mobile')
# for email_from, if only formatting differs, warning should not appear and
# email on partner should not be updated
lead_form.email_from = '"Hermes Conrad" <%s>' % partner_email_normalized
self.assertFalse(lead_form.partner_email_update)
lead_form.save()
self.assertEqual(partner.email, partner_email)
# for email_from, if only formatting differs, warning should not appear and
# email on partner should not be updated
lead_form.email_from = '"Hermes Conrad" <%s>' % partner_email_normalized
self.assertFalse(lead_form.partner_email_update)
lead_form.save()
self.assertEqual(partner.email, partner_email)
# for phone, if only formatting differs, warning should not appear and
# phone on partner should not be updated
lead_form.phone = partner_phone_sanitized
self.assertFalse(lead_form.partner_phone_update)
lead_form.save()
self.assertEqual(partner.phone, partner_phone)
# for phone, if only formatting differs, warning should not appear and
# phone on partner should not be updated
lead_form.phone = partner_phone_sanitized
self.assertFalse(lead_form.partner_phone_update)
lead_form.save()
self.assertEqual(partner.phone, partner_phone)
# LEAD/PARTNER SYNC: lead updates partner
new_email = '"John Zoidberg" <john.zoidberg@test.example.com>'
new_email_normalized = 'john.zoidberg@test.example.com'
lead_form.email_from = new_email
self.assertTrue(lead_form.partner_email_update)
new_phone = '+1 202 555 7799'
new_phone_formatted = phone_format(new_phone, 'US', '1')
lead_form.phone = new_phone
self.assertEqual(lead_form.phone, new_phone_formatted)
self.assertTrue(lead_form.partner_email_update)
self.assertTrue(lead_form.partner_phone_update)
# LEAD/PARTNER SYNC: lead updates partner
new_email = '"John Zoidberg" <john.zoidberg@test.example.com>'
new_email_normalized = 'john.zoidberg@test.example.com'
lead_form.email_from = new_email
self.assertTrue(lead_form.partner_email_update)
new_phone = '+1 202 555 7799'
new_phone_formatted = phone_format(new_phone, 'US', '1')
lead_form.phone = new_phone
self.assertEqual(lead_form.phone, new_phone_formatted)
self.assertTrue(lead_form.partner_email_update)
self.assertTrue(lead_form.partner_phone_update)
lead_form.save()
self.assertEqual(partner.email, new_email)
self.assertEqual(partner.email_normalized, new_email_normalized)
self.assertEqual(partner.phone, new_phone_formatted)
lead_form.save()
self.assertEqual(partner.email, new_email)
self.assertEqual(partner.email_normalized, new_email_normalized)
self.assertEqual(partner.phone, new_phone_formatted)
# LEAD/PARTNER SYNC: mobile does not update partner
new_mobile = '+1 202 555 6543'
new_mobile_formatted = phone_format(new_mobile, 'US', '1')
lead_form.mobile = new_mobile
lead_form.save()
self.assertEqual(lead.mobile, new_mobile_formatted)
self.assertEqual(partner.mobile, partner_mobile)
# LEAD/PARTNER SYNC: mobile does not update partner
new_mobile = '+1 202 555 6543'
new_mobile_formatted = phone_format(new_mobile, 'US', '1')
lead_form.mobile = new_mobile
lead_form.save()
self.assertEqual(lead.mobile, new_mobile_formatted)
self.assertEqual(partner.mobile, partner_mobile)
# LEAD/PARTNER SYNC: reseting lead values also resets partner for email
# and phone, but not for mobile
lead_form.email_from, lead_form.phone, lead.mobile = False, False, False
self.assertTrue(lead_form.partner_email_update)
self.assertTrue(lead_form.partner_phone_update)
lead_form.save()
self.assertFalse(partner.email)
self.assertFalse(partner.email_normalized)
self.assertFalse(partner.phone)
self.assertFalse(lead.phone)
self.assertFalse(lead.mobile)
self.assertFalse(lead.phone_sanitized)
self.assertEqual(partner.mobile, partner_mobile)
# if SMS is uninstalled, phone_sanitized is not available on partner
if 'phone_sanitized' in partner:
self.assertEqual(partner.phone_sanitized, partner_mobile_sanitized,
'Partner sanitized should be computed on mobile')
# LEAD/PARTNER SYNC: reseting lead values also resets partner for email
# and phone, but not for mobile
lead_form.email_from, lead_form.phone, lead.mobile = False, False, False
self.assertTrue(lead_form.partner_email_update)
self.assertTrue(lead_form.partner_phone_update)
lead_form.save()
self.assertFalse(partner.email)
self.assertFalse(partner.email_normalized)
self.assertFalse(partner.phone)
self.assertFalse(lead.phone)
self.assertFalse(lead.mobile)
self.assertFalse(lead.phone_sanitized)
self.assertEqual(partner.mobile, partner_mobile)
# if SMS is uninstalled, phone_sanitized is not available on partner
if 'phone_sanitized' in partner:
self.assertEqual(partner.phone_sanitized, partner_mobile_sanitized,
'Partner sanitized should be computed on mobile')
@users('user_sales_manager')
def test_crm_lead_partner_sync_email_phone_corner_cases(self):
@@ -401,69 +406,74 @@ class TestCRMLead(TestCrmCommon):
'mobile': '',
})
lead_form = Form(lead)
self.assertEqual(lead_form.email_from, test_email)
self.assertFalse(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
# This is a type == 'lead', not a type == 'opportunity'
# {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]}
# lead.is_partner_visible = bool(lead.type == 'opportunity' or lead.partner_id or is_debug_mode)
# Hence, debug mode required for `partner_id` to be visible
with self.debug_mode():
lead_form = Form(lead)
self.assertEqual(lead_form.email_from, test_email)
self.assertFalse(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
# email: False versus empty string
lead_form.partner_id = contact
self.assertTrue(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
lead_form.email_from = ''
self.assertFalse(lead_form.partner_email_update)
lead_form.email_from = False
self.assertFalse(lead_form.partner_email_update)
# email: False versus empty string
lead_form.partner_id = contact
self.assertTrue(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
lead_form.email_from = ''
self.assertFalse(lead_form.partner_email_update)
lead_form.email_from = False
self.assertFalse(lead_form.partner_email_update)
# phone: False versus empty string
lead_form.phone = '+1 202-555-0888'
self.assertFalse(lead_form.partner_email_update)
self.assertTrue(lead_form.partner_phone_update)
lead_form.phone = ''
self.assertFalse(lead_form.partner_phone_update)
lead_form.phone = False
self.assertFalse(lead_form.partner_phone_update)
# phone: False versus empty string
lead_form.phone = '+1 202-555-0888'
self.assertFalse(lead_form.partner_email_update)
self.assertTrue(lead_form.partner_phone_update)
lead_form.phone = ''
self.assertFalse(lead_form.partner_phone_update)
lead_form.phone = False
self.assertFalse(lead_form.partner_phone_update)
# email/phone: formatting should not trigger ribbon
lead.write({
'email_from': '"My Name" <%s>' % test_email,
'phone': '+1 202-555-0888',
})
contact.write({
'email': '"My Name" <%s>' % test_email,
'phone': '+1 202-555-0888',
})
# email/phone: formatting should not trigger ribbon
lead.write({
'email_from': '"My Name" <%s>' % test_email,
'phone': '+1 202-555-0888',
})
contact.write({
'email': '"My Name" <%s>' % test_email,
'phone': '+1 202-555-0888',
})
lead_form = Form(lead)
self.assertFalse(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
lead_form.partner_id = contact
self.assertFalse(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
lead_form.email_from = '"Another Name" <%s>' % test_email # same email normalized
self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write')
self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write')
lead_form.phone = '2025550888' # same number but another format
self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write')
self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write')
lead_form = Form(lead)
self.assertFalse(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
lead_form.partner_id = contact
self.assertFalse(lead_form.partner_email_update)
self.assertFalse(lead_form.partner_phone_update)
lead_form.email_from = '"Another Name" <%s>' % test_email # same email normalized
self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write')
self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write')
lead_form.phone = '2025550888' # same number but another format
self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write')
self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write')
# wrong value are also propagated
lead_form.phone = '666 789456789456789456'
self.assertTrue(lead_form.partner_phone_update)
# wrong value are also propagated
lead_form.phone = '666 789456789456789456'
self.assertTrue(lead_form.partner_phone_update)
# test country propagation allowing to correctly compute sanitized numbers
# by adding missing relevant information from contact
be_country = self.env.ref('base.be')
contact.write({
'country_id': be_country.id,
'phone': '+32456001122',
})
lead.write({'country_id': False})
lead_form = Form(lead)
lead_form.partner_id = contact
lead_form.phone = '0456 00 11 22'
self.assertFalse(lead_form.partner_phone_update)
self.assertEqual(lead_form.country_id, be_country)
# test country propagation allowing to correctly compute sanitized numbers
# by adding missing relevant information from contact
be_country = self.env.ref('base.be')
contact.write({
'country_id': be_country.id,
'phone': '+32456001122',
})
lead.write({'country_id': False})
lead_form = Form(lead)
lead_form.partner_id = contact
lead_form.phone = '0456 00 11 22'
self.assertFalse(lead_form.partner_phone_update)
self.assertEqual(lead_form.country_id, be_country)
@users('user_sales_manager')
+14
View File
@@ -46,6 +46,20 @@ class TestPartner(TestCrmCommon):
# test form tool
partner_form = Form(self.env['res.partner'], 'base.view_partner_form')
# `parent_id` is invisible when `is_company` is True (`company_type == 'company'`)
# and parent_id is not set
# So, set a temporary `parent_id` before setting the contact as company
# to make `parent_id` visible in the interface while being a company
# <field name="parent_id"
# attrs="{
# 'invisible': [
# '|',
# '&amp;', ('is_company','=', True),('parent_id', '=', False),
# ('company_name', '!=', False),('company_name', '!=', '')
# ]
# }"
# />
partner_form.parent_id = contact_company_1
partner_form.company_type = 'company'
partner_form.parent_id = contact_company
partner_form.name = 'Mom Corp'
@@ -16,6 +16,8 @@ class TestAutomaticLeaveDates(TestHrHolidaysCommon):
'name': 'Automatic Test',
'time_type': 'leave',
'requires_allocation': 'no',
# Required for `request_unit_half` to be visible in the view
'request_unit': 'half_day',
})
def test_no_attendances(self):
@@ -271,6 +271,18 @@ class TestLeaveRequests(TestHrHolidaysCommon):
leave_form.holiday_status_id = self.holidays_type_1
leave_form.request_date_from = date(2019, 5, 6)
leave_form.request_date_to = date(2019, 5, 6)
# TODO: The test is wrong by modifying `date_from` and `date_to`, which are invisible
# It should edit only `request_date_from` and `request_date_to` instead
# And there is really a bug. Using the web client, when you put your PC in Auckland timezone,
# and the admin preferences in Auckland Timezone
# and create a time off for the current day, the computation is completely wrong
# and compute the date to before the date from *-)
# For instance, for a time-off from 06/16/2022 to 06/16/2022 (1 day) it computes
# 06/16/2022 08:00:00 as date_from and 06/15/2022 17:00:00 as date_to
# Bug reported to the rd-fun-vidange channel to the dev who introduced the bug
# https://discord.com/channels/678381219515465750/687337760452902925/986918361768263710
leave_form._view['modifiers']['date_from']['invisible'] = False
leave_form._view['modifiers']['date_to']['invisible'] = False
leave_form.date_from = datetime(2019, 5, 6, 0, 0, 0)
leave_form.date_to = datetime(2019, 5, 6, 23, 59, 59)
leave = leave_form.save()
+3 -3
View File
@@ -111,9 +111,9 @@ class TestHrLeaveStressDays(TransactionCase):
with self.assertRaises(ValidationError), Form(self.env['hr.leave'].with_user(self.employee_user.id).with_context(default_employee_id=self.employee_emp.id)) as leave_form:
leave_form.holiday_status_id = self.leave_type
leave_form.date_from = datetime(2021, 11, 1)
leave_form.date_to = datetime(2021, 11, 1)
leave_form.request_date_from = datetime(2021, 11, 1)
leave_form.request_date_to = datetime(2021, 11, 1)
self.assertFalse(leave_form.has_stress_day)
leave_form.date_to = datetime(2021, 11, 5)
leave_form.request_date_to = datetime(2021, 11, 5)
self.assertTrue(leave_form.has_stress_day)
@@ -17,6 +17,9 @@ class TestUBLCommon(AccountEdiTestCommon):
def setUpClass(cls, chart_template_ref=None, edi_format_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
# Required for `product_uom_id` to be visible in the form views
cls.env.user.groups_id += cls.env.ref('uom.group_uom')
# Ensure the testing currency is using a valid ISO code.
real_usd = cls.env.ref('base.USD')
real_usd.name = 'FUSD'
@@ -122,8 +122,10 @@ class TestMailingListMerge(MassMailCommon):
active_model='mailing.list'
))
merge_form.new_list_name = False
merge_form.dest_list_id = self.mailing_list_3
merge_form.merge_options = 'existing'
# Need to set `merge_options` before `dest_lid_id` so `dest_list_id` is visible
# `'invisible': [('merge_options', '=', 'new')]`
merge_form.dest_list_id = self.mailing_list_3
merge_form.archive_src_lists = False
result_list = merge_form.save().action_mailing_lists_merge()
+5 -2
View File
@@ -66,7 +66,7 @@ class TestMrpCommon(common2.TestStockCommon):
login='hilda',
email='h.h@example.com',
notification_type='inbox',
groups='mrp.group_mrp_user, stock.group_stock_user, mrp.group_mrp_byproducts',
groups='mrp.group_mrp_user, stock.group_stock_user, mrp.group_mrp_byproducts, uom.group_uom',
)
cls.user_mrp_manager = mail_new_test_user(
cls.env,
@@ -74,8 +74,11 @@ class TestMrpCommon(common2.TestStockCommon):
login='gary',
email='g.g@example.com',
notification_type='inbox',
groups='mrp.group_mrp_manager, stock.group_stock_user, mrp.group_mrp_byproducts',
groups='mrp.group_mrp_manager, stock.group_stock_user, mrp.group_mrp_byproducts, uom.group_uom',
)
# Required for `product_uom_id` to be visible in the view
# This class is used by a lot of tests which sets `product_uom_id` on `mrp.production`
cls.env.user.groups_id += cls.env.ref('uom.group_uom')
cls.workcenter_1 = cls.env['mrp.workcenter'].create({
'name': 'Nuclear Workcenter',
+4
View File
@@ -85,6 +85,8 @@ class TestMrpProductionBackorder(TestMrpCommon):
should be MO/001-02.
Check that all MO are reachable through the procurement group.
"""
# Required for `manufacture_steps` to be visible in the view
self.env.user.groups_id += self.env.ref("stock.group_adv_location")
with Form(self.warehouse) as warehouse:
warehouse.manufacture_steps = 'pbm'
@@ -128,6 +130,8 @@ class TestMrpProductionBackorder(TestMrpCommon):
should be MO/001-02.
Check that all MO are reachable through the procurement group.
"""
# Required for `manufacture_steps` to be visible in the view
self.env.user.groups_id += self.env.ref("stock.group_adv_location")
with Form(self.warehouse) as warehouse:
warehouse.manufacture_steps = 'pbm_sam'
production, _, product_to_build, product_to_use_1, product_to_use_2 = self.generate_mo(qty_base_1=4, qty_final=4, picking_type_id=self.warehouse.manu_type_id)
+4
View File
@@ -496,6 +496,8 @@ class TestBoM(TestMrpCommon):
'name': 'Deserts Table'
})
# Required to display `operation_ids` in the form view
self.env.user.groups_id += self.env.ref("mrp.group_mrp_routings")
with Form(bom_crumble) as bom:
with bom.bom_line_ids.new() as line:
line.product_id = butter
@@ -660,6 +662,8 @@ class TestBoM(TestMrpCommon):
'name': 'Deserts Table'
})
# Required to display `operation_ids` in the form view
self.env.user.groups_id += self.env.ref("mrp.group_mrp_routings")
with Form(bom_drawer) as bom:
with bom.bom_line_ids.new() as line:
line.product_id = screw
+5
View File
@@ -134,6 +134,11 @@ class TestMrpMulticompany(common.TransactionCase):
})
mo_form = Form(self.env['mrp.production'].with_user(self.user_a))
mo_form.product_id = product
# The mo must be confirmed, no longer in draft, in order for `lot_producing_id` to be visible in the view
# <div class="o_row" attrs="{'invisible': ['|', ('state', '=', 'draft'), ('product_tracking', 'in', ('none', False))]}">
mo = mo_form.save()
mo.action_confirm()
mo_form = Form(mo)
mo_form.lot_producing_id = lot_b
mo = mo_form.save()
with self.assertRaises(UserError):
+36
View File
@@ -339,6 +339,8 @@ class TestMrpOrder(TestMrpCommon):
def test_update_quantity_4(self):
""" Workcenter 1 has 10' start time and 5' stop time """
# Required for `workerorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
bom = self.env['mrp.bom'].create({
'product_id': self.product_6.id,
'product_tmpl_id': self.product_6.product_tmpl_id.id,
@@ -517,6 +519,11 @@ class TestMrpOrder(TestMrpCommon):
self.assertEqual(production.move_raw_ids.mapped('manual_consumption'), [False, True, True])
# <field name="qty_producing" attrs="{'invisible': [('state', '=', 'draft')]}"/>
production.action_confirm()
production.action_assign()
production.is_locked = False
# test no updating
production_form = Form(production)
production_form.qty_producing = 5
@@ -689,6 +696,11 @@ class TestMrpOrder(TestMrpCommon):
self.stock_shelf_2 = self.stock_location_14
mo, _, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot', qty_base_1=10, qty_final=1)
# Required for `lot_producing_id` to be visible in the view
# <field name="lot_producing_id" attrs="{'invisible': [('product_tracking', 'in', ('none', False))]}"/>
p_final.tracking = 'lot'
self.assertEqual(len(mo), 1, 'MO should have been created')
first_lot_for_p1 = self.env['stock.lot'].create({
@@ -999,6 +1011,8 @@ class TestMrpOrder(TestMrpCommon):
byproduct3 none 1.0 dozen
Check qty producing update and moves finished values.
"""
# Required for `byproduct_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_byproducts')
dozen = self.env.ref('uom.product_uom_dozen')
self.byproduct1 = self.env['product.product'].create({
'name': 'Byproduct 1',
@@ -2368,6 +2382,8 @@ class TestMrpOrder(TestMrpCommon):
Create a second one in 10 minutes (expected should NOT go from 15 to 12.5, it should go from 15 to 10)
"""
# First production, the default is 60 and there is 0 productions of that operation
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_4
production = production_form.save()
@@ -2375,6 +2391,8 @@ class TestMrpOrder(TestMrpCommon):
production.action_confirm()
production.button_plan()
# Production planned, time to start, I produce all the 1 product
# 'invisible': [('state', '=', 'draft')]
production_form = Form(production)
production_form.qty_producing = 1
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 15 # in 15 minutes
@@ -2390,6 +2408,8 @@ class TestMrpOrder(TestMrpCommon):
production.action_confirm()
production.button_plan()
# Production planned, time to start, I produce all the 1 product
# 'invisible': [('state', '=', 'draft')]
production_form = Form(production)
production_form.qty_producing = 1
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 10 # In 10 minutes this time
@@ -2410,6 +2430,8 @@ class TestMrpOrder(TestMrpCommon):
Test that when tracking the 2 last production, if we make one with under capacity, and one with normal capacity,
the two are equivalent (1 done with capacity 2 in 10mn = 2 done with capacity 2 in 10mn)
"""
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_5
production = production_form.save()
@@ -2417,6 +2439,8 @@ class TestMrpOrder(TestMrpCommon):
production.button_plan()
# Production planned, time to start, I produce all the 1 product
# 'invisible': [('state', '=', 'draft')]
production_form = Form(production)
production_form.qty_producing = 1
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 10 # in 10 minutes
@@ -2434,6 +2458,8 @@ class TestMrpOrder(TestMrpCommon):
production.action_confirm()
production.button_plan()
# Production planned, time to start, I produce all the 2 product
# 'invisible': [('state', '=', 'draft')]
production_form = Form(production)
production_form.qty_producing = 2
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 10 # In 10 minutes this time
@@ -2457,6 +2483,8 @@ class TestMrpOrder(TestMrpCommon):
5 -> 30mn
...
"""
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_6
production = production_form.save()
@@ -2464,6 +2492,8 @@ class TestMrpOrder(TestMrpCommon):
production.button_plan()
# Production planned, time to start, I produce all the 1 product
# 'invisible': [('state', '=', 'draft')]
production_form = Form(production)
production_form.qty_producing = 1
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 10 # in 10 minutes
@@ -2760,6 +2790,8 @@ class TestMrpOrder(TestMrpCommon):
"""
Check that the work order is started only once when clicking the start button several times.
"""
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_2
production_form.product_qty = 1
@@ -2869,6 +2901,8 @@ class TestMrpOrder(TestMrpCommon):
-> The user replans one of the WO: the warnings should disappear and the
WO should be postponed.
"""
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
mos = self.env['mrp.production']
for _ in range(2):
mo_form = Form(self.env['mrp.production'])
@@ -2899,6 +2933,8 @@ class TestMrpOrder(TestMrpCommon):
-> The user replans one of the WO: the warnings should disappear and the
WO should be postponed.
"""
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
mos = self.env['mrp.production']
for _ in range(2):
mo_form = Form(self.env['mrp.production'])
+2
View File
@@ -681,6 +681,8 @@ class TestProcurement(TestMrpCommon):
This test ensures that, when running the scheduler, the generated MOs are based
on the correct BoMs
"""
# Required for `picking_type_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_adv_location')
warehouse = self.env.ref('stock.warehouse0')
stock_location01 = warehouse.lot_stock_id
@@ -11,6 +11,10 @@ class TestMultistepManufacturingWarehouse(TestMrpCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Required for `uom_id` to be visible in the view
cls.env.user.groups_id += cls.env.ref('uom.group_uom')
# Required for `manufacture_steps` to be visible in the view
cls.env.user.groups_id += cls.env.ref('stock.group_adv_location')
# Create warehouse
cls.customer_location = cls.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers')
warehouse_form = Form(cls.env['stock.warehouse'])
@@ -107,6 +107,8 @@ class TestAnalyticAccount(TransactionCase):
"""Test when workcenter and MO are using the same analytic account, no
duplicated lines will be post.
"""
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
# set wc analytic account to be the same of the one on the bom
self.workcenter.costs_hour_account_id = self.analytic_account
@@ -147,6 +149,8 @@ class TestAnalyticAccount(TransactionCase):
"""Test when workcenter and MO are using the same analytic account, no
duplicated lines will be post.
"""
# Required for `workorder_ids` to be visible in the view
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
# set wc analytic account to be different from the one on the bom
wc_analytic_account = self.env['account.analytic.account'].create({'name': 'wc_analytic_account'})
self.workcenter.costs_hour_account_id = wc_analytic_account
@@ -19,6 +19,8 @@ class TestBomPrice(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Required for `product_uom_id ` to be visible in the view
cls.env.user.groups_id += cls.env.ref('uom.group_uom')
cls.Product = cls.env['product.product']
cls.Bom = cls.env['mrp.bom']
@@ -160,6 +160,9 @@ class TestStockLandedCostsMrp(ValuationReconciliationTestCommon):
man_order = man_order_form.save()
man_order.action_confirm()
# produce product
# To edit `qty_producing`, the mo must no be draft. It's not thanks to the above `action_confirm()`
# but the values of the form do not update automatically, it must be reloaded.
man_order_form = Form(man_order)
man_order_form.qty_producing = 1
man_order_form.save()
man_order.button_mark_done()
@@ -237,6 +237,8 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking. Checks that the delivery and MO for its components are
automatically created.
"""
# Required for `location_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations')
# Tick "manufacture" and MTO on self.comp2
mto_route = self.env.ref('stock.route_warehouse0_mto')
mto_route.active = True
+2
View File
@@ -1008,6 +1008,8 @@ class TestVariantsArchive(common.TestProductCommon):
def test_uom_update_variant(self):
""" Changing the uom on the template do not behave the same
as changing on the product product."""
# Required for `uom_id` to be visible in the view
self.env.user.groups_id += self.env.ref("uom.group_uom")
units = self.env.ref('uom.product_uom_unit')
cm = self.env.ref('uom.product_uom_cm')
template = self.env['product.template'].create({
+38 -8
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from contextlib import contextmanager
from lxml import etree
from odoo.tests.common import TransactionCase, Form
from odoo.exceptions import AccessError, UserError
@@ -257,10 +258,24 @@ class TestMultiCompanyProject(TestMultiCompanyCommon):
# set parent on existing orphan task; the onchange will set the correct company and subtask project
self.task_2.write({'project_id': False})
with Form(self.task_2) as task_form:
task_form.name = 'Test Task 2 becomes child of Task 1 (other company)'
task_form.parent_id = self.task_1
task = task_form.save()
# For `parent_id` to be visible in the view, you need
# 1. The debug mode
# 2. `allow_subtasks` to be true
# <field name="parent_id" attrs="{'invisible': [('allow_subtasks', '=', False)]}" groups="base.group_no_one"/>
# `allow_subtasks` is a related to `allow_subtasks` on the project
# as the point of the test is to test the behavior of the task `_compute_project_id` when there is no project,
# `allow_subtasks` is by default invisible, and you shouldn't therefore be able to change it.
# So, to make it visible, temporary modify the view to make it visible even when `allow_subtasks` is `False`.
view = self.env.ref('project.view_task_form2').sudo()
tree = etree.fromstring(view.arch)
for node in tree.xpath('//field[@name="parent_id"][@attrs]'):
node.attrib.pop('attrs')
view.arch = etree.tostring(tree)
with self.debug_mode():
with Form(self.task_2) as task_form:
task_form.name = 'Test Task 2 becomes child of Task 1 (other company)'
task_form.parent_id = self.task_1
task = task_form.save()
self.assertEqual(task.company_id, task.project_id.company_id, "The company of the orphan subtask should be the one from its project.")
@@ -268,13 +283,28 @@ class TestMultiCompanyProject(TestMultiCompanyCommon):
# set up default subtask project
self.project_company_a.write({'allow_subtasks': True})
# For `parent_id` to be visible in the view, you need
# 1. The debug mode
# 2. `allow_subtasks` to be true
# <field name="parent_id" attrs="{'invisible': [('allow_subtasks', '=', False)]}" groups="base.group_no_one"/>
# `allow_subtasks` is a related to `allow_subtasks` on the project
# as the point of the test is to test the behavior of the task `_compute_project_id` when there is no project,
# `allow_subtasks` is by default invisible, and you shouldn't therefore be able to change it.
# So, to make it visible, temporary modify the view to make it visible even when `allow_subtasks` is `False`.
view = self.env.ref('project.view_task_form2').sudo()
tree = etree.fromstring(view.arch)
for node in tree.xpath('//field[@name="parent_id"][@attrs]'):
node.attrib.pop('attrs')
view.arch = etree.tostring(tree)
with self.sudo('employee-a'):
with self.allow_companies([self.company_a.id, self.company_b.id]):
with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form:
task_form.name = 'Test Subtask in company B'
task_form.parent_id = self.task_1
with self.debug_mode():
with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form:
task_form.name = 'Test Subtask in company B'
task_form.parent_id = self.task_1
task = task_form.save()
task = task_form.save()
self.assertEqual(task.project_id, self.task_1.project_id, "The default project of a subtask should be the default subtask project of the project from the mother task")
self.assertEqual(task.company_id, task.project_id.company_id, "The company of the orphan subtask should be the one from its project.")
@@ -465,6 +465,9 @@ class TestProjectrecurrence(TransactionCase):
tasks = self.env['project.task'].search(domain)
return tasks, len(tasks), len(tasks.filtered('parent_id'))
# Required for `child_ids` to be visible in the view
# {'invisible': [('allow_subtasks', '=', False)]}
self.project_recurring.allow_subtasks = True
parent_task = self.env['project.task'].create({
'name': 'Parent Task',
'project_id': self.project_recurring.id
@@ -558,6 +561,9 @@ class TestProjectrecurrence(TransactionCase):
return len(tasks), len(tasks.filtered('parent_id'))
# Phase 0 : Initialize test case
# Required for `child_ids` to be visible in the view
# {'invisible': [('allow_subtasks', '=', False)]}
self.project_recurring.allow_subtasks = True
parent_task = self.env['project.task'].create({
'name': 'Parent Task',
'project_id': self.project_recurring.id
@@ -144,6 +144,10 @@ class TestProjectSharing(TestProjectSharingCommon):
3.2) Create a sub-task
3.3) Create a second sub-task
"""
# 0) Allow to create subtasks in the project tasks
# Required for `child_ids` to be visible in the view
# {'invisible': [('allow_subtasks', '=', False)]}
self.project_cows.allow_subtasks = True
# 1) Give the 'read' access mode to a portal user in a project and try to create task with this user.
with self.assertRaises(AccessError, msg="Should not accept the portal user create a task in the project when he has not the edit access right."):
with self.get_project_sharing_form_view(self.task_cow.with_context({'tracking_disable': True, 'default_project_id': self.project_cows.id}), self.user_portal) as form:
+24 -20
View File
@@ -72,8 +72,10 @@ class TestProjectSubtasks(TestProjectCommon):
"""
form_views = self.env['ir.ui.view'].search([('model', '=', 'project.task'), ('type', '=', 'form')])
for form_view in form_views:
with Form(self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_pigs.id}), view=form_view) as task_form:
task_form.name = 'Test Task 1'
task_form = Form(self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_pigs.id, 'default_name': 'Test Task 1'}), view=form_view)
# Some views have the `name` field invisible
# As the goal is simply to test the default project field and not the name, we can skip setting the name
# in the view and set it using `default_name` instead
task = task_form.save()
self.assertEqual(task.project_id, self.project_pigs, "The project should be assigned from the default project, form_view name : %s." % form_view.name)
@@ -148,27 +150,29 @@ class TestProjectSubtasks(TestProjectCommon):
self.assertEqual(self.task_1.child_ids.display_project_id, self.project_goats, "Display Project of the task should be well assigned")
self.assertEqual(self.task_1.child_ids.project_id, self.project_goats, "Changing display project id on a subtask should change project id")
# 6)
with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form:
subtask_form.parent_id = self.env['project.task']
orphan_subtask = subtask_form.save()
# Debug mode required for `parent_id` to be visible in the view
with self.debug_mode():
# 6)
with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form:
subtask_form.parent_id = self.env['project.task']
orphan_subtask = subtask_form.save()
self.assertEqual(orphan_subtask.display_project_id, self.project_goats, "Display Project of the task should be well assigned")
self.assertEqual(orphan_subtask.project_id, self.project_goats, "Changing display project id on a subtask should change project id")
self.assertFalse(orphan_subtask.parent_id, "Parent should be false")
self.assertEqual(orphan_subtask.display_project_id, self.project_goats, "Display Project of the task should be well assigned")
self.assertEqual(orphan_subtask.project_id, self.project_goats, "Changing display project id on a subtask should change project id")
self.assertFalse(orphan_subtask.parent_id, "Parent should be false")
# 7)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.new() as subtask_form:
subtask_form.name = 'Test Subtask 1'
subtask_form.display_project_id = self.project_goats
with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form:
subtask_form.display_project_id = self.env['project.project']
subtask_form.parent_id = self.env['project.task']
orphan_subtask = subtask_form.save()
# 7)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.new() as subtask_form:
subtask_form.name = 'Test Subtask 1'
subtask_form.display_project_id = self.project_goats
with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form:
subtask_form.display_project_id = self.env['project.project']
subtask_form.parent_id = self.env['project.task']
orphan_subtask = subtask_form.save()
self.assertEqual(orphan_subtask.project_id, self.project_pigs, "Removing parent should not change project")
self.assertEqual(orphan_subtask.display_project_id, self.project_pigs, "Removing parent should make the display project set as project.")
self.assertEqual(orphan_subtask.project_id, self.project_pigs, "Removing parent should not change project")
self.assertEqual(orphan_subtask.display_project_id, self.project_pigs, "Removing parent should make the display project set as project.")
def test_subtask_stage(self):
"""
+2
View File
@@ -179,6 +179,8 @@ class TestPurchase(AccountTestInvoicingCommon):
according to the product_qty. Also check product_qty or product_packaging
are correctly calculated when one of them changed.
"""
# Required for `product_packaging_qty` to be visible in the view
self.env.user.groups_id += self.env.ref('product.group_stock_packaging')
packaging_single = self.env['product.packaging'].create({
'name': "I'm a packaging",
'product_id': self.product_a.id,
@@ -304,6 +304,8 @@ class TestPurchaseToInvoice(AccountTestInvoicingCommon):
""" Tests whether, when an analytic account rule is set, and user changes manually the analytic account on
the po, it is the same that is mentioned in the bill.
"""
# Required for `analytic.group_analytic_accounting` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
analytic_account_default = self.env['account.analytic.account'].create({'name': 'default'})
analytic_account_manual = self.env['account.analytic.account'].create({'name': 'manual'})
@@ -41,7 +41,39 @@ class TestPurchaseOrderReport(AccountTestInvoicingCommon):
f = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
f.invoice_date = f.date
f.partner_id = po.partner_id
f.purchase_id = po
# <field name="invoice_vendor_bill_id" position="after">
# <field name="purchase_id" invisible="1"/>
# <label for="purchase_vendor_bill_id" string="Auto-Complete" class="oe_edit_only"
# attrs="{'invisible': ['|', ('state','!=','draft'), ('move_type', '!=', 'in_invoice')]}" />
# <field name="purchase_vendor_bill_id" nolabel="1"
# attrs="{'invisible': ['|', ('state','!=','draft'), ('move_type', '!=', 'in_invoice')]}"
# class="oe_edit_only"
# domain="partner_id and [('company_id', '=', company_id), ('partner_id.commercial_partner_id', '=', commercial_partner_id)] or [('company_id', '=', company_id)]"
# placeholder="Select a purchase order or an old bill"
# context="{'show_total_amount': True}"
# options="{'no_create': True, 'no_open': True}"/>
# </field>
# @api.onchange('purchase_vendor_bill_id', 'purchase_id')
# def _onchange_purchase_auto_complete(self):
# ...
# elif self.purchase_vendor_bill_id.purchase_order_id:
# self.purchase_id = self.purchase_vendor_bill_id.purchase_order_id
# self.purchase_vendor_bill_id = False
# purchase_vendor_bill_id = fields.Many2one('purchase.bill.union'
# class PurchaseBillUnion(models.Model):
# _name = 'purchase.bill.union'
# ...
# def init(self):
# self.env.cr.execute("""
# ...
# SELECT
# -id, name, ...
# id as purchase_order_id
# FROM purchase_order
# ...
# )""")
# ...
f.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id)
invoice = f.save()
invoice.action_post()
po.flush_model()
@@ -53,7 +53,7 @@ class TestValuationReconciliation(ValuationReconciliationTestCommon):
move_form.invoice_date = date
move_form.partner_id = self.partner_a
move_form.currency_id = self.currency_data['currency']
move_form.purchase_id = purchase_order
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-purchase_order.id)
return move_form.save()
def test_shipment_invoice(self):
@@ -449,6 +449,8 @@ class TestCreatePicking(common.TestProductCommon):
'Delivery deadline date should be changed.')
def test_07_differed_schedule_date(self):
# Required for `reception_steps` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_adv_location')
warehouse = self.env['stock.warehouse'].search([], limit=1)
with Form(warehouse) as w:
@@ -27,6 +27,8 @@ class TestOnchangeProductId(TransactionCase):
cls.supplierinfo_model = cls.env["product.supplierinfo"]
def test_onchange_product_id(self):
# Required for `product_uom` to be visible in the view
self.env.user.groups_id += self.env.ref('uom.group_uom')
uom_id = self.product_uom_model.search([('name', '=', 'Units')])[0]
@@ -77,7 +77,7 @@ class TestPurchaseOrder(ValuationReconciliationTestCommon):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.partner_id = self.partner_a
move_form.purchase_id = self.po
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-self.po.id)
self.invoice = move_form.save()
self.assertEqual(self.po.order_line.mapped('qty_invoiced'), [5.0, 5.0], 'Purchase: all products should be invoiced"')
@@ -110,7 +110,7 @@ class TestPurchaseOrder(ValuationReconciliationTestCommon):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.invoice_date = move_form.date
move_form.partner_id = self.partner_a
move_form.purchase_id = self.po
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-self.po.id)
self.invoice = move_form.save()
self.invoice.action_post()
@@ -141,6 +141,13 @@ class TestPurchaseOrder(ValuationReconciliationTestCommon):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_refund'))
move_form.invoice_date = move_form.date
move_form.partner_id = self.partner_a
# Not supposed to see/change the purchase order of a refund invoice by default
# <field name="purchase_id" invisible="1"/>
# <label for="purchase_vendor_bill_id" string="Auto-Complete" class="oe_edit_only"
# attrs="{'invisible': ['|', ('state','!=','draft'), ('move_type', '!=', 'in_invoice')]}" />
# <field name="purchase_vendor_bill_id" nolabel="1"
# attrs="{'invisible': ['|', ('state','!=','draft'), ('move_type', '!=', 'in_invoice')]}"
move_form._view['modifiers']['purchase_id']['invisible'] = False
move_form.purchase_id = self.po
self.invoice = move_form.save()
move_form = Form(self.invoice)
@@ -106,6 +106,8 @@ class TestReorderingRule(TransactionCase):
- Increase the quantity on the PO, the extra quantity should follow the push rules and
thus go to stock
"""
# Required for `warehouse_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations')
warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1)
subloc_1 = self.env['stock.location'].create({'name': 'subloc_1', 'location_id': warehouse_1.lot_stock_id.id})
subloc_2 = self.env['stock.location'].create({'name': 'subloc_2', 'location_id': warehouse_1.lot_stock_id.id})
@@ -216,6 +218,8 @@ class TestReorderingRule(TransactionCase):
})
# create reordering rules
# Required for `warehouse_id` to be visible in the view
self.env['res.users'].browse(2).groups_id += self.env.ref('stock.group_stock_multi_locations')
orderpoint_form = Form(self.env['stock.warehouse.orderpoint'].with_user(2))
orderpoint_form.warehouse_id = warehouse_1
orderpoint_form.location_id = outside_loc
@@ -617,6 +621,8 @@ class TestReorderingRule(TransactionCase):
If the user triggers each orderpoint separately, it should still produce two
different purchase order lines (one for each orderpoint)
"""
# Required for `warehouse_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations')
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1)
stock_location = warehouse.lot_stock_id
sub_location = self.env['stock.location'].create({'name': 'subloc_1', 'location_id': stock_location.id})
@@ -382,7 +382,7 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.invoice_date = move_form.date
move_form.partner_id = self.partner_id
move_form.purchase_id = po1
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po1.id)
invoice_po1 = move_form.save()
invoice_po1.action_post()
@@ -408,7 +408,7 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.invoice_date = move_form.date
move_form.partner_id = self.partner_id
move_form.purchase_id = po2
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po2.id)
invoice_po2 = move_form.save()
invoice_po2.action_post()
@@ -433,6 +433,14 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_refund'))
move_form.invoice_date = move_form.date
move_form.partner_id = self.partner_id
# Not supposed to see/change the purchase order of a refund invoice by default
# <field name="purchase_id" invisible="1"/>
# <label for="purchase_vendor_bill_id" string="Auto-Complete" class="oe_edit_only"
# attrs="{'invisible': ['|', ('state','!=','draft'), ('move_type', '!=', 'in_invoice')]}" />
# <field name="purchase_vendor_bill_id" nolabel="1"
# attrs="{'invisible': ['|', ('state','!=','draft'), ('move_type', '!=', 'in_invoice')]}"
move_form._view['modifiers']['purchase_id']['invisible'] = False
move_form.purchase_id = po2
with move_form.invoice_line_ids.edit(0) as line_form:
line_form.quantity = 10
@@ -468,7 +476,7 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.invoice_date = move_form.date
move_form.partner_id = order.partner_id
move_form.purchase_id = order
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
with move_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 15.0
invoice = move_form.save()
@@ -1164,7 +1172,7 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
# Create an invoice with a different price and a discount
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
invoice_form.invoice_date = invoice_form.date
invoice_form.purchase_id = order
invoice_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
with invoice_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 100.0
line_form.discount = 10.0
@@ -1211,7 +1219,7 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
# Create an invoice with a different price and a discount
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
invoice_form.invoice_date = invoice_form.date
invoice_form.purchase_id = order
invoice_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
with invoice_form.invoice_line_ids.edit(0) as line_form:
line_form.tax_ids.clear()
line_form.discount = 10.0
@@ -1258,7 +1266,7 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
# Create an invoice with a different price and a discount
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
invoice_form.invoice_date = invoice_form.date
invoice_form.purchase_id = order
invoice_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
with invoice_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 100.0
line_form.discount = 10.0
+2
View File
@@ -326,6 +326,8 @@ class TestRepair(AccountTestInvoicingCommon):
"""Tests functionality of creating a repair directly from a return picking,
i.e. repair can be made and defaults to appropriate return values. """
# test return
# Required for `location_dest_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations')
picking_form = Form(self.env['stock.picking'])
picking_form.picking_type_id = self.stock_warehouse.return_type_id
picking_form.partner_id = self.res_partner_1
+6
View File
@@ -35,6 +35,8 @@ class TestReInvoice(TestSaleCommon):
)
def test_at_cost(self):
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
""" Test vendor bill at cost for product based on ordered and delivered quantities. """
# create SO line and confirm SO (with only one line)
sale_order_line1 = self.env['sale.order.line'].create({
@@ -146,6 +148,8 @@ class TestReInvoice(TestSaleCommon):
""" Test invoicing vendor bill at sales price for products based on delivered and ordered quantities. Check no existing SO line is incremented, but when invoicing a
second time, increment only the delivered so line.
"""
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
# create SO line and confirm SO (with only one line)
sale_order_line1 = self.env['sale.order.line'].create({
'product_id': self.company_data['product_delivery_sales_price'].id,
@@ -216,6 +220,8 @@ class TestReInvoice(TestSaleCommon):
def test_no_expense(self):
""" Test invoicing vendor bill with no policy. Check nothing happen. """
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
# confirm SO
sale_order_line = self.env['sale.order.line'].create({
'product_id': self.company_data['product_delivery_no'].id,
+4
View File
@@ -583,6 +583,8 @@ class TestSaleOrder(TestSaleCommon):
according to the product_qty. Also check product_qty or product_packaging
are correctly calculated when one of them changed.
"""
# Required for `product_packaging_qty` to be visible in the view
self.env.user.groups_id += self.env.ref('product.group_stock_packaging')
partner = self.env['res.partner'].create({'name': "I'm a partner"})
product_tmpl = self.env['product.template'].create({'name': "I'm a product"})
product = product_tmpl.product_variant_id
@@ -768,6 +770,8 @@ class TestSaleOrder(TestSaleCommon):
self.assertEqual(line.untaxed_amount_to_invoice, line.price_subtotal)
def test_discount_and_amount_undiscounted(self):
# Required for `discount` to be visible in the view
self.env.user.groups_id += self.env.ref('product.group_discount_per_so_line')
"""When adding a discount on a SO line, this test ensures that amount undiscounted is
consistent with the used tax"""
sale_order = self.env['sale.order'].create({
@@ -444,6 +444,8 @@ class TestSaleToInvoice(TestSaleCommon):
""" Tests whether, when an analytic account rule is set and the so has an analytic account,
the default analytic acount doesn't replace the one from the so in the invoice.
"""
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
analytic_account_default = self.env['account.analytic.account'].create({'name': 'default'})
analytic_account_so = self.env['account.analytic.account'].create({'name': 'so'})
@@ -11,6 +11,11 @@ class TestMultistepManufacturing(TestMrpCommon):
def setUpClass(cls):
super().setUpClass()
# Required for `uom_id ` to be visible in the view
cls.env.user.groups_id += cls.env.ref('uom.group_uom')
# Required for `manufacture_steps` to be visible in the view
cls.env.user.groups_id += cls.env.ref('stock.group_adv_location')
cls.env.ref('stock.route_warehouse0_mto').active = True
cls.MrpProduction = cls.env['mrp.production']
# Create warehouse
+3 -1
View File
@@ -15,6 +15,8 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
# Required for `uom_id` to be visible in the view
cls.env.user.groups_id += cls.env.ref('uom.group_uom')
cls.env.ref('stock.route_warehouse0_mto').active = True
# Useful models
@@ -213,7 +215,7 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon):
"""
for comp in components:
f = Form(self.env['stock.move'])
f.name = 'Test Receipt Components'
# <field name="name" invisible="1"/>
f.location_id = self.env.ref('stock.stock_location_suppliers')
f.location_dest_id = warehouse.lot_stock_id
f.product_id = comp
@@ -17,7 +17,9 @@ class TestSaleMrpLeadTime(TestStockCommon):
cls.env.ref('stock.route_warehouse0_mto').active = True
# Update the product_1 with type, route, Manufacturing Lead Time and Customer Lead Time
with Form(cls.product_1) as p1:
p1.type = 'product'
# `type` is invisible in the view,
# and it's a compute field based on `detailed_type` which is the field visible in the view
p1.detailed_type = 'product'
p1.produce_delay = 5.0
p1.sale_delay = 5.0
p1.route_ids.clear()
@@ -26,7 +28,9 @@ class TestSaleMrpLeadTime(TestStockCommon):
# Update the product_2 with type
with Form(cls.product_2) as p2:
p2.type = 'consu'
# `type` is invisible in the view,
# and it's a compute field based on `detailed_type` which is the field visible in the view
p2.detailed_type = 'consu'
# Create Bill of materials for product_1
with Form(cls.env['mrp.bom']) as bom:
@@ -10,6 +10,8 @@ from odoo.tools import mute_logger
class TestSaleMrpProcurement(TransactionCase):
def test_sale_mrp(self):
# Required for `uom_id` to be visible in the view
self.env.user.groups_id += self.env.ref('uom.group_uom')
self.env.ref('stock.route_warehouse0_mto').active = True
warehouse0 = self.env.ref('stock.warehouse0')
# In order to test the sale_mrp module in OpenERP, I start by creating a new product 'Slider Mobile'
@@ -77,6 +79,10 @@ class TestSaleMrpProcurement(TransactionCase):
to avoid generating multiple deliveries
to the customer location
"""
# Required for `uom_id` to be visible in the view
self.env.user.groups_id += self.env.ref('uom.group_uom')
# Required for `manufacture_step` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_adv_location')
self.env.ref('stock.route_warehouse0_mto').active = True
# Create warehouse
self.customer_location = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers')
@@ -436,6 +436,12 @@ class TestProjectBilling(TestCommonSaleTimesheet):
with Form(self.env['project.project'].with_context({'tracking_disable': True})) as project_form:
project_form.name = 'Test Billable Project'
project_form.allow_billable = True
# `sale_line_employee_ids` is not visible if `partner_id` is not set
# As the behavior of the test is to check the partner on the project
# is set to the partner of the order line, temporary make the field visible
# even if it's not the case in the reality, in the web client
# {'invisible': ['|', ('allow_billable', '=', False), ('partner_id', '=', False)]}
project_form._view['modifiers']['sale_line_employee_ids']['invisible'] = False
with project_form.sale_line_employee_ids.new() as mapping_form:
mapping_form.employee_id = self.employee_manager
mapping_form.sale_line_id = self.so.order_line[:1]
@@ -52,6 +52,8 @@ class TestReInvoice(TestCommonSaleTimesheet):
def test_at_cost(self):
""" Test vendor bill at cost for product based on ordered and delivered quantities. """
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
# create SO line and confirm SO (with only one line)
sale_order_line1 = self.env['sale.order.line'].create({
'product_id': self.company_data['product_order_cost'].id,
@@ -144,6 +146,8 @@ class TestReInvoice(TestCommonSaleTimesheet):
""" Test invoicing vendor bill at sales price for products based on delivered and ordered quantities. Check no existing SO line is incremented, but when invoicing a
second time, increment only the delivered so line.
"""
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
# create SO line and confirm SO (with only one line)
sale_order_line1 = self.env['sale.order.line'].create({
'product_id': self.company_data['product_delivery_sales_price'].id,
@@ -231,6 +235,8 @@ class TestReInvoice(TestCommonSaleTimesheet):
def test_no_expense(self):
""" Test invoicing vendor bill with no policy. Check nothing happen. """
# Required for `analytic_account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
# confirm SO
sale_order_line = self.env['sale.order.line'].create({
'product_id': self.company_data['product_order_no'].id,
+8
View File
@@ -1176,6 +1176,8 @@ class StockMove(TransactionCase):
"""Receive a package. Test the package will be move to a child location
with correct storage category.
"""
# Required for `result_package_id` to be visible in the view
self.env.user.groups_id += self.env.ref("stock.group_tracking_lot")
# storage category
storage_category = self.env['stock.storage.category'].create({
'name': "storage category"
@@ -1240,6 +1242,8 @@ class StockMove(TransactionCase):
"""Receive package with same package type twice. Check putaway rule can
be applied on the first one but not the second one due to no space.
"""
# Required for `result_package_id` to be visible in the view
self.env.user.groups_id += self.env.ref("stock.group_tracking_lot")
# storage category
storage_category = self.env['stock.storage.category'].create({
'name': "storage category"
@@ -1341,6 +1345,8 @@ class StockMove(TransactionCase):
only accept new product when empty. Check putaway rule can be applied on
the first one but not the second one.
"""
# Required for `result_package_id` to be visible in the view
self.env.user.groups_id += self.env.ref("stock.group_tracking_lot")
# storage category
storage_category = self.env['stock.storage.category'].create({
'name': "storage category",
@@ -1443,6 +1449,8 @@ class StockMove(TransactionCase):
accept same product. Check putaway rule can be applied on the first one
but not the second one.
"""
# Required for `result_package_id` to be visible in the view
self.env.user.groups_id += self.env.ref("stock.group_tracking_lot")
# storage category
storage_category = self.env['stock.storage.category'].create({
'name': "storage category",
+3 -1
View File
@@ -2102,6 +2102,8 @@ class TestSinglePicking(TestStockCommon):
self.assertEqual(delivery_order.show_mark_as_todo, False)
def test_owner_1(self):
# Required for `owner_id` to be visible in the view
self.env.user.groups_id += self.env.ref("stock.group_tracking_owner")
"""Make a receipt, set an owner and validate"""
owner1 = self.env['res.partner'].create({'name': 'owner'})
receipt = self.env['stock.picking'].create({
@@ -2169,7 +2171,7 @@ class TestSinglePicking(TestStockCommon):
), view='stock.view_picking_form')
receipt_form.partner_id = partner
receipt_form.picking_type_id = picking_type
receipt_form.location_id = supplier_location
# <field name="location_id" attrs="{'invisible': [('picking_type_code', '=', 'incoming')]}"
receipt_form.location_dest_id = stock_location
receipt = receipt_form.save()
with receipt_form.move_line_nosuggest_ids.new() as move_line:
+12 -2
View File
@@ -22,14 +22,20 @@ class TestMultiCompany(TransactionCase):
cls.user_a = cls.env['res.users'].create({
'name': 'user company a with access to company b',
'login': 'user a',
'groups_id': [(6, 0, [group_user.id, group_stock_manager.id])],
'groups_id': [(6, 0, [
group_user.id,
group_stock_manager.id,
])],
'company_id': cls.company_a.id,
'company_ids': [(6, 0, [cls.company_a.id, cls.company_b.id])]
})
cls.user_b = cls.env['res.users'].create({
'name': 'user company b with access to company a',
'login': 'user b',
'groups_id': [(6, 0, [group_user.id, group_stock_manager.id])],
'groups_id': [(6, 0, [
group_user.id,
group_stock_manager.id,
])],
'company_id': cls.company_b.id,
'company_ids': [(6, 0, [cls.company_a.id, cls.company_b.id])]
})
@@ -218,6 +224,8 @@ class TestMultiCompany(TransactionCase):
def test_orderpoint_1(self):
"""As a user of company A, create an orderpoint for company B. Check itsn't possible to
use a warehouse of companny A"""
# Required for `warehouse_id` and `location_id` to be visible in the view
self.user_a.groups_id += self.env.ref("stock.group_stock_multi_locations")
product = self.env['product.product'].create({
'type': 'product',
'name': 'shared product',
@@ -237,6 +245,8 @@ class TestMultiCompany(TransactionCase):
"""As a user of Company A, check it is not possible to change the company on an existing
orderpoint to Company B.
"""
# Required for `warehouse_id` and `location_id` to be visible in the view
self.user_a.groups_id += self.env.ref("stock.group_stock_multi_locations")
product = self.env['product.product'].create({
'type': 'product',
'name': 'shared product',
+41 -5
View File
@@ -459,9 +459,17 @@ class TestPacking(TestPackingCommon):
# Cancels the internal transfer and creates a new one.
internal_transfer.action_cancel()
internal_form = Form(self.env['stock.picking'])
# @api.depends('picking_type_id.show_operations')
# def _compute_show_operations(self):
# ...
# if self.env.context.get('force_detailed_view'):
# picking.show_operations = True
internal_form = Form(self.env['stock.picking'].with_context(force_detailed_view=True))
internal_form.picking_type_id = self.warehouse.int_type_id
internal_form.location_id = self.warehouse.wh_input_stock_loc_id
# The test specifically removes the ability to see the location fields
# grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
# self.env.user.write({'groups_id': [(3, grp_multi_loc.id)]})
# Hence, `internal_form.location_id` shouldn't be changed
with internal_form.package_level_ids_details.new() as pack_line:
pack_line.package_id = receipt_package.package_id
internal_transfer = internal_form.save()
@@ -598,9 +606,17 @@ class TestPacking(TestPackingCommon):
# Cancels the internal transfer and creates a new one.
internal_transfer.action_cancel()
internal_form = Form(self.env['stock.picking'])
# @api.depends('picking_type_id.show_operations')
# def _compute_show_operations(self):
# ...
# if self.env.context.get('force_detailed_view'):
# picking.show_operations = True
internal_form = Form(self.env['stock.picking'].with_context(force_detailed_view=True))
internal_form.picking_type_id = self.warehouse.int_type_id
internal_form.location_id = self.warehouse.wh_input_stock_loc_id
# The test specifically removes the ability to see the location fields
# grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
# self.env.user.write({'groups_id': [(3, grp_multi_loc.id)]})
# Hence, `internal_form.location_id` shouldn't be changed
with internal_form.package_level_ids_details.new() as pack_line:
pack_line.package_id = receipt_package.package_id
internal_transfer = internal_form.save()
@@ -736,6 +752,11 @@ class TestPacking(TestPackingCommon):
self.warehouse.delivery_steps = 'ship_only'
package = self.env["stock.quant.package"].create({"name": "Src Pack"})
self.env['stock.quant']._update_available_quantity(self.productA, self.stock_location, 100, package_id=package)
# Required for `package_level_ids_details` to be visible in the view
# <page string="Detailed Operations" attrs="{'invisible': [('show_operations', '=', False)]}">
# <field name="package_level_ids_details"
# attrs="{'invisible': ['|', ('picking_type_entire_packs', '=', False), ('show_operations', '=', False)]}"
self.warehouse.out_type_id.show_operations = True
self.warehouse.out_type_id.show_entire_packs = True
picking = self.env['stock.picking'].create({
'location_id': self.stock_location.id,
@@ -791,7 +812,12 @@ class TestPacking(TestPackingCommon):
move.product_id = self.productA
move.product_uom_qty = 75
picking.action_assign()
with Form(picking) as picking_form:
# @api.depends('picking_type_id.show_operations')
# def _compute_show_operations(self):
# ...
# if self.env.context.get('force_detailed_view'):
# picking.show_operations = True
with Form(picking.with_context(force_detailed_view=True)) as picking_form:
with picking_form.package_level_ids_details.new() as package_level:
package_level.package_id = package
with Form(picking) as picking_form:
@@ -873,6 +899,12 @@ class TestPacking(TestPackingCommon):
picking.action_put_in_pack()
picking.button_validate()
# Required for `package_level_ids_details` to be visible in the view
# <page string="Detailed Operations" attrs="{'invisible': [('show_operations', '=', False)]}">
# <field name="package_level_ids_details"
# attrs="{'invisible': ['|', ('picking_type_entire_packs', '=', False), ('show_operations', '=', False)]}"
delivery_type.show_operations = True
delivery_type.show_entire_packs = True
picking, _, _ = create_picking(delivery_type, delivery_type.default_location_src_id, self.customer_location)
packB = picking.package_level_ids[1]
with Form(picking) as picking_form:
@@ -938,6 +970,8 @@ class TestPacking(TestPackingCommon):
self.productA.weight = 1.0
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_storage_categories').id)]})
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_multi_locations').id)]})
# Required for `result_package_id` to be visible in the view
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_tracking_lot').id)]})
package_type = self.env['stock.package.type'].create({
'name': "Super Pallet",
@@ -1049,6 +1083,8 @@ class TestPacking(TestPackingCommon):
self.productB.weight = 1.0
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_storage_categories').id)]})
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_multi_locations').id)]})
# Required for `result_package_id` to be visible in the view
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_tracking_lot').id)]})
package_type = self.env['stock.package.type'].create({
'name': "Super Pallet",
+5
View File
@@ -110,6 +110,8 @@ class TestProcRule(TransactionCase):
self.assertEqual(move_dest.date_deadline, new_deadline, msg='deadline date should be unchanged')
def test_reordering_rule_1(self):
# Required for `location_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations')
warehouse = self.env['stock.warehouse'].search([], limit=1)
orderpoint_form = Form(self.env['stock.warehouse.orderpoint'])
orderpoint_form.product_id = self.product
@@ -156,6 +158,9 @@ class TestProcRule(TransactionCase):
reordering rule (RR). Add extra product to already confirmed picking => automatically
run another RR
"""
# Required for `location_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations')
self.productA = self.env['product.product'].create({
'name': 'Desk Combination',
'type': 'product',
+5 -1
View File
@@ -1181,11 +1181,15 @@ class TestReports(TestReportsCommon):
delivery_form.partner_id = self.partner
delivery_form.picking_type_id = picking_type_by_date
delivery_form.scheduled_date = datetime.now() + timedelta(days=5)
delivery_form.priority = '1'
with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product
move_line.product_uom_qty = 3
delivery_by_date_priority = delivery_form.save()
# <field name="priority" attrs="{'invisible': [('name','=','/')]}"/>
# The priority field is not visible until the name is set,
# which is done after a first save / the `create`
delivery_form.priority = '1'
delivery_by_date_priority = delivery_form.save()
delivery_by_date_priority.action_confirm()
# 'at_confirm' reservation => reservation_date = today
+2
View File
@@ -536,6 +536,8 @@ class TestWarehouse(TestStockCommon):
self.assertFalse(warehouse.pack_type_id.active)
def test_toggle_active_warehouse_2(self):
# Required for `delivery_steps` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_adv_location')
wh = Form(self.env['stock.warehouse'])
wh.name = "The attic of Willy"
wh.code = "WIL"
@@ -1011,6 +1011,8 @@ class TestAngloSaxonAccounting(TestStockValuationCommon):
"""
When reversing an invoice that contains some anglo-saxo AML, the new anglo-saxo AML should have the same value
"""
# Required for `account_id` to be visible in the view
self.env.user.groups_id += self.env.ref('account.group_account_readonly')
self.product1.categ_id.property_cost_method = 'average'
self._make_in_move(self.product1, 2, unit_cost=10)
@@ -67,6 +67,8 @@ class TestDropship(common.TransactionCase):
self.assertAlmostEqual(pol2.product_qty, sol2.product_uom_qty)
def test_00_dropship(self):
# Required for `route_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_adv_location')
# Create a vendor
supplier_dropship = self.env['res.partner'].create({'name': 'Vendor of Dropshipping test'})
@@ -10,6 +10,8 @@ from odoo.tests import tagged, common, Form
class TestLifoPrice(ValuationReconciliationTestCommon):
def test_lifoprice(self):
# Required for `uom_id` to be visible in the view
self.env.user.groups_id += self.env.ref('uom.group_uom')
# Set product category removal strategy as LIFO
product_category_001 = self.env['product.category'].create({
@@ -27,7 +29,18 @@ class TestLifoPrice(ValuationReconciliationTestCommon):
product_form.name = 'LIFO Ice Cream'
product_form.detailed_type = 'product'
product_form.categ_id = product_category_001
product_form.list_price = 100.0
# <field name="list_price" position="attributes">
# <attribute name="attrs">{'readonly': [('product_variant_count', '&gt;', 1)]}</attribute>
# <attribute name="invisible">1</attribute>
# </field>
# <field name="list_price" position="after">
# <field name="lst_price" class="oe_inline" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}"/>
# </field>
# @api.onchange('lst_price')
# def _set_product_lst_price(self):
# ...
# product.write({'list_price': value})
product_form.lst_price = 100.0
product_form.uom_id = self.env.ref('uom.product_uom_kgm')
product_form.uom_po_id = self.env.ref('uom.product_uom_kgm')
# these are not available (visible) in either product or variant
@@ -78,9 +91,17 @@ class TestLifoPrice(ValuationReconciliationTestCommon):
# Let us send some goods
self.company_data['default_warehouse'].out_type_id.show_operations = False
out_form = Form(self.env['stock.picking'])
# <field name="immediate_transfer" invisible="1"/>
# def _get_action(self, action_xmlid):
# ...
# context = {
# ...
# 'default_immediate_transfer': default_immediate_tranfer,
# ...
# }
# ...
out_form = Form(self.env['stock.picking'].with_context(default_immediate_transfer=True))
out_form.picking_type_id = self.company_data['default_warehouse'].out_type_id
out_form.immediate_transfer = True
with out_form.move_ids_without_package.new() as move:
move.product_id = product_lifo_icecream
move.quantity_done = 20.0
@@ -7,6 +7,10 @@ from odoo.tests import common, Form
class TestProcurementException(common.TransactionCase):
def test_00_procurement_exception(self):
# Required for `partner_invoice_id` to be visible in the view
self.env.user.groups_id += self.env.ref('account.group_delivery_invoice_address')
# Required for `route_id` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_adv_location')
res_partner_2 = self.env['res.partner'].create({'name': 'My Test Partner'})
res_partner_address = self.env['res.partner'].create({
@@ -17,7 +21,18 @@ class TestProcurementException(common.TransactionCase):
# I create a product with no supplier define for it.
product_form = Form(self.env['product.product'])
product_form.name = 'product with no seller'
product_form.list_price = 20.00
# <field name="list_price" position="attributes">
# <attribute name="attrs">{'readonly': [('product_variant_count', '&gt;', 1)]}</attribute>
# <attribute name="invisible">1</attribute>
# </field>
# <field name="list_price" position="after">
# <field name="lst_price" class="oe_inline" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}"/>
# </field>
# @api.onchange('lst_price')
# def _set_product_lst_price(self):
# ...
# product.write({'list_price': value})
product_form.lst_price = 20.00
product_form.categ_id = self.env.ref('product.product_category_1')
product_with_no_seller = product_form.save()
@@ -68,7 +68,7 @@ class TestStockValuation(ValuationReconciliationTestCommon):
# create the vendor bill
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.partner_id = vendor1
move_form.purchase_id = self.purchase_order1
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-self.purchase_order1.id)
move_form.invoice_date = move_form.date
for i in range(len(self.purchase_order1.order_line)):
with move_form.invoice_line_ids.edit(i) as line_form:
@@ -409,7 +409,7 @@ class TestLandedCostsWithPurchaseAndInv(TestStockValuationLCCommon):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.invoice_date = move_form.date
move_form.partner_id = order.partner_id
move_form.purchase_id = order
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
move = move_form.save()
move.action_post()
+11
View File
@@ -3,6 +3,7 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
from lxml import etree
from odoo import fields
from odoo.addons.survey.tests import common
@@ -17,6 +18,16 @@ class TestSurveyInvite(common.TestSurveyCommon):
res = super(TestSurveyInvite, self).setUp()
# by default signup not allowed
self.env["ir.config_parameter"].set_param('auth_signup.invitation_scope', 'b2b')
view = self.env.ref('survey.survey_invite_view_form').sudo()
tree = etree.fromstring(view.arch)
# Remove the invisible on `emails` to be able to test the onchange `_onchange_emails`
# which raises an error when attempting to change `emails`
# while the survey is set with `users_login_required` to True
# By default, `<field name="emails"/>` is invisible when `survey_users_login_required` is True,
# making it normally impossible to change by the user in the web client by default.
# For tests `test_survey_invite_authentication_nosignup` and `test_survey_invite_token_internal`
tree.xpath('//field[@name="emails"]')[0].attrib.pop('attrs')
view.arch = etree.tostring(tree)
return res
@users('survey_manager')
@@ -91,9 +91,12 @@ class TestCrmPerformance(CrmPerformanceCase):
""" Test a single lead creation using Form with a partner """
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=189): # tcf only: 180 - com runbot: 172
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['crm.lead']) as lead_form:
lead_form.partner_id = self.partners[0]
lead_form.name = 'Test Lead'
with self.debug_mode():
# {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]}
# lead.is_partner_visible = bool(lead.type == 'opportunity' or lead.partner_id or is_debug_mode)
with Form(self.env['crm.lead']) as lead_form:
lead_form.partner_id = self.partners[0]
lead_form.name = 'Test Lead'
_lead = lead_form.save()
@@ -109,14 +109,17 @@ class TestEventPerformance(EventPerformanceCase):
# no type, no website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=206): # tef only: 179? - com runbot: 160
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.website_menu = False
if has_social:
event_form.social_menu = False
_event = event_form.save()
# Require for `website_menu` to be visible
# <div name="event_menu_configuration" groups="base.group_no_one">
with self.debug_mode():
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.website_menu = False
if has_social:
event_form.social_menu = False
_event = event_form.save()
@users('event_user')
@warmup
@@ -127,14 +130,17 @@ class TestEventPerformance(EventPerformanceCase):
# no type, website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=671): # tef only: 638? - com runbot: 571 - ent runbot: 671
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.website_menu = True
if has_social:
event_form.social_menu = False
_event = event_form.save()
# Require for `website_menu` to be visible
# <div name="event_menu_configuration" groups="base.group_no_one">
with self.debug_mode():
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.website_menu = True
if has_social:
event_form.social_menu = False
_event = event_form.save()
@users('event_user')
@warmup
@@ -146,13 +152,16 @@ class TestEventPerformance(EventPerformanceCase):
# type and website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=700): # tef only: 601 - com runbot: 604 - ent runbot: 700
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.event_type_id = event_type
if has_social:
event_form.social_menu = False
# Require for `website_menu` to be visible
# <div name="event_menu_configuration" groups="base.group_no_one">
with self.debug_mode():
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.event_type_id = event_type
if has_social:
event_form.social_menu = False
@users('event_user')
@warmup
+3 -4
View File
@@ -208,10 +208,9 @@ class TestActivityFlow(TestActivityCommon):
'summary': 'Email Summary',
})
call_activity_type = ActivityType.create({'name': 'call'})
with Form(self.env['mail.activity'].with_context(default_res_model_id=self.env['ir.model']._get_id('mail.test.activity'))) as ActivityForm:
ActivityForm.res_model_id = self.env['ir.model']._get('mail.test.activity')
ActivityForm.res_id = self.test_record.id
with Form(self.env['mail.activity'].with_context(default_res_model_id=self.env['ir.model']._get_id('mail.test.activity'), default_res_id=self.test_record.id)) as ActivityForm:
# `res_model_id` and `res_id` are invisible, see view `mail.mail_activity_view_form_popup`
# they must be set using defaults, see `action_feedback_schedule_next`
ActivityForm.activity_type_id = call_activity_type
# activity summary should be empty
self.assertEqual(ActivityForm.summary, False)
@@ -12,6 +12,8 @@ class TestFormCreate(TransactionCase):
"""
def test_create_res_partner(self):
# Required for `property_account_payable_id`, `property_account_receivable_id` to be visible in the view
self.env.user.groups_id += self.env.ref('account.group_account_readonly')
partner_form = Form(self.env['res.partner'])
partner_form.name = 'a partner'
# YTI: Clean that brol
+6 -1
View File
@@ -121,7 +121,12 @@ class TestPartner(TransactionCase):
self.env['res.partner'].with_context(default_lang='de_DE'),
'base.view_partner_form'
)
partner_form.is_company = True
# <field name="is_company" invisible="1"/>
# <field name="company_type" widget="radio" options="{'horizontal': true}"/>
# @api.onchange('company_type')
# def onchange_company_type(self):
# self.is_company = (self.company_type == 'company')
partner_form.company_type = 'company'
partner_form.name = "Test Company"
self.assertEqual(partner_form.lang, 'de_DE', "New partner's lang should take default from context")
with partner_form.child_ids.new() as child:
+3 -1
View File
@@ -277,7 +277,9 @@ class TestUsers2(TransactionCase):
user_groups_ids = [str(group_id) for group_id in sorted(user_groups.ids)]
group_field_name = f"sel_groups_{'_'.join(user_groups_ids)}"
user_form = Form(self.env['res.users'], view='base.view_users_form')
# <group col="4" attrs="{'invisible': [('sel_groups_1_9_10', '!=', 1)]}" groups="base.group_no_one" class="o_label_nowrap">
with self.debug_mode():
user_form = Form(self.env['res.users'], view='base.view_users_form')
user_form.name = "Test"
user_form.login = "Test"
self.assertFalse(user_form.share)
+53 -5
View File
@@ -52,6 +52,7 @@ from odoo import api
from odoo.models import BaseModel
from odoo.exceptions import AccessError
from odoo.modules.registry import Registry
from odoo.osv import expression
from odoo.osv.expression import normalize_domain, TRUE_LEAF, FALSE_LEAF
from odoo.service import security
from odoo.sql_db import BaseCursor, Cursor
@@ -438,6 +439,23 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase):
self.uid = old_uid
self.env = self.env(user=self.uid)
@contextmanager
def debug_mode(self):
""" Enable the effects of group 'base.group_no_one'; mainly useful with :class:`Form`. """
origin_user_has_groups = BaseModel.user_has_groups
def user_has_groups(self, groups):
group_set = set(groups.split(','))
if '!base.group_no_one' in group_set:
return False
elif 'base.group_no_one' in group_set:
group_set.remove('base.group_no_one')
return not group_set or origin_user_has_groups(self, ','.join(group_set))
return origin_user_has_groups(self, groups)
with patch('odoo.models.BaseModel.user_has_groups', user_has_groups):
yield
@contextmanager
def _assertRaises(self, exception, *, msg=None):
""" Context manager that clears the environment upon failure. """
@@ -1935,19 +1953,46 @@ class Form(object):
* pre-processed modifiers (map of modifier name to json-loaded domain)
* pre-processed onchanges list
"""
inherited_modifiers = ['invisible']
fvg['fields'].setdefault('id', {'type': 'id'})
# pre-resolve modifiers & bind to arch toplevel
modifiers = fvg['modifiers'] = {'id': {'required': False, 'readonly': True}}
modifiers = fvg['modifiers'] = {'id': {'required': [FALSE_LEAF], 'readonly': [TRUE_LEAF]}}
contexts = fvg['contexts'] = {}
order = fvg['fields_ordered'] = []
for f in fvg['tree'].xpath('.//field[count(ancestor::field) = %s]' % fvg['tree'].xpath('count(ancestor::field)')):
field_level = fvg['tree'].xpath('count(ancestor::field)')
for f in fvg['tree'].xpath('.//field[count(ancestor::field) = %s]' % field_level):
fname = f.get('name')
order.append(fname)
modifiers[fname] = {
modifier: bool(domain) if isinstance(domain, int) else normalize_domain(domain)
node_modifiers = {
modifier: ([TRUE_LEAF] if domain else [FALSE_LEAF]) if isinstance(domain, int) else normalize_domain(domain)
for modifier, domain in json.loads(f.get('modifiers', '{}')).items()
}
for a in f.xpath('ancestor::*[@modifiers][count(ancestor::field) = %s]' % field_level):
ancestor_modifiers = json.loads(a.get('modifiers'))
for modifier in inherited_modifiers:
if modifier in ancestor_modifiers:
domain = ancestor_modifiers[modifier]
ancestor_domain = ([TRUE_LEAF] if domain else [FALSE_LEAF]) if isinstance(domain, int) else normalize_domain(domain)
node_domain = node_modifiers.get(modifier, [])
# Combine the field modifiers with his ancestor modifiers with an OR connector
# e.g. A field is invisible if its own invisible modifier is True
# OR if one of its ancestor invisible modifier is True
node_modifiers[modifier] = expression.OR([ancestor_domain, node_domain])
if fname in modifiers:
# The field is multiple times in the view, combine the modifier domains with an AND connector
# e.g. a field is invisible if all occurences of the field are invisible in the view.
# e.g. a field is readonly if all occurences of the field are readonly in the view.
for modifier in set(node_modifiers.keys()).union(modifiers[fname].keys()):
modifiers[fname][modifier] = expression.AND([
modifiers[fname].get(modifier, [FALSE_LEAF]),
node_modifiers.get(modifier, [FALSE_LEAF]),
])
else:
modifiers[fname] = node_modifiers
ctx = f.get('context')
if ctx:
contexts[fname] = ctx
@@ -2096,9 +2141,10 @@ class Form(object):
assert descr['type'] not in ('many2many', 'one2many'), \
"Can't set an o2m or m2m field, manipulate the corresponding proxies"
# TODO: consider invisible to be the same as readonly?
assert not self._get_modifier(field, 'readonly'), \
"can't write on readonly field {}".format(field)
assert not self._get_modifier(field, 'invisible'), \
"can't write on invisible field {}".format(field)
if descr['type'] == 'many2one':
assert isinstance(value, BaseModel) and value._name == descr['relation']
@@ -2491,6 +2537,8 @@ class X2MProxy(object):
def _assert_editable(self):
assert not self._parent._get_modifier(self._field, 'readonly'),\
'field %s is not editable' % self._field
assert not self._parent._get_modifier(self._field, 'invisible'),\
'field %s is not visible' % self._field
class O2MProxy(X2MProxy):
""" O2MProxy()