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.setdefault('complexity', 'normal')
253 info.setdefault('core', False)
254 info.setdefault('icon', '')
255 info['certificate'] = info.get('certificate') or None
256 info['web'] = info.get('web') or False
257 info['license'] = info.get('license') or 'AGPL-3'
258 info.setdefault('installable', True)
259 info.setdefault('active', False)
260 # If the following is provided, it is called after the module is --loaded.
261 info.setdefault('post_load', None)
262 for kind in ['data', 'demo', 'test',
263 'init_xml', 'update_xml', 'demo_xml']:
264 info.setdefault(kind, [])
267 #TODO: refactor the logger in this file to follow the logging guidelines
269 logging.getLogger('modules').debug('module %s: no descriptor file'
270 ' found: __openerp__.py or __terp__.py (deprecated)', module)
274 def init_module_models(cr, module_name, obj_list):
275 """ Initialize a list of models.
277 Call _auto_init and init on each model to create or update the
278 database tables supporting the models.
280 TODO better explanation of _auto_init and init.
283 logger.notifyChannel('init', netsvc.LOG_INFO,
284 'module %s: creating or updating database tables' % module_name)
287 result = obj._auto_init(cr, {'module': module_name})
290 if hasattr(obj, 'init'):
294 obj._auto_end(cr, {'module': module_name})
301 # Import hook to write a addon m in both sys.modules['m'] and
302 # sys.modules['openerp.addons.m']. Otherwise it could be loaded twice
303 # if imported twice using different names.
304 #class MyImportHook(object):
305 # def find_module(self, module_name, package_path):
306 # print ">>>", module_name, package_path
307 # def load_module(self, module_name):
308 # raise ImportError("Restricted")
310 #sys.meta_path.append(MyImportHook())
312 def register_module_classes(m):
313 """ Register module named m, if not already registered.
315 This loads the module and register all of its models, thanks to either
316 the MetaModel metaclass, or the explicit instantiation of the model.
321 mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
322 msg = "Couldn't load %smodule %s" % (mt, m)
323 logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
324 logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
329 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
330 mod_path = get_module_path(m)
332 initialize_sys_path()
334 zip_mod_path = mod_path + '.zip'
335 if not os.path.isfile(zip_mod_path):
338 zimp = zipimport.zipimporter(zip_mod_path)
348 """Returns the list of module names
352 name = os.path.basename(name)
353 if name[-4:] == '.zip':
357 def is_really_module(name):
358 name = opj(dir, name)
359 return os.path.isdir(name) or zipfile.is_zipfile(name)
360 return map(clean, filter(is_really_module, os.listdir(dir)))
363 initialize_sys_path()
365 plist.extend(listdir(ad))
366 return list(set(plist))
369 def get_modules_with_version():
370 modules = get_modules()
372 for module in modules:
374 info = load_information_from_description_file(module)
375 res[module] = "%s.%s" % (release.major_version, info['version'])
381 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: