1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 # Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
24 from os.path import join as opj
30 import openerp.osv as osv
31 import openerp.tools as tools
32 import openerp.tools.osutil as osutil
33 from openerp.tools.safe_eval import safe_eval as eval
34 from openerp.tools.translate import _
36 import openerp.netsvc as netsvc
39 import openerp.release as release
43 from zipfile import PyZipFile, ZIP_DEFLATED
44 from cStringIO import StringIO
48 import openerp.modules.db
49 import openerp.modules.graph
51 _ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)
54 # Modules already loaded
57 logger = netsvc.Logger()
59 def initialize_sys_path():
60 """ Add all addons paths in sys.path.
62 This ensures something like ``import crm`` works even if the addons are
63 not in the PYTHONPATH.
70 ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
72 sys.path.insert(1, _ad)
77 sys.path.insert(ad_cnt, adp)
80 ad_paths.append(_ad) # for get_module_path
83 def get_module_path(module, downloaded=False):
84 """Return the path of the given module.
86 Search the addons paths and return the first path where the given
87 module is found. If downloaded is True, return the default addons
88 path if nothing else is found.
93 if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
94 return opj(adp, module)
97 return opj(_ad, module)
98 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
102 def get_module_filetree(module, dir='.'):
103 path = get_module_path(module)
107 dir = os.path.normpath(dir)
110 if dir.startswith('..') or (dir and dir[0] == '/'):
111 raise Exception('Cannot access file outside the module')
113 if not os.path.isdir(path):
115 zip = zipfile.ZipFile(path + ".zip")
116 files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
118 files = osutil.listdir(path, True)
122 if not f.startswith(dir):
126 f = f[len(dir)+int(not dir.endswith('/')):]
127 lst = f.split(os.sep)
130 current = current.setdefault(lst.pop(0), {})
131 current[lst.pop(0)] = None
135 def zip_directory(directory, b64enc=True, src=True):
136 """Compress a directory
138 @param directory: The directory to compress
139 @param base64enc: if True the function will encode the zip file with base64
140 @param src: Integrate the source files
142 @return: a string containing the zip file
145 RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
147 def _zippy(archive, path, src=True):
148 path = os.path.abspath(path)
149 base = os.path.basename(path)
150 for f in osutil.listdir(path, True):
151 bf = os.path.basename(f)
152 if not RE_exclude.search(bf) and (src or bf in ('__openerp__.py', '__terp__.py') or not bf.endswith('.py')):
153 archive.write(os.path.join(path, f), os.path.join(base, f))
155 archname = StringIO()
156 archive = PyZipFile(archname, "w", ZIP_DEFLATED)
158 # for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8)
159 directory = tools.ustr(directory).encode('utf-8')
161 archive.writepy(directory)
162 _zippy(archive, directory, src=src)
164 archive_data = archname.getvalue()
168 return base64.encodestring(archive_data)
172 def get_module_as_zip(modulename, b64enc=True, src=True):
173 """Generate a module as zip file with the source or not and can do a base64 encoding
175 @param modulename: The module name
176 @param b64enc: if True the function will encode the zip file with base64
177 @param src: Integrate the source files
179 @return: a stream to store in a file-like object
182 ap = get_module_path(str(modulename))
184 raise Exception('Unable to find path for module %s' % modulename)
186 ap = ap.encode('utf8')
187 if os.path.isfile(ap + '.zip'):
188 val = file(ap + '.zip', 'rb').read()
190 val = base64.encodestring(val)
192 val = zip_directory(ap, b64enc, src)
197 def get_module_resource(module, *args):
198 """Return the full path of a resource of the given module.
200 @param module: the module
201 @param args: the resource path components
203 @return: absolute path to the resource
205 TODO name it get_resource_path
206 TODO make it available inside on osv object (self.get_resource_path)
208 a = get_module_path(module)
209 if not a: return False
210 resource_path = opj(a, *args)
211 if zipfile.is_zipfile( a +'.zip') :
212 zip = zipfile.ZipFile( a + ".zip")
213 files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
214 resource_path = '/'.join(args)
215 if resource_path in files:
216 return opj(a, resource_path)
217 elif os.path.exists(resource_path):
222 def load_information_from_description_file(module):
224 :param module: The name of the module (sale, purchase, ...)
227 terp_file = get_module_resource(module, '__openerp__.py')
229 terp_file = get_module_resource(module, '__terp__.py')
230 mod_path = get_module_path(module)
233 if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
234 terp_f = tools.file_open(terp_file)
236 info = eval(terp_f.read())
238 logger.notifyChannel('modules', netsvc.LOG_ERROR,
239 'module %s: exception while evaluating file %s' %
244 # TODO the version should probably be mandatory
245 info.setdefault('version', '0')
246 info.setdefault('category', 'Uncategorized')
247 info.setdefault('depends', [])
248 info.setdefault('author', '')
249 info.setdefault('website', '')
250 info.setdefault('name', False)
251 info.setdefault('description', '')
252 info['certificate'] = info.get('certificate') or None
253 info['web'] = info.get('web') or False
254 info['license'] = info.get('license') or 'AGPL-3'
255 info.setdefault('installable', True)
256 info.setdefault('active', False)
257 # If the following is provided, it is called after the module is --loaded.
258 info.setdefault('post_load', None)
259 for kind in ['data', 'demo', 'test',
260 'init_xml', 'update_xml', 'demo_xml']:
261 info.setdefault(kind, [])
264 #TODO: refactor the logger in this file to follow the logging guidelines
266 logging.getLogger('modules').debug('module %s: no descriptor file'
267 ' found: __openerp__.py or __terp__.py (deprecated)', module)
271 def init_module_models(cr, module_name, obj_list):
272 """ Initialize a list of models.
274 Call _auto_init and init on each model to create or update the
275 database tables supporting the models.
277 TODO better explanation of _auto_init and init.
280 logger.notifyChannel('init', netsvc.LOG_INFO,
281 'module %s: creating or updating database tables' % module_name)
284 result = obj._auto_init(cr, {'module': module_name})
287 if hasattr(obj, 'init'):
291 obj._auto_end(cr, {'module': module_name})
298 # Import hook to write a addon m in both sys.modules['m'] and
299 # sys.modules['openerp.addons.m']. Otherwise it could be loaded twice
300 # if imported twice using different names.
301 #class MyImportHook(object):
302 # def find_module(self, module_name, package_path):
303 # print ">>>", module_name, package_path
304 # def load_module(self, module_name):
305 # raise ImportError("Restricted")
307 #sys.meta_path.append(MyImportHook())
309 def register_module_classes(m):
310 """ Register module named m, if not already registered.
312 This loads the module and register all of its models, thanks to either
313 the MetaModel metaclass, or the explicit instantiation of the model.
318 mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
319 msg = "Couldn't load %smodule %s" % (mt, m)
320 logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
321 logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
326 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
327 mod_path = get_module_path(m)
329 initialize_sys_path()
331 zip_mod_path = mod_path + '.zip'
332 if not os.path.isfile(zip_mod_path):
335 zimp = zipimport.zipimporter(zip_mod_path)
345 """Returns the list of module names
349 name = os.path.basename(name)
350 if name[-4:] == '.zip':
354 def is_really_module(name):
355 name = opj(dir, name)
356 return os.path.isdir(name) or zipfile.is_zipfile(name)
357 return map(clean, filter(is_really_module, os.listdir(dir)))
360 initialize_sys_path()
362 plist.extend(listdir(ad))
363 return list(set(plist))
366 def get_modules_with_version():
367 modules = get_modules()
369 for module in modules:
371 info = load_information_from_description_file(module)
372 res[module] = "%s.%s" % (release.major_version, info['version'])
378 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: