[MERGE]merge with trunk web
[odoo/odoo.git] / openerp / modules / registry.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 """ Models registries.
23
24 """
25 import logging
26 import threading
27
28 import openerp.sql_db
29 import openerp.osv.orm
30 import openerp.cron
31 import openerp.tools
32 import openerp.modules.db
33 import openerp.tools.config
34
35 _logger = logging.getLogger(__name__)
36
37 class Registry(object):
38     """ Model registry for a particular database.
39
40     The registry is essentially a mapping between model names and model
41     instances. There is one registry instance per database.
42
43     """
44
45     def __init__(self, db_name):
46         self.models = {} # model name/model instance mapping
47         self._sql_error = {}
48         self._store_function = {}
49         self._init = True
50         self._init_parent = {}
51         
52         # modules fully loaded (maintained during init phase by `loading` module)
53         self._init_modules = set()
54
55         self.db_name = db_name
56         self.db = openerp.sql_db.db_connect(db_name)
57
58         cr = self.db.cursor()
59         has_unaccent = openerp.modules.db.has_unaccent(cr)
60         if openerp.tools.config['unaccent'] and not has_unaccent:
61             _logger.warning("The option --unaccent was given but no unaccent() function was found in database.")
62         self.has_unaccent = openerp.tools.config['unaccent'] and has_unaccent
63         cr.close()
64
65     def do_parent_store(self, cr):
66         for o in self._init_parent:
67             self.get(o)._parent_store_compute(cr)
68         self._init = False
69
70     def obj_list(self):
71         """ Return the list of model names in this registry."""
72         return self.models.keys()
73
74     def add(self, model_name, model):
75         """ Add or replace a model in the registry."""
76         self.models[model_name] = model
77
78     def get(self, model_name):
79         """ Return a model for a given name or None if it doesn't exist."""
80         return self.models.get(model_name)
81
82     def __getitem__(self, model_name):
83         """ Return a model for a given name or raise KeyError if it doesn't exist."""
84         return self.models[model_name]
85
86     def load(self, cr, module):
87         """ Load a given module in the registry.
88
89         At the Python level, the modules are already loaded, but not yet on a
90         per-registry level. This method populates a registry with the given
91         modules, i.e. it instanciates all the classes of a the given module
92         and registers them in the registry.
93
94         """
95
96         res = []
97
98         # Instantiate registered classes (via the MetaModel automatic discovery
99         # or via explicit constructor call), and add them to the pool.
100         for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):
101             res.append(cls.create_instance(self, cr))
102
103         return res
104
105     def schedule_cron_jobs(self):
106         """ Make the cron thread care about this registry/database jobs.
107         This will initiate the cron thread to check for any pending jobs for
108         this registry/database as soon as possible. Then it will continuously
109         monitor the ir.cron model for future jobs. See openerp.cron for
110         details.
111         """
112         openerp.cron.schedule_wakeup(openerp.cron.WAKE_UP_NOW, self.db.dbname)
113
114     def clear_caches(self):
115         """ Clear the caches
116         This clears the caches associated to methods decorated with
117         ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models.
118         """
119         for model in self.models.itervalues():
120             model.clear_caches()
121
122 class RegistryManager(object):
123     """ Model registries manager.
124
125         The manager is responsible for creation and deletion of model
126         registries (essentially database connection/model registry pairs).
127
128     """
129     # Mapping between db name and model registry.
130     # Accessed through the methods below.
131     registries = {}
132     registries_lock = threading.RLock()
133
134     @classmethod
135     def get(cls, db_name, force_demo=False, status=None, update_module=False,
136             pooljobs=True):
137         """ Return a registry for a given database name."""
138         try:
139             return cls.registries[db_name]
140         except KeyError:
141             return cls.new(db_name, force_demo, status,
142                            update_module, pooljobs)
143
144     @classmethod
145     def new(cls, db_name, force_demo=False, status=None,
146             update_module=False, pooljobs=True):
147         """ Create and return a new registry for a given database name.
148
149         The (possibly) previous registry for that database name is discarded.
150
151         """
152         import openerp.modules
153         with cls.registries_lock:
154             registry = Registry(db_name)
155
156             # Initializing a registry will call general code which will in turn
157             # call registries.get (this object) to obtain the registry being
158             # initialized. Make it available in the registries dictionary then
159             # remove it if an exception is raised.
160             cls.delete(db_name)
161             cls.registries[db_name] = registry
162             try:
163                 # This should be a method on Registry
164                 openerp.modules.load_modules(registry.db, force_demo, status, update_module)
165             except Exception:
166                 del cls.registries[db_name]
167                 raise
168
169             cr = registry.db.cursor()
170             try:
171                 registry.do_parent_store(cr)
172                 registry.get('ir.actions.report.xml').register_all(cr)
173                 cr.commit()
174             finally:
175                 cr.close()
176
177         if pooljobs:
178             registry.schedule_cron_jobs()
179
180         return registry
181
182     @classmethod
183     def delete(cls, db_name):
184         """Delete the registry linked to a given database.
185
186         This also cleans the associated caches. For good measure this also
187         cancels the associated cron job. But please note that the cron job can
188         be running and take some time before ending, and that you should not
189         remove a registry if it can still be used by some thread. So it might
190         be necessary to call yourself openerp.cron.Agent.cancel(db_name) and
191         and join (i.e. wait for) the thread.
192         """
193         with cls.registries_lock:
194             if db_name in cls.registries:
195                 cls.registries[db_name].clear_caches()
196                 del cls.registries[db_name]
197                 openerp.cron.cancel(db_name)
198
199
200     @classmethod
201     def delete_all(cls):
202         """Delete all the registries. """
203         with cls.registries_lock:
204             for db_name in cls.registries.keys():
205                 cls.delete(db_name)
206
207     @classmethod
208     def clear_caches(cls, db_name):
209         """Clear caches
210
211         This clears the caches associated to methods decorated with
212         ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models
213         of the given database name.
214
215         This method is given to spare you a ``RegistryManager.get(db_name)``
216         that would loads the given database if it was not already loaded.
217         """
218         with cls.registries_lock:
219             if db_name in cls.registries:
220                 cls.registries[db_name].clear_caches()
221
222
223 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: