Merge lp:~openerp-dev/openobject-server/trunk-base-model-thu into lp:openobject-server

Proposed by Antony Lesuisse (OpenERP)
Status: Work in progress
Proposed branch: lp:~openerp-dev/openobject-server/trunk-base-model-thu
Merge into: lp:openobject-server
Diff against target: 1565 lines (+602/-362)
31 files modified
doc/changelog.rst (+5/-0)
openerp/addons/__init__.py (+4/-12)
openerp/addons/base/__init__.py (+1/-0)
openerp/addons/base/ir/ir_actions.py (+2/-1)
openerp/addons/base/ir/ir_attachment.py (+2/-1)
openerp/addons/base/ir/ir_filters.py (+2/-1)
openerp/addons/base/ir/ir_model.py (+9/-4)
openerp/addons/base/ir/ir_translation.py (+3/-1)
openerp/addons/base/ir/ir_ui_view.py (+6/-3)
openerp/addons/base/ir/ir_values.py (+2/-1)
openerp/addons/base/ir/workflow/workflow.py (+4/-2)
openerp/addons/base/models.py (+19/-0)
openerp/addons/base/module/module.py (+6/-6)
openerp/conf/deprecation.py (+7/-0)
openerp/modules/__init__.py (+3/-7)
openerp/modules/graph.py (+1/-1)
openerp/modules/loading.py (+30/-6)
openerp/modules/module.py (+182/-158)
openerp/modules/registry.py (+30/-17)
openerp/osv/orm.py (+82/-140)
openerp/service/__init__.py (+6/-1)
openerp/tests/addons/test_base_model_a/__init__.py (+3/-0)
openerp/tests/addons/test_base_model_a/__openerp__.py (+15/-0)
openerp/tests/addons/test_base_model_a/models.py (+26/-0)
openerp/tests/addons/test_base_model_a/tests/__init__.py (+13/-0)
openerp/tests/addons/test_base_model_a/tests/test_base_model.py (+40/-0)
openerp/tests/addons/test_base_model_b/__init__.py (+3/-0)
openerp/tests/addons/test_base_model_b/__openerp__.py (+15/-0)
openerp/tests/addons/test_base_model_b/models.py (+18/-0)
openerp/tests/addons/test_base_model_b/tests/__init__.py (+13/-0)
openerp/tests/addons/test_base_model_b/tests/test_base_model.py (+50/-0)
To merge this branch: bzr merge lp:~openerp-dev/openobject-server/trunk-base-model-thu
Reviewer Review Type Date Requested Status
Vo Minh Thu (community) Needs Fixing
Raphael Collet (OpenERP) (community) Needs Fixing
Review via email: mp+159245@code.launchpad.net

Description of the change

It seems this should fix https://bugs.launchpad.net/openobject-server/+bug/996816.

This patch does two important things (in addition to some cleaning):
- make "copied" (_inherit + new _name) models inherit from the consolidated parent model, instead of inherit of the parent as-is at installation time (i.e. when not all modules have possibly changed the copied parent).
- introduce a 'base.model' model that is, by default, always a copied parent of any model. (I.e. the default value of _inherit is 'base.model' instead of None.)

'base.model' might be renamed to simply 'base'.

Other changes:
- properly return the super()._auto_init() result (which is a list of stored fields that must be initialized after the table creation).
(The trunk addons are already fixed, so the code calling _auto_init()
can now rightfuly assume that the return type is indeed a list.)
- Removed some exports (or their uses) in openerp.addons and openerp.modules.
- Introduce a deprecation warning (and its controlling flag and comment in openerp.conf.deprecation) for manual instanciation of models. (When such manual instanciations are completely removed, models will be able to be instanciated with a simple model() call, instead of using object.__new__().__init__().)
- The previous point is possible because now __new__() is useless: everything it was doing is handled by the MetaModel meta class.
- Removed load_openerp_module(). Instead, using simply import or __import__ is enough.
- The previous point means that zip files are no longer supported (the code for those was probably broken anyway).
- The ImportHook was needed in the past to lookup OpenERP modules in the different addons paths. Now it is also used to recursively import module dependencies (as-if the imported module had some import statements for its dependencies), and perform some bookkeeping about modules and models (rank modules and models, store the content of the __openerp__.py descriptor file, ...).
- The _name of a model is set by its meta class, instead of sometimes being left to None.
- The support for the _sql model attribute is gone (it was not used, and its purpose is already served with _auto_init()/init() overriding).

Note:
Custom (a.k.a. manual) models seem to be broken in this branch but in trunk too; I hope/guess I haven't made the situation worse.

To post a comment you must log in.
4866. By Vo Minh Thu

[FIX] _auto_init(): when _auto_init() is overridden it does not return the super() result, which might brake functional fields initialization.

4867. By Vo Minh Thu

[REF] loading: init_module_models() moved to loading.py, slightly cleaner.

4868. By Vo Minh Thu

[REF] orm: renamed todo_end into something more sensible (matching the variable used in loading.py).

4869. By Vo Minh Thu

[REM] orm: removed unused _sql feature.

4870. By Vo Minh Thu

[REF] module: get_module_resource() is no longer in addons.

4871. By Vo Minh Thu

[REF] orm: removed create_instance (used by ir_module.py).

4872. By Vo Minh Thu

[FIX] registry: re-consolidate all previously built models whenever a new module is loaded in the registry.

This is needed so we can recompute the full class hierarchy of
models copying parents that are changed by the currently being
loaded module.

4873. By Vo Minh Thu

[MERGE] merged trunk.

4874. By Vo Minh Thu

[IMP] registry: avoid consolidating and instanciating the exact same model again and again.

4875. By Vo Minh Thu

[DOC] changelog: base.model and new _inherit/_name behavior.

4876. By Vo Minh Thu

[MERGE] merged trunk.

4877. By Vo Minh Thu

[ADD] base.model: added tests.

4878. By Vo Minh Thu

[ADD] base.model: added tests.

4879. By Vo Minh Thu

[IMP] base.model: disabled tests when run from within openerp.

It is still possible to run them via oe though.

Revision history for this message
Raphael Collet (OpenERP) (rco-openerp) wrote :

Some tests are broken (see runbot).

I think you should split this branch in two:
 - the refactoring of the consolidation of models,
 - the other stuff, that is mostly orthogonal.

Please add documentation to your functions to explain what they do (stuff like "rank_modules() must have been called." does not help).

Your code is recursive-imperative: it traverses nodes and update them (openerp_rank, _parents, _rank, etc.) The problem is that it introduces *race conditions* whenever your server serves multiple databases! The ranking of modules is possibly independent from which modules are installed on a database, but the ranking of classes is not! The attributes _number, _parents, _children, _rank that you assign to addons classes depend on which modules are installed.

I suggest you to use memoized functions (which may be database-specific) instead of assigning the objects you are ranking. For instance, the sorting of modules by rank may be written as follows:

@memoize
def get_module_rank(name):
    """ return the rank of the module with the given name """
    module = get_module(name)
    depends = module.openerp_descriptor['depends']
    return max(map(get_module_rank, depends)) + 1 if depends else 0

# sorting module names by rank and name
modules = list(sorted(modules, key=lambda name: (get_module_rank(name), name)))

Raphael

review: Needs Fixing
Revision history for this message
Vo Minh Thu (thu) wrote :

Thanks for the review. I will check what you said properly, but quickly my first thoughts:

It seems you're right it is a bad idea to store registry-specific data in sys.modules :)

I'm not sure I could memoize them. I think I re-run them as each module is installed.

The other "orthogonal" stuff: I'm not they are that much orthogonal. I'm pretty sure some of them were possible because of the main change of this merge prop, and some other were actually necessary. I don't want to play with three branches (the orthogonal stuff that are not necessary for this branch, those that depends on this branch, and this branch) and re-base all the resulting commits.

review: Approve
Revision history for this message
Vo Minh Thu (thu) wrote :

About the runbot. Tests are not broken per se. They just can't be run all together at the same time, as some (more than one) of them assume a clean base model. That's boring, I don't know what I will do to the runbot to run them separately (and load the modules they belong to separately to). It seems I need a 'base' database, an 'all' one, and then a new one for the new tests.

Revision history for this message
Vo Minh Thu (thu) :
review: Needs Fixing

Unmerged revisions

4879. By Vo Minh Thu

[IMP] base.model: disabled tests when run from within openerp.

It is still possible to run them via oe though.

4878. By Vo Minh Thu

[ADD] base.model: added tests.

4877. By Vo Minh Thu

[ADD] base.model: added tests.

4876. By Vo Minh Thu

[MERGE] merged trunk.

4875. By Vo Minh Thu

[DOC] changelog: base.model and new _inherit/_name behavior.

4874. By Vo Minh Thu

[IMP] registry: avoid consolidating and instanciating the exact same model again and again.

4873. By Vo Minh Thu

[MERGE] merged trunk.

4872. By Vo Minh Thu

[FIX] registry: re-consolidate all previously built models whenever a new module is loaded in the registry.

This is needed so we can recompute the full class hierarchy of
models copying parents that are changed by the currently being
loaded module.

4871. By Vo Minh Thu

[REF] orm: removed create_instance (used by ir_module.py).

4870. By Vo Minh Thu

[REF] module: get_module_resource() is no longer in addons.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'doc/changelog.rst'
--- doc/changelog.rst 2013-06-21 07:44:28 +0000
+++ doc/changelog.rst 2013-07-10 14:52:28 +0000
@@ -6,6 +6,11 @@
6`trunk`6`trunk`
7-------7-------
88
9- Introduced a ``base.model``. It is to OpenERP models what ``object`` is
10 to Python classes. To make this possible, it means that now copied models
11 (ie. models using ``_inherit`` and providing a new ``_name``) will copy
12 the fully consolidated parent, instead of the parent as-is as the time
13 of module installation.
9- Almost removed ``LocalService()``. For reports,14- Almost removed ``LocalService()``. For reports,
10 ``openerp.osv.orm.Model.print_report()`` can be used. For workflows, see15 ``openerp.osv.orm.Model.print_report()`` can be used. For workflows, see
11 :ref:`orm-workflows`.16 :ref:`orm-workflows`.
1217
=== modified file 'openerp/addons/__init__.py'
--- openerp/addons/__init__.py 2012-01-09 12:41:20 +0000
+++ openerp/addons/__init__.py 2013-07-10 14:52:28 +0000
@@ -23,18 +23,10 @@
23""" Addons module.23""" Addons module.
2424
25This module serves to contain all OpenERP addons, across all configured addons25This module serves to contain all OpenERP addons, across all configured addons
26paths. For the code to manage those addons, see openerp.modules.26paths. For the code to manage those addons, see `openerp.modules`.
2727
28Addons are made available under `openerp.addons` after28Addons can be imported from `openerp.addons` after
29openerp.tools.config.parse_config() is called (so that the addons paths are29`openerp.modules.module.initialize_sys_path()` has been called.
30known).
31
32This module also conveniently reexports some symbols from openerp.modules.
33Importing them from here is deprecated.
34
35"""30"""
3631
37# get_module_path is used only by base_module_quality
38from openerp.modules import get_module_resource, get_module_path
39
40# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:32# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
4133
=== modified file 'openerp/addons/base/__init__.py'
--- openerp/addons/base/__init__.py 2012-08-22 06:28:23 +0000
+++ openerp/addons/base/__init__.py 2013-07-10 14:52:28 +0000
@@ -19,6 +19,7 @@
19#19#
20##############################################################################20##############################################################################
2121
22import models
22import ir23import ir
23import module24import module
24import res25import res
2526
=== modified file 'openerp/addons/base/ir/ir_actions.py'
--- openerp/addons/base/ir/ir_actions.py 2013-06-13 17:39:00 +0000
+++ openerp/addons/base/ir/ir_actions.py 2013-07-10 14:52:28 +0000
@@ -354,10 +354,11 @@
354 'multi': False,354 'multi': False,
355 }355 }
356 def _auto_init(self, cr, context=None):356 def _auto_init(self, cr, context=None):
357 super(act_window_view, self)._auto_init(cr, context)357 res = super(act_window_view, self)._auto_init(cr, context)
358 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'act_window_view_unique_mode_per_action\'')358 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'act_window_view_unique_mode_per_action\'')
359 if not cr.fetchone():359 if not cr.fetchone():
360 cr.execute('CREATE UNIQUE INDEX act_window_view_unique_mode_per_action ON ir_act_window_view (act_window_id, view_mode)')360 cr.execute('CREATE UNIQUE INDEX act_window_view_unique_mode_per_action ON ir_act_window_view (act_window_id, view_mode)')
361 return res
361act_window_view()362act_window_view()
362363
363class act_wizard(osv.osv):364class act_wizard(osv.osv):
364365
=== modified file 'openerp/addons/base/ir/ir_attachment.py'
--- openerp/addons/base/ir/ir_attachment.py 2013-06-12 15:28:26 +0000
+++ openerp/addons/base/ir/ir_attachment.py 2013-07-10 14:52:28 +0000
@@ -175,11 +175,12 @@
175 }175 }
176176
177 def _auto_init(self, cr, context=None):177 def _auto_init(self, cr, context=None):
178 super(ir_attachment, self)._auto_init(cr, context)178 res = super(ir_attachment, self)._auto_init(cr, context)
179 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))179 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
180 if not cr.fetchone():180 if not cr.fetchone():
181 cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')181 cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
182 cr.commit()182 cr.commit()
183 return res
183184
184 def check(self, cr, uid, ids, mode, context=None, values=None):185 def check(self, cr, uid, ids, mode, context=None, values=None):
185 """Restricts the access to an ir.attachment, according to referred model186 """Restricts the access to an ir.attachment, according to referred model
186187
=== modified file 'openerp/addons/base/ir/ir_filters.py'
--- openerp/addons/base/ir/ir_filters.py 2013-02-20 10:34:59 +0000
+++ openerp/addons/base/ir/ir_filters.py 2013-07-10 14:52:28 +0000
@@ -118,12 +118,13 @@
118 ]118 ]
119119
120 def _auto_init(self, cr, context=None):120 def _auto_init(self, cr, context=None):
121 super(ir_filters, self)._auto_init(cr, context)121 res = super(ir_filters, self)._auto_init(cr, context)
122 # Use unique index to implement unique constraint on the lowercase name (not possible using a constraint)122 # Use unique index to implement unique constraint on the lowercase name (not possible using a constraint)
123 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_index'")123 cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_index'")
124 if not cr.fetchone():124 if not cr.fetchone():
125 cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_index" ON ir_filters125 cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_index" ON ir_filters
126 (lower(name), model_id, COALESCE(user_id,-1))""")126 (lower(name), model_id, COALESCE(user_id,-1))""")
127 return res
127128
128 _columns = {129 _columns = {
129 'name': fields.char('Filter Name', size=64, translate=True, required=True),130 'name': fields.char('Filter Name', size=64, translate=True, required=True),
130131
=== modified file 'openerp/addons/base/ir/ir_model.py'
--- openerp/addons/base/ir/ir_model.py 2013-06-19 09:13:32 +0000
+++ openerp/addons/base/ir/ir_model.py 2013-07-10 14:52:28 +0000
@@ -203,9 +203,13 @@
203 def instanciate(self, cr, user, model, context=None):203 def instanciate(self, cr, user, model, context=None):
204 class x_custom_model(osv.osv):204 class x_custom_model(osv.osv):
205 _custom = True205 _custom = True
206 x_custom_model._name = model206 _name = model
207 x_custom_model._module = False207 _module = False
208 a = x_custom_model.create_instance(self.pool, cr)208 classes = list(openerp.modules.module.get_model_classes('base.model'))
209 classes = [x_custom_model] + classes
210 cls = openerp.osv.orm.consolidate(model, classes)
211 a = object.__new__(cls)
212 a.__init__(self.pool, cr)
209 if not a._columns:213 if not a._columns:
210 x_name = 'id'214 x_name = 'id'
211 elif 'x_name' in a._columns.keys():215 elif 'x_name' in a._columns.keys():
@@ -842,10 +846,11 @@
842 self.loads = self.pool.model_data_reference_ids846 self.loads = self.pool.model_data_reference_ids
843847
844 def _auto_init(self, cr, context=None):848 def _auto_init(self, cr, context=None):
845 super(ir_model_data, self)._auto_init(cr, context)849 res = super(ir_model_data, self)._auto_init(cr, context)
846 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')850 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
847 if not cr.fetchone():851 if not cr.fetchone():
848 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')852 cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)')
853 return res
849854
850 @tools.ormcache()855 @tools.ormcache()
851 def _get_id(self, cr, uid, module, xml_id):856 def _get_id(self, cr, uid, module, xml_id):
852857
=== modified file 'openerp/addons/base/ir/ir_translation.py'
--- openerp/addons/base/ir/ir_translation.py 2013-04-18 11:46:27 +0000
+++ openerp/addons/base/ir/ir_translation.py 2013-07-10 14:52:28 +0000
@@ -223,7 +223,7 @@
223 'Language code of translation item must be among known languages' ), ]223 'Language code of translation item must be among known languages' ), ]
224224
225 def _auto_init(self, cr, context=None):225 def _auto_init(self, cr, context=None):
226 super(ir_translation, self)._auto_init(cr, context)226 res = super(ir_translation, self)._auto_init(cr, context)
227227
228 # FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree.228 # FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree.
229 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_ltns',))229 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_ltns',))
@@ -247,6 +247,8 @@
247 cr.execute('CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)')247 cr.execute('CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)')
248 cr.commit()248 cr.commit()
249249
250 return res
251
250 def _check_selection_field_value(self, cr, uid, field, value, context=None):252 def _check_selection_field_value(self, cr, uid, field, value, context=None):
251 if field == 'lang':253 if field == 'lang':
252 return254 return
253255
=== modified file 'openerp/addons/base/ir/ir_ui_view.py'
--- openerp/addons/base/ir/ir_ui_view.py 2013-06-05 12:11:43 +0000
+++ openerp/addons/base/ir/ir_ui_view.py 2013-07-10 14:52:28 +0000
@@ -41,10 +41,11 @@
41 }41 }
4242
43 def _auto_init(self, cr, context=None):43 def _auto_init(self, cr, context=None):
44 super(view_custom, self)._auto_init(cr, context)44 res = super(view_custom, self)._auto_init(cr, context)
45 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_custom_user_id_ref_id\'')45 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_custom_user_id_ref_id\'')
46 if not cr.fetchone():46 if not cr.fetchone():
47 cr.execute('CREATE INDEX ir_ui_view_custom_user_id_ref_id ON ir_ui_view_custom (user_id, ref_id)')47 cr.execute('CREATE INDEX ir_ui_view_custom_user_id_ref_id ON ir_ui_view_custom (user_id, ref_id)')
48 return res
4849
49class view(osv.osv):50class view(osv.osv):
50 _name = 'ir.ui.view'51 _name = 'ir.ui.view'
@@ -165,10 +166,11 @@
165 ]166 ]
166167
167 def _auto_init(self, cr, context=None):168 def _auto_init(self, cr, context=None):
168 super(view, self)._auto_init(cr, context)169 res = super(view, self)._auto_init(cr, context)
169 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_model_type_inherit_id\'')170 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_model_type_inherit_id\'')
170 if not cr.fetchone():171 if not cr.fetchone():
171 cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)')172 cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)')
173 return res
172174
173 def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None):175 def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None):
174 """Retrieves the architecture of views that inherit from the given view, from the sets of176 """Retrieves the architecture of views that inherit from the given view, from the sets of
@@ -293,10 +295,11 @@
293 }295 }
294296
295 def _auto_init(self, cr, context=None):297 def _auto_init(self, cr, context=None):
296 super(view_sc, self)._auto_init(cr, context)298 res = super(view_sc, self)._auto_init(cr, context)
297 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_sc_user_id_resource\'')299 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_sc_user_id_resource\'')
298 if not cr.fetchone():300 if not cr.fetchone():
299 cr.execute('CREATE INDEX ir_ui_view_sc_user_id_resource ON ir_ui_view_sc (user_id, resource)')301 cr.execute('CREATE INDEX ir_ui_view_sc_user_id_resource ON ir_ui_view_sc (user_id, resource)')
302 return res
300303
301 def get_sc(self, cr, uid, user_id, model='ir.ui.menu', context=None):304 def get_sc(self, cr, uid, user_id, model='ir.ui.menu', context=None):
302 ids = self.search(cr, uid, [('user_id','=',user_id),('resource','=',model)], context=context)305 ids = self.search(cr, uid, [('user_id','=',user_id),('resource','=',model)], context=context)
303306
=== modified file 'openerp/addons/base/ir/ir_values.py'
--- openerp/addons/base/ir/ir_values.py 2013-06-13 17:39:00 +0000
+++ openerp/addons/base/ir/ir_values.py 2013-07-10 14:52:28 +0000
@@ -183,10 +183,11 @@
183 }183 }
184184
185 def _auto_init(self, cr, context=None):185 def _auto_init(self, cr, context=None):
186 super(ir_values, self)._auto_init(cr, context)186 res = super(ir_values, self)._auto_init(cr, context)
187 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_values_key_model_key2_res_id_user_id_idx\'')187 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_values_key_model_key2_res_id_user_id_idx\'')
188 if not cr.fetchone():188 if not cr.fetchone():
189 cr.execute('CREATE INDEX ir_values_key_model_key2_res_id_user_id_idx ON ir_values (key, model, key2, res_id, user_id)')189 cr.execute('CREATE INDEX ir_values_key_model_key2_res_id_user_id_idx ON ir_values (key, model, key2, res_id, user_id)')
190 return res
190191
191 def set_default(self, cr, uid, model, field_name, value, for_all_users=True, company_id=False, condition=False):192 def set_default(self, cr, uid, model, field_name, value, for_all_users=True, company_id=False, condition=False):
192 """Defines a default value for the given model and field_name. Any previous193 """Defines a default value for the given model and field_name. Any previous
193194
=== modified file 'openerp/addons/base/ir/workflow/workflow.py'
--- openerp/addons/base/ir/workflow/workflow.py 2013-06-13 17:39:00 +0000
+++ openerp/addons/base/ir/workflow/workflow.py 2013-07-10 14:52:28 +0000
@@ -137,13 +137,14 @@
137 'state': fields.char('Status', size=32),137 'state': fields.char('Status', size=32),
138 }138 }
139 def _auto_init(self, cr, context=None):139 def _auto_init(self, cr, context=None):
140 super(wkf_instance, self)._auto_init(cr, context)140 res = super(wkf_instance, self)._auto_init(cr, context)
141 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_type_res_id_state_index\'')141 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_type_res_id_state_index\'')
142 if not cr.fetchone():142 if not cr.fetchone():
143 cr.execute('CREATE INDEX wkf_instance_res_type_res_id_state_index ON wkf_instance (res_type, res_id, state)')143 cr.execute('CREATE INDEX wkf_instance_res_type_res_id_state_index ON wkf_instance (res_type, res_id, state)')
144 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_id_wkf_id_index\'')144 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_id_wkf_id_index\'')
145 if not cr.fetchone():145 if not cr.fetchone():
146 cr.execute('CREATE INDEX wkf_instance_res_id_wkf_id_index ON wkf_instance (res_id, wkf_id)')146 cr.execute('CREATE INDEX wkf_instance_res_id_wkf_id_index ON wkf_instance (res_id, wkf_id)')
147 return res
147148
148wkf_instance()149wkf_instance()
149150
@@ -172,10 +173,11 @@
172 'workitem_id': fields.many2one('workflow.workitem', 'Workitem', required=True, ondelete="cascade"),173 'workitem_id': fields.many2one('workflow.workitem', 'Workitem', required=True, ondelete="cascade"),
173 }174 }
174 def _auto_init(self, cr, context=None):175 def _auto_init(self, cr, context=None):
175 super(wkf_triggers, self)._auto_init(cr, context)176 res = super(wkf_triggers, self)._auto_init(cr, context)
176 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_triggers_res_id_model_index\'')177 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_triggers_res_id_model_index\'')
177 if not cr.fetchone():178 if not cr.fetchone():
178 cr.execute('CREATE INDEX wkf_triggers_res_id_model_index ON wkf_triggers (res_id, model)')179 cr.execute('CREATE INDEX wkf_triggers_res_id_model_index ON wkf_triggers (res_id, model)')
180 return res
179wkf_triggers()181wkf_triggers()
180182
181183
182184
=== added file 'openerp/addons/base/models.py'
--- openerp/addons/base/models.py 1970-01-01 00:00:00 +0000
+++ openerp/addons/base/models.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,19 @@
1from openerp.osv.orm import BaseModel
2
3# Lovely: `base.model` inherits from `BaseModel`.
4#
5# `BaseModel` and `Model` are exactly the same, but with diamond inheritance of
6# `base.model`, if `Model` was used instead, `Model` would be higher in the MRO
7# than `AbstractModel`, e.g. with models inheriting from `mail.message` or
8# `mail.thread`. By using `BaseModel`, we keep `Model` below `AbstractModel`
9# and its `_auto=True` attribute "wins" over the `_auto=False` of
10# `AbstractModel`.
11class base_model(BaseModel):
12 _inherit = None
13 _name = 'base.model'
14
15 _auto = False # No table in database.
16 _transient = False
17
18 _columns = {
19 }
020
=== modified file 'openerp/addons/base/module/module.py'
--- openerp/addons/base/module/module.py 2013-06-28 10:04:07 +0000
+++ openerp/addons/base/module/module.py 2013-07-10 14:52:28 +0000
@@ -40,7 +40,7 @@
40 from StringIO import StringIO # NOQA40 from StringIO import StringIO # NOQA
4141
42import openerp42import openerp
43from openerp import modules, tools, addons43from openerp import modules, tools
44from openerp.modules.db import create_categories44from openerp.modules.db import create_categories
45from openerp.tools.parse_version import parse_version45from openerp.tools.parse_version import parse_version
46from openerp.tools.translate import _46from openerp.tools.translate import _
@@ -153,7 +153,7 @@
153 def _get_desc(self, cr, uid, ids, field_name=None, arg=None, context=None):153 def _get_desc(self, cr, uid, ids, field_name=None, arg=None, context=None):
154 res = dict.fromkeys(ids, '')154 res = dict.fromkeys(ids, '')
155 for module in self.browse(cr, uid, ids, context=context):155 for module in self.browse(cr, uid, ids, context=context):
156 path = addons.get_module_resource(module.name, 'static/description/index.html')156 path = openerp.modules.module.get_module_resource(module.name, 'static/description/index.html')
157 if path:157 if path:
158 with tools.file_open(path, 'rb') as desc_file:158 with tools.file_open(path, 'rb') as desc_file:
159 doc = desc_file.read()159 doc = desc_file.read()
@@ -169,7 +169,7 @@
169 return res169 return res
170170
171 def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None):171 def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None):
172 default_version = modules.adapt_version('1.0')172 default_version = modules.module.adapt_version('1.0')
173 res = dict.fromkeys(ids, default_version)173 res = dict.fromkeys(ids, default_version)
174 for m in self.browse(cr, uid, ids):174 for m in self.browse(cr, uid, ids):
175 res[m.id] = self.get_module_info(m.name).get('version', default_version)175 res[m.id] = self.get_module_info(m.name).get('version', default_version)
@@ -243,7 +243,7 @@
243 def _get_icon_image(self, cr, uid, ids, field_name=None, arg=None, context=None):243 def _get_icon_image(self, cr, uid, ids, field_name=None, arg=None, context=None):
244 res = dict.fromkeys(ids, '')244 res = dict.fromkeys(ids, '')
245 for module in self.browse(cr, uid, ids, context=context):245 for module in self.browse(cr, uid, ids, context=context):
246 path = addons.get_module_resource(module.name, 'static', 'description', 'icon.png')246 path = openerp.modules.module.get_module_resource(module.name, 'static', 'description', 'icon.png')
247 if path:247 if path:
248 image_file = tools.file_open(path, 'rb')248 image_file = tools.file_open(path, 'rb')
249 try:249 try:
@@ -584,7 +584,7 @@
584 def update_list(self, cr, uid, context=None):584 def update_list(self, cr, uid, context=None):
585 res = [0, 0] # [update, add]585 res = [0, 0] # [update, add]
586586
587 default_version = modules.adapt_version('1.0')587 default_version = modules.module.adapt_version('1.0')
588 known_mods = self.browse(cr, uid, self.search(cr, uid, []))588 known_mods = self.browse(cr, uid, self.search(cr, uid, []))
589 known_mods_names = dict([(m.name, m) for m in known_mods])589 known_mods_names = dict([(m.name, m) for m in known_mods])
590590
@@ -631,7 +631,7 @@
631631
632 def download(self, cr, uid, ids, download=True, context=None):632 def download(self, cr, uid, ids, download=True, context=None):
633 res = []633 res = []
634 default_version = modules.adapt_version('1.0')634 default_version = modules.module.adapt_version('1.0')
635 for mod in self.browse(cr, uid, ids, context=context):635 for mod in self.browse(cr, uid, ids, context=context):
636 if not mod.url:636 if not mod.url:
637 continue637 continue
638638
=== modified file 'openerp/conf/deprecation.py'
--- openerp/conf/deprecation.py 2013-03-27 16:40:45 +0000
+++ openerp/conf/deprecation.py 2013-07-10 14:52:28 +0000
@@ -58,4 +58,11 @@
58# but no warning was dispayed in the logs).58# but no warning was dispayed in the logs).
59openerp_pooler = True59openerp_pooler = True
6060
61# If True, calling a model class after defining it is allowed.
62# class my_model(Model):
63# pass
64# my_model()
65# Introduced around 2013.04.
66allow_explicit_model_call = True
67
61# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:68# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
6269
=== modified file 'openerp/modules/__init__.py'
--- openerp/modules/__init__.py 2012-08-24 14:23:23 +0000
+++ openerp/modules/__init__.py 2013-07-10 14:52:28 +0000
@@ -28,14 +28,10 @@
2828
29# TODO temporarily expose those things29# TODO temporarily expose those things
30from openerp.modules.module import \30from openerp.modules.module import \
31 get_modules, get_modules_with_version, \31 get_modules, \
32 load_information_from_description_file, \32 load_information_from_description_file, \
33 get_module_resource, zip_directory, \33 get_module_resource, \
34 get_module_path, initialize_sys_path, \34 get_module_path
35 load_openerp_module, init_module_models, \
36 adapt_version
37
38from openerp.modules.loading import load_modules
3935
4036
41# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:37# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
4238
=== modified file 'openerp/modules/graph.py'
--- openerp/modules/graph.py 2013-03-27 17:06:39 +0000
+++ openerp/modules/graph.py 2013-07-10 14:52:28 +0000
@@ -98,7 +98,7 @@
98 # done by db.initialize, so it is possible to not do it again here.98 # done by db.initialize, so it is possible to not do it again here.
99 info = openerp.modules.module.load_information_from_description_file(module)99 info = openerp.modules.module.load_information_from_description_file(module)
100 if info and info['installable']:100 if info and info['installable']:
101 packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version101 packages.append((module, info)) # TODO directly a dict
102 else:102 else:
103 _logger.warning('module %s: not installable, skipped', module)103 _logger.warning('module %s: not installable, skipped', module)
104104
105105
=== modified file 'openerp/modules/loading.py'
--- openerp/modules/loading.py 2013-05-07 11:20:24 +0000
+++ openerp/modules/loading.py 2013-07-10 14:52:28 +0000
@@ -40,8 +40,7 @@
40from openerp import SUPERUSER_ID40from openerp import SUPERUSER_ID
4141
42from openerp.tools.translate import _42from openerp.tools.translate import _
43from openerp.modules.module import initialize_sys_path, \43from openerp.modules.module import adapt_version
44 load_openerp_module, init_module_models, adapt_version
4544
46_logger = logging.getLogger(__name__)45_logger = logging.getLogger(__name__)
47_test_logger = logging.getLogger('openerp.tests')46_test_logger = logging.getLogger('openerp.tests')
@@ -149,9 +148,14 @@
149148
150 _logger.debug('module %s: loading objects', package.name)149 _logger.debug('module %s: loading objects', package.name)
151 migrations.migrate_module(package, 'pre')150 migrations.migrate_module(package, 'pre')
152 load_openerp_module(package.name)151 __import__('openerp.addons.' + package.name)
153152 mod = sys.modules['openerp.addons.' + package.name]
154 models = registry.load(cr, package)153 post_load = mod.openerp_descriptor['post_load']
154 if post_load:
155 getattr(mod, post_load)()
156
157
158 models = registry.add_module(cr, mod)
155159
156 loaded_modules.append(package.name)160 loaded_modules.append(package.name)
157 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):161 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
@@ -223,6 +227,27 @@
223227
224 return loaded_modules, processed_modules228 return loaded_modules, processed_modules
225229
230def init_module_models(cr, module_name, models):
231 """ Initialize a list of models.
232
233 Call _auto_init and init on each model to create or update the
234 database tables supporting the models. Call _auto_end to create
235 foreign keys. Call _update_stored_functional_field to initialize
236 stored functional fields.
237 """
238 _logger.info('module %s: creating or updating database tables', module_name)
239 stored_fields = []
240 for model in models:
241 stored_fields += model._auto_init(cr, {'module': module_name})
242 model.init(cr)
243 cr.commit()
244 for model in models:
245 model._auto_end(cr, {'module': module_name})
246 cr.commit()
247 for order, model, field_name in sorted(stored_fields):
248 model._update_stored_functional_field(cr, field_name)
249 cr.commit()
250
226def _check_module_names(cr, module_names):251def _check_module_names(cr, module_names):
227 mod_names = set(module_names)252 mod_names = set(module_names)
228 if 'base' in mod_names:253 if 'base' in mod_names:
@@ -257,7 +282,6 @@
257 # TODO status['progress'] reporting is broken: used twice (and reset each282 # TODO status['progress'] reporting is broken: used twice (and reset each
258 # time to zero) in load_module_graph, not fine-grained enough.283 # time to zero) in load_module_graph, not fine-grained enough.
259 # It should be a method exposed by the registry.284 # It should be a method exposed by the registry.
260 initialize_sys_path()
261285
262 force = []286 force = []
263 if force_demo:287 if force_demo:
264288
=== modified file 'openerp/modules/module.py'
--- openerp/modules/module.py 2013-06-28 15:07:55 +0000
+++ openerp/modules/module.py 2013-07-10 14:52:28 +0000
@@ -26,7 +26,6 @@
26from os.path import join as opj26from os.path import join as opj
27import sys27import sys
28import types28import types
29import zipimport
3029
31import openerp.tools as tools30import openerp.tools as tools
32import openerp.tools.osutil as osutil31import openerp.tools.osutil as osutil
@@ -36,9 +35,6 @@
36import openerp.release as release35import openerp.release as release
3736
38import re37import re
39import base64
40from zipfile import PyZipFile, ZIP_DEFLATED
41from cStringIO import StringIO
4238
43import logging39import logging
4440
@@ -48,21 +44,177 @@
48_ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)44_ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)
49ad_paths = []45ad_paths = []
5046
51# Modules already loaded47
52loaded = []48class Module(object):
5349 modules = []
54_logger = logging.getLogger(__name__)50 all_classes = []
51 dirty = False
52
53def derive_module_info():
54 rank_modules()
55 number_classes()
56 find_all_parents()
57 find_all_new_parents()
58 rank_classes()
59
60 Module.dirty = False
61
62def get_module(module_name):
63 return sys.modules['openerp.addons.' + module_name]
64
65def rank_modules():
66 for module in map(get_module, Module.modules):
67 module.rank = None
68 for module in map(get_module, Module.modules):
69 rank_module(module)
70 Module.modules = list(sorted(Module.modules, cmp=lambda x, y: cmp((get_module(x).openerp_rank, x), (get_module(y).openerp_rank, y))))
71
72def number_classes():
73 """rank_modules() must have been called."""
74 i = 0
75 Module.all_classes = []
76 for m in map(get_module, Module.modules):
77 for cls in m.openerp_classes:
78 Module.all_classes.append(cls)
79 cls._number = i
80 i += 1
81
82def rank_module(module):
83 if module.openerp_rank is None:
84 depends = module.openerp_descriptor['depends']
85 if depends:
86 module.openerp_rank = max(map(lambda d: rank_module(sys.modules['openerp.addons.' + d]), depends)) + 1
87 else:
88 module.openerp_rank = 0
89
90 return module.openerp_rank
91
92def rank_classes():
93 """find_all_new_parents() must have been called."""
94 for cls in Module.all_classes:
95 cls._rank = None
96 for cls in Module.all_classes:
97 rank_class(cls)
98
99 Module.ranked_classes = sorted(Module.all_classes, cmp=lambda x, y: cmp((x._rank, x._name), (y._rank, y._name)))
100
101def rank_class(cls):
102 if cls._rank is None:
103 if cls._parents:
104 cls._rank = max(map(lambda c: rank_class(c), cls._parents)) + 1
105 parent_modules = [p._original_module for p in cls._parents]
106 cls._original_module = [k for k in Module.modules if k in parent_modules][0]
107 else:
108 cls._rank = 0
109 cls._original_module = cls._module
110
111 return cls._rank
112
113def find_all_parents():
114 """number_classes() must have been called."""
115 for cls in Module.all_classes:
116 find_parents(cls)
117
118def find_parents(cls):
119 if isinstance(cls._inherit, list):
120 parents = cls._inherit
121 elif cls._inherit:
122 parents = [cls._inherit]
123 else:
124 parents = []
125 cls._parents = map(find_parent(cls), parents)
126 return cls._parents
127
128def find_parent(cls):
129 def f(name):
130 parent = None
131 for c in reversed(Module.all_classes[:cls._number]):
132 if c._name == name:
133 if not hasattr(c, '_children'):
134 c._children = []
135 c._children.append(cls)
136 parent = c
137 break
138 assert parent, "Cannot find class for model `%s`, specified as a parent of `%s`." % (name, cls._name)
139 return c
140 return f
141
142def find_all_new_parents():
143 """find_all_parents() must have been called."""
144 for cls in Module.all_classes:
145 cls._parents = map(find_new_parent(cls), cls._parents)
146
147def find_new_parent(cls):
148 def f(parent):
149 assert cls._name
150 assert parent._name
151 if cls._name != parent._name:
152 # cls is a copy of parent
153 for c in reversed(Module.all_classes):
154 if c._name == parent._name:
155 return c
156 return parent
157 return f
158
159def get_model_classes(name):
160 if Module.dirty:
161 derive_module_info()
162
163 last = None
164 for cls in reversed(Module.ranked_classes):
165 if cls._name == name:
166 last = cls
167 break
168 assert last, "No class found defining model `%s`.\n%s" % (name, map(lambda x: x._name, Module.ranked_classes))
169
170 return get_parents(cls, [])
171
172def get_parents(cls, accum):
173 if cls in accum:
174 accum.remove(cls) # Not accurate, we need to replicate Python MRO.
175 accum.append(cls)
176 for p in cls._parents:
177 get_parents(p, accum)
178 return accum
179
180def display_dot():
181 print 'digraph classes {'
182 print ' rankdir=BT;'
183 for cls in Module.all_classes:
184 print ' %s [label = "%s|%s"];' % (cls._number, cls._module, cls._name)
185
186 for cls in Module.all_classes:
187 for p in cls._parents:
188 if cls._name != p._name:
189 # cls is a copy of p
190 print ' %s -> %s [color="#1ED5FA"];' % (cls._number, p._number)
191 else:
192 print ' %s -> %s;' % (cls._number, p._number)
193
194 for m in map(get_module, Module.modules):
195 print ' subgraph %s {' % m.name
196 for cls in m.classes:
197 print ' %s;' % cls._number
198 print ' }'
199 print '}'
200
201# Temporary list for each OpenERP module all its model classes.
202# The list is available on the `openerp_classes` attribute
203# (on `sys.modules[module_name]`) but it doesn't exist yet
204# when the classes are registered.
205module_to_models = {}
55206
56class AddonsImportHook(object):207class AddonsImportHook(object):
57 """208 """
58 Import hook to load OpenERP addons from multiple paths.209 Import hook to load OpenERP addons from multiple paths. The dependencies
210 (declared in the `__openerp__.py` file) are imported too. A few attributes
211 are set on the module.
59212
60 OpenERP implements its own import-hook to load its addons. OpenERP213 OpenERP implements its own import-hook to load its addons. OpenERP
61 addons are Python modules. Originally, they were each living in their214 addons are Python modules. They must be imported from the `openerp.addons`
62 own top-level namespace, e.g. the sale module, or the hr module. For215 namespace. E.g. to import the `sale` module, use::
63 backward compatibility, `import <module>` is still supported. Now they216
64 are living in `openerp.addons`. The good way to import such modules is217 > import openerp.addons.sale
65 thus `import openerp.addons.module`.
66 """218 """
67219
68 def find_module(self, module_name, package_path):220 def find_module(self, module_name, package_path):
@@ -71,27 +223,38 @@
71 return self # We act as a loader too.223 return self # We act as a loader too.
72224
73 def load_module(self, module_name):225 def load_module(self, module_name):
74
75 module_parts = module_name.split('.')226 module_parts = module_name.split('.')
76 if len(module_parts) == 3 and module_name.startswith('openerp.addons.'):227 if len(module_parts) == 3 and module_name.startswith('openerp.addons.'):
77 module_part = module_parts[2]228 module_part = module_parts[2]
78 if module_name in sys.modules:229 if module_name in sys.modules:
79 return sys.modules[module_name]230 return sys.modules[module_name]
80231
232 # Import the dependencies.
233 info = load_information_from_description_file(module_part)
234 for d in info['depends']:
235 __import__('openerp.addons.' + d)
236
237 # Import the module.
81 # Note: we don't support circular import.238 # Note: we don't support circular import.
82 f, path, descr = imp.find_module(module_part, ad_paths)239 f, path, descr = imp.find_module(module_part, ad_paths)
83 mod = imp.load_module('openerp.addons.' + module_part, f, path, descr)240 mod = imp.load_module('openerp.addons.' + module_part, f, path, descr)
241
242 # We also use the import-hook to do some bookkeeping directly on
243 # the module itself.
244 mod.openerp_name = module_part
245 mod.openerp_descriptor = info
246 mod.openerp_classes = module_to_models.get(module_part, [])
247 mod.openerp_rank = None
84 sys.modules['openerp.addons.' + module_part] = mod248 sys.modules['openerp.addons.' + module_part] = mod
249 Module.modules.append(module_part)
250 Module.dirty = True
251
85 return mod252 return mod
86253
87def initialize_sys_path():254def initialize_sys_path():
88 """255 """
89 Setup an import-hook to be able to import OpenERP addons from the different256 Setup an import-hook to be able to import OpenERP addons from the different
90 addons paths.257 addons paths.
91
92 This ensures something like ``import crm`` (or even
93 ``import openerp.addons.crm``) works even if the addons are not in the
94 PYTHONPATH.
95 """258 """
96 global ad_paths259 global ad_paths
97 if ad_paths:260 if ad_paths:
@@ -154,68 +317,6 @@
154317
155 return tree318 return tree
156319
157def zip_directory(directory, b64enc=True, src=True):
158 """Compress a directory
159
160 @param directory: The directory to compress
161 @param base64enc: if True the function will encode the zip file with base64
162 @param src: Integrate the source files
163
164 @return: a string containing the zip file
165 """
166
167 RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
168
169 def _zippy(archive, path, src=True):
170 path = os.path.abspath(path)
171 base = os.path.basename(path)
172 for f in osutil.listdir(path, True):
173 bf = os.path.basename(f)
174 if not RE_exclude.search(bf) and (src or bf == '__openerp__.py' or not bf.endswith('.py')):
175 archive.write(os.path.join(path, f), os.path.join(base, f))
176
177 archname = StringIO()
178 archive = PyZipFile(archname, "w", ZIP_DEFLATED)
179
180 # for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8)
181 directory = tools.ustr(directory).encode('utf-8')
182
183 archive.writepy(directory)
184 _zippy(archive, directory, src=src)
185 archive.close()
186 archive_data = archname.getvalue()
187 archname.close()
188
189 if b64enc:
190 return base64.encodestring(archive_data)
191
192 return archive_data
193
194def get_module_as_zip(modulename, b64enc=True, src=True):
195 """Generate a module as zip file with the source or not and can do a base64 encoding
196
197 @param modulename: The module name
198 @param b64enc: if True the function will encode the zip file with base64
199 @param src: Integrate the source files
200
201 @return: a stream to store in a file-like object
202 """
203
204 ap = get_module_path(str(modulename))
205 if not ap:
206 raise Exception('Unable to find path for module %s' % modulename)
207
208 ap = ap.encode('utf8')
209 if os.path.isfile(ap + '.zip'):
210 val = file(ap + '.zip', 'rb').read()
211 if b64enc:
212 val = base64.encodestring(val)
213 else:
214 val = zip_directory(ap, b64enc, src)
215
216 return val
217
218
219def get_module_resource(module, *args):320def get_module_resource(module, *args):
220 """Return the full path of a resource of the given module.321 """Return the full path of a resource of the given module.
221322
@@ -295,76 +396,10 @@
295 info['version'] = adapt_version(info['version'])396 info['version'] = adapt_version(info['version'])
296 return info397 return info
297398
298 #TODO: refactor the logger in this file to follow the logging guidelines
299 # for 6.0
300 _logger.debug('module %s: no __openerp__.py file found.', module)399 _logger.debug('module %s: no __openerp__.py file found.', module)
301 return {}400 return {}
302401
303402
304def init_module_models(cr, module_name, obj_list):
305 """ Initialize a list of models.
306
307 Call _auto_init and init on each model to create or update the
308 database tables supporting the models.
309
310 TODO better explanation of _auto_init and init.
311
312 """
313 _logger.info('module %s: creating or updating database tables', module_name)
314 todo = []
315 for obj in obj_list:
316 result = obj._auto_init(cr, {'module': module_name})
317 if result:
318 todo += result
319 if hasattr(obj, 'init'):
320 obj.init(cr)
321 cr.commit()
322 for obj in obj_list:
323 obj._auto_end(cr, {'module': module_name})
324 cr.commit()
325 todo.sort()
326 for t in todo:
327 t[1](cr, *t[2])
328 cr.commit()
329
330def load_openerp_module(module_name):
331 """ Load an OpenERP module, if not already loaded.
332
333 This loads the module and register all of its models, thanks to either
334 the MetaModel metaclass, or the explicit instantiation of the model.
335 This is also used to load server-wide module (i.e. it is also used
336 when there is no model to register).
337 """
338 global loaded
339 if module_name in loaded:
340 return
341
342 initialize_sys_path()
343 try:
344 mod_path = get_module_path(module_name)
345 zip_mod_path = '' if not mod_path else mod_path + '.zip'
346 if not os.path.isfile(zip_mod_path):
347 __import__('openerp.addons.' + module_name)
348 else:
349 zimp = zipimport.zipimporter(zip_mod_path)
350 zimp.load_module(module_name)
351
352 # Call the module's post-load hook. This can done before any model or
353 # data has been initialized. This is ok as the post-load hook is for
354 # server-wide (instead of registry-specific) functionalities.
355 info = load_information_from_description_file(module_name)
356 if info['post_load']:
357 getattr(sys.modules['openerp.addons.' + module_name], info['post_load'])()
358
359 except Exception, e:
360 mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
361 msg = "Couldn't load %smodule %s" % (mt, module_name)
362 _logger.critical(msg)
363 _logger.critical(e)
364 raise
365 else:
366 loaded.append(module_name)
367
368def get_modules():403def get_modules():
369 """Returns the list of module names404 """Returns the list of module names
370 """405 """
@@ -388,17 +423,6 @@
388 return list(set(plist))423 return list(set(plist))
389424
390425
391def get_modules_with_version():
392 modules = get_modules()
393 res = dict.fromkeys(modules, adapt_version('1.0'))
394 for module in modules:
395 try:
396 info = load_information_from_description_file(module)
397 res[module] = info['version']
398 except Exception:
399 continue
400 return res
401
402def adapt_version(version):426def adapt_version(version):
403 serie = release.major_version427 serie = release.major_version
404 if version == serie or not version.startswith(serie + '.'):428 if version == serie or not version.startswith(serie + '.'):
405429
=== modified file 'openerp/modules/registry.py'
--- openerp/modules/registry.py 2013-06-19 10:53:35 +0000
+++ openerp/modules/registry.py 2013-07-10 14:52:28 +0000
@@ -24,13 +24,16 @@
24"""24"""
25from collections import Mapping25from collections import Mapping
26from contextlib import contextmanager26from contextlib import contextmanager
27import itertools
27import logging28import logging
29import sys
28import threading30import threading
2931
30import openerp.sql_db32import openerp.sql_db
31import openerp.osv.orm33import openerp.osv.orm
32import openerp.tools34import openerp.tools
33import openerp.modules.db35import openerp.modules.db
36import openerp.modules.loading
34import openerp.tools.config37import openerp.tools.config
35from openerp.tools import assertion_report38from openerp.tools import assertion_report
3639
@@ -53,6 +56,8 @@
53 self._init_parent = {}56 self._init_parent = {}
54 self._assertion_report = assertion_report.assertion_report()57 self._assertion_report = assertion_report.assertion_report()
55 self.fields_by_model = None58 self.fields_by_model = None
59 # Set of (set of classes) already consolidated and instanciated.
60 self.identities = set()
5661
57 # modules fully loaded (maintained during init phase by `loading` module)62 # modules fully loaded (maintained during init phase by `loading` module)
58 self._init_modules = set()63 self._init_modules = set()
@@ -115,24 +120,32 @@
115 """ Add or replace a model in the registry."""120 """ Add or replace a model in the registry."""
116 self.models[model_name] = model121 self.models[model_name] = model
117122
118 def load(self, cr, module):123 def add_module(self, cr, module):
119 """ Load a given module in the registry.124 module_names = [module.openerp_name] + list(self._init_modules)
120
121 At the Python level, the modules are already loaded, but not yet on a
122 per-registry level. This method populates a registry with the given
123 modules, i.e. it instanciates all the classes of a the given module
124 and registers them in the registry.
125
126 """
127 models_to_load = [] # need to preserve loading order125 models_to_load = [] # need to preserve loading order
128 # Instantiate registered classes (via the MetaModel automatic discovery126 modules = [sys.modules['openerp.addons.' + m] for m in module_names]
129 # or via explicit constructor call), and add them to the pool.127 # Re-consolidate all previously consolidated (and instanciated)
130 for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):128 # models, instead of only the one from the current module.
131 # models register themselves in self.models129 # We end up re-creating more models than strictly necessary;
132 model = cls.create_instance(self, cr)130 # normaly, only the parents (and their children) must be re-done.
133 if model._name not in models_to_load:131 for model in itertools.chain(*tuple(module.openerp_classes for module in modules)):
132 if model._name in models_to_load:
134 # avoid double-loading models whose declaration is split133 # avoid double-loading models whose declaration is split
135 models_to_load.append(model._name)134 continue
135 classes = list(openerp.modules.module.get_model_classes(model._name))
136 classes = filter(lambda ext: ext._module in module_names, classes)
137 # Derive an 'identity' for this set of classes so we can avoid
138 # reloading the model if it has not been changed by this module.
139 # There would be nothing wrong doing it, it would just take
140 # additional time.
141 i = tuple([id(c) for c in classes])
142 if i in self.identities:
143 continue
144 self.identities.add(i)
145 models_to_load.append(model._name)
146 cls = openerp.osv.orm.consolidate(model._name, classes)
147 obj = object.__new__(cls)
148 obj.__init__(self, cr)
136 return [self.models[m] for m in models_to_load]149 return [self.models[m] for m in models_to_load]
137150
138 def clear_caches(self):151 def clear_caches(self):
@@ -229,7 +242,7 @@
229 cls.registries[db_name] = registry242 cls.registries[db_name] = registry
230 try:243 try:
231 # This should be a method on Registry244 # This should be a method on Registry
232 openerp.modules.load_modules(registry.db, force_demo, status, update_module)245 openerp.modules.loading.load_modules(registry.db, force_demo, status, update_module)
233 except Exception:246 except Exception:
234 del cls.registries[db_name]247 del cls.registries[db_name]
235 raise248 raise
236249
=== modified file 'openerp/osv/orm.py'
--- openerp/osv/orm.py 2013-07-02 14:47:45 +0000
+++ openerp/osv/orm.py 2013-07-10 14:52:28 +0000
@@ -599,6 +599,53 @@
599 return pg_type599 return pg_type
600600
601601
602def consolidate(model_name, classes):
603 """ Create a consolidated model defined by the provided classes.
604 """
605 attributes = ['_columns', '_defaults', '_inherits', '_constraints',
606 '_sql_constraints']
607
608 assert classes, "Model `%s` consolidation cannot be done because there is no class to define it." % model_name
609 nattr = {'_register': False}
610 for s in attributes:
611 new = copy.copy(getattr(classes[-1], s))
612 for parent in list(reversed(classes))[1:]:
613 if s == '_columns':
614 # Duplicate float fields because they have a .digits
615 # cache (which must be per-registry, not server-wide).
616 for c in new.keys():
617 if new[c]._type == 'float':
618 new[c] = copy.copy(new[c])
619 if hasattr(new, 'update'):
620 new.update(parent.__dict__.get(s, {}))
621 elif s=='_constraints':
622 for c in parent.__dict__.get(s, []):
623 exist = False
624 for c2 in range(len(new)):
625 #For _constraints, we should check field and methods as well
626 if new[c2][2]==c[2] and (new[c2][0] == c[0] \
627 or getattr(new[c2][0],'__name__', True) == \
628 getattr(c[0],'__name__', False)):
629 # If new class defines a constraint with
630 # same function name, we let it override
631 # the old one.
632
633 new[c2] = c
634 exist = True
635 break
636 if not exist:
637 new.append(c)
638 else:
639 new.extend(parent.__dict__.get(s, []))
640 nattr[s] = new
641
642 # Keep links to non-inherited constraints, e.g. useful when exporting translations
643 nattr['_local_constraints'] = classes[0].__dict__.get('_constraints', [])
644 nattr['_local_sql_constraints'] = classes[0].__dict__.get('_sql_constraints', [])
645 cls = type(model_name, tuple(classes), nattr)
646 return cls
647
648
602class MetaModel(type):649class MetaModel(type):
603 """ Metaclass for the Model.650 """ Metaclass for the Model.
604651
@@ -609,14 +656,23 @@
609656
610 """657 """
611658
612 module_to_models = {}
613
614 def __init__(self, name, bases, attrs):659 def __init__(self, name, bases, attrs):
615 if not self._register:660 if not self._register:
616 self._register = True661 self._register = True
617 super(MetaModel, self).__init__(name, bases, attrs)662 super(MetaModel, self).__init__(name, bases, attrs)
618 return663 return
619664
665 # Derive the _name of the class.
666 if not self._name and isinstance(self._inherit, list) and len(self._inherit) == 1:
667 self._name = self._inherit[0]
668 elif not self._name and isinstance(self._inherit, list):
669 raise Exception, "_name must be provide when there are multiple _inherit parents."
670 elif not self._name:
671 self._name = self._inherit
672 assert self._name
673 # Sets the instance _inherit attribute (with the classs attribute).
674 self._inherit = self._inherit
675
620 # The (OpenERP) module name can be in the `openerp.addons` namespace676 # The (OpenERP) module name can be in the `openerp.addons` namespace
621 # or not. For instance module `sale` can be imported as677 # or not. For instance module `sale` can be imported as
622 # `openerp.addons.sale` (the good way) or `sale` (for backward678 # `openerp.addons.sale` (the good way) or `sale` (for backward
@@ -627,12 +683,15 @@
627 module_name = self.__module__.split('.')[2]683 module_name = self.__module__.split('.')[2]
628 else:684 else:
629 module_name = self.__module__.split('.')[0]685 module_name = self.__module__.split('.')[0]
686 _logger.warning('OpenERP module `%s` is not in the `openerp.addons` namespace.', module_name)
687
630 if not hasattr(self, '_module'):688 if not hasattr(self, '_module'):
631 self._module = module_name689 self._module = module_name
632690
633 # Remember which models to instanciate for this module.691 # Remember which models to instanciate for this module.
634 if not self._custom:692 if not self._custom:
635 self.module_to_models.setdefault(self._module, []).append(self)693 module_model_list = openerp.modules.module.module_to_models.setdefault(self._module, [])
694 module_model_list.append(self)
636695
637696
638# Definition of log access columns, automatically added to models if697# Definition of log access columns, automatically added to models if
@@ -688,6 +747,8 @@
688 # Transience747 # Transience
689 _transient = False # True in a TransientModel748 _transient = False # True in a TransientModel
690749
750 _inherit = 'base.model'
751
691 # structure:752 # structure:
692 # { 'parent_model': 'm2o_field', ... }753 # { 'parent_model': 'm2o_field', ... }
693 _inherits = {}754 _inherits = {}
@@ -835,119 +896,10 @@
835 break896 break
836 cr.commit()897 cr.commit()
837898
838 #
839 # Goal: try to apply inheritance at the instanciation level and
840 # put objects in the pool var
841 #
842 @classmethod
843 def create_instance(cls, pool, cr):
844 """ Instanciate a given model.
845
846 This class method instanciates the class of some model (i.e. a class
847 deriving from osv or osv_memory). The class might be the class passed
848 in argument or, if it inherits from another class, a class constructed
849 by combining the two classes.
850
851 The ``attributes`` argument specifies which parent class attributes
852 have to be combined.
853
854 TODO: the creation of the combined class is repeated at each call of
855 this method. This is probably unnecessary.
856
857 """
858 attributes = ['_columns', '_defaults', '_inherits', '_constraints',
859 '_sql_constraints']
860
861 parent_names = getattr(cls, '_inherit', None)
862 if parent_names:
863 if isinstance(parent_names, (str, unicode)):
864 name = cls._name or parent_names
865 parent_names = [parent_names]
866 else:
867 name = cls._name
868 if not name:
869 raise TypeError('_name is mandatory in case of multiple inheritance')
870
871 for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
872 if parent_name not in pool:
873 raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
874 'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
875 parent_model = pool[parent_name]
876 if not getattr(cls, '_original_module', None) and name == parent_model._name:
877 cls._original_module = parent_model._original_module
878 parent_class = parent_model.__class__
879 nattr = {}
880 for s in attributes:
881 new = copy.copy(getattr(parent_model, s, {}))
882 if s == '_columns':
883 # Don't _inherit custom fields.
884 for c in new.keys():
885 if new[c].manual:
886 del new[c]
887 # Duplicate float fields because they have a .digits
888 # cache (which must be per-registry, not server-wide).
889 for c in new.keys():
890 if new[c]._type == 'float':
891 new[c] = copy.copy(new[c])
892 if hasattr(new, 'update'):
893 new.update(cls.__dict__.get(s, {}))
894 elif s=='_constraints':
895 for c in cls.__dict__.get(s, []):
896 exist = False
897 for c2 in range(len(new)):
898 #For _constraints, we should check field and methods as well
899 if new[c2][2]==c[2] and (new[c2][0] == c[0] \
900 or getattr(new[c2][0],'__name__', True) == \
901 getattr(c[0],'__name__', False)):
902 # If new class defines a constraint with
903 # same function name, we let it override
904 # the old one.
905
906 new[c2] = c
907 exist = True
908 break
909 if not exist:
910 new.append(c)
911 else:
912 new.extend(cls.__dict__.get(s, []))
913 nattr[s] = new
914
915 # Keep links to non-inherited constraints, e.g. useful when exporting translations
916 nattr['_local_constraints'] = cls.__dict__.get('_constraints', [])
917 nattr['_local_sql_constraints'] = cls.__dict__.get('_sql_constraints', [])
918
919 cls = type(name, (cls, parent_class), dict(nattr, _register=False))
920 else:
921 cls._local_constraints = getattr(cls, '_constraints', [])
922 cls._local_sql_constraints = getattr(cls, '_sql_constraints', [])
923
924 if not getattr(cls, '_original_module', None):
925 cls._original_module = cls._module
926 obj = object.__new__(cls)
927 obj.__init__(pool, cr)
928 return obj
929
930 def __new__(cls):899 def __new__(cls):
931 """Register this model.900 assert openerp.conf.deprecation.allow_explicit_model_call
932901 _logger.warning("Instanciating a model (here `%s`) after defining it"
933 This doesn't create an instance but simply register the model902 " is deprecated.", cls._name or cls._inherit)
934 as being part of the module where it is defined.
935
936 """
937
938
939 # Set the module name (e.g. base, sale, accounting, ...) on the class.
940 module = cls.__module__.split('.')[0]
941 if not hasattr(cls, '_module'):
942 cls._module = module
943
944 # Record this class in the list of models to instantiate for this module,
945 # managed by the metaclass.
946 module_model_list = MetaModel.module_to_models.setdefault(cls._module, [])
947 if cls not in module_model_list:
948 if not cls._custom:
949 module_model_list.append(cls)
950
951 # Since we don't return an instance here, the __init__903 # Since we don't return an instance here, the __init__
952 # method won't be called.904 # method won't be called.
953 return None905 return None
@@ -1064,8 +1016,6 @@
1064 self._inherits_reload()1016 self._inherits_reload()
1065 if not self._sequence:1017 if not self._sequence:
1066 self._sequence = self._table + '_id_seq'1018 self._sequence = self._table + '_id_seq'
1067 for k in self._defaults:
1068 assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,)
1069 for f in self._columns:1019 for f in self._columns:
1070 self._columns[f].restart()1020 self._columns[f].restart()
10711021
@@ -2776,9 +2726,10 @@
2776 pos = browse_rec(root, pos)2726 pos = browse_rec(root, pos)
2777 return True2727 return True
27782728
2779 def _update_store(self, cr, f, k):2729 def _update_stored_functional_field(self, cr, k):
2780 _logger.info("storing computed values of fields.function '%s'", k)2730 _logger.info("Computing and storing functional field `%s`.", k)
2781 ss = self._columns[k]._symbol_set2731 f = self._columns[k]
2732 ss = f._symbol_set
2782 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])2733 update_query = 'UPDATE "%s" SET "%s"=%s WHERE id=%%s' % (self._table, k, ss[0])
2783 cr.execute('select id from '+self._table)2734 cr.execute('select id from '+self._table)
2784 ids_lst = map(lambda x: x[0], cr.fetchall())2735 ids_lst = map(lambda x: x[0], cr.fetchall())
@@ -2970,7 +2921,7 @@
2970 if context is None:2921 if context is None:
2971 context = {}2922 context = {}
2972 store_compute = False2923 store_compute = False
2973 todo_end = []2924 stored_fields = []
2974 update_custom_fields = context.get('update_custom_fields', False)2925 update_custom_fields = context.get('update_custom_fields', False)
2975 self._field_create(cr, context=context)2926 self._field_create(cr, context=context)
2976 create = not self._table_exist(cr)2927 create = not self._table_exist(cr)
@@ -3172,10 +3123,11 @@
31723123
3173 # remember the functions to call for the stored fields3124 # remember the functions to call for the stored fields
3174 if isinstance(f, fields.function):3125 if isinstance(f, fields.function):
3175 order = 10
3176 if f.store is not True: # i.e. if f.store is a dict3126 if f.store is not True: # i.e. if f.store is a dict
3177 order = f.store[f.store.keys()[0]][2]3127 order = f.store[f.store.keys()[0]][2]
3178 todo_end.append((order, self._update_store, (f, k)))3128 else:
3129 order = 10
3130 stored_fields.append((order, self, k))
31793131
3180 # and add constraints if needed3132 # and add constraints if needed
3181 if isinstance(f, fields.many2one):3133 if isinstance(f, fields.many2one):
@@ -3202,23 +3154,19 @@
3202 _logger.warning(msg, k, self._table, self._table, k)3154 _logger.warning(msg, k, self._table, self._table, k)
3203 cr.commit()3155 cr.commit()
32043156
3205 else:
3206 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
3207 create = not bool(cr.fetchone())
3208
3209 cr.commit() # start a new transaction3157 cr.commit() # start a new transaction
32103158
3211 if self._auto:3159 if self._auto:
3212 self._add_sql_constraints(cr)3160 self._add_sql_constraints(cr)
32133161
3214 if create:
3215 self._execute_sql(cr)
3216
3217 if store_compute:3162 if store_compute:
3218 self._parent_store_compute(cr)3163 self._parent_store_compute(cr)
3219 cr.commit()3164 cr.commit()
32203165
3221 return todo_end3166 return stored_fields
3167
3168 def init(self, cr):
3169 pass
32223170
3223 def _auto_end(self, cr, context=None):3171 def _auto_end(self, cr, context=None):
3224 """ Create the foreign keys recorded by _auto_init. """3172 """ Create the foreign keys recorded by _auto_init. """
@@ -3228,6 +3176,9 @@
3228 cr.commit()3176 cr.commit()
3229 del self._foreign_keys3177 del self._foreign_keys
32303178
3179 for k in self._defaults:
3180 assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,)
3181
32313182
3232 def _table_exist(self, cr):3183 def _table_exist(self, cr):
3233 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))3184 cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
@@ -3393,15 +3344,6 @@
3393 cr.rollback()3344 cr.rollback()
33943345
33953346
3396 def _execute_sql(self, cr):
3397 """ Execute the SQL code from the _sql attribute (if any)."""
3398 if hasattr(self, "_sql"):
3399 for line in self._sql.split(';'):
3400 line2 = line.replace('\n', '').strip()
3401 if line2:
3402 cr.execute(line2)
3403 cr.commit()
3404
3405 #3347 #
3406 # Update objects that uses this one to update their _inherits fields3348 # Update objects that uses this one to update their _inherits fields
3407 #3349 #
34083350
=== modified file 'openerp/service/__init__.py'
--- openerp/service/__init__.py 2013-06-12 15:28:26 +0000
+++ openerp/service/__init__.py 2013-07-10 14:52:28 +0000
@@ -60,7 +60,12 @@
60def load_server_wide_modules():60def load_server_wide_modules():
61 for m in openerp.conf.server_wide_modules:61 for m in openerp.conf.server_wide_modules:
62 try:62 try:
63 openerp.modules.module.load_openerp_module(m)63 __import__('openerp.addons.' + m)
64 mod = sys.modules['openerp.addons.' + m]
65 post_load = mod.openerp_descriptor['post_load']
66 if post_load:
67 getattr(mod, post_load)()
68
64 except Exception:69 except Exception:
65 msg = ''70 msg = ''
66 if m == 'web':71 if m == 'web':
6772
=== added directory 'openerp/tests/addons/test_base_model_a'
=== added file 'openerp/tests/addons/test_base_model_a/__init__.py'
--- openerp/tests/addons/test_base_model_a/__init__.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_a/__init__.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,3 @@
1# -*- coding: utf-8 -*-
2import models
3# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
04
=== added file 'openerp/tests/addons/test_base_model_a/__openerp__.py'
--- openerp/tests/addons/test_base_model_a/__openerp__.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_a/__openerp__.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,15 @@
1# -*- coding: utf-8 -*-
2{
3 'name': 'test-base-model-a',
4 'version': '0.1',
5 'category': 'Tests',
6 'description': """A module to test the BaseModel inheritance.""",
7 'author': 'OpenERP SA',
8 'maintainer': 'OpenERP SA',
9 'website': 'http://www.openerp.com',
10 'depends': ['base'],
11 'data': [],
12 'installable': True,
13 'auto_install': False,
14}
15# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
016
=== added file 'openerp/tests/addons/test_base_model_a/models.py'
--- openerp/tests/addons/test_base_model_a/models.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_a/models.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,26 @@
1# -*- coding: utf-8 -*-
2import openerp
3
4class a(openerp.osv.orm.Model):
5 """ This model is just a copy of BaseModel.
6 In the context of the base.model tests, we will check if the _columns of
7 this model is indeed the consolidated _colmuns of base.model.
8
9 This test module will first add a column_n to base.model and thus we will
10 check in the unit tests if test.base.model.a contains it.
11
12 In the second test_base_model_b addons, we will add a column_o and again
13 check if test.base.model.a contains it in addition to column_n.
14 """
15 _name = 'test.base.model.a'
16
17class n(openerp.osv.orm.AbstractModel):
18 """ This model augments BaseModel.
19 """
20 _inherit = 'base.model'
21
22 _columns = {
23 'column_n': openerp.osv.fields.char(),
24 }
25
26# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
027
=== added directory 'openerp/tests/addons/test_base_model_a/tests'
=== added file 'openerp/tests/addons/test_base_model_a/tests/__init__.py'
--- openerp/tests/addons/test_base_model_a/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_a/tests/__init__.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,13 @@
1# -*- coding: utf-8 -*-
2
3from . import test_base_model
4
5fast_suite = [
6]
7
8checks = [
9 # This test assumes to be installed alone.
10 # test_base_model,
11]
12
13# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
014
=== added file 'openerp/tests/addons/test_base_model_a/tests/test_base_model.py'
--- openerp/tests/addons/test_base_model_a/tests/test_base_model.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_a/tests/test_base_model.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,40 @@
1# -*- coding: utf-8 -*-
2import openerp.modules.registry
3import openerp
4
5from openerp.tests import common
6from openerp.tools.misc import mute_logger
7
8class test_base_model_a(common.TransactionCase):
9
10 def __init__(self, *args, **kwargs):
11 super(test_base_model_a, self).__init__(*args, **kwargs)
12 self.model = None
13
14 def setUp(self):
15 super(test_base_model_a, self).setUp()
16 self.model_a = self.registry('test.base.model.a')
17
18 def test_model_name(self):
19 self.assertEqual(
20 self.model_a._name,
21 'test.base.model.a')
22
23 def test_model_columns(self):
24 self.assertEqual(
25 self.model_a._columns.keys(),
26 ['column_n'])
27
28 def test_model_inherit(self):
29 self.assertEqual(
30 self.model_a._inherit,
31 'base.model')
32
33 def test_model_table(self):
34 # Assert no table is backing base.model.
35 self.cr.execute("SELECT relname FROM pg_class WHERE relkind = 'r' AND relname=%s", ('base_model',))
36 self.assertTrue(not self.cr.dictfetchall())
37
38 # Make sure the previous assertion works.
39 self.cr.execute("SELECT relname FROM pg_class WHERE relkind = 'r' AND relname=%s", ('res_partner',))
40 self.assertTrue(self.cr.dictfetchall())
041
=== added directory 'openerp/tests/addons/test_base_model_b'
=== added file 'openerp/tests/addons/test_base_model_b/__init__.py'
--- openerp/tests/addons/test_base_model_b/__init__.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_b/__init__.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,3 @@
1# -*- coding: utf-8 -*-
2import models
3# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
04
=== added file 'openerp/tests/addons/test_base_model_b/__openerp__.py'
--- openerp/tests/addons/test_base_model_b/__openerp__.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_b/__openerp__.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,15 @@
1# -*- coding: utf-8 -*-
2{
3 'name': 'test-base-model-b',
4 'version': '0.1',
5 'category': 'Tests',
6 'description': """A module to test the BaseModel inheritance.""",
7 'author': 'OpenERP SA',
8 'maintainer': 'OpenERP SA',
9 'website': 'http://www.openerp.com',
10 'depends': ['base', 'test_base_model_a'],
11 'data': [],
12 'installable': True,
13 'auto_install': False,
14}
15# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
016
=== added file 'openerp/tests/addons/test_base_model_b/models.py'
--- openerp/tests/addons/test_base_model_b/models.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_b/models.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,18 @@
1# -*- coding: utf-8 -*-
2import openerp
3
4class b(openerp.osv.orm.Model):
5 """ This model is just a copy of BaseModel.
6 """
7 _name = 'test.base.model.b'
8
9class o(openerp.osv.orm.AbstractModel):
10 """ This model augments BaseModel.
11 """
12 _inherit = 'base.model'
13
14 _columns = {
15 'column_o': openerp.osv.fields.char(),
16 }
17
18# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
019
=== added directory 'openerp/tests/addons/test_base_model_b/tests'
=== added file 'openerp/tests/addons/test_base_model_b/tests/__init__.py'
--- openerp/tests/addons/test_base_model_b/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_b/tests/__init__.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,13 @@
1# -*- coding: utf-8 -*-
2
3from . import test_base_model
4
5fast_suite = [
6]
7
8checks = [
9 # This test assumes to be installed alone.
10 # test_base_model,
11]
12
13# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
014
=== added file 'openerp/tests/addons/test_base_model_b/tests/test_base_model.py'
--- openerp/tests/addons/test_base_model_b/tests/test_base_model.py 1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_base_model_b/tests/test_base_model.py 2013-07-10 14:52:28 +0000
@@ -0,0 +1,50 @@
1# -*- coding: utf-8 -*-
2import openerp.modules.registry
3import openerp
4
5from openerp.tests import common
6from openerp.tools.misc import mute_logger
7
8class test_base_model_b(common.TransactionCase):
9
10 def __init__(self, *args, **kwargs):
11 super(test_base_model_b, self).__init__(*args, **kwargs)
12 self.model = None
13
14 def setUp(self):
15 super(test_base_model_b, self).setUp()
16 self.model_a = self.registry('test.base.model.a')
17 self.model_b = self.registry('test.base.model.b')
18
19 def test_model_name(self):
20 self.assertEqual(
21 self.model_a._name,
22 'test.base.model.a')
23 self.assertEqual(
24 self.model_b._name,
25 'test.base.model.b')
26
27 def test_model_columns(self):
28 self.assertEqual(
29 sorted(self.model_a._columns.keys()),
30 ['column_n', 'column_o'])
31 self.assertEqual(
32 sorted(self.model_b._columns.keys()),
33 ['column_n', 'column_o'])
34
35 def test_model_inherit(self):
36 self.assertEqual(
37 self.model_a._inherit,
38 'base.model')
39 self.assertEqual(
40 self.model_b._inherit,
41 'base.model')
42
43 def test_model_table(self):
44 # Assert no table is backing base.model.
45 self.cr.execute("SELECT relname FROM pg_class WHERE relkind = 'r' AND relname=%s", ('base_model',))
46 self.assertTrue(not self.cr.dictfetchall())
47
48 # Make sure the previous assertion works.
49 self.cr.execute("SELECT relname FROM pg_class WHERE relkind = 'r' AND relname=%s", ('res_partner',))
50 self.assertTrue(self.cr.dictfetchall())