diff --git a/addons/analytic_user_function/analytic_user_function_view.xml b/addons/analytic_user_function/analytic_user_function_view.xml
index 67d8672f44b..132f44ebef5 100644
--- a/addons/analytic_user_function/analytic_user_function_view.xml
+++ b/addons/analytic_user_function/analytic_user_function_view.xml
@@ -78,8 +78,10 @@
-
-
+
+ [('type','=','normal'),('state', '<>', 'close')]
+ on_change_account_id(account_id, user_id, unit_amount)
+ {'default_invoice_on_timesheets': 1}
@@ -90,8 +92,9 @@
hr.analytic.timesheet
-
-
+
+ [('type','=','normal'),('state', '<>', 'close')]
+ on_change_account_id(account_id, user_id, unit_amount)
@@ -112,8 +115,9 @@
hr.analytic.timesheet
-
-
+
+ [('type','=','normal'),('state', '<>', 'close')]
+ on_change_account_id(account_id, user_id, unit_amount)
diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py
index 79be277de09..70c6cceaed4 100644
--- a/addons/google_calendar/google_calendar.py
+++ b/addons/google_calendar/google_calendar.py
@@ -536,7 +536,7 @@ class google_calendar(osv.AbstractModel):
for user_to_sync in ids:
_logger.info("Calendar Synchro - Starting synchronization for a new user [%s] " % user_to_sync)
try:
- resp = self.synchronize_events(cr, uid, [user_to_sync], lastSync=True, context=None)
+ resp = self.synchronize_events(cr, user_to_sync, False, lastSync=True, context=None)
if resp.get("status") == "need_reset":
_logger.info("[%s] Calendar Synchro - Failed - NEED RESET !" % user_to_sync)
else:
@@ -564,7 +564,7 @@ class google_calendar(osv.AbstractModel):
user_to_sync = ids and ids[0] or uid
current_user = self.pool['res.users'].browse(cr, SUPERUSER_ID, user_to_sync, context=context)
- st, current_google, ask_time = self.get_calendar_primary_id(cr, uid, context=context)
+ st, current_google, ask_time = self.get_calendar_primary_id(cr, user_to_sync, context=context)
if current_user.google_calendar_cal_id:
if current_google != current_user.google_calendar_cal_id:
@@ -577,8 +577,8 @@ class google_calendar(osv.AbstractModel):
"url": ''
}
- if lastSync and self.get_last_sync_date(cr, uid, context=context) and not self.get_disable_since_synchro(cr, uid, context=context):
- lastSync = self.get_last_sync_date(cr, uid, context)
+ if lastSync and self.get_last_sync_date(cr, user_to_sync, context=context) and not self.get_disable_since_synchro(cr, user_to_sync, context=context):
+ lastSync = self.get_last_sync_date(cr, user_to_sync, context)
_logger.info("[%s] Calendar Synchro - MODE SINCE_MODIFIED : %s !" % (user_to_sync, lastSync.strftime(DEFAULT_SERVER_DATETIME_FORMAT)))
else:
lastSync = False
@@ -589,14 +589,14 @@ class google_calendar(osv.AbstractModel):
_logger.info("[%s] Calendar Synchro - MODE FULL SYNCHRO - NEW CAL ID" % user_to_sync)
new_ids = []
- new_ids += self.create_new_events(cr, uid, context=context)
- new_ids += self.bind_recurring_events_to_google(cr, uid, context)
+ new_ids += self.create_new_events(cr, user_to_sync, context=context)
+ new_ids += self.bind_recurring_events_to_google(cr, user_to_sync, context)
- res = self.update_events(cr, uid, lastSync, context)
+ res = self.update_events(cr, user_to_sync, lastSync, context)
current_user.write({'google_calendar_last_sync_date': ask_time})
return {
- "status": res and "need_refresh" or "no_new_event_form_google",
+ "status": res and "need_refresh" or "no_new_event_from_google",
"url": ''
}
diff --git a/addons/google_calendar/google_calendar.xml b/addons/google_calendar/google_calendar.xml
index 40bca105d1e..d2516f10233 100644
--- a/addons/google_calendar/google_calendar.xml
+++ b/addons/google_calendar/google_calendar.xml
@@ -3,7 +3,6 @@
-
Calendar synchronization
diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml
index 0a3f5e79c79..1f66de06c7f 100644
--- a/addons/hr_timesheet/hr_timesheet_view.xml
+++ b/addons/hr_timesheet/hr_timesheet_view.xml
@@ -11,7 +11,7 @@
-
+
@@ -49,7 +49,7 @@
-
+
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml b/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml
index b7f03683945..9250677ab28 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml
@@ -45,8 +45,10 @@
hr.analytic.timesheet
-
-
+
+ [('type','in',['normal','contract']),('state', '!=', 'close'),('invoice_on_timesheets','=',1)]
+ on_change_account_id(account_id, user_id)
+ {'default_invoice_on_timesheets': 1}
@@ -67,8 +69,10 @@
hr.analytic.timesheet
-
-
+
+ [('type','in',['normal','contract']),('state', '!=', 'close'),('invoice_on_timesheets','=',1)]
+ on_change_account_id(account_id, user_id)
+ {'default_invoice_on_timesheets': 1}
diff --git a/addons/pad/__openerp__.py b/addons/pad/__openerp__.py
index b4bbc722f8a..24a05414206 100644
--- a/addons/pad/__openerp__.py
+++ b/addons/pad/__openerp__.py
@@ -8,7 +8,7 @@ Adds enhanced support for (Ether)Pad attachments in the web client.
===================================================================
Lets the company customize which Pad installation should be used to link to new
-pads (by default, http://ietherpad.com/).
+pads (by default, http://etherpad.com/).
""",
'author': 'OpenERP SA',
'website': 'https://www.odoo.com/page/notes',
diff --git a/addons/project_issue/security/project_issue_security.xml b/addons/project_issue/security/project_issue_security.xml
index c8ed12086e4..ea7674ea794 100644
--- a/addons/project_issue/security/project_issue_security.xml
+++ b/addons/project_issue/security/project_issue_security.xml
@@ -16,7 +16,7 @@
('project_id.privacy_visibility', 'in', ['public', 'employees']),
'&',
('project_id.privacy_visibility', '=', 'followers'),
- ('message_follower_ids', 'in', [user.id]),
+ ('message_follower_ids', 'in', [user.partner_id.id]),
]
diff --git a/addons/report/models/report.py b/addons/report/models/report.py
index 574aa3c7947..1be7d6efa0b 100644
--- a/addons/report/models/report.py
+++ b/addons/report/models/report.py
@@ -87,6 +87,31 @@ class Report(osv.Model):
#--------------------------------------------------------------------------
# Extension of ir_ui_view.render with arguments frequently used in reports
#--------------------------------------------------------------------------
+
+ def translate_doc(self, cr, uid, doc_id, model, lang_field, template, values, context=None):
+ """Helper used when a report should be translated into a specific lang.
+
+
+
+
+
+ :param doc_id: id of the record to translate
+ :param model: model of the record to translate
+ :param lang_field': field of the record containing the lang
+ :param template: name of the template to translate into the lang_field
+ """
+ ctx = context.copy()
+ doc = self.pool[model].browse(cr, uid, doc_id, context=ctx)
+ qcontext = values.copy()
+ # Do not force-translate if we chose to display the report in a specific lang
+ if ctx.get('translatable') is True:
+ qcontext['o'] = doc
+ else:
+ # Reach the lang we want to translate the doc into
+ ctx['lang'] = eval('doc.%s' % lang_field, {'doc': doc})
+ qcontext['o'] = self.pool[model].browse(cr, uid, doc_id, context=ctx)
+ return self.pool['ir.ui.view'].render(cr, uid, template, qcontext, context=ctx)
+
def render(self, cr, uid, ids, template, values=None, context=None):
"""Allow to render a QWeb template python-side. This function returns the 'ir.ui.view'
render but embellish it with some variables/methods used in reports.
@@ -105,28 +130,7 @@ class Report(osv.Model):
view_obj = self.pool['ir.ui.view']
def translate_doc(doc_id, model, lang_field, template):
- """Helper used when a report should be translated into a specific lang.
-
-
-
-
-
- :param doc_id: id of the record to translate
- :param model: model of the record to translate
- :param lang_field': field of the record containing the lang
- :param template: name of the template to translate into the lang_field
- """
- ctx = context.copy()
- doc = self.pool[model].browse(cr, uid, doc_id, context=ctx)
- qcontext = values.copy()
- # Do not force-translate if we chose to display the report in a specific lang
- if ctx.get('translatable') is True:
- qcontext['o'] = doc
- else:
- # Reach the lang we want to translate the doc into
- ctx['lang'] = eval('doc.%s' % lang_field, {'doc': doc})
- qcontext['o'] = self.pool[model].browse(cr, uid, doc_id, context=ctx)
- return view_obj.render(cr, uid, template, qcontext, context=ctx)
+ return self.translate_doc(cr, uid, doc_id, model, lang_field, template, values, context=context)
user = self.pool['res.users'].browse(cr, uid, uid)
website = None
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index b01c8090491..95472605143 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -3478,7 +3478,7 @@ instance.web.form.M2ODialog = instance.web.Dialog.extend({
var text = _.str.sprintf(_t("You are creating a new %s, are you sure it does not exist yet?"), self.name);
this.$("p").text( text );
this.$buttons.html(QWeb.render("M2ODialog.buttons"));
- this.$("input").val(this.getParent().last_query);
+ this.$("input").val(this.getParent().$input.val());
this.$buttons.find(".oe_form_m2o_qc_button").click(function(e){
if (self.$("input").val() != ''){
self.getParent()._quick_create(self.$("input").val());
@@ -3660,13 +3660,13 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
self.display_value_backup = {};
self.display_value["" + self.last_search[0][0]] = self.last_search[0][1];
self.reinit_value(self.last_search[0][0]);
+ self.last_search = []
} else {
used = true;
self.render_value();
}
} else {
used = true;
- self.reinit_value(false);
}
self.floating = false;
}
@@ -3740,7 +3740,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
// disabled to solve a bug, but may cause others
//close: anyoneLoosesFocus,
minLength: 0,
- delay: 250,
+ delay: 200,
});
var appendTo = this.$input.parents('.oe-view-manager-content, .modal-dialog').last();
if (appendTo.length === 0) {
diff --git a/addons/website_quote/models/order.py b/addons/website_quote/models/order.py
index 81f19b3c65f..6b15301c7cd 100644
--- a/addons/website_quote/models/order.py
+++ b/addons/website_quote/models/order.py
@@ -71,7 +71,7 @@ class sale_quote_line(osv.osv):
if product_obj.description_sale:
name += '\n' + product_obj.description_sale
vals.update({
- 'price_unit': product_obj.list_price,
+ 'price_unit': product_obj.lst_price,
'product_uom_id': product_obj.uom_id.id,
'website_description': product_obj and (product_obj.quote_description or product_obj.website_description) or '',
'name': name,
diff --git a/addons/website_report/__init__.py b/addons/website_report/__init__.py
index e69de29bb2d..a0526c7c636 100644
--- a/addons/website_report/__init__.py
+++ b/addons/website_report/__init__.py
@@ -0,0 +1,2 @@
+import controllers
+import report
diff --git a/addons/website_report/controllers/__init__.py b/addons/website_report/controllers/__init__.py
new file mode 100644
index 00000000000..8ee9bae18d9
--- /dev/null
+++ b/addons/website_report/controllers/__init__.py
@@ -0,0 +1 @@
+import main
diff --git a/addons/website_report/controllers/main.py b/addons/website_report/controllers/main.py
new file mode 100644
index 00000000000..11b4257b6c6
--- /dev/null
+++ b/addons/website_report/controllers/main.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2014-Today OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.website.controllers.main import Website
+from openerp.http import request, route
+
+
+class Website(Website):
+
+ @route()
+ def customize_template_get(self, xml_id, full=False):
+ res = super(Website, self).customize_template_get(xml_id, full=full)
+ if full and request.session.get('report_view_ids'):
+ res += request.session['report_view_ids']
+ return res
diff --git a/addons/website_report/report.py b/addons/website_report/report.py
new file mode 100644
index 00000000000..4da33a4d7c2
--- /dev/null
+++ b/addons/website_report/report.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2014-Today OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.web.http import request
+from openerp.osv import osv
+
+
+class Report(osv.Model):
+ _inherit = 'report'
+
+ def translate_doc(self, cr, uid, doc_id, model, lang_field, template, values, context=None):
+ if request and hasattr(request, 'website'):
+ if request.website is not None:
+ v = request.website.get_template(template)
+ request.session['report_view_ids'].append({
+ 'name': v.name,
+ 'id': v.id,
+ 'xml_id': v.xml_id,
+ 'inherit_id': v.inherit_id.id,
+ 'header': False,
+ 'active': v.active,
+ })
+ return super(Report, self).translate_doc(cr, uid, doc_id, model, lang_field, template, values, context=context)
+
+ def render(self, cr, uid, ids, template, values=None, context=None):
+ if request and hasattr(request, 'website'):
+ if request.website is not None:
+ request.session['report_view_ids'] = []
+ return super(Report, self).render(cr, uid, ids, template, values=values, context=context)
diff --git a/doc/_themes/odoodoc/static/odoodoc.css_t b/doc/_themes/odoodoc/static/odoodoc.css_t
index a70db9fb825..64a14dc61e4 100644
--- a/doc/_themes/odoodoc/static/odoodoc.css_t
+++ b/doc/_themes/odoodoc/static/odoodoc.css_t
@@ -1 +1 @@
-@import "style.css";
+@import url("style.css");
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index 5b2a013aa05..aeead6e664e 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -1228,20 +1228,25 @@ class ir_model_data(osv.osv):
and a module in ir_model_data and noupdate set to false, but not
present in self.loads.
"""
- if not modules:
+ if not modules or config.get('import_partial'):
return True
- to_unlink = []
+
+ bad_imd_ids = []
+ context = {MODULE_UNINSTALL_FLAG: True}
cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
- WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""",
- (tuple(modules), False))
+ WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC
+ """, (tuple(modules), False))
for (id, name, model, res_id, module) in cr.fetchall():
- if (module,name) not in self.loads:
- to_unlink.append((model,res_id))
- if not config.get('import_partial'):
- for (model, res_id) in to_unlink:
+ if (module, name) not in self.loads:
if model in self.pool:
- _logger.info('Deleting %s@%s', res_id, model)
- self.pool[model].unlink(cr, uid, [res_id])
+ _logger.info('Deleting %s@%s (%s.%s)', res_id, model, module, name)
+ if self.pool[model].exists(cr, uid, [res_id], context=context):
+ self.pool[model].unlink(cr, uid, [res_id], context=context)
+ else:
+ bad_imd_ids.append(id)
+ if bad_imd_ids:
+ self.unlink(cr, uid, bad_imd_ids, context=context)
+ self.loads.clear()
class wizard_model_menu(osv.osv_memory):
_name = 'wizard.ir.model.menu.create'
diff --git a/openerp/addons/test_inherit/models.py b/openerp/addons/test_inherit/models.py
index f5728bbf0f6..7313e12c2a3 100644
--- a/openerp/addons/test_inherit/models.py
+++ b/openerp/addons/test_inherit/models.py
@@ -25,10 +25,9 @@ class mother(models.Model):
# in the child object
class daughter(models.Model):
_name = 'test.inherit.daughter'
- _inherits = {'test.inherit.mother': 'template_id'}
template_id = fields.Many2one('test.inherit.mother', 'Template',
- required=True, ondelete='cascade')
+ delegate=True, required=True, ondelete='cascade')
field_in_daughter = fields.Char('Field1')
diff --git a/openerp/addons/test_inherit/tests/test_inherit.py b/openerp/addons/test_inherit/tests/test_inherit.py
index 653f37f1f04..c277692b7d3 100644
--- a/openerp/addons/test_inherit/tests/test_inherit.py
+++ b/openerp/addons/test_inherit/tests/test_inherit.py
@@ -3,7 +3,13 @@ from openerp.tests import common
class test_inherits(common.TransactionCase):
- def test_access_from_child_to_parent_model(self):
+ def test_00_inherits(self):
+ """ Check that a many2one field with delegate=True adds an entry in _inherits """
+ daughter = self.env['test.inherit.daughter']
+
+ self.assertEqual(daughter._inherits, {'test.inherit.mother': 'template_id'})
+
+ def test_10_access_from_child_to_parent_model(self):
""" check whether added field in model is accessible from children models (_inherits) """
# This test checks if the new added column of a parent model
# is accessible from the child model. This test has been written
@@ -15,7 +21,7 @@ class test_inherits(common.TransactionCase):
self.assertIn('field_in_mother', mother._fields)
self.assertIn('field_in_mother', daughter._fields)
- def test_field_extension(self):
+ def test_20_field_extension(self):
""" check the extension of a field in an inherited model """
mother = self.env['test.inherit.mother']
daughter = self.env['test.inherit.daughter']
@@ -41,7 +47,7 @@ class test_inherits(common.TransactionCase):
self.assertEqual(field.string, "Template")
self.assertTrue(field.required)
- def test_depends_extension(self):
+ def test_30_depends_extension(self):
""" check that @depends on overridden compute methods extends dependencies """
mother = self.env['test.inherit.mother']
field = mother._fields['surname']
@@ -49,7 +55,7 @@ class test_inherits(common.TransactionCase):
# the field dependencies are added
self.assertItemsEqual(field.depends, ['name', 'field_in_mother'])
- def test_selection_extension(self):
+ def test_40_selection_extension(self):
""" check that attribute selection_add=... extends selection on fields. """
mother = self.env['test.inherit.mother']
diff --git a/openerp/addons/test_new_api/ir.model.access.csv b/openerp/addons/test_new_api/ir.model.access.csv
index d43088d808b..5b62045ae08 100644
--- a/openerp/addons/test_new_api/ir.model.access.csv
+++ b/openerp/addons/test_new_api/ir.model.access.csv
@@ -2,5 +2,4 @@
access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1
access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1
access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1
-access_talk,test_new_api_talk,test_new_api.model_test_new_api_talk,,1,1,1,1
access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1
diff --git a/openerp/addons/test_new_api/models.py b/openerp/addons/test_new_api/models.py
index 9866c8b6fd4..55611f6029d 100644
--- a/openerp/addons/test_new_api/models.py
+++ b/openerp/addons/test_new_api/models.py
@@ -153,12 +153,6 @@ class Message(models.Model):
self.double_size = self.double_size + size
-class Talk(models.Model):
- _name = 'test_new_api.talk'
-
- parent = fields.Many2one('test_new_api.discussion', delegate=True, required=True)
-
-
class MixedModel(models.Model):
_name = 'test_new_api.mixed'
diff --git a/openerp/addons/test_new_api/tests/test_new_fields.py b/openerp/addons/test_new_api/tests/test_new_fields.py
index 5441698e7ed..af42443fbc0 100644
--- a/openerp/addons/test_new_api/tests/test_new_fields.py
+++ b/openerp/addons/test_new_api/tests/test_new_fields.py
@@ -373,20 +373,3 @@ class TestMagicFields(common.TransactionCase):
record = self.env['test_new_api.discussion'].create({'name': 'Booba'})
self.assertEqual(record.create_uid, self.env.user)
self.assertEqual(record.write_uid, self.env.user)
-
-
-class TestInherits(common.TransactionCase):
-
- def test_inherits(self):
- """ Check that a many2one field with delegate=True adds an entry in _inherits """
- Talk = self.env['test_new_api.talk']
- self.assertEqual(Talk._inherits, {'test_new_api.discussion': 'parent'})
- self.assertIn('name', Talk._fields)
- self.assertEqual(Talk._fields['name'].related, ('parent', 'name'))
-
- talk = Talk.create({'name': 'Foo'})
- discussion = talk.parent
- self.assertTrue(discussion)
- self.assertEqual(talk._name, 'test_new_api.talk')
- self.assertEqual(discussion._name, 'test_new_api.discussion')
- self.assertEqual(talk.name, discussion.name)
diff --git a/openerp/fields.py b/openerp/fields.py
index adc9fcacc93..0c1f058ca91 100644
--- a/openerp/fields.py
+++ b/openerp/fields.py
@@ -297,6 +297,13 @@ class Field(object):
self._attrs = {key: val for key, val in kwargs.iteritems() if val is not None}
self._free_attrs = []
+ # self._triggers is a set of pairs (field, path) that represents the
+ # computed fields that depend on `self`. When `self` is modified, it
+ # invalidates the cache of each `field`, and registers the records to
+ # recompute based on `path`. See method `modified` below for details.
+ self._triggers = set()
+ self.inverse_fields = []
+
def new(self, **kwargs):
""" Return a field of the same type as `self`, with its own parameters. """
return type(self)(**kwargs)
@@ -396,16 +403,6 @@ class Field(object):
# Field setup
#
- def reset(self):
- """ Prepare `self` for a new setup. """
- self.setup_done = False
- # self._triggers is a set of pairs (field, path) that represents the
- # computed fields that depend on `self`. When `self` is modified, it
- # invalidates the cache of each `field`, and registers the records to
- # recompute based on `path`. See method `modified` below for details.
- self._triggers = set()
- self.inverse_fields = []
-
def setup(self, env):
""" Make sure that `self` is set up, except for recomputation triggers. """
if not self.setup_done:
@@ -640,8 +637,11 @@ class Field(object):
#
def to_column(self):
- """ return a low-level field object corresponding to `self` """
- assert self.store or self.column
+ """ Return a column object corresponding to `self`, or ``None``. """
+ if not self.store and self.compute:
+ # non-stored computed fields do not have a corresponding column
+ self.column = None
+ return None
# determine column parameters
#_logger.debug("Create fields._column for Field %s", self)
@@ -677,6 +677,7 @@ class Field(object):
_column_groups = property(attrgetter('groups'))
_column_change_default = property(attrgetter('change_default'))
_column_deprecated = property(attrgetter('deprecated'))
+ _column_compute = property(lambda self: bool(self.compute))
############################################################################
#
diff --git a/openerp/models.py b/openerp/models.py
index f82a4059373..83103617b57 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -631,8 +631,8 @@ class BaseModel(object):
attrs = {
'_name': name,
'_register': False,
- '_columns': {}, # filled by _setup_fields()
- '_defaults': {}, # filled by Field._determine_default()
+ '_columns': None, # recomputed in _setup_fields()
+ '_defaults': None, # recomputed in _setup_base()
'_inherits': dict(cls._inherits),
'_depends': dict(cls._depends),
'_constraints': list(cls._constraints),
@@ -739,15 +739,40 @@ class BaseModel(object):
for (key, _, msg) in cls._sql_constraints:
cls.pool._sql_error[cls._table + '_' + key] = msg
- # collect constraint and onchange methods
- cls._constraint_methods = []
- cls._onchange_methods = defaultdict(list)
- for attr, func in getmembers(cls, callable):
- if hasattr(func, '_constrains'):
- cls._constraint_methods.append(func)
- if hasattr(func, '_onchange'):
- for name in func._onchange:
- cls._onchange_methods[name].append(func)
+ @property
+ def _constraint_methods(self):
+ """ Return a list of methods implementing Python constraints. """
+ def is_constraint(func):
+ return callable(func) and hasattr(func, '_constrains')
+
+ cls = type(self)
+ methods = []
+ for attr, func in getmembers(cls, is_constraint):
+ if not all(name in cls._fields for name in func._constrains):
+ _logger.warning("@constrains%r parameters must be field names", func._constrains)
+ methods.append(func)
+
+ # optimization: memoize result on cls, it will not be recomputed
+ cls._constraint_methods = methods
+ return methods
+
+ @property
+ def _onchange_methods(self):
+ """ Return a dictionary mapping field names to onchange methods. """
+ def is_onchange(func):
+ return callable(func) and hasattr(func, '_onchange')
+
+ cls = type(self)
+ methods = defaultdict(list)
+ for attr, func in getmembers(cls, is_onchange):
+ for name in func._onchange:
+ if name not in cls._fields:
+ _logger.warning("@onchange%r parameters must be field names", func._onchange)
+ methods[name].append(func)
+
+ # optimization: memoize result on cls, it will not be recomputed
+ cls._onchange_methods = methods
+ return methods
def __new__(cls):
# In the past, this method was registering the model class in the server.
@@ -794,19 +819,6 @@ class BaseModel(object):
"TransientModels must have log_access turned on, " \
"in order to implement their access rights policy"
- # retrieve new-style fields (from above registry class) and duplicate
- # them (to avoid clashes with inheritance between different models)
- cls._fields = {}
- above = cls.__bases__[0]
- for attr, field in getmembers(above, Field.__instancecheck__):
- cls._add_field(attr, field.new())
-
- # introduce magic fields
- cls._add_magic_fields()
-
- # register constraints and onchange methods
- cls._init_constraints_onchanges()
-
# prepare ormcache, which must be shared by all instances of the model
cls._ormcache = {}
@@ -2951,26 +2963,28 @@ class BaseModel(object):
if cls._setup_done:
return
- # first make sure that parent models determine all their fields
+ # 1. determine the proper fields of the model; duplicate them on cls to
+ # avoid clashes with inheritance between different models
+ for name in getattr(cls, '_fields', {}):
+ delattr(cls, name)
+
+ # retrieve fields from parent classes
+ cls._fields = {}
+ cls._defaults = {}
+ for attr, field in getmembers(cls, Field.__instancecheck__):
+ cls._add_field(attr, field.new())
+
+ # add magic and custom fields
+ cls._add_magic_fields()
+ cls._init_manual_fields(self._cr, partial)
+
+ # 2. make sure that parent models determine their own fields, then add
+ # inherited fields to cls
cls._inherits_check()
for parent in cls._inherits:
self.env[parent]._setup_base(partial)
-
- # remove inherited fields from cls._fields
- for name, field in cls._fields.items():
- if field.inherited:
- del cls._fields[name]
-
- # retrieve custom fields
- cls._init_manual_fields(self._cr, partial)
-
- # retrieve inherited fields
cls._init_inherited_fields()
- # prepare the setup of fields
- for field in cls._fields.itervalues():
- field.reset()
-
cls._setup_done = True
@api.model
@@ -2978,11 +2992,13 @@ class BaseModel(object):
""" Setup the fields, except for recomputation triggers. """
cls = type(self)
- # set up fields, and update their corresponding columns
+ # set up fields, and determine their corresponding column
+ cls._columns = {}
for name, field in cls._fields.iteritems():
field.setup(self.env)
- if field.store or field.column:
- cls._columns[name] = field.to_column()
+ column = field.to_column()
+ if column:
+ cls._columns[name] = column
# group fields by compute to determine field.computed_fields
fields_by_compute = defaultdict(list)
@@ -3017,14 +3033,8 @@ class BaseModel(object):
# register stuff about low-level function fields
cls._init_function_fields(cls.pool, self._cr)
- # check constraints
- for func in cls._constraint_methods:
- if not all(name in cls._fields for name in func._constrains):
- _logger.warning("@constrains%r parameters must be field names", func._constrains)
- for name in cls._onchange_methods:
- if name not in cls._fields:
- func = cls._onchange_methods[name]
- _logger.warning("@onchange%r parameters must be field names", func._onchange)
+ # register constraints and onchange methods
+ cls._init_constraints_onchanges()
# check defaults
for name in cls._defaults:
diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py
index 3bf0c268961..c8a961c68f5 100644
--- a/openerp/osv/fields.py
+++ b/openerp/osv/fields.py
@@ -1293,9 +1293,14 @@ class function(_column):
self._symbol_set = type_class._symbol_set
def new(self, **args):
- # HACK: function fields are tricky to recreate, simply return a copy
- import copy
- return copy.copy(self)
+ if args.get('compute'):
+ # field is computed, we need an instance of the given type
+ type_class = globals()[self._type]
+ return type_class(**args)
+ else:
+ # HACK: function fields are tricky to recreate, simply return a copy
+ import copy
+ return copy.copy(self)
def to_field_args(self):
args = super(function, self).to_field_args()