Merge lp:~openerp-dev/openobject-server/trunk-trunk-datadir-chs into lp:openobject-server

Proposed by Christophe Simonis (OpenERP)
Status: Merged
Merged at revision: 5103
Proposed branch: lp:~openerp-dev/openobject-server/trunk-trunk-datadir-chs
Merge into: lp:openobject-server
Diff against target: 1115 lines (+642/-168)
13 files modified
openerp/addons/base/ir/ir_attachment.py (+41/-29)
openerp/addons/base/module/wizard/base_module_import.py (+0/-2)
openerp/addons/base/tests/test_ir_attachment.py (+68/-65)
openerp/cli/server.py (+1/-1)
openerp/http.py (+1/-22)
openerp/modules/module.py (+9/-7)
openerp/release.py (+2/-2)
openerp/service/db.py (+1/-6)
openerp/service/server.py (+2/-3)
openerp/tools/__init__.py (+1/-0)
openerp/tools/appdirs.py (+477/-0)
openerp/tools/config.py (+29/-4)
openerp/tools/translate.py (+10/-27)
To merge this branch: bzr merge lp:~openerp-dev/openobject-server/trunk-trunk-datadir-chs
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+202059@code.launchpad.net
To post a comment you must log in.
5046. By Christophe Simonis (OpenERP)

[FIX] attachments: filestore use dbname instead of dbuuid

5047. By Antony Lesuisse (OpenERP)

[MERGE] trunk

5048. By Antony Lesuisse (OpenERP)

[FIX] move appsdirs to tools

5049. By Christophe Simonis (OpenERP)

merge upstream

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openerp/addons/base/ir/ir_attachment.py'
2--- openerp/addons/base/ir/ir_attachment.py 2014-01-16 09:17:16 +0000
3+++ openerp/addons/base/ir/ir_attachment.py 2014-02-27 16:58:29 +0000
4@@ -64,19 +64,37 @@
5 data[attachment.id] = False
6 return data
7
8+ def _storage(self, cr, uid, context=None):
9+ return self.pool['ir.config_parameter'].get_param(cr, SUPERUSER_ID, 'ir_attachment.location', 'file')
10+
11+ @tools.ormcache()
12+ def _filestore(self, cr, uid, context=None):
13+ return os.path.join(tools.config['data_dir'], 'filestore', cr.dbname)
14+
15 # 'data' field implementation
16 def _full_path(self, cr, uid, location, path):
17- # location = 'file:filestore'
18- assert location.startswith('file:'), "Unhandled filestore location %s" % location
19- location = location[5:]
20-
21- # sanitize location name and path
22- location = re.sub('[.]','',location)
23- location = location.strip('/\\')
24-
25- path = re.sub('[.]','',path)
26+ # sanitize ath
27+ path = re.sub('[.]', '', path)
28 path = path.strip('/\\')
29- return os.path.join(tools.config['root_path'], location, cr.dbname, path)
30+ return os.path.join(self._filestore(cr, uid), path)
31+
32+ def _get_path(self, cr, uid, location, bin_data):
33+ sha = hashlib.sha1(bin_data).hexdigest()
34+
35+ # retro compatibility
36+ fname = sha[:3] + '/' + sha
37+ full_path = self._full_path(cr, uid, location, fname)
38+ if os.path.isfile(full_path):
39+ return fname, full_path # keep existing path
40+
41+ # scatter files across 256 dirs
42+ # we use '/' in the db (even on windows)
43+ fname = sha[:2] + '/' + sha
44+ full_path = self._full_path(cr, uid, location, fname)
45+ dirname = os.path.dirname(full_path)
46+ if not os.path.isdir(dirname):
47+ os.makedirs(dirname)
48+ return fname, full_path
49
50 def _file_read(self, cr, uid, location, fname, bin_size=False):
51 full_path = self._full_path(cr, uid, location, fname)
52@@ -92,18 +110,13 @@
53
54 def _file_write(self, cr, uid, location, value):
55 bin_value = value.decode('base64')
56- fname = hashlib.sha1(bin_value).hexdigest()
57- # scatter files across 1024 dirs
58- # we use '/' in the db (even on windows)
59- fname = fname[:3] + '/' + fname
60- full_path = self._full_path(cr, uid, location, fname)
61- try:
62- dirname = os.path.dirname(full_path)
63- if not os.path.isdir(dirname):
64- os.makedirs(dirname)
65- open(full_path,'wb').write(bin_value)
66- except IOError:
67- _logger.error("_file_write writing %s",full_path)
68+ fname, full_path = self._get_path(cr, uid, location, bin_value)
69+ if not os.path.exists(full_path):
70+ try:
71+ with open(full_path, 'wb') as fp:
72+ fp.write(bin_value)
73+ except IOError:
74+ _logger.error("_file_write writing %s", full_path)
75 return fname
76
77 def _file_delete(self, cr, uid, location, fname):
78@@ -122,10 +135,10 @@
79 if context is None:
80 context = {}
81 result = {}
82- location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
83+ location = self._storage(cr, uid, context)
84 bin_size = context.get('bin_size')
85 for attach in self.browse(cr, uid, ids, context=context):
86- if location and attach.store_fname:
87+ if location != 'db' and attach.store_fname:
88 result[attach.id] = self._file_read(cr, uid, location, attach.store_fname, bin_size)
89 else:
90 result[attach.id] = attach.db_datas
91@@ -137,9 +150,9 @@
92 return True
93 if context is None:
94 context = {}
95- location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
96+ location = self._storage(cr, uid, context)
97 file_size = len(value.decode('base64'))
98- if location:
99+ if location != 'db':
100 attach = self.browse(cr, uid, id, context=context)
101 if attach.store_fname:
102 self._file_delete(cr, uid, location, attach.store_fname)
103@@ -285,8 +298,8 @@
104 if isinstance(ids, (int, long)):
105 ids = [ids]
106 self.check(cr, uid, ids, 'unlink', context=context)
107- location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
108- if location:
109+ location = self._storage(cr, uid, context)
110+ if location != 'db':
111 for attach in self.browse(cr, uid, ids, context=context):
112 if attach.store_fname:
113 self._file_delete(cr, uid, location, attach.store_fname)
114@@ -303,4 +316,3 @@
115 cr, uid, 'base', 'action_attachment', context=context)
116
117 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
118-
119
120=== modified file 'openerp/addons/base/module/wizard/base_module_import.py'
121--- openerp/addons/base/module/wizard/base_module_import.py 2013-10-18 15:48:05 +0000
122+++ openerp/addons/base/module/wizard/base_module_import.py 2014-02-27 16:58:29 +0000
123@@ -28,8 +28,6 @@
124 from openerp.osv import osv, fields
125 from openerp.tools.translate import _
126
127-ADDONS_PATH = tools.config['addons_path'].split(",")[-1]
128-
129 class base_module_import(osv.osv_memory):
130 """ Import Module """
131
132
133=== modified file 'openerp/addons/base/tests/test_ir_attachment.py'
134--- openerp/addons/base/tests/test_ir_attachment.py 2012-12-16 19:03:17 +0000
135+++ openerp/addons/base/tests/test_ir_attachment.py 2014-02-27 16:58:29 +0000
136@@ -1,89 +1,92 @@
137 import hashlib
138 import os
139
140-import unittest2
141-
142 import openerp
143 import openerp.tests.common
144
145+HASH_SPLIT = 2 # FIXME: testing implementations detail is not a good idea
146+
147 class test_ir_attachment(openerp.tests.common.TransactionCase):
148-
149- def test_00_attachment_flow(self):
150+ def setUp(self):
151+ super(test_ir_attachment, self).setUp()
152 registry, cr, uid = self.registry, self.cr, self.uid
153- root_path = openerp.tools.config['root_path']
154- ira = registry('ir.attachment')
155+ self.ira = registry('ir.attachment')
156+ self.filestore = self.ira._filestore(cr, uid)
157
158 # Blob1
159- blob1 = 'blob1'
160- blob1_b64 = blob1.encode('base64')
161- blob1_hash = hashlib.sha1(blob1).hexdigest()
162- blob1_fname = blob1_hash[:3] + '/' + blob1_hash
163+ self.blob1 = 'blob1'
164+ self.blob1_b64 = self.blob1.encode('base64')
165+ blob1_hash = hashlib.sha1(self.blob1).hexdigest()
166+ self.blob1_fname = blob1_hash[:HASH_SPLIT] + '/' + blob1_hash
167
168 # Blob2
169 blob2 = 'blob2'
170- blob2_b64 = blob2.encode('base64')
171- blob2_hash = hashlib.sha1(blob2).hexdigest()
172- blob2_fname = blob2_hash[:3] + '/' + blob2_hash
173+ self.blob2_b64 = blob2.encode('base64')
174+
175+ def test_01_store_in_db(self):
176+ registry, cr, uid = self.registry, self.cr, self.uid
177+
178+ # force storing in database
179+ registry('ir.config_parameter').set_param(cr, uid, 'ir_attachment.location', 'db')
180
181 # 'ir_attachment.location' is undefined test database storage
182- a1 = ira.create(cr, uid, {'name': 'a1', 'datas': blob1_b64})
183- a1_read = ira.read(cr, uid, [a1], ['datas'])
184- self.assertEqual(a1_read[0]['datas'], blob1_b64)
185-
186- cr.execute("select id,db_datas from ir_attachment where id = %s", (a1,) )
187- a1_db_datas = str(cr.fetchall()[0][1])
188- self.assertEqual(a1_db_datas, blob1_b64)
189-
190- # define a location for filestore
191- registry('ir.config_parameter').set_param(cr, uid, 'ir_attachment.location', 'file:///filestore')
192-
193- # Test file storage
194- a2 = ira.create(cr, uid, {'name': 'a2', 'datas': blob1_b64})
195- a2_read = ira.read(cr, uid, [a2], ['datas'])
196- self.assertEqual(a2_read[0]['datas'], blob1_b64)
197-
198- cr.execute("select id,store_fname from ir_attachment where id = %s", (a2,) )
199- a2_store_fname = cr.fetchall()[0][1]
200- self.assertEqual(a2_store_fname, blob1_fname)
201-
202- a2_fn = os.path.join(root_path, 'filestore', cr.dbname, blob1_hash[:3], blob1_hash)
203- fc = file(a2_fn).read()
204- self.assertEqual(fc, blob1)
205-
206- # create a3 with same blob
207- a3 = ira.create(cr, uid, {'name': 'a3', 'datas': blob1_b64})
208- a3_read = ira.read(cr, uid, [a3], ['datas'])
209- self.assertEqual(a3_read[0]['datas'], blob1_b64)
210-
211- cr.execute("select id,store_fname from ir_attachment where id = %s", (a3,) )
212- a3_store_fname = cr.fetchall()[0][1]
213+ a1 = self.ira.create(cr, uid, {'name': 'a1', 'datas': self.blob1_b64})
214+ a1_read = self.ira.read(cr, uid, [a1], ['datas'])
215+ self.assertEqual(a1_read[0]['datas'], self.blob1_b64)
216+
217+ a1_db_datas = self.ira.browse(cr, uid, a1).db_datas
218+ self.assertEqual(a1_db_datas, self.blob1_b64)
219+
220+ def test_02_store_on_disk(self):
221+ registry, cr, uid = self.registry, self.cr, self.uid
222+
223+ a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64})
224+ a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
225+
226+ self.assertEqual(a2_store_fname, self.blob1_fname)
227+ self.assertTrue(os.path.isfile(os.path.join(self.filestore, a2_store_fname)))
228+
229+ def test_03_no_duplication(self):
230+ registry, cr, uid = self.registry, self.cr, self.uid
231+
232+ a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64})
233+ a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
234+
235+ a3 = self.ira.create(cr, uid, {'name': 'a3', 'datas': self.blob1_b64})
236+ a3_store_fname = self.ira.browse(cr, uid, a3).store_fname
237+
238 self.assertEqual(a3_store_fname, a2_store_fname)
239
240- # create a4 blob2
241- a4 = ira.create(cr, uid, {'name': 'a4', 'datas': blob2_b64})
242- a4_read = ira.read(cr, uid, [a4], ['datas'])
243- self.assertEqual(a4_read[0]['datas'], blob2_b64)
244-
245- a4_fn = os.path.join(root_path, 'filestore', cr.dbname, blob2_hash[:3], blob2_hash)
246- self.assertTrue(os.path.isfile(a4_fn))
247-
248- # delete a3 but file stays
249- ira.unlink(cr, uid, [a3])
250+ def test_04_keep_file(self):
251+ registry, cr, uid = self.registry, self.cr, self.uid
252+
253+ a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64})
254+ a3 = self.ira.create(cr, uid, {'name': 'a3', 'datas': self.blob1_b64})
255+
256+ a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
257+ a2_fn = os.path.join(self.filestore, a2_store_fname)
258+
259+ self.ira.unlink(cr, uid, [a3])
260 self.assertTrue(os.path.isfile(a2_fn))
261
262 # delete a2 it is unlinked
263- ira.unlink(cr, uid, [a2])
264+ self.ira.unlink(cr, uid, [a2])
265 self.assertFalse(os.path.isfile(a2_fn))
266
267- # update a4 blob2 by blob1
268- ira.write(cr, uid, [a4], {'datas': blob1_b64})
269- a4_read = ira.read(cr, uid, [a4], ['datas'])
270- self.assertEqual(a4_read[0]['datas'], blob1_b64)
271-
272- # file of a4 disapear and a2 reappear
273- self.assertFalse(os.path.isfile(a4_fn))
274+ def test_05_change_data_change_file(self):
275+ registry, cr, uid = self.registry, self.cr, self.uid
276+
277+ a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64})
278+ a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
279+ a2_fn = os.path.join(self.filestore, a2_store_fname)
280+
281 self.assertTrue(os.path.isfile(a2_fn))
282
283- # everybody applause
284-
285-
286+ self.ira.write(cr, uid, [a2], {'datas': self.blob2_b64})
287+ self.assertFalse(os.path.isfile(a2_fn))
288+
289+ new_a2_store_fname = self.ira.browse(cr, uid, a2).store_fname
290+ self.assertNotEqual(a2_store_fname, new_a2_store_fname)
291+
292+ new_a2_fn = os.path.join(self.filestore, new_a2_store_fname)
293+ self.assertTrue(os.path.isfile(new_a2_fn))
294
295=== modified file 'openerp/cli/server.py'
296--- openerp/cli/server.py 2014-02-09 01:46:36 +0000
297+++ openerp/cli/server.py 2014-02-27 16:58:29 +0000
298@@ -72,7 +72,7 @@
299 """
300 config = openerp.tools.config
301 _logger.info("OpenERP version %s", __version__)
302- for name, value in [('addons paths', config['addons_path']),
303+ for name, value in [('addons paths', openerp.modules.module.ad_paths),
304 ('database hostname', config['db_host'] or 'localhost'),
305 ('database port', config['db_port'] or '5432'),
306 ('database user', config['db_user'])]:
307
308=== modified file 'openerp/http.py'
309--- openerp/http.py 2014-02-26 16:16:27 +0000
310+++ openerp/http.py 2014-02-27 16:58:29 +0000
311@@ -995,33 +995,12 @@
312 start_response(status, new_headers)
313 return self.app(environ, start_wrapped)
314
315-def session_path():
316- try:
317- import pwd
318- username = pwd.getpwuid(os.geteuid()).pw_name
319- except ImportError:
320- try:
321- username = getpass.getuser()
322- except Exception:
323- username = "unknown"
324- path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
325- try:
326- os.mkdir(path, 0700)
327- except OSError as exc:
328- if exc.errno == errno.EEXIST:
329- # directory exists: ensure it has the correct permissions
330- # this will fail if the directory is not owned by the current user
331- os.chmod(path, 0700)
332- else:
333- raise
334- return path
335-
336 class Root(object):
337 """Root WSGI application for the OpenERP Web Client.
338 """
339 def __init__(self):
340 # Setup http sessions
341- path = session_path()
342+ path = openerp.tools.config.session_dir
343 _logger.debug('HTTP sessions stored in: %s', path)
344 self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession)
345
346
347=== modified file 'openerp/modules/module.py'
348--- openerp/modules/module.py 2014-02-18 10:18:47 +0000
349+++ openerp/modules/module.py 2014-02-27 16:58:29 +0000
350@@ -3,7 +3,7 @@
351 #
352 # OpenERP, Open Source Management Solution
353 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
354-# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
355+# Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>).
356 #
357 # This program is free software: you can redistribute it and/or modify
358 # it under the terms of the GNU Affero General Public License as
359@@ -40,9 +40,6 @@
360 _logger = logging.getLogger(__name__)
361 _test_logger = logging.getLogger('openerp.tests')
362
363-# addons path ','.joined
364-_ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)
365-
366 # addons path as a list
367 ad_paths = []
368
369@@ -90,8 +87,13 @@
370 if ad_paths:
371 return
372
373- ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
374- ad_paths.append(os.path.abspath(_ad)) # for get_module_path
375+ ad_paths = [tools.config.addons_data_dir]
376+ ad_paths += map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
377+
378+ # add base module path
379+ base_path = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons'))
380+ ad_paths += [base_path]
381+
382 sys.meta_path.append(AddonsImportHook())
383
384 def get_module_path(module, downloaded=False, display_warning=True):
385@@ -108,7 +110,7 @@
386 return opj(adp, module)
387
388 if downloaded:
389- return opj(_ad, module)
390+ return opj(tools.config.addons_data_dir, module)
391 if display_warning:
392 _logger.warning('module %s: module not found', module)
393 return False
394
395=== modified file 'openerp/release.py'
396--- openerp/release.py 2014-02-11 10:53:15 +0000
397+++ openerp/release.py 2014-02-27 16:58:29 +0000
398@@ -32,7 +32,7 @@
399 # (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0)
400 version_info = (8, 0, 0, ALPHA, 1)
401 version = '.'.join(map(str, version_info[:2])) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '')
402-serie = major_version = '.'.join(map(str, version_info[:2]))
403+series = serie = major_version = '.'.join(map(str, version_info[:2]))
404
405 description = 'OpenERP Server'
406 long_desc = '''OpenERP is a complete ERP and CRM. The main features are accounting (analytic
407@@ -50,6 +50,6 @@
408 author_email = 'info@openerp.com'
409 license = 'AGPL-3'
410
411-nt_service_name = "openerp-server-" + serie
412+nt_service_name = "openerp-server-" + series
413
414 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
415
416=== modified file 'openerp/service/db.py'
417--- openerp/service/db.py 2014-02-20 13:27:00 +0000
418+++ openerp/service/db.py 2014-02-27 16:58:29 +0000
419@@ -276,15 +276,10 @@
420 cr.autocommit(True) # avoid transaction block
421 try:
422 cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
423+ _logger.info('RENAME DB: %s -> %s', old_name, new_name)
424 except Exception, e:
425 _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e)
426 raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
427- else:
428- fs = os.path.join(openerp.tools.config['root_path'], 'filestore')
429- if os.path.exists(os.path.join(fs, old_name)):
430- os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
431-
432- _logger.info('RENAME DB: %s -> %s', old_name, new_name)
433 return True
434
435 def exp_db_exist(db_name):
436
437=== modified file 'openerp/service/server.py'
438--- openerp/service/server.py 2014-02-21 23:10:10 +0000
439+++ openerp/service/server.py 2014-02-27 16:58:29 +0000
440@@ -108,15 +108,14 @@
441 self.handler = EventHandler(self)
442 self.notifier = pyinotify.Notifier(self.wm, self.handler, timeout=0)
443 mask = pyinotify.IN_MODIFY | pyinotify.IN_CREATE # IN_MOVED_FROM, IN_MOVED_TO ?
444- for path in openerp.tools.config.options["addons_path"].split(','):
445+ for path in openerp.modules.modules.ad_paths:
446 _logger.info('Watching addons folder %s', path)
447 self.wm.add_watch(path, mask, rec=True)
448
449 def process_data(self, files):
450 xml_files = [i for i in files if i.endswith('.xml')]
451- addons_path = openerp.tools.config.options["addons_path"].split(',')
452 for i in xml_files:
453- for path in addons_path:
454+ for path in openerp.modules.modules.ad_paths:
455 if i.startswith(path):
456 # find out wich addons path the file belongs to
457 # and extract it's module name
458
459=== modified file 'openerp/tools/__init__.py'
460--- openerp/tools/__init__.py 2013-02-12 14:24:10 +0000
461+++ openerp/tools/__init__.py 2014-02-27 16:58:29 +0000
462@@ -21,6 +21,7 @@
463
464 import copy
465 import win32
466+import appdirs
467 from config import config
468 from misc import *
469 from convert import *
470
471=== added file 'openerp/tools/appdirs.py'
472--- openerp/tools/appdirs.py 1970-01-01 00:00:00 +0000
473+++ openerp/tools/appdirs.py 2014-02-27 16:58:29 +0000
474@@ -0,0 +1,477 @@
475+#!/usr/bin/env python
476+# -*- coding: utf-8 -*-
477+# Copyright (c) 2005-2010 ActiveState Software Inc.
478+# Copyright (c) 2013 Eddy Petrișor
479+
480+"""Utilities for determining application-specific dirs.
481+
482+See <http://github.com/ActiveState/appdirs> for details and usage.
483+"""
484+# Dev Notes:
485+# - MSDN on where to store app data files:
486+# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
487+# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
488+# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
489+
490+__version_info__ = (1, 3, 0)
491+__version__ = '.'.join(map(str, __version_info__))
492+
493+
494+import sys
495+import os
496+
497+PY3 = sys.version_info[0] == 3
498+
499+if PY3:
500+ unicode = str
501+
502+
503+
504+def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
505+ r"""Return full path to the user-specific data dir for this application.
506+
507+ "appname" is the name of application.
508+ If None, just the system directory is returned.
509+ "appauthor" (only required and used on Windows) is the name of the
510+ appauthor or distributing body for this application. Typically
511+ it is the owning company name. This falls back to appname.
512+ "version" is an optional version path element to append to the
513+ path. You might want to use this if you want multiple versions
514+ of your app to be able to run independently. If used, this
515+ would typically be "<major>.<minor>".
516+ Only applied when appname is present.
517+ "roaming" (boolean, default False) can be set True to use the Windows
518+ roaming appdata directory. That means that for users on a Windows
519+ network setup for roaming profiles, this user data will be
520+ sync'd on login. See
521+ <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
522+ for a discussion of issues.
523+
524+ Typical user data directories are:
525+ Mac OS X: ~/Library/Application Support/<AppName>
526+ Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
527+ Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
528+ Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
529+ Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
530+ Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
531+
532+ For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
533+ That means, by deafult "~/.local/share/<AppName>".
534+ """
535+ if sys.platform == "win32":
536+ if appauthor is None:
537+ appauthor = appname
538+ const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
539+ path = os.path.normpath(_get_win_folder(const))
540+ if appname:
541+ path = os.path.join(path, appauthor, appname)
542+ elif sys.platform == 'darwin':
543+ path = os.path.expanduser('~/Library/Application Support/')
544+ if appname:
545+ path = os.path.join(path, appname)
546+ else:
547+ path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
548+ if appname:
549+ path = os.path.join(path, appname)
550+ if appname and version:
551+ path = os.path.join(path, version)
552+ return path
553+
554+
555+def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
556+ """Return full path to the user-shared data dir for this application.
557+
558+ "appname" is the name of application.
559+ If None, just the system directory is returned.
560+ "appauthor" (only required and used on Windows) is the name of the
561+ appauthor or distributing body for this application. Typically
562+ it is the owning company name. This falls back to appname.
563+ "version" is an optional version path element to append to the
564+ path. You might want to use this if you want multiple versions
565+ of your app to be able to run independently. If used, this
566+ would typically be "<major>.<minor>".
567+ Only applied when appname is present.
568+ "multipath" is an optional parameter only applicable to *nix
569+ which indicates that the entire list of data dirs should be
570+ returned. By default, the first item from XDG_DATA_DIRS is
571+ returned, or '/usr/local/share/<AppName>',
572+ if XDG_DATA_DIRS is not set
573+
574+ Typical user data directories are:
575+ Mac OS X: /Library/Application Support/<AppName>
576+ Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
577+ Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
578+ Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
579+ Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
580+
581+ For Unix, this is using the $XDG_DATA_DIRS[0] default.
582+
583+ WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
584+ """
585+ if sys.platform == "win32":
586+ if appauthor is None:
587+ appauthor = appname
588+ path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
589+ if appname:
590+ path = os.path.join(path, appauthor, appname)
591+ elif sys.platform == 'darwin':
592+ path = os.path.expanduser('/Library/Application Support')
593+ if appname:
594+ path = os.path.join(path, appname)
595+ else:
596+ # XDG default for $XDG_DATA_DIRS
597+ # only first, if multipath is False
598+ path = os.getenv('XDG_DATA_DIRS',
599+ os.pathsep.join(['/usr/local/share', '/usr/share']))
600+ pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ]
601+ if appname:
602+ if version:
603+ appname = os.path.join(appname, version)
604+ pathlist = [ os.sep.join([x, appname]) for x in pathlist ]
605+
606+ if multipath:
607+ path = os.pathsep.join(pathlist)
608+ else:
609+ path = pathlist[0]
610+ return path
611+
612+ if appname and version:
613+ path = os.path.join(path, version)
614+ return path
615+
616+
617+def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
618+ r"""Return full path to the user-specific config dir for this application.
619+
620+ "appname" is the name of application.
621+ If None, just the system directory is returned.
622+ "appauthor" (only required and used on Windows) is the name of the
623+ appauthor or distributing body for this application. Typically
624+ it is the owning company name. This falls back to appname.
625+ "version" is an optional version path element to append to the
626+ path. You might want to use this if you want multiple versions
627+ of your app to be able to run independently. If used, this
628+ would typically be "<major>.<minor>".
629+ Only applied when appname is present.
630+ "roaming" (boolean, default False) can be set True to use the Windows
631+ roaming appdata directory. That means that for users on a Windows
632+ network setup for roaming profiles, this user data will be
633+ sync'd on login. See
634+ <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
635+ for a discussion of issues.
636+
637+ Typical user data directories are:
638+ Mac OS X: same as user_data_dir
639+ Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
640+ Win *: same as user_data_dir
641+
642+ For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
643+ That means, by deafult "~/.local/share/<AppName>".
644+ """
645+ if sys.platform in [ "win32", "darwin" ]:
646+ path = user_data_dir(appname, appauthor, None, roaming)
647+ else:
648+ path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
649+ if appname:
650+ path = os.path.join(path, appname)
651+ if appname and version:
652+ path = os.path.join(path, version)
653+ return path
654+
655+
656+def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
657+ """Return full path to the user-shared data dir for this application.
658+
659+ "appname" is the name of application.
660+ If None, just the system directory is returned.
661+ "appauthor" (only required and used on Windows) is the name of the
662+ appauthor or distributing body for this application. Typically
663+ it is the owning company name. This falls back to appname.
664+ "version" is an optional version path element to append to the
665+ path. You might want to use this if you want multiple versions
666+ of your app to be able to run independently. If used, this
667+ would typically be "<major>.<minor>".
668+ Only applied when appname is present.
669+ "multipath" is an optional parameter only applicable to *nix
670+ which indicates that the entire list of config dirs should be
671+ returned. By default, the first item from XDG_CONFIG_DIRS is
672+ returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
673+
674+ Typical user data directories are:
675+ Mac OS X: same as site_data_dir
676+ Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
677+ $XDG_CONFIG_DIRS
678+ Win *: same as site_data_dir
679+ Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
680+
681+ For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
682+
683+ WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
684+ """
685+ if sys.platform in [ "win32", "darwin" ]:
686+ path = site_data_dir(appname, appauthor)
687+ if appname and version:
688+ path = os.path.join(path, version)
689+ else:
690+ # XDG default for $XDG_CONFIG_DIRS
691+ # only first, if multipath is False
692+ path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
693+ pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ]
694+ if appname:
695+ if version:
696+ appname = os.path.join(appname, version)
697+ pathlist = [ os.sep.join([x, appname]) for x in pathlist ]
698+
699+ if multipath:
700+ path = os.pathsep.join(pathlist)
701+ else:
702+ path = pathlist[0]
703+ return path
704+
705+def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
706+ r"""Return full path to the user-specific cache dir for this application.
707+
708+ "appname" is the name of application.
709+ If None, just the system directory is returned.
710+ "appauthor" (only required and used on Windows) is the name of the
711+ appauthor or distributing body for this application. Typically
712+ it is the owning company name. This falls back to appname.
713+ "version" is an optional version path element to append to the
714+ path. You might want to use this if you want multiple versions
715+ of your app to be able to run independently. If used, this
716+ would typically be "<major>.<minor>".
717+ Only applied when appname is present.
718+ "opinion" (boolean) can be False to disable the appending of
719+ "Cache" to the base app data dir for Windows. See
720+ discussion below.
721+
722+ Typical user cache directories are:
723+ Mac OS X: ~/Library/Caches/<AppName>
724+ Unix: ~/.cache/<AppName> (XDG default)
725+ Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
726+ Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
727+
728+ On Windows the only suggestion in the MSDN docs is that local settings go in
729+ the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
730+ app data dir (the default returned by `user_data_dir` above). Apps typically
731+ put cache data somewhere *under* the given dir here. Some examples:
732+ ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
733+ ...\Acme\SuperApp\Cache\1.0
734+ OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
735+ This can be disabled with the `opinion=False` option.
736+ """
737+ if sys.platform == "win32":
738+ if appauthor is None:
739+ appauthor = appname
740+ path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
741+ if appname:
742+ path = os.path.join(path, appauthor, appname)
743+ if opinion:
744+ path = os.path.join(path, "Cache")
745+ elif sys.platform == 'darwin':
746+ path = os.path.expanduser('~/Library/Caches')
747+ if appname:
748+ path = os.path.join(path, appname)
749+ else:
750+ path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
751+ if appname:
752+ path = os.path.join(path, appname)
753+ if appname and version:
754+ path = os.path.join(path, version)
755+ return path
756+
757+def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
758+ r"""Return full path to the user-specific log dir for this application.
759+
760+ "appname" is the name of application.
761+ If None, just the system directory is returned.
762+ "appauthor" (only required and used on Windows) is the name of the
763+ appauthor or distributing body for this application. Typically
764+ it is the owning company name. This falls back to appname.
765+ "version" is an optional version path element to append to the
766+ path. You might want to use this if you want multiple versions
767+ of your app to be able to run independently. If used, this
768+ would typically be "<major>.<minor>".
769+ Only applied when appname is present.
770+ "opinion" (boolean) can be False to disable the appending of
771+ "Logs" to the base app data dir for Windows, and "log" to the
772+ base cache dir for Unix. See discussion below.
773+
774+ Typical user cache directories are:
775+ Mac OS X: ~/Library/Logs/<AppName>
776+ Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
777+ Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
778+ Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
779+
780+ On Windows the only suggestion in the MSDN docs is that local settings
781+ go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
782+ examples of what some windows apps use for a logs dir.)
783+
784+ OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
785+ value for Windows and appends "log" to the user cache dir for Unix.
786+ This can be disabled with the `opinion=False` option.
787+ """
788+ if sys.platform == "darwin":
789+ path = os.path.join(
790+ os.path.expanduser('~/Library/Logs'),
791+ appname)
792+ elif sys.platform == "win32":
793+ path = user_data_dir(appname, appauthor, version); version=False
794+ if opinion:
795+ path = os.path.join(path, "Logs")
796+ else:
797+ path = user_cache_dir(appname, appauthor, version); version=False
798+ if opinion:
799+ path = os.path.join(path, "log")
800+ if appname and version:
801+ path = os.path.join(path, version)
802+ return path
803+
804+
805+class AppDirs(object):
806+ """Convenience wrapper for getting application dirs."""
807+ def __init__(self, appname, appauthor=None, version=None,
808+ roaming=False, multipath=False):
809+ self.appname = appname
810+ self.appauthor = appauthor
811+ self.version = version
812+ self.roaming = roaming
813+ self.multipath = multipath
814+ @property
815+ def user_data_dir(self):
816+ return user_data_dir(self.appname, self.appauthor,
817+ version=self.version, roaming=self.roaming)
818+ @property
819+ def site_data_dir(self):
820+ return site_data_dir(self.appname, self.appauthor,
821+ version=self.version, multipath=self.multipath)
822+ @property
823+ def user_config_dir(self):
824+ return user_config_dir(self.appname, self.appauthor,
825+ version=self.version, roaming=self.roaming)
826+ @property
827+ def site_config_dir(self):
828+ return site_data_dir(self.appname, self.appauthor,
829+ version=self.version, multipath=self.multipath)
830+ @property
831+ def user_cache_dir(self):
832+ return user_cache_dir(self.appname, self.appauthor,
833+ version=self.version)
834+ @property
835+ def user_log_dir(self):
836+ return user_log_dir(self.appname, self.appauthor,
837+ version=self.version)
838+
839+
840+
841+
842+#---- internal support stuff
843+
844+def _get_win_folder_from_registry(csidl_name):
845+ """This is a fallback technique at best. I'm not sure if using the
846+ registry for this guarantees us the correct answer for all CSIDL_*
847+ names.
848+ """
849+ import _winreg
850+
851+ shell_folder_name = {
852+ "CSIDL_APPDATA": "AppData",
853+ "CSIDL_COMMON_APPDATA": "Common AppData",
854+ "CSIDL_LOCAL_APPDATA": "Local AppData",
855+ }[csidl_name]
856+
857+ key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
858+ r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
859+ dir, type = _winreg.QueryValueEx(key, shell_folder_name)
860+ return dir
861+
862+def _get_win_folder_with_pywin32(csidl_name):
863+ from win32com.shell import shellcon, shell
864+ dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
865+ # Try to make this a unicode path because SHGetFolderPath does
866+ # not return unicode strings when there is unicode data in the
867+ # path.
868+ try:
869+ dir = unicode(dir)
870+
871+ # Downgrade to short path name if have highbit chars. See
872+ # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
873+ has_high_char = False
874+ for c in dir:
875+ if ord(c) > 255:
876+ has_high_char = True
877+ break
878+ if has_high_char:
879+ try:
880+ import win32api
881+ dir = win32api.GetShortPathName(dir)
882+ except ImportError:
883+ pass
884+ except UnicodeError:
885+ pass
886+ return dir
887+
888+def _get_win_folder_with_ctypes(csidl_name):
889+ import ctypes
890+
891+ csidl_const = {
892+ "CSIDL_APPDATA": 26,
893+ "CSIDL_COMMON_APPDATA": 35,
894+ "CSIDL_LOCAL_APPDATA": 28,
895+ }[csidl_name]
896+
897+ buf = ctypes.create_unicode_buffer(1024)
898+ ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
899+
900+ # Downgrade to short path name if have highbit chars. See
901+ # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
902+ has_high_char = False
903+ for c in buf:
904+ if ord(c) > 255:
905+ has_high_char = True
906+ break
907+ if has_high_char:
908+ buf2 = ctypes.create_unicode_buffer(1024)
909+ if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
910+ buf = buf2
911+
912+ return buf.value
913+
914+if sys.platform == "win32":
915+ try:
916+ import win32com.shell
917+ _get_win_folder = _get_win_folder_with_pywin32
918+ except ImportError:
919+ try:
920+ import ctypes
921+ _get_win_folder = _get_win_folder_with_ctypes
922+ except ImportError:
923+ _get_win_folder = _get_win_folder_from_registry
924+
925+
926+
927+#---- self test code
928+
929+if __name__ == "__main__":
930+ appname = "MyApp"
931+ appauthor = "MyCompany"
932+
933+ props = ("user_data_dir", "site_data_dir",
934+ "user_config_dir", "site_config_dir",
935+ "user_cache_dir", "user_log_dir")
936+
937+ print("-- app dirs (with optional 'version')")
938+ dirs = AppDirs(appname, appauthor, version="1.0")
939+ for prop in props:
940+ print("%s: %s" % (prop, getattr(dirs, prop)))
941+
942+ print("\n-- app dirs (without optional 'version')")
943+ dirs = AppDirs(appname, appauthor)
944+ for prop in props:
945+ print("%s: %s" % (prop, getattr(dirs, prop)))
946+
947+ print("\n-- app dirs (without optional 'appauthor')")
948+ dirs = AppDirs(appname)
949+ for prop in props:
950+ print("%s: %s" % (prop, getattr(dirs, prop)))
951+
952
953=== modified file 'openerp/tools/config.py'
954--- openerp/tools/config.py 2014-02-12 22:52:40 +0000
955+++ openerp/tools/config.py 2014-02-27 16:58:29 +0000
956@@ -3,7 +3,7 @@
957 #
958 # OpenERP, Open Source Management Solution
959 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
960-# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
961+# Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>).
962 #
963 # This program is free software: you can redistribute it and/or modify
964 # it under the terms of the GNU Affero General Public License as
965@@ -29,6 +29,7 @@
966 import openerp.loglevels as loglevels
967 import logging
968 import openerp.release as release
969+import appdirs
970
971 class MyOption (optparse.Option, object):
972 """ optparse Option with two additional attributes.
973@@ -59,6 +60,9 @@
974
975 DEFAULT_LOG_HANDLER = [':INFO']
976
977+def _get_default_datadir():
978+ return appdirs.user_data_dir(appname='OpenERP', appauthor=release.author)
979+
980 class configmanager(object):
981 def __init__(self, fname=None):
982 # Options not exposed on the command line. Command line options will be added
983@@ -106,6 +110,9 @@
984 help="specify additional addons paths (separated by commas).",
985 action="callback", callback=self._check_addons_path, nargs=1, type="string")
986 group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules default=web")
987+
988+ group.add_option("-D", "--data-dir", dest="data_dir", my_default=_get_default_datadir(),
989+ help="Directory where to store OpenERP data")
990 parser.add_option_group(group)
991
992 # XML-RPC / HTTP
993@@ -348,6 +355,7 @@
994 # (../etc from the server)
995 # if the server is run by an unprivileged user, he has to specify location of a config file where he has the rights to write,
996 # else he won't be able to save the configurations, or even to start the server...
997+ # TODO use appdirs
998 if os.name == 'nt':
999 rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'openerp-server.conf')
1000 else:
1001@@ -358,7 +366,6 @@
1002 or os.environ.get('OPENERP_SERVER') or rcfilepath)
1003 self.load()
1004
1005-
1006 # Verify that we want to log or not, if not the output will go to stdout
1007 if self.options['logfile'] in ('None', 'False'):
1008 self.options['logfile'] = False
1009@@ -387,7 +394,6 @@
1010 elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
1011 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
1012
1013-
1014 if isinstance(self.options['log_handler'], basestring):
1015 self.options['log_handler'] = self.options['log_handler'].split(',')
1016
1017@@ -399,7 +405,8 @@
1018 'list_db', 'xmlrpcs', 'proxy_mode',
1019 'test_file', 'test_enable', 'test_commit', 'test_report_directory',
1020 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
1021- 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request', 'auto_reload'
1022+ 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request',
1023+ 'auto_reload', 'data_dir',
1024 ]
1025
1026 for arg in keys:
1027@@ -617,6 +624,24 @@
1028 def __getitem__(self, key):
1029 return self.options[key]
1030
1031+ @property
1032+ def addons_data_dir(self):
1033+ d = os.path.join(self['data_dir'], 'addons', release.series)
1034+ if not os.path.exists(d):
1035+ os.makedirs(d, 0700)
1036+ else:
1037+ os.chmod(d, 0700)
1038+ return d
1039+
1040+ @property
1041+ def session_dir(self):
1042+ d = os.path.join(self['data_dir'], 'sessions', release.series)
1043+ if not os.path.exists(d):
1044+ os.makedirs(d, 0700)
1045+ else:
1046+ os.chmod(d, 0700)
1047+ return d
1048+
1049 config = configmanager()
1050
1051
1052
1053=== modified file 'openerp/tools/translate.py'
1054--- openerp/tools/translate.py 2014-02-06 11:02:20 +0000
1055+++ openerp/tools/translate.py 2014-02-27 16:58:29 +0000
1056@@ -778,49 +778,32 @@
1057 if model_obj._sql_constraints:
1058 push_local_constraints(module, model_obj, 'sql_constraints')
1059
1060- def get_module_from_path(path, mod_paths=None):
1061- if not mod_paths:
1062- # First, construct a list of possible paths
1063- def_path = os.path.abspath(os.path.join(config.config['root_path'], 'addons')) # default addons path (base)
1064- ad_paths= map(lambda m: os.path.abspath(m.strip()),config.config['addons_path'].split(','))
1065- mod_paths=[def_path]
1066- for adp in ad_paths:
1067- mod_paths.append(adp)
1068- if not os.path.isabs(adp):
1069- mod_paths.append(adp)
1070- elif adp.startswith(def_path):
1071- mod_paths.append(adp[len(def_path)+1:])
1072- for mp in mod_paths:
1073- if path.startswith(mp) and (os.path.dirname(path) != mp):
1074- path = path[len(mp)+1:]
1075- return path.split(os.path.sep)[0]
1076- return 'base' # files that are not in a module are considered as being in 'base' module
1077
1078 modobj = registry['ir.module.module']
1079 installed_modids = modobj.search(cr, uid, [('state', '=', 'installed')])
1080 installed_modules = map(lambda m: m['name'], modobj.read(cr, uid, installed_modids, ['name']))
1081
1082- root_path = os.path.join(config.config['root_path'], 'addons')
1083-
1084- apaths = map(os.path.abspath, map(str.strip, config.config['addons_path'].split(',')))
1085- if root_path in apaths:
1086- path_list = apaths
1087- else :
1088- path_list = [root_path,] + apaths
1089-
1090+ path_list = list(openerp.modules.module.ad_paths)
1091 # Also scan these non-addon paths
1092 for bin_path in ['osv', 'report' ]:
1093 path_list.append(os.path.join(config.config['root_path'], bin_path))
1094
1095 _logger.debug("Scanning modules at paths: ", path_list)
1096
1097- mod_paths = []
1098+ mod_paths = list(path_list)
1099+
1100+ def get_module_from_path(path):
1101+ for mp in mod_paths:
1102+ if path.startswith(mp) and (os.path.dirname(path) != mp):
1103+ path = path[len(mp)+1:]
1104+ return path.split(os.path.sep)[0]
1105+ return 'base' # files that are not in a module are considered as being in 'base' module
1106
1107 def verified_module_filepaths(fname, path, root):
1108 fabsolutepath = join(root, fname)
1109 frelativepath = fabsolutepath[len(path):]
1110 display_path = "addons%s" % frelativepath
1111- module = get_module_from_path(fabsolutepath, mod_paths=mod_paths)
1112+ module = get_module_from_path(fabsolutepath)
1113 if ('all' in modules or module in modules) and module in installed_modules:
1114 return module, fabsolutepath, frelativepath, display_path
1115 return None, None, None, None