[IMP] models: use slots for BaseModel
This restricts the attributes of a BaseModel instance to `env`, `_ids` and `_prefetch_ids`. This way, one can only assign fields on a record; other assignments are programming errors. This also reduces the memory footprint of records from 168 to 64 bytes (-62%), and makes their instanciation faster. closes odoo/odoo#51075 Related: odoo/enterprise#10529 Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
This commit is contained in:
@@ -1252,7 +1252,7 @@ class AccountBankStatementLine(models.Model):
|
||||
|
||||
# Update the payment date to match the current bank statement line's date.
|
||||
if counterpart_line.payment_id:
|
||||
counterpart_line.payment_id.payment_date = self.date
|
||||
counterpart_line.payment_id.date = self.date
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# BUSINESS METHODS
|
||||
|
||||
@@ -1249,15 +1249,15 @@ class TestAccountBankStatement(TestAccountBankStatementCommon):
|
||||
test_line_1 = test_move.line_ids.filtered(lambda line: line.name == 'test line 1')
|
||||
test_line_2 = test_move.line_ids.filtered(lambda line: line.name == 'test line 2')
|
||||
|
||||
def _prepare_reconciliation_with_payments(lines_vals_list):
|
||||
statement_line_wo_patch = self.env['account.bank.statement.line'].browse(self.statement_line.id)
|
||||
return statement_line_wo_patch._prepare_reconciliation(lines_vals_list, create_payment_for_invoice=True)
|
||||
statement_line = self.statement_line
|
||||
StatementLine_prepare_reconciliation = type(statement_line)._prepare_reconciliation
|
||||
|
||||
with patch.object(
|
||||
self.statement_line,
|
||||
'_prepare_reconciliation',
|
||||
lambda *args, **kwargs: _prepare_reconciliation_with_payments(*args)
|
||||
):
|
||||
def _prepare_reconciliation(self, lines_vals_list, create_payment_for_invoice=False):
|
||||
if self == statement_line:
|
||||
create_payment_for_invoice = True
|
||||
return StatementLine_prepare_reconciliation(self, lines_vals_list, create_payment_for_invoice)
|
||||
|
||||
with patch.object(type(statement_line), '_prepare_reconciliation', _prepare_reconciliation):
|
||||
self.statement_line.reconcile([
|
||||
{'id': test_line_1.id, 'balance': -50.0},
|
||||
{'id': test_line_2.id},
|
||||
|
||||
@@ -76,29 +76,27 @@ class HrHolidaySummaryReport(models.AbstractModel):
|
||||
res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name
|
||||
date_from += timedelta(1)
|
||||
count += holiday.number_of_days
|
||||
self.sum = count
|
||||
return res
|
||||
employee = self.env['hr.employee'].browse(empid)
|
||||
return {'emp': employee.name, 'display': res, 'sum': count}
|
||||
|
||||
def _get_data_from_report(self, data):
|
||||
res = []
|
||||
Employee = self.env['hr.employee']
|
||||
if 'depts' in data:
|
||||
for department in self.env['hr.department'].browse(data['depts']):
|
||||
res.append({'dept' : department.name, 'data': [], 'color': self._get_day(data['date_from'])})
|
||||
for emp in Employee.search([('department_id', '=', department.id)]):
|
||||
res[len(res)-1]['data'].append({
|
||||
'emp': emp.name,
|
||||
'display': self._get_leaves_summary(data['date_from'], emp.id, data['holiday_type']),
|
||||
'sum': self.sum
|
||||
})
|
||||
elif 'emp' in data:
|
||||
res.append({'data':[]})
|
||||
for emp in Employee.browse(data['emp']):
|
||||
res[0]['data'].append({
|
||||
'emp': emp.name,
|
||||
'display': self._get_leaves_summary(data['date_from'], emp.id, data['holiday_type']),
|
||||
'sum': self.sum
|
||||
res.append({
|
||||
'dept': department.name,
|
||||
'data': [
|
||||
self._get_leaves_summary(data['date_from'], emp.id, data['holiday_type'])
|
||||
for emp in Employee.search([('department_id', '=', department.id)])
|
||||
],
|
||||
'color': self._get_day(data['date_from']),
|
||||
})
|
||||
elif 'emp' in data:
|
||||
res.append({'data': [
|
||||
self._get_leaves_summary(data['date_from'], emp.id, data['holiday_type'])
|
||||
for emp in Employee.browse(data['emp'])
|
||||
]})
|
||||
return res
|
||||
|
||||
def _get_holidays_status(self):
|
||||
|
||||
@@ -9,7 +9,7 @@ class TestUBL(common.TransactionCase):
|
||||
super(TestUBL, self).setUp()
|
||||
# Force user Belgium country.
|
||||
self.env.user.company_id = self.env['res.company'].create({'name': 'MyCompany'})
|
||||
self.env.user.company_id.country = self.env.ref('base.be')
|
||||
self.env.user.company_id.country_id = self.env.ref('base.be')
|
||||
self.env.ref('l10n_be.l10nbe_chart_template').try_loading()
|
||||
self.partner_id = self.env['res.partner'].create({'name': 'TestUser', 'vat': 'BE0123456789'})
|
||||
|
||||
|
||||
@@ -1264,20 +1264,6 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon):
|
||||
production sooner """
|
||||
self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2
|
||||
self.env['mrp.workcenter'].search([]).write({'tz': 'UTC'}) # compute all date in UTC
|
||||
mrp_routing_0 = self.env['mrp.routing'].create({
|
||||
'name': 'Primary Assembly',
|
||||
})
|
||||
mrp_workcenter_3 = self.env['mrp.workcenter'].create({
|
||||
'name': 'Assembly Line 1',
|
||||
'resource_calendar_id': self.env.ref('resource.resource_calendar_std').id,
|
||||
})
|
||||
mrp_routing_workcenter_0 = self.env['mrp.routing.workcenter'].create({
|
||||
'routing_id': mrp_routing_0.id,
|
||||
'workcenter_id': mrp_workcenter_3.id,
|
||||
'name': 'Manual Assembly',
|
||||
'time_cycle': 60,
|
||||
})
|
||||
self.planning_bom.routing = mrp_routing_0
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.product_id = self.product_4
|
||||
mo_form.bom_id = self.planning_bom
|
||||
|
||||
@@ -31,13 +31,14 @@ class PaymentWizard(models.TransientModel):
|
||||
acc_number = fields.Char("Account Number", default=lambda self: self._get_default_payment_acquirer_onboarding_value('acc_number'))
|
||||
manual_post_msg = fields.Html("Payment Instructions")
|
||||
|
||||
_data_fetched = fields.Boolean(store=False)
|
||||
|
||||
@api.onchange('journal_name', 'acc_number')
|
||||
def _set_manual_post_msg_value(self):
|
||||
self.manual_post_msg = _('<h3>Please make a payment to: </h3><ul><li>Bank: %s</li><li>Account Number: %s</li><li>Account Holder: %s</li></ul>') %\
|
||||
(self.journal_name or _("Bank") , self.acc_number or _("Account"), self.env.company.name)
|
||||
|
||||
_payment_acquirer_onboarding_cache = {}
|
||||
_data_fetched = False
|
||||
|
||||
def _get_manual_payment_acquirer(self, env=None):
|
||||
if env is None:
|
||||
|
||||
@@ -51,7 +51,6 @@ class TestAngloSaxonCommon(common.TransactionCase):
|
||||
self.cash_journal = self.env['account.journal'].create({'name': 'CASH journal', 'type': 'cash', 'code': 'CSH00'})
|
||||
self.sale_journal = self.env['account.journal'].create({'name': 'SALE journal', 'type': 'sale', 'code': 'INV00'})
|
||||
self.pos_config.invoice_journal_id = self.sale_journal
|
||||
self.pos_config.journal_ids = [self.cash_journal.id]
|
||||
self.cash_payment_method = self.env['pos.payment.method'].create({
|
||||
'name': 'Cash Test',
|
||||
'is_cash_count': True,
|
||||
|
||||
@@ -494,8 +494,7 @@ class TestStockValuationWithCOA(AccountTestCommon):
|
||||
})
|
||||
|
||||
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
||||
self.product1.product_tmpl_id.categ_id.valuation = 'real_time'
|
||||
|
||||
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
||||
|
||||
# Receive 10@10 ; create the vendor bill
|
||||
po1 = self.env['purchase.order'].create({
|
||||
|
||||
@@ -140,9 +140,9 @@ class TestRobustness(SavepointCase):
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
|
||||
move1.result_package_id = False
|
||||
|
||||
self.assertEqual(move1.move_line_ids.package_id, package)
|
||||
package.unpack()
|
||||
self.assertEqual(move1.move_line_ids.package_id, self.env['stock.quant.package'])
|
||||
|
||||
# unreserve
|
||||
move1._do_unreserve()
|
||||
|
||||
@@ -108,7 +108,7 @@ class TestStockValuationCommon(SavepointCase):
|
||||
'picking_type_id': self.picking_type_out.id,
|
||||
})
|
||||
if unit_cost:
|
||||
dropshipped.unit_cost = unit_cost
|
||||
dropshipped.price_unit = unit_cost
|
||||
dropshipped._action_confirm()
|
||||
dropshipped._action_assign()
|
||||
dropshipped.move_line_ids.qty_done = quantity
|
||||
|
||||
@@ -183,7 +183,7 @@ class TestStockValuation(StockAccountTestCommon):
|
||||
|
||||
def test_dropship_fifo_perpetual_continental_ordered(self):
|
||||
self.env.company.anglo_saxon_accounting = False
|
||||
self.product1.product_tmpl_id.categ_id.proprty_cost_method = 'fifo'
|
||||
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
||||
self.product1.product_tmpl_id.standard_price = 10
|
||||
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
||||
self.product1.product_tmpl_id.invoice_policy = 'order'
|
||||
|
||||
@@ -334,8 +334,6 @@ class StockLandedCostLine(models.Model):
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_id(self):
|
||||
if not self.product_id:
|
||||
self.quantity = 0.0
|
||||
self.name = self.product_id.name or ''
|
||||
self.split_method = self.product_id.product_tmpl_id.split_method_landed_cost or self.split_method or 'equal'
|
||||
self.price_unit = self.product_id.standard_price or 0.0
|
||||
|
||||
@@ -181,9 +181,6 @@ class TestActivityFlow(TestActivityCommon):
|
||||
'datas': 'testtest',
|
||||
})
|
||||
|
||||
# Adding the attachments to the activity
|
||||
activity.attachment_ids = attachments
|
||||
|
||||
# Checking if the attachment has been forwarded to the message
|
||||
# when marking an activity as "Done"
|
||||
activity.action_feedback()
|
||||
|
||||
@@ -405,10 +405,6 @@ class View(models.Model):
|
||||
func = getattr(values['main_object'], 'get_backend_menu_id', False)
|
||||
values['backend_menu_id'] = func and func() or self.env.ref('website.menu_website_configuration').id
|
||||
|
||||
# Fallback incase main_object dont't inherit 'website.seo.metadata'
|
||||
if not hasattr(values['main_object'], 'get_website_meta'):
|
||||
values['main_object'].get_website_meta = lambda: {}
|
||||
|
||||
if self._context != new_context:
|
||||
self = self.with_context(new_context)
|
||||
return super(View, self)._render(values, engine=engine, minimal_qcontext=minimal_qcontext)
|
||||
|
||||
@@ -1011,3 +1011,8 @@ class BaseModel(models.AbstractModel):
|
||||
return self.website_id._get_http_domain()
|
||||
else:
|
||||
return super(BaseModel, self).get_base_url()
|
||||
|
||||
def get_website_meta(self):
|
||||
# dummy version of 'get_website_meta' above; this is a graceful fallback
|
||||
# for models that don't inherit from 'website.seo.metadata'
|
||||
return {}
|
||||
|
||||
@@ -234,6 +234,7 @@ class frozendict(dict):
|
||||
|
||||
|
||||
class QWeb(object):
|
||||
__slots__ = ()
|
||||
|
||||
_void_elements = frozenset([
|
||||
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',
|
||||
|
||||
@@ -14,6 +14,8 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResConfigModuleInstallationMixin(object):
|
||||
__slots__ = ()
|
||||
|
||||
@api.model
|
||||
def _install_modules(self, modules):
|
||||
""" Install the requested modules.
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests import common
|
||||
|
||||
ANSWER_TO_ULTIMATE_QUESTION = 42
|
||||
|
||||
class TestAttributes(common.TransactionCase):
|
||||
|
||||
def test_we_can_add_attributes(self):
|
||||
def test_we_cannot_add_attributes(self):
|
||||
Model = self.env['test_new_api.category']
|
||||
instance = Model.create({'name': 'Foo'})
|
||||
|
||||
# assign an unknown attribute
|
||||
instance.unknown = ANSWER_TO_ULTIMATE_QUESTION
|
||||
|
||||
# Does the attribute exist in the instance of the model ?
|
||||
self.assertTrue(hasattr(instance, 'unknown'))
|
||||
|
||||
# Is it the right type ?
|
||||
self.assertIsInstance(instance.unknown, int)
|
||||
|
||||
# Is it the right value, in case of, we don't know ;-)
|
||||
self.assertEqual(instance.unknown, ANSWER_TO_ULTIMATE_QUESTION)
|
||||
|
||||
# We are paranoiac !
|
||||
self.assertEqual(getattr(instance, 'unknown'), ANSWER_TO_ULTIMATE_QUESTION)
|
||||
with self.assertRaises(AttributeError):
|
||||
instance.unknown = 42
|
||||
|
||||
+9
-2
@@ -53,7 +53,7 @@ from . import api
|
||||
from . import tools
|
||||
from .exceptions import AccessError, MissingError, ValidationError, UserError
|
||||
from .osv.query import Query
|
||||
from .tools import frozendict, lazy_classproperty, lazy_property, ormcache, \
|
||||
from .tools import frozendict, lazy_classproperty, ormcache, \
|
||||
Collector, LastOrderedSet, OrderedSet, IterableGenerator, \
|
||||
groupby, unique
|
||||
from .tools.config import config
|
||||
@@ -151,6 +151,10 @@ class MetaModel(api.Meta):
|
||||
|
||||
module_to_models = defaultdict(list)
|
||||
|
||||
def __new__(meta, name, bases, attrs):
|
||||
attrs.setdefault('__slots__', ())
|
||||
return super().__new__(meta, name, bases, attrs)
|
||||
|
||||
def __init__(self, name, bases, attrs):
|
||||
if not self._register:
|
||||
self._register = True
|
||||
@@ -253,6 +257,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
To create a class that should not be instantiated,
|
||||
the :attr:`~odoo.models.BaseModel._register` attribute may be set to False.
|
||||
"""
|
||||
__slots__ = ['env', '_ids', '_prefetch_ids']
|
||||
|
||||
_auto = False
|
||||
"""Whether a database table should be created (default: ``True``).
|
||||
@@ -5607,7 +5612,7 @@ Record ids: %(records)s
|
||||
# Cache and recomputation management
|
||||
#
|
||||
|
||||
@lazy_property
|
||||
@property
|
||||
def _cache(self):
|
||||
""" Return the cache of ``self``, mapping field names to values. """
|
||||
return RecordCache(self)
|
||||
@@ -6226,6 +6231,8 @@ collections.Sequence.register(BaseModel)
|
||||
|
||||
class RecordCache(MutableMapping):
|
||||
""" A mapping from field names to values, to read and update the cache of a record. """
|
||||
__slots__ = ['_record']
|
||||
|
||||
def __init__(self, record):
|
||||
assert len(record) == 1, "Unexpected RecordCache(%s)" % record
|
||||
self._record = record
|
||||
|
||||
Reference in New Issue
Block a user