Merge lp:~openerp-dev/openobject-server/trunk-base-model-thu into lp:openobject-server
- trunk-base-model-thu
- Merge into trunk
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 |
Related bugs: |
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 |
Commit message
Description of the change
It seems this should fix https:/
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()
(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.
- The previous point is possible because now __new__() is useless: everything it was doing is handled by the MetaModel meta class.
- Removed load_openerp_
- 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.
- 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.
Raphael Collet (OpenERP) (rco-openerp) wrote : | # |
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.
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.
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
1 | === modified file 'doc/changelog.rst' | |||
2 | --- doc/changelog.rst 2013-06-21 07:44:28 +0000 | |||
3 | +++ doc/changelog.rst 2013-07-10 14:52:28 +0000 | |||
4 | @@ -6,6 +6,11 @@ | |||
5 | 6 | `trunk` | 6 | `trunk` |
6 | 7 | ------- | 7 | ------- |
7 | 8 | 8 | ||
8 | 9 | - Introduced a ``base.model``. It is to OpenERP models what ``object`` is | ||
9 | 10 | to Python classes. To make this possible, it means that now copied models | ||
10 | 11 | (ie. models using ``_inherit`` and providing a new ``_name``) will copy | ||
11 | 12 | the fully consolidated parent, instead of the parent as-is as the time | ||
12 | 13 | of module installation. | ||
13 | 9 | - Almost removed ``LocalService()``. For reports, | 14 | - Almost removed ``LocalService()``. For reports, |
14 | 10 | ``openerp.osv.orm.Model.print_report()`` can be used. For workflows, see | 15 | ``openerp.osv.orm.Model.print_report()`` can be used. For workflows, see |
15 | 11 | :ref:`orm-workflows`. | 16 | :ref:`orm-workflows`. |
16 | 12 | 17 | ||
17 | === modified file 'openerp/addons/__init__.py' | |||
18 | --- openerp/addons/__init__.py 2012-01-09 12:41:20 +0000 | |||
19 | +++ openerp/addons/__init__.py 2013-07-10 14:52:28 +0000 | |||
20 | @@ -23,18 +23,10 @@ | |||
21 | 23 | """ Addons module. | 23 | """ Addons module. |
22 | 24 | 24 | ||
23 | 25 | This module serves to contain all OpenERP addons, across all configured addons | 25 | This module serves to contain all OpenERP addons, across all configured addons |
33 | 26 | paths. For the code to manage those addons, see openerp.modules. | 26 | paths. For the code to manage those addons, see `openerp.modules`. |
34 | 27 | 27 | ||
35 | 28 | Addons are made available under `openerp.addons` after | 28 | Addons can be imported from `openerp.addons` after |
36 | 29 | openerp.tools.config.parse_config() is called (so that the addons paths are | 29 | `openerp.modules.module.initialize_sys_path()` has been called. |
28 | 30 | known). | ||
29 | 31 | |||
30 | 32 | This module also conveniently reexports some symbols from openerp.modules. | ||
31 | 33 | Importing them from here is deprecated. | ||
32 | 34 | |||
37 | 35 | """ | 30 | """ |
38 | 36 | 31 | ||
39 | 37 | # get_module_path is used only by base_module_quality | ||
40 | 38 | from openerp.modules import get_module_resource, get_module_path | ||
41 | 39 | |||
42 | 40 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 32 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
43 | 41 | 33 | ||
44 | === modified file 'openerp/addons/base/__init__.py' | |||
45 | --- openerp/addons/base/__init__.py 2012-08-22 06:28:23 +0000 | |||
46 | +++ openerp/addons/base/__init__.py 2013-07-10 14:52:28 +0000 | |||
47 | @@ -19,6 +19,7 @@ | |||
48 | 19 | # | 19 | # |
49 | 20 | ############################################################################## | 20 | ############################################################################## |
50 | 21 | 21 | ||
51 | 22 | import models | ||
52 | 22 | import ir | 23 | import ir |
53 | 23 | import module | 24 | import module |
54 | 24 | import res | 25 | import res |
55 | 25 | 26 | ||
56 | === modified file 'openerp/addons/base/ir/ir_actions.py' | |||
57 | --- openerp/addons/base/ir/ir_actions.py 2013-06-13 17:39:00 +0000 | |||
58 | +++ openerp/addons/base/ir/ir_actions.py 2013-07-10 14:52:28 +0000 | |||
59 | @@ -354,10 +354,11 @@ | |||
60 | 354 | 'multi': False, | 354 | 'multi': False, |
61 | 355 | } | 355 | } |
62 | 356 | def _auto_init(self, cr, context=None): | 356 | def _auto_init(self, cr, context=None): |
64 | 357 | super(act_window_view, self)._auto_init(cr, context) | 357 | res = super(act_window_view, self)._auto_init(cr, context) |
65 | 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\'') |
66 | 359 | if not cr.fetchone(): | 359 | if not cr.fetchone(): |
67 | 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)') |
68 | 361 | return res | ||
69 | 361 | act_window_view() | 362 | act_window_view() |
70 | 362 | 363 | ||
71 | 363 | class act_wizard(osv.osv): | 364 | class act_wizard(osv.osv): |
72 | 364 | 365 | ||
73 | === modified file 'openerp/addons/base/ir/ir_attachment.py' | |||
74 | --- openerp/addons/base/ir/ir_attachment.py 2013-06-12 15:28:26 +0000 | |||
75 | +++ openerp/addons/base/ir/ir_attachment.py 2013-07-10 14:52:28 +0000 | |||
76 | @@ -175,11 +175,12 @@ | |||
77 | 175 | } | 175 | } |
78 | 176 | 176 | ||
79 | 177 | def _auto_init(self, cr, context=None): | 177 | def _auto_init(self, cr, context=None): |
81 | 178 | super(ir_attachment, self)._auto_init(cr, context) | 178 | res = super(ir_attachment, self)._auto_init(cr, context) |
82 | 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',)) |
83 | 180 | if not cr.fetchone(): | 180 | if not cr.fetchone(): |
84 | 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)') |
85 | 182 | cr.commit() | 182 | cr.commit() |
86 | 183 | return res | ||
87 | 183 | 184 | ||
88 | 184 | def check(self, cr, uid, ids, mode, context=None, values=None): | 185 | def check(self, cr, uid, ids, mode, context=None, values=None): |
89 | 185 | """Restricts the access to an ir.attachment, according to referred model | 186 | """Restricts the access to an ir.attachment, according to referred model |
90 | 186 | 187 | ||
91 | === modified file 'openerp/addons/base/ir/ir_filters.py' | |||
92 | --- openerp/addons/base/ir/ir_filters.py 2013-02-20 10:34:59 +0000 | |||
93 | +++ openerp/addons/base/ir/ir_filters.py 2013-07-10 14:52:28 +0000 | |||
94 | @@ -118,12 +118,13 @@ | |||
95 | 118 | ] | 118 | ] |
96 | 119 | 119 | ||
97 | 120 | def _auto_init(self, cr, context=None): | 120 | def _auto_init(self, cr, context=None): |
99 | 121 | super(ir_filters, self)._auto_init(cr, context) | 121 | res = super(ir_filters, self)._auto_init(cr, context) |
100 | 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) |
101 | 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'") |
102 | 124 | if not cr.fetchone(): | 124 | if not cr.fetchone(): |
103 | 125 | cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_index" ON ir_filters | 125 | cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_index" ON ir_filters |
104 | 126 | (lower(name), model_id, COALESCE(user_id,-1))""") | 126 | (lower(name), model_id, COALESCE(user_id,-1))""") |
105 | 127 | return res | ||
106 | 127 | 128 | ||
107 | 128 | _columns = { | 129 | _columns = { |
108 | 129 | 'name': fields.char('Filter Name', size=64, translate=True, required=True), | 130 | 'name': fields.char('Filter Name', size=64, translate=True, required=True), |
109 | 130 | 131 | ||
110 | === modified file 'openerp/addons/base/ir/ir_model.py' | |||
111 | --- openerp/addons/base/ir/ir_model.py 2013-06-19 09:13:32 +0000 | |||
112 | +++ openerp/addons/base/ir/ir_model.py 2013-07-10 14:52:28 +0000 | |||
113 | @@ -203,9 +203,13 @@ | |||
114 | 203 | def instanciate(self, cr, user, model, context=None): | 203 | def instanciate(self, cr, user, model, context=None): |
115 | 204 | class x_custom_model(osv.osv): | 204 | class x_custom_model(osv.osv): |
116 | 205 | _custom = True | 205 | _custom = True |
120 | 206 | x_custom_model._name = model | 206 | _name = model |
121 | 207 | x_custom_model._module = False | 207 | _module = False |
122 | 208 | a = x_custom_model.create_instance(self.pool, cr) | 208 | classes = list(openerp.modules.module.get_model_classes('base.model')) |
123 | 209 | classes = [x_custom_model] + classes | ||
124 | 210 | cls = openerp.osv.orm.consolidate(model, classes) | ||
125 | 211 | a = object.__new__(cls) | ||
126 | 212 | a.__init__(self.pool, cr) | ||
127 | 209 | if not a._columns: | 213 | if not a._columns: |
128 | 210 | x_name = 'id' | 214 | x_name = 'id' |
129 | 211 | elif 'x_name' in a._columns.keys(): | 215 | elif 'x_name' in a._columns.keys(): |
130 | @@ -842,10 +846,11 @@ | |||
131 | 842 | self.loads = self.pool.model_data_reference_ids | 846 | self.loads = self.pool.model_data_reference_ids |
132 | 843 | 847 | ||
133 | 844 | def _auto_init(self, cr, context=None): | 848 | def _auto_init(self, cr, context=None): |
135 | 845 | super(ir_model_data, self)._auto_init(cr, context) | 849 | res = super(ir_model_data, self)._auto_init(cr, context) |
136 | 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\'') |
137 | 847 | if not cr.fetchone(): | 851 | if not cr.fetchone(): |
138 | 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)') |
139 | 853 | return res | ||
140 | 849 | 854 | ||
141 | 850 | @tools.ormcache() | 855 | @tools.ormcache() |
142 | 851 | def _get_id(self, cr, uid, module, xml_id): | 856 | def _get_id(self, cr, uid, module, xml_id): |
143 | 852 | 857 | ||
144 | === modified file 'openerp/addons/base/ir/ir_translation.py' | |||
145 | --- openerp/addons/base/ir/ir_translation.py 2013-04-18 11:46:27 +0000 | |||
146 | +++ openerp/addons/base/ir/ir_translation.py 2013-07-10 14:52:28 +0000 | |||
147 | @@ -223,7 +223,7 @@ | |||
148 | 223 | 'Language code of translation item must be among known languages' ), ] | 223 | 'Language code of translation item must be among known languages' ), ] |
149 | 224 | 224 | ||
150 | 225 | def _auto_init(self, cr, context=None): | 225 | def _auto_init(self, cr, context=None): |
152 | 226 | super(ir_translation, self)._auto_init(cr, context) | 226 | res = super(ir_translation, self)._auto_init(cr, context) |
153 | 227 | 227 | ||
154 | 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. |
155 | 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',)) |
156 | @@ -247,6 +247,8 @@ | |||
157 | 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)') |
158 | 248 | cr.commit() | 248 | cr.commit() |
159 | 249 | 249 | ||
160 | 250 | return res | ||
161 | 251 | |||
162 | 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): |
163 | 251 | if field == 'lang': | 253 | if field == 'lang': |
164 | 252 | return | 254 | return |
165 | 253 | 255 | ||
166 | === modified file 'openerp/addons/base/ir/ir_ui_view.py' | |||
167 | --- openerp/addons/base/ir/ir_ui_view.py 2013-06-05 12:11:43 +0000 | |||
168 | +++ openerp/addons/base/ir/ir_ui_view.py 2013-07-10 14:52:28 +0000 | |||
169 | @@ -41,10 +41,11 @@ | |||
170 | 41 | } | 41 | } |
171 | 42 | 42 | ||
172 | 43 | def _auto_init(self, cr, context=None): | 43 | def _auto_init(self, cr, context=None): |
174 | 44 | super(view_custom, self)._auto_init(cr, context) | 44 | res = super(view_custom, self)._auto_init(cr, context) |
175 | 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\'') |
176 | 46 | if not cr.fetchone(): | 46 | if not cr.fetchone(): |
177 | 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)') |
178 | 48 | return res | ||
179 | 48 | 49 | ||
180 | 49 | class view(osv.osv): | 50 | class view(osv.osv): |
181 | 50 | _name = 'ir.ui.view' | 51 | _name = 'ir.ui.view' |
182 | @@ -165,10 +166,11 @@ | |||
183 | 165 | ] | 166 | ] |
184 | 166 | 167 | ||
185 | 167 | def _auto_init(self, cr, context=None): | 168 | def _auto_init(self, cr, context=None): |
187 | 168 | super(view, self)._auto_init(cr, context) | 169 | res = super(view, self)._auto_init(cr, context) |
188 | 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\'') |
189 | 170 | if not cr.fetchone(): | 171 | if not cr.fetchone(): |
190 | 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)') |
191 | 173 | return res | ||
192 | 172 | 174 | ||
193 | 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): |
194 | 174 | """Retrieves the architecture of views that inherit from the given view, from the sets of | 176 | """Retrieves the architecture of views that inherit from the given view, from the sets of |
195 | @@ -293,10 +295,11 @@ | |||
196 | 293 | } | 295 | } |
197 | 294 | 296 | ||
198 | 295 | def _auto_init(self, cr, context=None): | 297 | def _auto_init(self, cr, context=None): |
200 | 296 | super(view_sc, self)._auto_init(cr, context) | 298 | res = super(view_sc, self)._auto_init(cr, context) |
201 | 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\'') |
202 | 298 | if not cr.fetchone(): | 300 | if not cr.fetchone(): |
203 | 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)') |
204 | 302 | return res | ||
205 | 300 | 303 | ||
206 | 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): |
207 | 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) |
208 | 303 | 306 | ||
209 | === modified file 'openerp/addons/base/ir/ir_values.py' | |||
210 | --- openerp/addons/base/ir/ir_values.py 2013-06-13 17:39:00 +0000 | |||
211 | +++ openerp/addons/base/ir/ir_values.py 2013-07-10 14:52:28 +0000 | |||
212 | @@ -183,10 +183,11 @@ | |||
213 | 183 | } | 183 | } |
214 | 184 | 184 | ||
215 | 185 | def _auto_init(self, cr, context=None): | 185 | def _auto_init(self, cr, context=None): |
217 | 186 | super(ir_values, self)._auto_init(cr, context) | 186 | res = super(ir_values, self)._auto_init(cr, context) |
218 | 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\'') |
219 | 188 | if not cr.fetchone(): | 188 | if not cr.fetchone(): |
220 | 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)') |
221 | 190 | return res | ||
222 | 190 | 191 | ||
223 | 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): |
224 | 192 | """Defines a default value for the given model and field_name. Any previous | 193 | """Defines a default value for the given model and field_name. Any previous |
225 | 193 | 194 | ||
226 | === modified file 'openerp/addons/base/ir/workflow/workflow.py' | |||
227 | --- openerp/addons/base/ir/workflow/workflow.py 2013-06-13 17:39:00 +0000 | |||
228 | +++ openerp/addons/base/ir/workflow/workflow.py 2013-07-10 14:52:28 +0000 | |||
229 | @@ -137,13 +137,14 @@ | |||
230 | 137 | 'state': fields.char('Status', size=32), | 137 | 'state': fields.char('Status', size=32), |
231 | 138 | } | 138 | } |
232 | 139 | def _auto_init(self, cr, context=None): | 139 | def _auto_init(self, cr, context=None): |
234 | 140 | super(wkf_instance, self)._auto_init(cr, context) | 140 | res = super(wkf_instance, self)._auto_init(cr, context) |
235 | 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\'') |
236 | 142 | if not cr.fetchone(): | 142 | if not cr.fetchone(): |
237 | 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)') |
238 | 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\'') |
239 | 145 | if not cr.fetchone(): | 145 | if not cr.fetchone(): |
240 | 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)') |
241 | 147 | return res | ||
242 | 147 | 148 | ||
243 | 148 | wkf_instance() | 149 | wkf_instance() |
244 | 149 | 150 | ||
245 | @@ -172,10 +173,11 @@ | |||
246 | 172 | 'workitem_id': fields.many2one('workflow.workitem', 'Workitem', required=True, ondelete="cascade"), | 173 | 'workitem_id': fields.many2one('workflow.workitem', 'Workitem', required=True, ondelete="cascade"), |
247 | 173 | } | 174 | } |
248 | 174 | def _auto_init(self, cr, context=None): | 175 | def _auto_init(self, cr, context=None): |
250 | 175 | super(wkf_triggers, self)._auto_init(cr, context) | 176 | res = super(wkf_triggers, self)._auto_init(cr, context) |
251 | 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\'') |
252 | 177 | if not cr.fetchone(): | 178 | if not cr.fetchone(): |
253 | 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)') |
254 | 180 | return res | ||
255 | 179 | wkf_triggers() | 181 | wkf_triggers() |
256 | 180 | 182 | ||
257 | 181 | 183 | ||
258 | 182 | 184 | ||
259 | === added file 'openerp/addons/base/models.py' | |||
260 | --- openerp/addons/base/models.py 1970-01-01 00:00:00 +0000 | |||
261 | +++ openerp/addons/base/models.py 2013-07-10 14:52:28 +0000 | |||
262 | @@ -0,0 +1,19 @@ | |||
263 | 1 | from openerp.osv.orm import BaseModel | ||
264 | 2 | |||
265 | 3 | # Lovely: `base.model` inherits from `BaseModel`. | ||
266 | 4 | # | ||
267 | 5 | # `BaseModel` and `Model` are exactly the same, but with diamond inheritance of | ||
268 | 6 | # `base.model`, if `Model` was used instead, `Model` would be higher in the MRO | ||
269 | 7 | # than `AbstractModel`, e.g. with models inheriting from `mail.message` or | ||
270 | 8 | # `mail.thread`. By using `BaseModel`, we keep `Model` below `AbstractModel` | ||
271 | 9 | # and its `_auto=True` attribute "wins" over the `_auto=False` of | ||
272 | 10 | # `AbstractModel`. | ||
273 | 11 | class base_model(BaseModel): | ||
274 | 12 | _inherit = None | ||
275 | 13 | _name = 'base.model' | ||
276 | 14 | |||
277 | 15 | _auto = False # No table in database. | ||
278 | 16 | _transient = False | ||
279 | 17 | |||
280 | 18 | _columns = { | ||
281 | 19 | } | ||
282 | 0 | 20 | ||
283 | === modified file 'openerp/addons/base/module/module.py' | |||
284 | --- openerp/addons/base/module/module.py 2013-06-28 10:04:07 +0000 | |||
285 | +++ openerp/addons/base/module/module.py 2013-07-10 14:52:28 +0000 | |||
286 | @@ -40,7 +40,7 @@ | |||
287 | 40 | from StringIO import StringIO # NOQA | 40 | from StringIO import StringIO # NOQA |
288 | 41 | 41 | ||
289 | 42 | import openerp | 42 | import openerp |
291 | 43 | from openerp import modules, tools, addons | 43 | from openerp import modules, tools |
292 | 44 | from openerp.modules.db import create_categories | 44 | from openerp.modules.db import create_categories |
293 | 45 | from openerp.tools.parse_version import parse_version | 45 | from openerp.tools.parse_version import parse_version |
294 | 46 | from openerp.tools.translate import _ | 46 | from openerp.tools.translate import _ |
295 | @@ -153,7 +153,7 @@ | |||
296 | 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): |
297 | 154 | res = dict.fromkeys(ids, '') | 154 | res = dict.fromkeys(ids, '') |
298 | 155 | for module in self.browse(cr, uid, ids, context=context): | 155 | for module in self.browse(cr, uid, ids, context=context): |
300 | 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') |
301 | 157 | if path: | 157 | if path: |
302 | 158 | with tools.file_open(path, 'rb') as desc_file: | 158 | with tools.file_open(path, 'rb') as desc_file: |
303 | 159 | doc = desc_file.read() | 159 | doc = desc_file.read() |
304 | @@ -169,7 +169,7 @@ | |||
305 | 169 | return res | 169 | return res |
306 | 170 | 170 | ||
307 | 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): |
309 | 172 | default_version = modules.adapt_version('1.0') | 172 | default_version = modules.module.adapt_version('1.0') |
310 | 173 | res = dict.fromkeys(ids, default_version) | 173 | res = dict.fromkeys(ids, default_version) |
311 | 174 | for m in self.browse(cr, uid, ids): | 174 | for m in self.browse(cr, uid, ids): |
312 | 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) |
313 | @@ -243,7 +243,7 @@ | |||
314 | 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): |
315 | 244 | res = dict.fromkeys(ids, '') | 244 | res = dict.fromkeys(ids, '') |
316 | 245 | for module in self.browse(cr, uid, ids, context=context): | 245 | for module in self.browse(cr, uid, ids, context=context): |
318 | 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') |
319 | 247 | if path: | 247 | if path: |
320 | 248 | image_file = tools.file_open(path, 'rb') | 248 | image_file = tools.file_open(path, 'rb') |
321 | 249 | try: | 249 | try: |
322 | @@ -584,7 +584,7 @@ | |||
323 | 584 | def update_list(self, cr, uid, context=None): | 584 | def update_list(self, cr, uid, context=None): |
324 | 585 | res = [0, 0] # [update, add] | 585 | res = [0, 0] # [update, add] |
325 | 586 | 586 | ||
327 | 587 | default_version = modules.adapt_version('1.0') | 587 | default_version = modules.module.adapt_version('1.0') |
328 | 588 | known_mods = self.browse(cr, uid, self.search(cr, uid, [])) | 588 | known_mods = self.browse(cr, uid, self.search(cr, uid, [])) |
329 | 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]) |
330 | 590 | 590 | ||
331 | @@ -631,7 +631,7 @@ | |||
332 | 631 | 631 | ||
333 | 632 | def download(self, cr, uid, ids, download=True, context=None): | 632 | def download(self, cr, uid, ids, download=True, context=None): |
334 | 633 | res = [] | 633 | res = [] |
336 | 634 | default_version = modules.adapt_version('1.0') | 634 | default_version = modules.module.adapt_version('1.0') |
337 | 635 | for mod in self.browse(cr, uid, ids, context=context): | 635 | for mod in self.browse(cr, uid, ids, context=context): |
338 | 636 | if not mod.url: | 636 | if not mod.url: |
339 | 637 | continue | 637 | continue |
340 | 638 | 638 | ||
341 | === modified file 'openerp/conf/deprecation.py' | |||
342 | --- openerp/conf/deprecation.py 2013-03-27 16:40:45 +0000 | |||
343 | +++ openerp/conf/deprecation.py 2013-07-10 14:52:28 +0000 | |||
344 | @@ -58,4 +58,11 @@ | |||
345 | 58 | # but no warning was dispayed in the logs). | 58 | # but no warning was dispayed in the logs). |
346 | 59 | openerp_pooler = True | 59 | openerp_pooler = True |
347 | 60 | 60 | ||
348 | 61 | # If True, calling a model class after defining it is allowed. | ||
349 | 62 | # class my_model(Model): | ||
350 | 63 | # pass | ||
351 | 64 | # my_model() | ||
352 | 65 | # Introduced around 2013.04. | ||
353 | 66 | allow_explicit_model_call = True | ||
354 | 67 | |||
355 | 61 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 68 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
356 | 62 | 69 | ||
357 | === modified file 'openerp/modules/__init__.py' | |||
358 | --- openerp/modules/__init__.py 2012-08-24 14:23:23 +0000 | |||
359 | +++ openerp/modules/__init__.py 2013-07-10 14:52:28 +0000 | |||
360 | @@ -28,14 +28,10 @@ | |||
361 | 28 | 28 | ||
362 | 29 | # TODO temporarily expose those things | 29 | # TODO temporarily expose those things |
363 | 30 | from openerp.modules.module import \ | 30 | from openerp.modules.module import \ |
365 | 31 | get_modules, get_modules_with_version, \ | 31 | get_modules, \ |
366 | 32 | load_information_from_description_file, \ | 32 | load_information_from_description_file, \ |
373 | 33 | get_module_resource, zip_directory, \ | 33 | get_module_resource, \ |
374 | 34 | get_module_path, initialize_sys_path, \ | 34 | get_module_path |
369 | 35 | load_openerp_module, init_module_models, \ | ||
370 | 36 | adapt_version | ||
371 | 37 | |||
372 | 38 | from openerp.modules.loading import load_modules | ||
375 | 39 | 35 | ||
376 | 40 | 36 | ||
377 | 41 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 37 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
378 | 42 | 38 | ||
379 | === modified file 'openerp/modules/graph.py' | |||
380 | --- openerp/modules/graph.py 2013-03-27 17:06:39 +0000 | |||
381 | +++ openerp/modules/graph.py 2013-07-10 14:52:28 +0000 | |||
382 | @@ -98,7 +98,7 @@ | |||
383 | 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. |
384 | 99 | info = openerp.modules.module.load_information_from_description_file(module) | 99 | info = openerp.modules.module.load_information_from_description_file(module) |
385 | 100 | if info and info['installable']: | 100 | if info and info['installable']: |
387 | 101 | packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version | 101 | packages.append((module, info)) # TODO directly a dict |
388 | 102 | else: | 102 | else: |
389 | 103 | _logger.warning('module %s: not installable, skipped', module) | 103 | _logger.warning('module %s: not installable, skipped', module) |
390 | 104 | 104 | ||
391 | 105 | 105 | ||
392 | === modified file 'openerp/modules/loading.py' | |||
393 | --- openerp/modules/loading.py 2013-05-07 11:20:24 +0000 | |||
394 | +++ openerp/modules/loading.py 2013-07-10 14:52:28 +0000 | |||
395 | @@ -40,8 +40,7 @@ | |||
396 | 40 | from openerp import SUPERUSER_ID | 40 | from openerp import SUPERUSER_ID |
397 | 41 | 41 | ||
398 | 42 | from openerp.tools.translate import _ | 42 | from openerp.tools.translate import _ |
401 | 43 | from openerp.modules.module import initialize_sys_path, \ | 43 | from openerp.modules.module import adapt_version |
400 | 44 | load_openerp_module, init_module_models, adapt_version | ||
402 | 45 | 44 | ||
403 | 46 | _logger = logging.getLogger(__name__) | 45 | _logger = logging.getLogger(__name__) |
404 | 47 | _test_logger = logging.getLogger('openerp.tests') | 46 | _test_logger = logging.getLogger('openerp.tests') |
405 | @@ -149,9 +148,14 @@ | |||
406 | 149 | 148 | ||
407 | 150 | _logger.debug('module %s: loading objects', package.name) | 149 | _logger.debug('module %s: loading objects', package.name) |
408 | 151 | migrations.migrate_module(package, 'pre') | 150 | migrations.migrate_module(package, 'pre') |
412 | 152 | load_openerp_module(package.name) | 151 | __import__('openerp.addons.' + package.name) |
413 | 153 | 152 | mod = sys.modules['openerp.addons.' + package.name] | |
414 | 154 | models = registry.load(cr, package) | 153 | post_load = mod.openerp_descriptor['post_load'] |
415 | 154 | if post_load: | ||
416 | 155 | getattr(mod, post_load)() | ||
417 | 156 | |||
418 | 157 | |||
419 | 158 | models = registry.add_module(cr, mod) | ||
420 | 155 | 159 | ||
421 | 156 | loaded_modules.append(package.name) | 160 | loaded_modules.append(package.name) |
422 | 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'): |
423 | @@ -223,6 +227,27 @@ | |||
424 | 223 | 227 | ||
425 | 224 | return loaded_modules, processed_modules | 228 | return loaded_modules, processed_modules |
426 | 225 | 229 | ||
427 | 230 | def init_module_models(cr, module_name, models): | ||
428 | 231 | """ Initialize a list of models. | ||
429 | 232 | |||
430 | 233 | Call _auto_init and init on each model to create or update the | ||
431 | 234 | database tables supporting the models. Call _auto_end to create | ||
432 | 235 | foreign keys. Call _update_stored_functional_field to initialize | ||
433 | 236 | stored functional fields. | ||
434 | 237 | """ | ||
435 | 238 | _logger.info('module %s: creating or updating database tables', module_name) | ||
436 | 239 | stored_fields = [] | ||
437 | 240 | for model in models: | ||
438 | 241 | stored_fields += model._auto_init(cr, {'module': module_name}) | ||
439 | 242 | model.init(cr) | ||
440 | 243 | cr.commit() | ||
441 | 244 | for model in models: | ||
442 | 245 | model._auto_end(cr, {'module': module_name}) | ||
443 | 246 | cr.commit() | ||
444 | 247 | for order, model, field_name in sorted(stored_fields): | ||
445 | 248 | model._update_stored_functional_field(cr, field_name) | ||
446 | 249 | cr.commit() | ||
447 | 250 | |||
448 | 226 | def _check_module_names(cr, module_names): | 251 | def _check_module_names(cr, module_names): |
449 | 227 | mod_names = set(module_names) | 252 | mod_names = set(module_names) |
450 | 228 | if 'base' in mod_names: | 253 | if 'base' in mod_names: |
451 | @@ -257,7 +282,6 @@ | |||
452 | 257 | # TODO status['progress'] reporting is broken: used twice (and reset each | 282 | # TODO status['progress'] reporting is broken: used twice (and reset each |
453 | 258 | # time to zero) in load_module_graph, not fine-grained enough. | 283 | # time to zero) in load_module_graph, not fine-grained enough. |
454 | 259 | # It should be a method exposed by the registry. | 284 | # It should be a method exposed by the registry. |
455 | 260 | initialize_sys_path() | ||
456 | 261 | 285 | ||
457 | 262 | force = [] | 286 | force = [] |
458 | 263 | if force_demo: | 287 | if force_demo: |
459 | 264 | 288 | ||
460 | === modified file 'openerp/modules/module.py' | |||
461 | --- openerp/modules/module.py 2013-06-28 15:07:55 +0000 | |||
462 | +++ openerp/modules/module.py 2013-07-10 14:52:28 +0000 | |||
463 | @@ -26,7 +26,6 @@ | |||
464 | 26 | from os.path import join as opj | 26 | from os.path import join as opj |
465 | 27 | import sys | 27 | import sys |
466 | 28 | import types | 28 | import types |
467 | 29 | import zipimport | ||
468 | 30 | 29 | ||
469 | 31 | import openerp.tools as tools | 30 | import openerp.tools as tools |
470 | 32 | import openerp.tools.osutil as osutil | 31 | import openerp.tools.osutil as osutil |
471 | @@ -36,9 +35,6 @@ | |||
472 | 36 | import openerp.release as release | 35 | import openerp.release as release |
473 | 37 | 36 | ||
474 | 38 | import re | 37 | import re |
475 | 39 | import base64 | ||
476 | 40 | from zipfile import PyZipFile, ZIP_DEFLATED | ||
477 | 41 | from cStringIO import StringIO | ||
478 | 42 | 38 | ||
479 | 43 | import logging | 39 | import logging |
480 | 44 | 40 | ||
481 | @@ -48,21 +44,177 @@ | |||
482 | 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) |
483 | 49 | ad_paths = [] | 45 | ad_paths = [] |
484 | 50 | 46 | ||
489 | 51 | # Modules already loaded | 47 | |
490 | 52 | loaded = [] | 48 | class Module(object): |
491 | 53 | 49 | modules = [] | |
492 | 54 | _logger = logging.getLogger(__name__) | 50 | all_classes = [] |
493 | 51 | dirty = False | ||
494 | 52 | |||
495 | 53 | def derive_module_info(): | ||
496 | 54 | rank_modules() | ||
497 | 55 | number_classes() | ||
498 | 56 | find_all_parents() | ||
499 | 57 | find_all_new_parents() | ||
500 | 58 | rank_classes() | ||
501 | 59 | |||
502 | 60 | Module.dirty = False | ||
503 | 61 | |||
504 | 62 | def get_module(module_name): | ||
505 | 63 | return sys.modules['openerp.addons.' + module_name] | ||
506 | 64 | |||
507 | 65 | def rank_modules(): | ||
508 | 66 | for module in map(get_module, Module.modules): | ||
509 | 67 | module.rank = None | ||
510 | 68 | for module in map(get_module, Module.modules): | ||
511 | 69 | rank_module(module) | ||
512 | 70 | Module.modules = list(sorted(Module.modules, cmp=lambda x, y: cmp((get_module(x).openerp_rank, x), (get_module(y).openerp_rank, y)))) | ||
513 | 71 | |||
514 | 72 | def number_classes(): | ||
515 | 73 | """rank_modules() must have been called.""" | ||
516 | 74 | i = 0 | ||
517 | 75 | Module.all_classes = [] | ||
518 | 76 | for m in map(get_module, Module.modules): | ||
519 | 77 | for cls in m.openerp_classes: | ||
520 | 78 | Module.all_classes.append(cls) | ||
521 | 79 | cls._number = i | ||
522 | 80 | i += 1 | ||
523 | 81 | |||
524 | 82 | def rank_module(module): | ||
525 | 83 | if module.openerp_rank is None: | ||
526 | 84 | depends = module.openerp_descriptor['depends'] | ||
527 | 85 | if depends: | ||
528 | 86 | module.openerp_rank = max(map(lambda d: rank_module(sys.modules['openerp.addons.' + d]), depends)) + 1 | ||
529 | 87 | else: | ||
530 | 88 | module.openerp_rank = 0 | ||
531 | 89 | |||
532 | 90 | return module.openerp_rank | ||
533 | 91 | |||
534 | 92 | def rank_classes(): | ||
535 | 93 | """find_all_new_parents() must have been called.""" | ||
536 | 94 | for cls in Module.all_classes: | ||
537 | 95 | cls._rank = None | ||
538 | 96 | for cls in Module.all_classes: | ||
539 | 97 | rank_class(cls) | ||
540 | 98 | |||
541 | 99 | Module.ranked_classes = sorted(Module.all_classes, cmp=lambda x, y: cmp((x._rank, x._name), (y._rank, y._name))) | ||
542 | 100 | |||
543 | 101 | def rank_class(cls): | ||
544 | 102 | if cls._rank is None: | ||
545 | 103 | if cls._parents: | ||
546 | 104 | cls._rank = max(map(lambda c: rank_class(c), cls._parents)) + 1 | ||
547 | 105 | parent_modules = [p._original_module for p in cls._parents] | ||
548 | 106 | cls._original_module = [k for k in Module.modules if k in parent_modules][0] | ||
549 | 107 | else: | ||
550 | 108 | cls._rank = 0 | ||
551 | 109 | cls._original_module = cls._module | ||
552 | 110 | |||
553 | 111 | return cls._rank | ||
554 | 112 | |||
555 | 113 | def find_all_parents(): | ||
556 | 114 | """number_classes() must have been called.""" | ||
557 | 115 | for cls in Module.all_classes: | ||
558 | 116 | find_parents(cls) | ||
559 | 117 | |||
560 | 118 | def find_parents(cls): | ||
561 | 119 | if isinstance(cls._inherit, list): | ||
562 | 120 | parents = cls._inherit | ||
563 | 121 | elif cls._inherit: | ||
564 | 122 | parents = [cls._inherit] | ||
565 | 123 | else: | ||
566 | 124 | parents = [] | ||
567 | 125 | cls._parents = map(find_parent(cls), parents) | ||
568 | 126 | return cls._parents | ||
569 | 127 | |||
570 | 128 | def find_parent(cls): | ||
571 | 129 | def f(name): | ||
572 | 130 | parent = None | ||
573 | 131 | for c in reversed(Module.all_classes[:cls._number]): | ||
574 | 132 | if c._name == name: | ||
575 | 133 | if not hasattr(c, '_children'): | ||
576 | 134 | c._children = [] | ||
577 | 135 | c._children.append(cls) | ||
578 | 136 | parent = c | ||
579 | 137 | break | ||
580 | 138 | assert parent, "Cannot find class for model `%s`, specified as a parent of `%s`." % (name, cls._name) | ||
581 | 139 | return c | ||
582 | 140 | return f | ||
583 | 141 | |||
584 | 142 | def find_all_new_parents(): | ||
585 | 143 | """find_all_parents() must have been called.""" | ||
586 | 144 | for cls in Module.all_classes: | ||
587 | 145 | cls._parents = map(find_new_parent(cls), cls._parents) | ||
588 | 146 | |||
589 | 147 | def find_new_parent(cls): | ||
590 | 148 | def f(parent): | ||
591 | 149 | assert cls._name | ||
592 | 150 | assert parent._name | ||
593 | 151 | if cls._name != parent._name: | ||
594 | 152 | # cls is a copy of parent | ||
595 | 153 | for c in reversed(Module.all_classes): | ||
596 | 154 | if c._name == parent._name: | ||
597 | 155 | return c | ||
598 | 156 | return parent | ||
599 | 157 | return f | ||
600 | 158 | |||
601 | 159 | def get_model_classes(name): | ||
602 | 160 | if Module.dirty: | ||
603 | 161 | derive_module_info() | ||
604 | 162 | |||
605 | 163 | last = None | ||
606 | 164 | for cls in reversed(Module.ranked_classes): | ||
607 | 165 | if cls._name == name: | ||
608 | 166 | last = cls | ||
609 | 167 | break | ||
610 | 168 | assert last, "No class found defining model `%s`.\n%s" % (name, map(lambda x: x._name, Module.ranked_classes)) | ||
611 | 169 | |||
612 | 170 | return get_parents(cls, []) | ||
613 | 171 | |||
614 | 172 | def get_parents(cls, accum): | ||
615 | 173 | if cls in accum: | ||
616 | 174 | accum.remove(cls) # Not accurate, we need to replicate Python MRO. | ||
617 | 175 | accum.append(cls) | ||
618 | 176 | for p in cls._parents: | ||
619 | 177 | get_parents(p, accum) | ||
620 | 178 | return accum | ||
621 | 179 | |||
622 | 180 | def display_dot(): | ||
623 | 181 | print 'digraph classes {' | ||
624 | 182 | print ' rankdir=BT;' | ||
625 | 183 | for cls in Module.all_classes: | ||
626 | 184 | print ' %s [label = "%s|%s"];' % (cls._number, cls._module, cls._name) | ||
627 | 185 | |||
628 | 186 | for cls in Module.all_classes: | ||
629 | 187 | for p in cls._parents: | ||
630 | 188 | if cls._name != p._name: | ||
631 | 189 | # cls is a copy of p | ||
632 | 190 | print ' %s -> %s [color="#1ED5FA"];' % (cls._number, p._number) | ||
633 | 191 | else: | ||
634 | 192 | print ' %s -> %s;' % (cls._number, p._number) | ||
635 | 193 | |||
636 | 194 | for m in map(get_module, Module.modules): | ||
637 | 195 | print ' subgraph %s {' % m.name | ||
638 | 196 | for cls in m.classes: | ||
639 | 197 | print ' %s;' % cls._number | ||
640 | 198 | print ' }' | ||
641 | 199 | print '}' | ||
642 | 200 | |||
643 | 201 | # Temporary list for each OpenERP module all its model classes. | ||
644 | 202 | # The list is available on the `openerp_classes` attribute | ||
645 | 203 | # (on `sys.modules[module_name]`) but it doesn't exist yet | ||
646 | 204 | # when the classes are registered. | ||
647 | 205 | module_to_models = {} | ||
648 | 55 | 206 | ||
649 | 56 | class AddonsImportHook(object): | 207 | class AddonsImportHook(object): |
650 | 57 | """ | 208 | """ |
652 | 58 | Import hook to load OpenERP addons from multiple paths. | 209 | Import hook to load OpenERP addons from multiple paths. The dependencies |
653 | 210 | (declared in the `__openerp__.py` file) are imported too. A few attributes | ||
654 | 211 | are set on the module. | ||
655 | 59 | 212 | ||
656 | 60 | OpenERP implements its own import-hook to load its addons. OpenERP | 213 | OpenERP implements its own import-hook to load its addons. OpenERP |
662 | 61 | addons are Python modules. Originally, they were each living in their | 214 | addons are Python modules. They must be imported from the `openerp.addons` |
663 | 62 | own top-level namespace, e.g. the sale module, or the hr module. For | 215 | namespace. E.g. to import the `sale` module, use:: |
664 | 63 | backward compatibility, `import <module>` is still supported. Now they | 216 | |
665 | 64 | are living in `openerp.addons`. The good way to import such modules is | 217 | > import openerp.addons.sale |
661 | 65 | thus `import openerp.addons.module`. | ||
666 | 66 | """ | 218 | """ |
667 | 67 | 219 | ||
668 | 68 | def find_module(self, module_name, package_path): | 220 | def find_module(self, module_name, package_path): |
669 | @@ -71,27 +223,38 @@ | |||
670 | 71 | return self # We act as a loader too. | 223 | return self # We act as a loader too. |
671 | 72 | 224 | ||
672 | 73 | def load_module(self, module_name): | 225 | def load_module(self, module_name): |
673 | 74 | |||
674 | 75 | module_parts = module_name.split('.') | 226 | module_parts = module_name.split('.') |
675 | 76 | if len(module_parts) == 3 and module_name.startswith('openerp.addons.'): | 227 | if len(module_parts) == 3 and module_name.startswith('openerp.addons.'): |
676 | 77 | module_part = module_parts[2] | 228 | module_part = module_parts[2] |
677 | 78 | if module_name in sys.modules: | 229 | if module_name in sys.modules: |
678 | 79 | return sys.modules[module_name] | 230 | return sys.modules[module_name] |
679 | 80 | 231 | ||
680 | 232 | # Import the dependencies. | ||
681 | 233 | info = load_information_from_description_file(module_part) | ||
682 | 234 | for d in info['depends']: | ||
683 | 235 | __import__('openerp.addons.' + d) | ||
684 | 236 | |||
685 | 237 | # Import the module. | ||
686 | 81 | # Note: we don't support circular import. | 238 | # Note: we don't support circular import. |
687 | 82 | f, path, descr = imp.find_module(module_part, ad_paths) | 239 | f, path, descr = imp.find_module(module_part, ad_paths) |
688 | 83 | mod = imp.load_module('openerp.addons.' + module_part, f, path, descr) | 240 | mod = imp.load_module('openerp.addons.' + module_part, f, path, descr) |
689 | 241 | |||
690 | 242 | # We also use the import-hook to do some bookkeeping directly on | ||
691 | 243 | # the module itself. | ||
692 | 244 | mod.openerp_name = module_part | ||
693 | 245 | mod.openerp_descriptor = info | ||
694 | 246 | mod.openerp_classes = module_to_models.get(module_part, []) | ||
695 | 247 | mod.openerp_rank = None | ||
696 | 84 | sys.modules['openerp.addons.' + module_part] = mod | 248 | sys.modules['openerp.addons.' + module_part] = mod |
697 | 249 | Module.modules.append(module_part) | ||
698 | 250 | Module.dirty = True | ||
699 | 251 | |||
700 | 85 | return mod | 252 | return mod |
701 | 86 | 253 | ||
702 | 87 | def initialize_sys_path(): | 254 | def initialize_sys_path(): |
703 | 88 | """ | 255 | """ |
704 | 89 | Setup an import-hook to be able to import OpenERP addons from the different | 256 | Setup an import-hook to be able to import OpenERP addons from the different |
705 | 90 | addons paths. | 257 | addons paths. |
706 | 91 | |||
707 | 92 | This ensures something like ``import crm`` (or even | ||
708 | 93 | ``import openerp.addons.crm``) works even if the addons are not in the | ||
709 | 94 | PYTHONPATH. | ||
710 | 95 | """ | 258 | """ |
711 | 96 | global ad_paths | 259 | global ad_paths |
712 | 97 | if ad_paths: | 260 | if ad_paths: |
713 | @@ -154,68 +317,6 @@ | |||
714 | 154 | 317 | ||
715 | 155 | return tree | 318 | return tree |
716 | 156 | 319 | ||
717 | 157 | def zip_directory(directory, b64enc=True, src=True): | ||
718 | 158 | """Compress a directory | ||
719 | 159 | |||
720 | 160 | @param directory: The directory to compress | ||
721 | 161 | @param base64enc: if True the function will encode the zip file with base64 | ||
722 | 162 | @param src: Integrate the source files | ||
723 | 163 | |||
724 | 164 | @return: a string containing the zip file | ||
725 | 165 | """ | ||
726 | 166 | |||
727 | 167 | RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I) | ||
728 | 168 | |||
729 | 169 | def _zippy(archive, path, src=True): | ||
730 | 170 | path = os.path.abspath(path) | ||
731 | 171 | base = os.path.basename(path) | ||
732 | 172 | for f in osutil.listdir(path, True): | ||
733 | 173 | bf = os.path.basename(f) | ||
734 | 174 | if not RE_exclude.search(bf) and (src or bf == '__openerp__.py' or not bf.endswith('.py')): | ||
735 | 175 | archive.write(os.path.join(path, f), os.path.join(base, f)) | ||
736 | 176 | |||
737 | 177 | archname = StringIO() | ||
738 | 178 | archive = PyZipFile(archname, "w", ZIP_DEFLATED) | ||
739 | 179 | |||
740 | 180 | # for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8) | ||
741 | 181 | directory = tools.ustr(directory).encode('utf-8') | ||
742 | 182 | |||
743 | 183 | archive.writepy(directory) | ||
744 | 184 | _zippy(archive, directory, src=src) | ||
745 | 185 | archive.close() | ||
746 | 186 | archive_data = archname.getvalue() | ||
747 | 187 | archname.close() | ||
748 | 188 | |||
749 | 189 | if b64enc: | ||
750 | 190 | return base64.encodestring(archive_data) | ||
751 | 191 | |||
752 | 192 | return archive_data | ||
753 | 193 | |||
754 | 194 | def get_module_as_zip(modulename, b64enc=True, src=True): | ||
755 | 195 | """Generate a module as zip file with the source or not and can do a base64 encoding | ||
756 | 196 | |||
757 | 197 | @param modulename: The module name | ||
758 | 198 | @param b64enc: if True the function will encode the zip file with base64 | ||
759 | 199 | @param src: Integrate the source files | ||
760 | 200 | |||
761 | 201 | @return: a stream to store in a file-like object | ||
762 | 202 | """ | ||
763 | 203 | |||
764 | 204 | ap = get_module_path(str(modulename)) | ||
765 | 205 | if not ap: | ||
766 | 206 | raise Exception('Unable to find path for module %s' % modulename) | ||
767 | 207 | |||
768 | 208 | ap = ap.encode('utf8') | ||
769 | 209 | if os.path.isfile(ap + '.zip'): | ||
770 | 210 | val = file(ap + '.zip', 'rb').read() | ||
771 | 211 | if b64enc: | ||
772 | 212 | val = base64.encodestring(val) | ||
773 | 213 | else: | ||
774 | 214 | val = zip_directory(ap, b64enc, src) | ||
775 | 215 | |||
776 | 216 | return val | ||
777 | 217 | |||
778 | 218 | |||
779 | 219 | def get_module_resource(module, *args): | 320 | def get_module_resource(module, *args): |
780 | 220 | """Return the full path of a resource of the given module. | 321 | """Return the full path of a resource of the given module. |
781 | 221 | 322 | ||
782 | @@ -295,76 +396,10 @@ | |||
783 | 295 | info['version'] = adapt_version(info['version']) | 396 | info['version'] = adapt_version(info['version']) |
784 | 296 | return info | 397 | return info |
785 | 297 | 398 | ||
786 | 298 | #TODO: refactor the logger in this file to follow the logging guidelines | ||
787 | 299 | # for 6.0 | ||
788 | 300 | _logger.debug('module %s: no __openerp__.py file found.', module) | 399 | _logger.debug('module %s: no __openerp__.py file found.', module) |
789 | 301 | return {} | 400 | return {} |
790 | 302 | 401 | ||
791 | 303 | 402 | ||
792 | 304 | def init_module_models(cr, module_name, obj_list): | ||
793 | 305 | """ Initialize a list of models. | ||
794 | 306 | |||
795 | 307 | Call _auto_init and init on each model to create or update the | ||
796 | 308 | database tables supporting the models. | ||
797 | 309 | |||
798 | 310 | TODO better explanation of _auto_init and init. | ||
799 | 311 | |||
800 | 312 | """ | ||
801 | 313 | _logger.info('module %s: creating or updating database tables', module_name) | ||
802 | 314 | todo = [] | ||
803 | 315 | for obj in obj_list: | ||
804 | 316 | result = obj._auto_init(cr, {'module': module_name}) | ||
805 | 317 | if result: | ||
806 | 318 | todo += result | ||
807 | 319 | if hasattr(obj, 'init'): | ||
808 | 320 | obj.init(cr) | ||
809 | 321 | cr.commit() | ||
810 | 322 | for obj in obj_list: | ||
811 | 323 | obj._auto_end(cr, {'module': module_name}) | ||
812 | 324 | cr.commit() | ||
813 | 325 | todo.sort() | ||
814 | 326 | for t in todo: | ||
815 | 327 | t[1](cr, *t[2]) | ||
816 | 328 | cr.commit() | ||
817 | 329 | |||
818 | 330 | def load_openerp_module(module_name): | ||
819 | 331 | """ Load an OpenERP module, if not already loaded. | ||
820 | 332 | |||
821 | 333 | This loads the module and register all of its models, thanks to either | ||
822 | 334 | the MetaModel metaclass, or the explicit instantiation of the model. | ||
823 | 335 | This is also used to load server-wide module (i.e. it is also used | ||
824 | 336 | when there is no model to register). | ||
825 | 337 | """ | ||
826 | 338 | global loaded | ||
827 | 339 | if module_name in loaded: | ||
828 | 340 | return | ||
829 | 341 | |||
830 | 342 | initialize_sys_path() | ||
831 | 343 | try: | ||
832 | 344 | mod_path = get_module_path(module_name) | ||
833 | 345 | zip_mod_path = '' if not mod_path else mod_path + '.zip' | ||
834 | 346 | if not os.path.isfile(zip_mod_path): | ||
835 | 347 | __import__('openerp.addons.' + module_name) | ||
836 | 348 | else: | ||
837 | 349 | zimp = zipimport.zipimporter(zip_mod_path) | ||
838 | 350 | zimp.load_module(module_name) | ||
839 | 351 | |||
840 | 352 | # Call the module's post-load hook. This can done before any model or | ||
841 | 353 | # data has been initialized. This is ok as the post-load hook is for | ||
842 | 354 | # server-wide (instead of registry-specific) functionalities. | ||
843 | 355 | info = load_information_from_description_file(module_name) | ||
844 | 356 | if info['post_load']: | ||
845 | 357 | getattr(sys.modules['openerp.addons.' + module_name], info['post_load'])() | ||
846 | 358 | |||
847 | 359 | except Exception, e: | ||
848 | 360 | mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or '' | ||
849 | 361 | msg = "Couldn't load %smodule %s" % (mt, module_name) | ||
850 | 362 | _logger.critical(msg) | ||
851 | 363 | _logger.critical(e) | ||
852 | 364 | raise | ||
853 | 365 | else: | ||
854 | 366 | loaded.append(module_name) | ||
855 | 367 | |||
856 | 368 | def get_modules(): | 403 | def get_modules(): |
857 | 369 | """Returns the list of module names | 404 | """Returns the list of module names |
858 | 370 | """ | 405 | """ |
859 | @@ -388,17 +423,6 @@ | |||
860 | 388 | return list(set(plist)) | 423 | return list(set(plist)) |
861 | 389 | 424 | ||
862 | 390 | 425 | ||
863 | 391 | def get_modules_with_version(): | ||
864 | 392 | modules = get_modules() | ||
865 | 393 | res = dict.fromkeys(modules, adapt_version('1.0')) | ||
866 | 394 | for module in modules: | ||
867 | 395 | try: | ||
868 | 396 | info = load_information_from_description_file(module) | ||
869 | 397 | res[module] = info['version'] | ||
870 | 398 | except Exception: | ||
871 | 399 | continue | ||
872 | 400 | return res | ||
873 | 401 | |||
874 | 402 | def adapt_version(version): | 426 | def adapt_version(version): |
875 | 403 | serie = release.major_version | 427 | serie = release.major_version |
876 | 404 | if version == serie or not version.startswith(serie + '.'): | 428 | if version == serie or not version.startswith(serie + '.'): |
877 | 405 | 429 | ||
878 | === modified file 'openerp/modules/registry.py' | |||
879 | --- openerp/modules/registry.py 2013-06-19 10:53:35 +0000 | |||
880 | +++ openerp/modules/registry.py 2013-07-10 14:52:28 +0000 | |||
881 | @@ -24,13 +24,16 @@ | |||
882 | 24 | """ | 24 | """ |
883 | 25 | from collections import Mapping | 25 | from collections import Mapping |
884 | 26 | from contextlib import contextmanager | 26 | from contextlib import contextmanager |
885 | 27 | import itertools | ||
886 | 27 | import logging | 28 | import logging |
887 | 29 | import sys | ||
888 | 28 | import threading | 30 | import threading |
889 | 29 | 31 | ||
890 | 30 | import openerp.sql_db | 32 | import openerp.sql_db |
891 | 31 | import openerp.osv.orm | 33 | import openerp.osv.orm |
892 | 32 | import openerp.tools | 34 | import openerp.tools |
893 | 33 | import openerp.modules.db | 35 | import openerp.modules.db |
894 | 36 | import openerp.modules.loading | ||
895 | 34 | import openerp.tools.config | 37 | import openerp.tools.config |
896 | 35 | from openerp.tools import assertion_report | 38 | from openerp.tools import assertion_report |
897 | 36 | 39 | ||
898 | @@ -53,6 +56,8 @@ | |||
899 | 53 | self._init_parent = {} | 56 | self._init_parent = {} |
900 | 54 | self._assertion_report = assertion_report.assertion_report() | 57 | self._assertion_report = assertion_report.assertion_report() |
901 | 55 | self.fields_by_model = None | 58 | self.fields_by_model = None |
902 | 59 | # Set of (set of classes) already consolidated and instanciated. | ||
903 | 60 | self.identities = set() | ||
904 | 56 | 61 | ||
905 | 57 | # modules fully loaded (maintained during init phase by `loading` module) | 62 | # modules fully loaded (maintained during init phase by `loading` module) |
906 | 58 | self._init_modules = set() | 63 | self._init_modules = set() |
907 | @@ -115,24 +120,32 @@ | |||
908 | 115 | """ Add or replace a model in the registry.""" | 120 | """ Add or replace a model in the registry.""" |
909 | 116 | self.models[model_name] = model | 121 | self.models[model_name] = model |
910 | 117 | 122 | ||
920 | 118 | def load(self, cr, module): | 123 | def add_module(self, cr, module): |
921 | 119 | """ Load a given module in the registry. | 124 | module_names = [module.openerp_name] + list(self._init_modules) |
913 | 120 | |||
914 | 121 | At the Python level, the modules are already loaded, but not yet on a | ||
915 | 122 | per-registry level. This method populates a registry with the given | ||
916 | 123 | modules, i.e. it instanciates all the classes of a the given module | ||
917 | 124 | and registers them in the registry. | ||
918 | 125 | |||
919 | 126 | """ | ||
922 | 127 | models_to_load = [] # need to preserve loading order | 125 | models_to_load = [] # need to preserve loading order |
929 | 128 | # Instantiate registered classes (via the MetaModel automatic discovery | 126 | modules = [sys.modules['openerp.addons.' + m] for m in module_names] |
930 | 129 | # or via explicit constructor call), and add them to the pool. | 127 | # Re-consolidate all previously consolidated (and instanciated) |
931 | 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. |
932 | 131 | # models register themselves in self.models | 129 | # We end up re-creating more models than strictly necessary; |
933 | 132 | model = cls.create_instance(self, cr) | 130 | # normaly, only the parents (and their children) must be re-done. |
934 | 133 | if model._name not in models_to_load: | 131 | for model in itertools.chain(*tuple(module.openerp_classes for module in modules)): |
935 | 132 | if model._name in models_to_load: | ||
936 | 134 | # avoid double-loading models whose declaration is split | 133 | # avoid double-loading models whose declaration is split |
938 | 135 | models_to_load.append(model._name) | 134 | continue |
939 | 135 | classes = list(openerp.modules.module.get_model_classes(model._name)) | ||
940 | 136 | classes = filter(lambda ext: ext._module in module_names, classes) | ||
941 | 137 | # Derive an 'identity' for this set of classes so we can avoid | ||
942 | 138 | # reloading the model if it has not been changed by this module. | ||
943 | 139 | # There would be nothing wrong doing it, it would just take | ||
944 | 140 | # additional time. | ||
945 | 141 | i = tuple([id(c) for c in classes]) | ||
946 | 142 | if i in self.identities: | ||
947 | 143 | continue | ||
948 | 144 | self.identities.add(i) | ||
949 | 145 | models_to_load.append(model._name) | ||
950 | 146 | cls = openerp.osv.orm.consolidate(model._name, classes) | ||
951 | 147 | obj = object.__new__(cls) | ||
952 | 148 | obj.__init__(self, cr) | ||
953 | 136 | return [self.models[m] for m in models_to_load] | 149 | return [self.models[m] for m in models_to_load] |
954 | 137 | 150 | ||
955 | 138 | def clear_caches(self): | 151 | def clear_caches(self): |
956 | @@ -229,7 +242,7 @@ | |||
957 | 229 | cls.registries[db_name] = registry | 242 | cls.registries[db_name] = registry |
958 | 230 | try: | 243 | try: |
959 | 231 | # This should be a method on Registry | 244 | # This should be a method on Registry |
961 | 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) |
962 | 233 | except Exception: | 246 | except Exception: |
963 | 234 | del cls.registries[db_name] | 247 | del cls.registries[db_name] |
964 | 235 | raise | 248 | raise |
965 | 236 | 249 | ||
966 | === modified file 'openerp/osv/orm.py' | |||
967 | --- openerp/osv/orm.py 2013-07-02 14:47:45 +0000 | |||
968 | +++ openerp/osv/orm.py 2013-07-10 14:52:28 +0000 | |||
969 | @@ -599,6 +599,53 @@ | |||
970 | 599 | return pg_type | 599 | return pg_type |
971 | 600 | 600 | ||
972 | 601 | 601 | ||
973 | 602 | def consolidate(model_name, classes): | ||
974 | 603 | """ Create a consolidated model defined by the provided classes. | ||
975 | 604 | """ | ||
976 | 605 | attributes = ['_columns', '_defaults', '_inherits', '_constraints', | ||
977 | 606 | '_sql_constraints'] | ||
978 | 607 | |||
979 | 608 | assert classes, "Model `%s` consolidation cannot be done because there is no class to define it." % model_name | ||
980 | 609 | nattr = {'_register': False} | ||
981 | 610 | for s in attributes: | ||
982 | 611 | new = copy.copy(getattr(classes[-1], s)) | ||
983 | 612 | for parent in list(reversed(classes))[1:]: | ||
984 | 613 | if s == '_columns': | ||
985 | 614 | # Duplicate float fields because they have a .digits | ||
986 | 615 | # cache (which must be per-registry, not server-wide). | ||
987 | 616 | for c in new.keys(): | ||
988 | 617 | if new[c]._type == 'float': | ||
989 | 618 | new[c] = copy.copy(new[c]) | ||
990 | 619 | if hasattr(new, 'update'): | ||
991 | 620 | new.update(parent.__dict__.get(s, {})) | ||
992 | 621 | elif s=='_constraints': | ||
993 | 622 | for c in parent.__dict__.get(s, []): | ||
994 | 623 | exist = False | ||
995 | 624 | for c2 in range(len(new)): | ||
996 | 625 | #For _constraints, we should check field and methods as well | ||
997 | 626 | if new[c2][2]==c[2] and (new[c2][0] == c[0] \ | ||
998 | 627 | or getattr(new[c2][0],'__name__', True) == \ | ||
999 | 628 | getattr(c[0],'__name__', False)): | ||
1000 | 629 | # If new class defines a constraint with | ||
1001 | 630 | # same function name, we let it override | ||
1002 | 631 | # the old one. | ||
1003 | 632 | |||
1004 | 633 | new[c2] = c | ||
1005 | 634 | exist = True | ||
1006 | 635 | break | ||
1007 | 636 | if not exist: | ||
1008 | 637 | new.append(c) | ||
1009 | 638 | else: | ||
1010 | 639 | new.extend(parent.__dict__.get(s, [])) | ||
1011 | 640 | nattr[s] = new | ||
1012 | 641 | |||
1013 | 642 | # Keep links to non-inherited constraints, e.g. useful when exporting translations | ||
1014 | 643 | nattr['_local_constraints'] = classes[0].__dict__.get('_constraints', []) | ||
1015 | 644 | nattr['_local_sql_constraints'] = classes[0].__dict__.get('_sql_constraints', []) | ||
1016 | 645 | cls = type(model_name, tuple(classes), nattr) | ||
1017 | 646 | return cls | ||
1018 | 647 | |||
1019 | 648 | |||
1020 | 602 | class MetaModel(type): | 649 | class MetaModel(type): |
1021 | 603 | """ Metaclass for the Model. | 650 | """ Metaclass for the Model. |
1022 | 604 | 651 | ||
1023 | @@ -609,14 +656,23 @@ | |||
1024 | 609 | 656 | ||
1025 | 610 | """ | 657 | """ |
1026 | 611 | 658 | ||
1027 | 612 | module_to_models = {} | ||
1028 | 613 | |||
1029 | 614 | def __init__(self, name, bases, attrs): | 659 | def __init__(self, name, bases, attrs): |
1030 | 615 | if not self._register: | 660 | if not self._register: |
1031 | 616 | self._register = True | 661 | self._register = True |
1032 | 617 | super(MetaModel, self).__init__(name, bases, attrs) | 662 | super(MetaModel, self).__init__(name, bases, attrs) |
1033 | 618 | return | 663 | return |
1034 | 619 | 664 | ||
1035 | 665 | # Derive the _name of the class. | ||
1036 | 666 | if not self._name and isinstance(self._inherit, list) and len(self._inherit) == 1: | ||
1037 | 667 | self._name = self._inherit[0] | ||
1038 | 668 | elif not self._name and isinstance(self._inherit, list): | ||
1039 | 669 | raise Exception, "_name must be provide when there are multiple _inherit parents." | ||
1040 | 670 | elif not self._name: | ||
1041 | 671 | self._name = self._inherit | ||
1042 | 672 | assert self._name | ||
1043 | 673 | # Sets the instance _inherit attribute (with the classs attribute). | ||
1044 | 674 | self._inherit = self._inherit | ||
1045 | 675 | |||
1046 | 620 | # The (OpenERP) module name can be in the `openerp.addons` namespace | 676 | # The (OpenERP) module name can be in the `openerp.addons` namespace |
1047 | 621 | # or not. For instance module `sale` can be imported as | 677 | # or not. For instance module `sale` can be imported as |
1048 | 622 | # `openerp.addons.sale` (the good way) or `sale` (for backward | 678 | # `openerp.addons.sale` (the good way) or `sale` (for backward |
1049 | @@ -627,12 +683,15 @@ | |||
1050 | 627 | module_name = self.__module__.split('.')[2] | 683 | module_name = self.__module__.split('.')[2] |
1051 | 628 | else: | 684 | else: |
1052 | 629 | module_name = self.__module__.split('.')[0] | 685 | module_name = self.__module__.split('.')[0] |
1053 | 686 | _logger.warning('OpenERP module `%s` is not in the `openerp.addons` namespace.', module_name) | ||
1054 | 687 | |||
1055 | 630 | if not hasattr(self, '_module'): | 688 | if not hasattr(self, '_module'): |
1056 | 631 | self._module = module_name | 689 | self._module = module_name |
1057 | 632 | 690 | ||
1058 | 633 | # Remember which models to instanciate for this module. | 691 | # Remember which models to instanciate for this module. |
1059 | 634 | if not self._custom: | 692 | if not self._custom: |
1061 | 635 | self.module_to_models.setdefault(self._module, []).append(self) | 693 | module_model_list = openerp.modules.module.module_to_models.setdefault(self._module, []) |
1062 | 694 | module_model_list.append(self) | ||
1063 | 636 | 695 | ||
1064 | 637 | 696 | ||
1065 | 638 | # Definition of log access columns, automatically added to models if | 697 | # Definition of log access columns, automatically added to models if |
1066 | @@ -688,6 +747,8 @@ | |||
1067 | 688 | # Transience | 747 | # Transience |
1068 | 689 | _transient = False # True in a TransientModel | 748 | _transient = False # True in a TransientModel |
1069 | 690 | 749 | ||
1070 | 750 | _inherit = 'base.model' | ||
1071 | 751 | |||
1072 | 691 | # structure: | 752 | # structure: |
1073 | 692 | # { 'parent_model': 'm2o_field', ... } | 753 | # { 'parent_model': 'm2o_field', ... } |
1074 | 693 | _inherits = {} | 754 | _inherits = {} |
1075 | @@ -835,119 +896,10 @@ | |||
1076 | 835 | break | 896 | break |
1077 | 836 | cr.commit() | 897 | cr.commit() |
1078 | 837 | 898 | ||
1079 | 838 | # | ||
1080 | 839 | # Goal: try to apply inheritance at the instanciation level and | ||
1081 | 840 | # put objects in the pool var | ||
1082 | 841 | # | ||
1083 | 842 | @classmethod | ||
1084 | 843 | def create_instance(cls, pool, cr): | ||
1085 | 844 | """ Instanciate a given model. | ||
1086 | 845 | |||
1087 | 846 | This class method instanciates the class of some model (i.e. a class | ||
1088 | 847 | deriving from osv or osv_memory). The class might be the class passed | ||
1089 | 848 | in argument or, if it inherits from another class, a class constructed | ||
1090 | 849 | by combining the two classes. | ||
1091 | 850 | |||
1092 | 851 | The ``attributes`` argument specifies which parent class attributes | ||
1093 | 852 | have to be combined. | ||
1094 | 853 | |||
1095 | 854 | TODO: the creation of the combined class is repeated at each call of | ||
1096 | 855 | this method. This is probably unnecessary. | ||
1097 | 856 | |||
1098 | 857 | """ | ||
1099 | 858 | attributes = ['_columns', '_defaults', '_inherits', '_constraints', | ||
1100 | 859 | '_sql_constraints'] | ||
1101 | 860 | |||
1102 | 861 | parent_names = getattr(cls, '_inherit', None) | ||
1103 | 862 | if parent_names: | ||
1104 | 863 | if isinstance(parent_names, (str, unicode)): | ||
1105 | 864 | name = cls._name or parent_names | ||
1106 | 865 | parent_names = [parent_names] | ||
1107 | 866 | else: | ||
1108 | 867 | name = cls._name | ||
1109 | 868 | if not name: | ||
1110 | 869 | raise TypeError('_name is mandatory in case of multiple inheritance') | ||
1111 | 870 | |||
1112 | 871 | for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]): | ||
1113 | 872 | if parent_name not in pool: | ||
1114 | 873 | raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n' | ||
1115 | 874 | 'You may need to add a dependency on the parent class\' module.' % (name, parent_name)) | ||
1116 | 875 | parent_model = pool[parent_name] | ||
1117 | 876 | if not getattr(cls, '_original_module', None) and name == parent_model._name: | ||
1118 | 877 | cls._original_module = parent_model._original_module | ||
1119 | 878 | parent_class = parent_model.__class__ | ||
1120 | 879 | nattr = {} | ||
1121 | 880 | for s in attributes: | ||
1122 | 881 | new = copy.copy(getattr(parent_model, s, {})) | ||
1123 | 882 | if s == '_columns': | ||
1124 | 883 | # Don't _inherit custom fields. | ||
1125 | 884 | for c in new.keys(): | ||
1126 | 885 | if new[c].manual: | ||
1127 | 886 | del new[c] | ||
1128 | 887 | # Duplicate float fields because they have a .digits | ||
1129 | 888 | # cache (which must be per-registry, not server-wide). | ||
1130 | 889 | for c in new.keys(): | ||
1131 | 890 | if new[c]._type == 'float': | ||
1132 | 891 | new[c] = copy.copy(new[c]) | ||
1133 | 892 | if hasattr(new, 'update'): | ||
1134 | 893 | new.update(cls.__dict__.get(s, {})) | ||
1135 | 894 | elif s=='_constraints': | ||
1136 | 895 | for c in cls.__dict__.get(s, []): | ||
1137 | 896 | exist = False | ||
1138 | 897 | for c2 in range(len(new)): | ||
1139 | 898 | #For _constraints, we should check field and methods as well | ||
1140 | 899 | if new[c2][2]==c[2] and (new[c2][0] == c[0] \ | ||
1141 | 900 | or getattr(new[c2][0],'__name__', True) == \ | ||
1142 | 901 | getattr(c[0],'__name__', False)): | ||
1143 | 902 | # If new class defines a constraint with | ||
1144 | 903 | # same function name, we let it override | ||
1145 | 904 | # the old one. | ||
1146 | 905 | |||
1147 | 906 | new[c2] = c | ||
1148 | 907 | exist = True | ||
1149 | 908 | break | ||
1150 | 909 | if not exist: | ||
1151 | 910 | new.append(c) | ||
1152 | 911 | else: | ||
1153 | 912 | new.extend(cls.__dict__.get(s, [])) | ||
1154 | 913 | nattr[s] = new | ||
1155 | 914 | |||
1156 | 915 | # Keep links to non-inherited constraints, e.g. useful when exporting translations | ||
1157 | 916 | nattr['_local_constraints'] = cls.__dict__.get('_constraints', []) | ||
1158 | 917 | nattr['_local_sql_constraints'] = cls.__dict__.get('_sql_constraints', []) | ||
1159 | 918 | |||
1160 | 919 | cls = type(name, (cls, parent_class), dict(nattr, _register=False)) | ||
1161 | 920 | else: | ||
1162 | 921 | cls._local_constraints = getattr(cls, '_constraints', []) | ||
1163 | 922 | cls._local_sql_constraints = getattr(cls, '_sql_constraints', []) | ||
1164 | 923 | |||
1165 | 924 | if not getattr(cls, '_original_module', None): | ||
1166 | 925 | cls._original_module = cls._module | ||
1167 | 926 | obj = object.__new__(cls) | ||
1168 | 927 | obj.__init__(pool, cr) | ||
1169 | 928 | return obj | ||
1170 | 929 | |||
1171 | 930 | def __new__(cls): | 899 | def __new__(cls): |
1192 | 931 | """Register this model. | 900 | assert openerp.conf.deprecation.allow_explicit_model_call |
1193 | 932 | 901 | _logger.warning("Instanciating a model (here `%s`) after defining it" | |
1194 | 933 | This doesn't create an instance but simply register the model | 902 | " is deprecated.", cls._name or cls._inherit) |
1175 | 934 | as being part of the module where it is defined. | ||
1176 | 935 | |||
1177 | 936 | """ | ||
1178 | 937 | |||
1179 | 938 | |||
1180 | 939 | # Set the module name (e.g. base, sale, accounting, ...) on the class. | ||
1181 | 940 | module = cls.__module__.split('.')[0] | ||
1182 | 941 | if not hasattr(cls, '_module'): | ||
1183 | 942 | cls._module = module | ||
1184 | 943 | |||
1185 | 944 | # Record this class in the list of models to instantiate for this module, | ||
1186 | 945 | # managed by the metaclass. | ||
1187 | 946 | module_model_list = MetaModel.module_to_models.setdefault(cls._module, []) | ||
1188 | 947 | if cls not in module_model_list: | ||
1189 | 948 | if not cls._custom: | ||
1190 | 949 | module_model_list.append(cls) | ||
1191 | 950 | |||
1195 | 951 | # Since we don't return an instance here, the __init__ | 903 | # Since we don't return an instance here, the __init__ |
1196 | 952 | # method won't be called. | 904 | # method won't be called. |
1197 | 953 | return None | 905 | return None |
1198 | @@ -1064,8 +1016,6 @@ | |||
1199 | 1064 | self._inherits_reload() | 1016 | self._inherits_reload() |
1200 | 1065 | if not self._sequence: | 1017 | if not self._sequence: |
1201 | 1066 | self._sequence = self._table + '_id_seq' | 1018 | self._sequence = self._table + '_id_seq' |
1202 | 1067 | for k in self._defaults: | ||
1203 | 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,) | ||
1204 | 1069 | for f in self._columns: | 1019 | for f in self._columns: |
1205 | 1070 | self._columns[f].restart() | 1020 | self._columns[f].restart() |
1206 | 1071 | 1021 | ||
1207 | @@ -2776,9 +2726,10 @@ | |||
1208 | 2776 | pos = browse_rec(root, pos) | 2726 | pos = browse_rec(root, pos) |
1209 | 2777 | return True | 2727 | return True |
1210 | 2778 | 2728 | ||
1214 | 2779 | def _update_store(self, cr, f, k): | 2729 | def _update_stored_functional_field(self, cr, k): |
1215 | 2780 | _logger.info("storing computed values of fields.function '%s'", k) | 2730 | _logger.info("Computing and storing functional field `%s`.", k) |
1216 | 2781 | ss = self._columns[k]._symbol_set | 2731 | f = self._columns[k] |
1217 | 2732 | ss = f._symbol_set | ||
1218 | 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]) |
1219 | 2783 | cr.execute('select id from '+self._table) | 2734 | cr.execute('select id from '+self._table) |
1220 | 2784 | ids_lst = map(lambda x: x[0], cr.fetchall()) | 2735 | ids_lst = map(lambda x: x[0], cr.fetchall()) |
1221 | @@ -2970,7 +2921,7 @@ | |||
1222 | 2970 | if context is None: | 2921 | if context is None: |
1223 | 2971 | context = {} | 2922 | context = {} |
1224 | 2972 | store_compute = False | 2923 | store_compute = False |
1226 | 2973 | todo_end = [] | 2924 | stored_fields = [] |
1227 | 2974 | update_custom_fields = context.get('update_custom_fields', False) | 2925 | update_custom_fields = context.get('update_custom_fields', False) |
1228 | 2975 | self._field_create(cr, context=context) | 2926 | self._field_create(cr, context=context) |
1229 | 2976 | create = not self._table_exist(cr) | 2927 | create = not self._table_exist(cr) |
1230 | @@ -3172,10 +3123,11 @@ | |||
1231 | 3172 | 3123 | ||
1232 | 3173 | # remember the functions to call for the stored fields | 3124 | # remember the functions to call for the stored fields |
1233 | 3174 | if isinstance(f, fields.function): | 3125 | if isinstance(f, fields.function): |
1234 | 3175 | order = 10 | ||
1235 | 3176 | if f.store is not True: # i.e. if f.store is a dict | 3126 | if f.store is not True: # i.e. if f.store is a dict |
1236 | 3177 | order = f.store[f.store.keys()[0]][2] | 3127 | order = f.store[f.store.keys()[0]][2] |
1238 | 3178 | todo_end.append((order, self._update_store, (f, k))) | 3128 | else: |
1239 | 3129 | order = 10 | ||
1240 | 3130 | stored_fields.append((order, self, k)) | ||
1241 | 3179 | 3131 | ||
1242 | 3180 | # and add constraints if needed | 3132 | # and add constraints if needed |
1243 | 3181 | if isinstance(f, fields.many2one): | 3133 | if isinstance(f, fields.many2one): |
1244 | @@ -3202,23 +3154,19 @@ | |||
1245 | 3202 | _logger.warning(msg, k, self._table, self._table, k) | 3154 | _logger.warning(msg, k, self._table, self._table, k) |
1246 | 3203 | cr.commit() | 3155 | cr.commit() |
1247 | 3204 | 3156 | ||
1248 | 3205 | else: | ||
1249 | 3206 | cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,)) | ||
1250 | 3207 | create = not bool(cr.fetchone()) | ||
1251 | 3208 | |||
1252 | 3209 | cr.commit() # start a new transaction | 3157 | cr.commit() # start a new transaction |
1253 | 3210 | 3158 | ||
1254 | 3211 | if self._auto: | 3159 | if self._auto: |
1255 | 3212 | self._add_sql_constraints(cr) | 3160 | self._add_sql_constraints(cr) |
1256 | 3213 | 3161 | ||
1257 | 3214 | if create: | ||
1258 | 3215 | self._execute_sql(cr) | ||
1259 | 3216 | |||
1260 | 3217 | if store_compute: | 3162 | if store_compute: |
1261 | 3218 | self._parent_store_compute(cr) | 3163 | self._parent_store_compute(cr) |
1262 | 3219 | cr.commit() | 3164 | cr.commit() |
1263 | 3220 | 3165 | ||
1265 | 3221 | return todo_end | 3166 | return stored_fields |
1266 | 3167 | |||
1267 | 3168 | def init(self, cr): | ||
1268 | 3169 | pass | ||
1269 | 3222 | 3170 | ||
1270 | 3223 | def _auto_end(self, cr, context=None): | 3171 | def _auto_end(self, cr, context=None): |
1271 | 3224 | """ Create the foreign keys recorded by _auto_init. """ | 3172 | """ Create the foreign keys recorded by _auto_init. """ |
1272 | @@ -3228,6 +3176,9 @@ | |||
1273 | 3228 | cr.commit() | 3176 | cr.commit() |
1274 | 3229 | del self._foreign_keys | 3177 | del self._foreign_keys |
1275 | 3230 | 3178 | ||
1276 | 3179 | for k in self._defaults: | ||
1277 | 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,) | ||
1278 | 3181 | |||
1279 | 3231 | 3182 | ||
1280 | 3232 | def _table_exist(self, cr): | 3183 | def _table_exist(self, cr): |
1281 | 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,)) |
1282 | @@ -3393,15 +3344,6 @@ | |||
1283 | 3393 | cr.rollback() | 3344 | cr.rollback() |
1284 | 3394 | 3345 | ||
1285 | 3395 | 3346 | ||
1286 | 3396 | def _execute_sql(self, cr): | ||
1287 | 3397 | """ Execute the SQL code from the _sql attribute (if any).""" | ||
1288 | 3398 | if hasattr(self, "_sql"): | ||
1289 | 3399 | for line in self._sql.split(';'): | ||
1290 | 3400 | line2 = line.replace('\n', '').strip() | ||
1291 | 3401 | if line2: | ||
1292 | 3402 | cr.execute(line2) | ||
1293 | 3403 | cr.commit() | ||
1294 | 3404 | |||
1295 | 3405 | # | 3347 | # |
1296 | 3406 | # Update objects that uses this one to update their _inherits fields | 3348 | # Update objects that uses this one to update their _inherits fields |
1297 | 3407 | # | 3349 | # |
1298 | 3408 | 3350 | ||
1299 | === modified file 'openerp/service/__init__.py' | |||
1300 | --- openerp/service/__init__.py 2013-06-12 15:28:26 +0000 | |||
1301 | +++ openerp/service/__init__.py 2013-07-10 14:52:28 +0000 | |||
1302 | @@ -60,7 +60,12 @@ | |||
1303 | 60 | def load_server_wide_modules(): | 60 | def load_server_wide_modules(): |
1304 | 61 | for m in openerp.conf.server_wide_modules: | 61 | for m in openerp.conf.server_wide_modules: |
1305 | 62 | try: | 62 | try: |
1307 | 63 | openerp.modules.module.load_openerp_module(m) | 63 | __import__('openerp.addons.' + m) |
1308 | 64 | mod = sys.modules['openerp.addons.' + m] | ||
1309 | 65 | post_load = mod.openerp_descriptor['post_load'] | ||
1310 | 66 | if post_load: | ||
1311 | 67 | getattr(mod, post_load)() | ||
1312 | 68 | |||
1313 | 64 | except Exception: | 69 | except Exception: |
1314 | 65 | msg = '' | 70 | msg = '' |
1315 | 66 | if m == 'web': | 71 | if m == 'web': |
1316 | 67 | 72 | ||
1317 | === added directory 'openerp/tests/addons/test_base_model_a' | |||
1318 | === added file 'openerp/tests/addons/test_base_model_a/__init__.py' | |||
1319 | --- openerp/tests/addons/test_base_model_a/__init__.py 1970-01-01 00:00:00 +0000 | |||
1320 | +++ openerp/tests/addons/test_base_model_a/__init__.py 2013-07-10 14:52:28 +0000 | |||
1321 | @@ -0,0 +1,3 @@ | |||
1322 | 1 | # -*- coding: utf-8 -*- | ||
1323 | 2 | import models | ||
1324 | 3 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | ||
1325 | 0 | 4 | ||
1326 | === added file 'openerp/tests/addons/test_base_model_a/__openerp__.py' | |||
1327 | --- openerp/tests/addons/test_base_model_a/__openerp__.py 1970-01-01 00:00:00 +0000 | |||
1328 | +++ openerp/tests/addons/test_base_model_a/__openerp__.py 2013-07-10 14:52:28 +0000 | |||
1329 | @@ -0,0 +1,15 @@ | |||
1330 | 1 | # -*- coding: utf-8 -*- | ||
1331 | 2 | { | ||
1332 | 3 | 'name': 'test-base-model-a', | ||
1333 | 4 | 'version': '0.1', | ||
1334 | 5 | 'category': 'Tests', | ||
1335 | 6 | 'description': """A module to test the BaseModel inheritance.""", | ||
1336 | 7 | 'author': 'OpenERP SA', | ||
1337 | 8 | 'maintainer': 'OpenERP SA', | ||
1338 | 9 | 'website': 'http://www.openerp.com', | ||
1339 | 10 | 'depends': ['base'], | ||
1340 | 11 | 'data': [], | ||
1341 | 12 | 'installable': True, | ||
1342 | 13 | 'auto_install': False, | ||
1343 | 14 | } | ||
1344 | 15 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | ||
1345 | 0 | 16 | ||
1346 | === added file 'openerp/tests/addons/test_base_model_a/models.py' | |||
1347 | --- openerp/tests/addons/test_base_model_a/models.py 1970-01-01 00:00:00 +0000 | |||
1348 | +++ openerp/tests/addons/test_base_model_a/models.py 2013-07-10 14:52:28 +0000 | |||
1349 | @@ -0,0 +1,26 @@ | |||
1350 | 1 | # -*- coding: utf-8 -*- | ||
1351 | 2 | import openerp | ||
1352 | 3 | |||
1353 | 4 | class a(openerp.osv.orm.Model): | ||
1354 | 5 | """ This model is just a copy of BaseModel. | ||
1355 | 6 | In the context of the base.model tests, we will check if the _columns of | ||
1356 | 7 | this model is indeed the consolidated _colmuns of base.model. | ||
1357 | 8 | |||
1358 | 9 | This test module will first add a column_n to base.model and thus we will | ||
1359 | 10 | check in the unit tests if test.base.model.a contains it. | ||
1360 | 11 | |||
1361 | 12 | In the second test_base_model_b addons, we will add a column_o and again | ||
1362 | 13 | check if test.base.model.a contains it in addition to column_n. | ||
1363 | 14 | """ | ||
1364 | 15 | _name = 'test.base.model.a' | ||
1365 | 16 | |||
1366 | 17 | class n(openerp.osv.orm.AbstractModel): | ||
1367 | 18 | """ This model augments BaseModel. | ||
1368 | 19 | """ | ||
1369 | 20 | _inherit = 'base.model' | ||
1370 | 21 | |||
1371 | 22 | _columns = { | ||
1372 | 23 | 'column_n': openerp.osv.fields.char(), | ||
1373 | 24 | } | ||
1374 | 25 | |||
1375 | 26 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | ||
1376 | 0 | 27 | ||
1377 | === added directory 'openerp/tests/addons/test_base_model_a/tests' | |||
1378 | === added file 'openerp/tests/addons/test_base_model_a/tests/__init__.py' | |||
1379 | --- openerp/tests/addons/test_base_model_a/tests/__init__.py 1970-01-01 00:00:00 +0000 | |||
1380 | +++ openerp/tests/addons/test_base_model_a/tests/__init__.py 2013-07-10 14:52:28 +0000 | |||
1381 | @@ -0,0 +1,13 @@ | |||
1382 | 1 | # -*- coding: utf-8 -*- | ||
1383 | 2 | |||
1384 | 3 | from . import test_base_model | ||
1385 | 4 | |||
1386 | 5 | fast_suite = [ | ||
1387 | 6 | ] | ||
1388 | 7 | |||
1389 | 8 | checks = [ | ||
1390 | 9 | # This test assumes to be installed alone. | ||
1391 | 10 | # test_base_model, | ||
1392 | 11 | ] | ||
1393 | 12 | |||
1394 | 13 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | ||
1395 | 0 | 14 | ||
1396 | === added file 'openerp/tests/addons/test_base_model_a/tests/test_base_model.py' | |||
1397 | --- openerp/tests/addons/test_base_model_a/tests/test_base_model.py 1970-01-01 00:00:00 +0000 | |||
1398 | +++ openerp/tests/addons/test_base_model_a/tests/test_base_model.py 2013-07-10 14:52:28 +0000 | |||
1399 | @@ -0,0 +1,40 @@ | |||
1400 | 1 | # -*- coding: utf-8 -*- | ||
1401 | 2 | import openerp.modules.registry | ||
1402 | 3 | import openerp | ||
1403 | 4 | |||
1404 | 5 | from openerp.tests import common | ||
1405 | 6 | from openerp.tools.misc import mute_logger | ||
1406 | 7 | |||
1407 | 8 | class test_base_model_a(common.TransactionCase): | ||
1408 | 9 | |||
1409 | 10 | def __init__(self, *args, **kwargs): | ||
1410 | 11 | super(test_base_model_a, self).__init__(*args, **kwargs) | ||
1411 | 12 | self.model = None | ||
1412 | 13 | |||
1413 | 14 | def setUp(self): | ||
1414 | 15 | super(test_base_model_a, self).setUp() | ||
1415 | 16 | self.model_a = self.registry('test.base.model.a') | ||
1416 | 17 | |||
1417 | 18 | def test_model_name(self): | ||
1418 | 19 | self.assertEqual( | ||
1419 | 20 | self.model_a._name, | ||
1420 | 21 | 'test.base.model.a') | ||
1421 | 22 | |||
1422 | 23 | def test_model_columns(self): | ||
1423 | 24 | self.assertEqual( | ||
1424 | 25 | self.model_a._columns.keys(), | ||
1425 | 26 | ['column_n']) | ||
1426 | 27 | |||
1427 | 28 | def test_model_inherit(self): | ||
1428 | 29 | self.assertEqual( | ||
1429 | 30 | self.model_a._inherit, | ||
1430 | 31 | 'base.model') | ||
1431 | 32 | |||
1432 | 33 | def test_model_table(self): | ||
1433 | 34 | # Assert no table is backing base.model. | ||
1434 | 35 | self.cr.execute("SELECT relname FROM pg_class WHERE relkind = 'r' AND relname=%s", ('base_model',)) | ||
1435 | 36 | self.assertTrue(not self.cr.dictfetchall()) | ||
1436 | 37 | |||
1437 | 38 | # Make sure the previous assertion works. | ||
1438 | 39 | self.cr.execute("SELECT relname FROM pg_class WHERE relkind = 'r' AND relname=%s", ('res_partner',)) | ||
1439 | 40 | self.assertTrue(self.cr.dictfetchall()) | ||
1440 | 0 | 41 | ||
1441 | === added directory 'openerp/tests/addons/test_base_model_b' | |||
1442 | === added file 'openerp/tests/addons/test_base_model_b/__init__.py' | |||
1443 | --- openerp/tests/addons/test_base_model_b/__init__.py 1970-01-01 00:00:00 +0000 | |||
1444 | +++ openerp/tests/addons/test_base_model_b/__init__.py 2013-07-10 14:52:28 +0000 | |||
1445 | @@ -0,0 +1,3 @@ | |||
1446 | 1 | # -*- coding: utf-8 -*- | ||
1447 | 2 | import models | ||
1448 | 3 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | ||
1449 | 0 | 4 | ||
1450 | === added file 'openerp/tests/addons/test_base_model_b/__openerp__.py' | |||
1451 | --- openerp/tests/addons/test_base_model_b/__openerp__.py 1970-01-01 00:00:00 +0000 | |||
1452 | +++ openerp/tests/addons/test_base_model_b/__openerp__.py 2013-07-10 14:52:28 +0000 | |||
1453 | @@ -0,0 +1,15 @@ | |||
1454 | 1 | # -*- coding: utf-8 -*- | ||
1455 | 2 | { | ||
1456 | 3 | 'name': 'test-base-model-b', | ||
1457 | 4 | 'version': '0.1', | ||
1458 | 5 | 'category': 'Tests', | ||
1459 | 6 | 'description': """A module to test the BaseModel inheritance.""", | ||
1460 | 7 | 'author': 'OpenERP SA', | ||
1461 | 8 | 'maintainer': 'OpenERP SA', | ||
1462 | 9 | 'website': 'http://www.openerp.com', | ||
1463 | 10 | 'depends': ['base', 'test_base_model_a'], | ||
1464 | 11 | 'data': [], | ||
1465 | 12 | 'installable': True, | ||
1466 | 13 | 'auto_install': False, | ||
1467 | 14 | } | ||
1468 | 15 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | ||
1469 | 0 | 16 | ||
1470 | === added file 'openerp/tests/addons/test_base_model_b/models.py' | |||
1471 | --- openerp/tests/addons/test_base_model_b/models.py 1970-01-01 00:00:00 +0000 | |||
1472 | +++ openerp/tests/addons/test_base_model_b/models.py 2013-07-10 14:52:28 +0000 | |||
1473 | @@ -0,0 +1,18 @@ | |||
1474 | 1 | # -*- coding: utf-8 -*- | ||
1475 | 2 | import openerp | ||
1476 | 3 | |||
1477 | 4 | class b(openerp.osv.orm.Model): | ||
1478 | 5 | """ This model is just a copy of BaseModel. | ||
1479 | 6 | """ | ||
1480 | 7 | _name = 'test.base.model.b' | ||
1481 | 8 | |||
1482 | 9 | class o(openerp.osv.orm.AbstractModel): | ||
1483 | 10 | """ This model augments BaseModel. | ||
1484 | 11 | """ | ||
1485 | 12 | _inherit = 'base.model' | ||
1486 | 13 | |||
1487 | 14 | _columns = { | ||
1488 | 15 | 'column_o': openerp.osv.fields.char(), | ||
1489 | 16 | } | ||
1490 | 17 | |||
1491 | 18 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | ||
1492 | 0 | 19 | ||
1493 | === added directory 'openerp/tests/addons/test_base_model_b/tests' | |||
1494 | === added file 'openerp/tests/addons/test_base_model_b/tests/__init__.py' | |||
1495 | --- openerp/tests/addons/test_base_model_b/tests/__init__.py 1970-01-01 00:00:00 +0000 | |||
1496 | +++ openerp/tests/addons/test_base_model_b/tests/__init__.py 2013-07-10 14:52:28 +0000 | |||
1497 | @@ -0,0 +1,13 @@ | |||
1498 | 1 | # -*- coding: utf-8 -*- | ||
1499 | 2 | |||
1500 | 3 | from . import test_base_model | ||
1501 | 4 | |||
1502 | 5 | fast_suite = [ | ||
1503 | 6 | ] | ||
1504 | 7 | |||
1505 | 8 | checks = [ | ||
1506 | 9 | # This test assumes to be installed alone. | ||
1507 | 10 | # test_base_model, | ||
1508 | 11 | ] | ||
1509 | 12 | |||
1510 | 13 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | ||
1511 | 0 | 14 | ||
1512 | === added file 'openerp/tests/addons/test_base_model_b/tests/test_base_model.py' | |||
1513 | --- openerp/tests/addons/test_base_model_b/tests/test_base_model.py 1970-01-01 00:00:00 +0000 | |||
1514 | +++ openerp/tests/addons/test_base_model_b/tests/test_base_model.py 2013-07-10 14:52:28 +0000 | |||
1515 | @@ -0,0 +1,50 @@ | |||
1516 | 1 | # -*- coding: utf-8 -*- | ||
1517 | 2 | import openerp.modules.registry | ||
1518 | 3 | import openerp | ||
1519 | 4 | |||
1520 | 5 | from openerp.tests import common | ||
1521 | 6 | from openerp.tools.misc import mute_logger | ||
1522 | 7 | |||
1523 | 8 | class test_base_model_b(common.TransactionCase): | ||
1524 | 9 | |||
1525 | 10 | def __init__(self, *args, **kwargs): | ||
1526 | 11 | super(test_base_model_b, self).__init__(*args, **kwargs) | ||
1527 | 12 | self.model = None | ||
1528 | 13 | |||
1529 | 14 | def setUp(self): | ||
1530 | 15 | super(test_base_model_b, self).setUp() | ||
1531 | 16 | self.model_a = self.registry('test.base.model.a') | ||
1532 | 17 | self.model_b = self.registry('test.base.model.b') | ||
1533 | 18 | |||
1534 | 19 | def test_model_name(self): | ||
1535 | 20 | self.assertEqual( | ||
1536 | 21 | self.model_a._name, | ||
1537 | 22 | 'test.base.model.a') | ||
1538 | 23 | self.assertEqual( | ||
1539 | 24 | self.model_b._name, | ||
1540 | 25 | 'test.base.model.b') | ||
1541 | 26 | |||
1542 | 27 | def test_model_columns(self): | ||
1543 | 28 | self.assertEqual( | ||
1544 | 29 | sorted(self.model_a._columns.keys()), | ||
1545 | 30 | ['column_n', 'column_o']) | ||
1546 | 31 | self.assertEqual( | ||
1547 | 32 | sorted(self.model_b._columns.keys()), | ||
1548 | 33 | ['column_n', 'column_o']) | ||
1549 | 34 | |||
1550 | 35 | def test_model_inherit(self): | ||
1551 | 36 | self.assertEqual( | ||
1552 | 37 | self.model_a._inherit, | ||
1553 | 38 | 'base.model') | ||
1554 | 39 | self.assertEqual( | ||
1555 | 40 | self.model_b._inherit, | ||
1556 | 41 | 'base.model') | ||
1557 | 42 | |||
1558 | 43 | def test_model_table(self): | ||
1559 | 44 | # Assert no table is backing base.model. | ||
1560 | 45 | self.cr.execute("SELECT relname FROM pg_class WHERE relkind = 'r' AND relname=%s", ('base_model',)) | ||
1561 | 46 | self.assertTrue(not self.cr.dictfetchall()) | ||
1562 | 47 | |||
1563 | 48 | # Make sure the previous assertion works. | ||
1564 | 49 | self.cr.execute("SELECT relname FROM pg_class WHERE relkind = 'r' AND relname=%s", ('res_partner',)) | ||
1565 | 50 | self.assertTrue(self.cr.dictfetchall()) |
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 rank(name) : openerp_ descriptor[ 'depends' ] get_module_ rank, depends)) + 1 if depends else 0
def get_module_
""" return the rank of the module with the given name """
module = get_module(name)
depends = module.
return max(map(
# sorting module names by rank and name modules, key=lambda name: (get_module_ rank(name) , name)))
modules = list(sorted(
Raphael