OpenERP 邮件发送问题修复 – 解决 Qq/163 等邮箱发送失败问题

openerp-mail-server-smtp-user

Fixed email from header.修复OpenERP 邮件头From 格式错误。

默认情况下,OpenERP 打包邮件时, 直接使用 email from 作为邮件头的 From 值。如qq/163等邮箱会严格校验email from 和 发件人帐号是否一致,如不一致则返回发送失败。如以下情况:

1
2
3
4
5
6
7
8
9
10
11
OE 默认情况
发件人为: admin@ex.com
发送账户为:11111@qq.com

OE 打包邮件内容为 email['From'] == 'admin@ex.com' (qq/163 校验失败)

安装本模块后:
发件人为: admin@ex.com
发送账户为:11111@qq.com

OE 打包邮件内容为 email['From'] == 'admin@ex.com <11111@qq.com> ' (qq/163 校验成功)

下载地址: https://github.com/buke/openerp-mail-server-smtp-user

0条评论

使用 Inotify 监视 Python 源码文件自动重新加载进程

在上一篇文章 http://buke.github.io/blog/2013/06/30/reload-the-process-when-source-chage-is-detected/ 中,实现了监视 py 文件并自动重启进程。

但核心实现是定时轮询获取文件的最新修改时间,效率低下而且很不 pythonic。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    while True:
        try:
            print_stdout(process)
            max_mtime = max(file_times(PATH,extension))
            if max_mtime > last_mtime:
                last_mtime = max_mtime
                print 'Restarting process...'
                stop_process(process)
                process = start_process(command)
            time.sleep(WAIT)
        except (KeyboardInterrupt, SystemExit):
                print "Caught KeyboardInterrupt, terminating process"
                stop_process(process)
                break

对于程序员来说,定时轮询基本是不能接受滴 ~

所幸的是,linxu 2.6 + 开始在内核提供 inotify,为监控文件变化提供了良好的事件支持。来自 wiki 的介绍:

inotify是Linux核心子系统之一,做为文件系统的附加功能,它可监控文件系统并将异动通知应用程序。本系统的出现取代了旧有Linux核心里,拥有类似功能之dnotify模块。

因此,https://github.com/buke/autoreload for linux 使用 pyinotify 模块加以改进。 完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
# AutoReload Process Using inofify
# wangbuke <wangbuke@gmail.com>
# taken from https://github.com/stevekrenzel/autoreload
#
# To use autoreload:
# 1. Make sure the script is executable by running chmod +x autoreload
# 2. Run ./autoreload <path> <ext1,ext2,extn> <cmd>
# e.g: $ ./autoreload '.' '.py,.xml,.conf' './openerp-server -c openerp-server.conf'
#
##############################################################################

import os
import signal
import subprocess
import sys
import time
import pyinotify
from pyinotify import log

class ReloadNotifier(pyinotify.Notifier):
    def loop(self, callback=None, daemonize=False, **args):
        if daemonize:
            self.__daemonize(**args)

        # Read and process events forever
        while 1:
            try:
                self._default_proc_fun._print_stdout()
                self.process_events()
                if (callback is not None) and (callback(self) is True):
                    break
                ref_time = time.time()
                # check_events is blocking
                if self.check_events():
                    self._sleep(ref_time)
                    self.read_events()
            except KeyboardInterrupt:
                # Stop monitoring if sigint is caught (Control-C).
                log.debug('Pyinotify stops monitoring.')
                self._default_proc_fun._stop_process()
                break
        # Close internals
        self.stop()

class OnChangeHandler(pyinotify.ProcessEvent):
    def my_init(self, cwd, extension, cmd):
        self.cwd = cwd
        self.extensions = extension.split(',')
        self.cmd = cmd
        self.process = None
        self._start_process()

    def _start_process(self):
        self.process = subprocess.Popen(self.cmd, shell=True, preexec_fn=os.setsid)

    def _stop_process(self):
        os.killpg(self.process.pid, signal.SIGTERM)
        self.process.wait()

    def _restart_process(self):
        print '==> Modification detected, restart process. <=='
        self._stop_process()
        self._start_process()

    def _print_stdout(self):
        stdout = self.process.stdout
        if stdout != None:
            print stdout

    def process_IN_CREATE(self, event):
        if any(event.pathname.endswith(ext) for ext in self.extensions) or "IN_ISDIR" in event.maskname:
            self._restart_process()

    def process_IN_DELETE(self, event):
        if any(event.pathname.endswith(ext) for ext in self.extensions) or "IN_ISDIR" in event.maskname:
            self._restart_process()

    def process_IN_CLOSE_WRITE(self, event):
        if any(event.pathname.endswith(ext) for ext in self.extensions) or "IN_ISDIR" in event.maskname:
            self._restart_process()

    def process_IN_MOVED_FROM(self, event):
        if any(event.pathname.endswith(ext) for ext in self.extensions) or "IN_ISDIR" in event.maskname:
            self._restart_process()

    def process_IN_MOVED_TO(self, event):
        if any(event.pathname.endswith(ext) for ext in self.extensions) or "IN_ISDIR" in event.maskname:
            self._restart_process()

def autoreload(path, extension, cmd):
    wm = pyinotify.WatchManager()
    handler = OnChangeHandler(cwd=path, extension=extension, cmd=cmd)
    notifier = ReloadNotifier(wm, default_proc_fun=handler)

    mask = pyinotify.IN_CLOSE_WRITE | pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO | pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_DELETE_SELF | pyinotify.IN_MOVE_SELF
    wm.add_watch(path, mask, rec=True, auto_add=True)

    print '==> Start monitoring %s (type c^c to exit) <==' % path
    notifier.loop()

if __name__ == '__main__':
    path = sys.argv[1]
    extension = sys.argv[2]
    cmd = sys.argv[3]
    autoreload(path, extension, cmd)

用法: 1. $ chmod +x autoreload (修改为可执行文件)

  1. Run ./autoreload <ext1,ext2,extn> e.g: $ ./autoreload ‘.’ ‘.py,.xml,.conf’ ‘./openerp-server -c openerp-server.conf’

以上代码已开源并发布在https://github.com/buke/autoreload , 欢迎反馈意见和建议!

0条评论

修改py文件自动重启加载进程方法

一般来说我们的 Python 程序在运行时会自动编译为 .pyc 文件,这样可以加快 Python 程序的启动速度。但在开发调试的时,我们不得不频繁重启进程,让人不胜其烦。

秉承“不会偷懒的程序员不是好程序员”的原则,写了个 autoreload 脚本(兼容 linux/windows),项目开源地址 https://github.com/buke/autoreload

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#    AutoReload Process
#    wangbuke <wangbuke@gmail.com>
#    taken from https://github.com/stevekrenzel/autoreload
#    
#    To use autoreload:    
#    1. Make sure the script is executable by running chmod +x autoreload
#    2. Run ./autoreload <command to run and reload> 
#       e.g: $ ./autoreload openerp-server
#    
##############################################################################

import sys
import subprocess
import time
import os
import signal

PATH = '.'
WAIT = 1

def start_process(cmd):
    if os.name == 'nt':
        process = subprocess.Popen(command, shell=True)
    else:
        process = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid)
    return process

def stop_process(process):
    if os.name == 'nt':
        process.kill()
    else:
        os.killpg(process.pid, signal.SIGTERM) # Send the signal to all the process groups
    process.wait()

def file_filter(name):
    return (not name.startswith(".")) and (not name.endswith(".pyc"))  and (not name.endswith(".pyo"))and (not name.endswith(".swp"))

def file_times(path):
    for root, dirs, files in os.walk(path):
        for file in filter(file_filter, files):
            yield os.stat(os.path.join(root, file)).st_mtime

def print_stdout(process):
    stdout = process.stdout
    if stdout != None:
        print stdout

if __name__ == "__main__":

    command = ' '.join(sys.argv[1:])
    process = start_process(command)
    last_mtime = max(file_times(PATH))

    while True:
        try:
            print_stdout(process)
            max_mtime = max(file_times(PATH))
            if max_mtime > last_mtime:
                last_mtime = max_mtime
                print 'Restarting process...'
                stop_process(process)
                process = start_process(command)
            time.sleep(WAIT)
        except (KeyboardInterrupt, SystemExit):
                print "Caught KeyboardInterrupt, terminating process"
                stop_process(process)
                break

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

可配置参数: * PATH = ‘.’ #默认为 autoreload 文件所在的当前目录。 * WAIT = 1 # 默认扫描间隔,单位秒

用法: 1、修改该文件为可执行脚步 (windows 下执行 python autoreload)

1
$ chomd +x ./autorelaod

2、运行

1
$ ./autoreload "openerp-server -c openerp-server.conf"

项目开源地址 https://github.com/buke/autoreload , 如有意见或建议请到上面地址提交反馈。谢谢!

0条评论

复制 Virtualbox/vmware Linux虚拟机网络无法访问错误的解决方法

使用 VirtualBox/VMWare 复制 centos/redhat 或 debian/ubuntu 虚拟机时,如果你选择了初始化网卡MAC地址,复制之后往往会发现网络无法使用。

如执行以下命令则提示错误

1
2
# ifup eth0
Device eth0 does not seem to be present, delaying initialisation

原因是VirtualBox/VMWare 在复制时改变了网卡的MAC地址,和原虚拟机网卡eth0地址不符。

解决办法:

1、删除网络规则文件

1
# rm -f /etc/udev/rules.d/70-persistent-net.rules

2、修改 eth0 的配置文件 (debian/ubuntu 跳过)

1
2
# vim /etc/sysconfig/network-scripts/ifcfg-eth0
删除 HWADDR 和 UUID 行即可

3、重启

1
# reboot

完成!

0条评论

使用 Eclipse Pydev 调试 GreenOpenERP for Windows

关于 GreenOpenERP

OpenERP 绿色版 For Windows,源码运行,解压即用,集成 python/postgresql/openerp

相关介绍和下载,请看 http://buke.github.io/blog/2013/03/10/greenopenerp-portable-openerp-for-windows/

关于 Eclipse 和 Pydev

Eclipse, 请到http://www.eclipse.org/ 下载安装。

Pydev, 请到http://pydev.org/ 下载安装。(推荐使用eclipse 在线安装 pydev,打开eclipse 后,点击菜单 “help” -> “Install New Software”, 输入 “http://pydev.org/updates” 刷新后选择 pydev 安装即可。)

源码调试 GreenOpenERP

此处假设您已经安装好 Eclipse 和 Pydev,并可以正确运行。

1、下载 GreenOpenERP

http://sourceforge.net/projects/greenopenerp/files/ 下载并解压 GreenOpenERP-7.0-xxxxxxxx-xxxxxx

2、配置 pydev 的 python interpreters

打开 eclipse , 选择菜单 “Window” -> “Preferences”

点击 pydev,选择 Interpreter - Python , 如有旧的Interpreter 请先删除。点击 New ,在弹出的窗口中 Browse 到下载解压的 GreenOpenERP-7.0-xxxxxxxx-xxxxxx\python\python.exe

选择 Select All, 然后点击 OK

回到刚才窗口, python interpreters 的相关参数应已配好。点击 Apply 后,点 Ok

3、选择 pydev 开发界面。

Eclipse 默认是 java 开发界面,点击右上角的小图表,在弹出的窗口选择 pydev

4、导入 GreenOpenERP Project

在 Package Explorer 界面内点击右键,在弹出的菜单里选择 Import

选择 Existing Project into Workspace 后,点 Next

点击 Browse 后,选择解压后的 GreenOpenERP 文件夹, 随后点击 Finish

5、启动 Postgresql

运行 GreenOpenERP 文件夹的 start-pg.bat 启动 pg 数据库。(关闭pg 数据库则运行 stop-pg.bat)

6、启动调试

在相应文件设置好断点后(也可不设置断点),在 eclipse 的 Package Explorer 界面右键点击 openerp-server, 在弹出菜单选择 Debug As -> Python Run

7、完成

送断点调试界面图一张

8、注意事项

  • 调试前需运行 start-pg.bat 启动数据库。
  • 如需关闭数据库,请运行 stop-pg.bat 关闭。切勿直接关闭cmd窗口。
  • 正常运行(非调试)和关闭 GreenOpenERP,请运行 start.bat 和 stop.bat .

Openerp 使用 Postgresql 存储过程和视图

OpenERP 使用 postgresql 存储过程和试图,步骤如下:

STEP1: 在模块的 init 函数中定义存储过程

1
2
3
4
5
6
7
8
9
10
11
12
    def init(self, cr):
        ''' create stored procedure '''
        cr.execute("""CREATE OR REPLACE FUNCTION fn_fi_report_childs(int)
        RETURNS TABLE(id int) AS $$
            WITH RECURSIVE t AS (
                SELECT id,parent_id  FROM fi_report WHERE id = $1
              UNION ALL
                SELECT fi_report.id, fi_report.parent_id FROM fi_report, t WHERE fi_report.parent_id = t.id
            )
            SELECT id FROM t;
        $$ LANGUAGE SQL
            """)

或者定义视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    def init(self, cr):
        tools.drop_view_if_exists(cr, 'analytic_entries_report')
        cr.execute("""
            create or replace view analytic_entries_report as (
                 select
                     min(a.id) as id,
                     count(distinct a.id) as nbr,
                     a.date as date,
                     to_char(a.date, 'YYYY') as year,
                     to_char(a.date, 'MM') as month,
                     to_char(a.date, 'YYYY-MM-DD') as day,
                     a.user_id as user_id,
                     a.name as name,
                     analytic.partner_id as partner_id,
                     a.company_id as company_id,
                     a.currency_id as currency_id,
                     a.account_id as account_id,
                     a.general_account_id as general_account_id,
                     a.journal_id as journal_id,
                     a.move_id as move_id,
                     a.product_id as product_id,
                     a.product_uom_id as product_uom_id,
                     sum(a.amount) as amount,
                     sum(a.unit_amount) as unit_amount
                 from
                     account_analytic_line a, account_analytic_account analytic
                 where analytic.id = a.account_id
                 group by
                     a.date, a.user_id,a.name,analytic.partner_id,a.company_id,a.currency_id,
                     a.account_id,a.general_account_id,a.journal_id,
                     a.move_id,a.product_id,a.product_uom_id
            )
        """)

STEP2: 在模块的函数中使用存储过程

1
2
    def get_amount(self,cr,uid,id,period_id,context=None):
        cr.execute('SELECT * FROM fn_fi_report_childs(%s)', (id,))

而视图的话,则如普通的表一样使用。

STEP3: 完成!

0条评论

GreenOpenERP – 绿色版 OpenERP for Windows

OpenERP 绿色版 For Windows,源码运行,解压即用,集成 python/postgresql/openerp。

软件版本

  • OpenERP (OpenERP 版本不定时更新, 看博主心情 :P )
  • Python=2.7.3
  • Postgresql=9.2.3

用法

下载解压ZIP文件,然后运行 *.bat 批处理文件即可。(注意文件路径:不能含有中文等非西文字符)

执行脚本

  • start.bat – 运行 Postgresql 和 Openerp
  • start-hide.bat – 隐藏命令行窗口,运行Postgresql 和 Openerp
  • stop.bat – 停止 Postgresql 和 Openerp
  • bin/pgAdmin3.exe – 运行 start.bat 之后,可以运行 pgAdmin 管理PG

下载地址

http://sourceforge.net/projects/greenopenerp/files/

作者: wangbuke wangbuke@gmail.com

博客: http://buke.github.com/

UPDATE

添加 6.1.1 版本下载

0条评论

OpenERP 模块动态加载原理及启动代码分析

一般来说我们在编程中,对象定义都是预先定义好的。一些 OOP 语言(包括 Python/Java)允许对象是 自省的(也称为 反射)。即,自省对象能够描述自己:实例属于哪个类?类有哪些祖先?对象可以用哪些方法和属性?自省让处理对象的函数或方法根据传递给函数或方法的对象类型来做决定。即允许对象在运行时动态改变方法成员等属性。

得益于OpenERP ORM 模型的精巧设计,实际上 OpenERP 运行时也是动态读取模块信息并动态构建对象的。如在模块开发中,继承了 ‘res.users’, 新增一个方法或新增一个字段。在OpenERP 导入该模块后, OpenERP 会马上重构 ‘res.users’ 对象并将新增的方法或字段添加到该对象。那么,OpenERP 是如何做到这点的呢? 让我们从OpenERP 的启动部分开始分析:

首先,OpenERP 启动相关的服务, 这时并没有建立数据库链接和载入对象

1
2
3
4
5
6
7
8
    if not config["stop_after_init"]:
        setup_pid_file()
        # Some module register themselves when they are loaded so we need the
        # services to be running before loading any registry.
        if config['workers']:
            openerp.service.start_services_workers()
        else:
            openerp.service.start_services()

不过可以在配置文件中指定 ‘db_name’ 参数,可以让 OpenERP 在启动时加载指定数据库的对象并启动 Cron。 实际生产环境中建议启用该参数,否则需要在启动OpenERP后,登录一次OpenERP 在会加载对象并启动CRON

1
2
3
    if config['db_name']:
        for dbname in config['db_name'].split(','):
            preload_registry(dbname)

不指定 ‘db_name’ 参数情况下,OpenERP 会直到用户登录时才会初始化指定数据库。

1
2
3
4
def login(db, login, password):
    pool = pooler.get_pool(db)
    user_obj = pool.get('res.users')
    return user_obj.login(db, login, password)

打开 get_db 和 get_db_and_pool 定义

1
2
3
4
5
6
7
8
9
def get_db(db_name):
    """Return a database connection. The corresponding registry is initialized."""
    return get_db_and_pool(db_name)[0]


def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False):
    """Create and return a database connection and a newly initialized registry."""
    registry = RegistryManager.get(db_name, force_demo, status, update_module)
    return registry.db, registry

顺藤摸瓜,看RegistryManager.new 定义

1
2
3
4
5
6
            try:
                # This should be a method on Registry
                openerp.modules.load_modules(registry.db, force_demo, status, update_module)
            except Exception:
                del cls.registries[db_name]
                raise

然后看到 load_modules, 终于要加载了,嘿嘿。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
        # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) 
        graph = openerp.modules.graph.Graph()
        graph.add_module(cr, 'base', force)
        if not graph:
            _logger.critical('module base cannot be loaded! (hint: verify addons-path)')
            raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))

        # processed_modules: for cleanup step after install
        # loaded_modules: to avoid double loading
        report = pool._assertion_report
        loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report)

        if tools.config['load_language']:
            for lang in tools.config['load_language'].split(','):
                tools.load_language(cr, lang)

        # STEP 2: Mark other modules to be loaded/updated
        if update_module:
            modobj = pool.get('ir.module.module')
            if ('base' in tools.config['init']) or ('base' in tools.config['update']):
                _logger.info('updating modules list')
                modobj.update_list(cr, SUPERUSER_ID)

            _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))

            mods = [k for k in tools.config['init'] if tools.config['init'][k]]
            if mods:
                ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
                if ids:
                    modobj.button_install(cr, SUPERUSER_ID, ids)

            mods = [k for k in tools.config['update'] if tools.config['update'][k]]
            if mods:
                ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
                if ids:
                    modobj.button_upgrade(cr, SUPERUSER_ID, ids)

            cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))


        # STEP 3: Load marked modules (skipping base which was done in STEP 1)
        # IMPORTANT: this is done in two parts, first loading all installed or
        #            partially installed modules (i.e. installed/to upgrade), to
        #            offer a consistent system to the second part: installing
        #            newly selected modules.
        #            We include the modules 'to remove' in the first step, because
        #            they are part of the "currently installed" modules. They will
        #            be dropped in STEP 6 later, before restarting the loading
        #            process.
        states_to_load = ['installed', 'to upgrade', 'to remove']
        processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
        processed_modules.extend(processed)
        if update_module:
            states_to_load = ['to install']
            processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
            processed_modules.extend(processed)

这里,第一步是加载核心模块 [‘base’],第二步加载需要升级或预载的模块,第三步加载已安装的模块。实际加载语句是:

1
        processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)

查看 load_marked_module 定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks):
    """Loads modules marked with ``states``, adding them to ``graph`` and
       ``loaded_modules`` and returns a list of installed/upgraded modules."""
    processed_modules = []
    while True:
        cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
        module_list = [name for (name,) in cr.fetchall() if name not in graph]
        graph.add_modules(cr, module_list, force)
        _logger.debug('Updating graph with %d more modules', len(module_list))
        loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks)
        processed_modules.extend(processed)
        loaded_modules.extend(loaded)
        if not processed: break
    return processed_modules

重点是 load_module_graph

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    # register, instantiate and initialize models for each modules
    for index, package in enumerate(graph):
        module_name = package.name
        module_id = package.id

        if skip_modules and module_name in skip_modules:
            continue

        _logger.debug('module %s: loading objects', package.name)
        migrations.migrate_module(package, 'pre')
        load_openerp_module(package.name)

        models = pool.load(cr, package)

        loaded_modules.append(package.name)
        if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
            init_module_models(cr, package.name, models)
        pool._init_modules.add(package.name)
        status['progress'] = float(index) / len(graph)

主要是下面2句,

1
2
3
        load_openerp_module(package.name)

        models = pool.load(cr, package)

先看 load_openerp_module

1
2
3
4
5
6
7
8
9
    initialize_sys_path()
    try:
        mod_path = get_module_path(module_name)
        zip_mod_path = '' if not mod_path else mod_path + '.zip'
        if not os.path.isfile(zip_mod_path):
            __import__('openerp.addons.' + module_name)
        else:
            zimp = zipimport.zipimporter(zip_mod_path)
            zimp.load_module(module_name)

上面代码中 import 了一个模块。如果您看过 digitalsatori 校长的大作 OpenERP与Python 元编程, 下面就涉及到元类了:

import 的时候 就会调用元类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    def __init__(self, name, bases, attrs):
        if not self._register:
            self._register = True
            super(MetaModel, self).__init__(name, bases, attrs)
            return

        # The (OpenERP) module name can be in the `openerp.addons` namespace
        # or not. For instance module `sale` can be imported as
        # `openerp.addons.sale` (the good way) or `sale` (for backward
        # compatibility).
        module_parts = self.__module__.split('.')
        if len(module_parts) > 2 and module_parts[0] == 'openerp' and \
            module_parts[1] == 'addons':
            module_name = self.__module__.split('.')[2]
        else:
            module_name = self.__module__.split('.')[0]
        if not hasattr(self, '_module'):
            self._module = module_name

        # Remember which models to instanciate for this module.
        if not self._custom:
            self.module_to_models.setdefault(self._module, []).append(self)

上面的代码基本上就是将自身类加入到 module_to_models 字典中。

然后我们来看pool.load

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    def load(self, cr, module):
        """ Load a given module in the registry.

        At the Python level, the modules are already loaded, but not yet on a
        per-registry level. This method populates a registry with the given
        modules, i.e. it instanciates all the classes of a the given module
        and registers them in the registry.

        """
        models_to_load = [] # need to preserve loading order
        # Instantiate registered classes (via the MetaModel automatic discovery
        # or via explicit constructor call), and add them to the pool.
        for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):
            # models register themselves in self.models
            model = cls.create_instance(self, cr)
            if model._name not in models_to_load:
                # avoid double-loading models whose declaration is split
                models_to_load.append(model._name)
        return [self.models[m] for m in models_to_load]

这里我们可以看到 MetaModel 的身影,cls.create_instance(self, cr) 这里就是动态构造对象的核心代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
        parent_names = getattr(cls, '_inherit', None)
        # 判断是否有继承父类 
        if parent_names:
            if isinstance(parent_names, (str, unicode)):
                name = cls._name or parent_names
                parent_names = [parent_names]
            else:
                name = cls._name
            if not name:
                raise TypeError('_name is mandatory in case of multiple inheritance')

            for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
                # 读取父类
                parent_model = pool.get(parent_name)
                if not parent_model:
                    raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
                        'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
                if not getattr(cls, '_original_module', None) and name == parent_model._name:
                    cls._original_module = parent_model._original_module
                parent_class = parent_model.__class__
                nattr = {}
                # 复制父类属性
                for s in attributes:
                    new = copy.copy(getattr(parent_model, s, {}))
                    if s == '_columns':
                        # Don't _inherit custom fields.
                        for c in new.keys():
                            if new[c].manual:
                                del new[c]
                        # Duplicate float fields because they have a .digits
                        # cache (which must be per-registry, not server-wide).
                        for c in new.keys():
                            if new[c]._type == 'float':
                                new[c] = copy.copy(new[c])
                    if hasattr(new, 'update'):
                        new.update(cls.__dict__.get(s, {}))
                    elif s=='_constraints':
                        for c in cls.__dict__.get(s, []):
                            exist = False
                            for c2 in range(len(new)):
                                #For _constraints, we should check field and methods as well
                                if new[c2][2]==c[2] and (new[c2][0] == c[0] \
                                        or getattr(new[c2][0],'__name__', True) == \
                                            getattr(c[0],'__name__', False)):
                                    # If new class defines a constraint with
                                    # same function name, we let it override
                                    # the old one.

                                    new[c2] = c
                                    exist = True
                                    break
                            if not exist:
                                new.append(c)
                    else:
                        new.extend(cls.__dict__.get(s, []))
                    nattr[s] = new

                # Keep links to non-inherited constraints, e.g. useful when exporting translations
                nattr['_local_constraints'] = cls.__dict__.get('_constraints', [])
                nattr['_local_sql_constraints'] = cls.__dict__.get('_sql_constraints', [])
                # 调用元类构造函数
                cls = type(name, (cls, parent_class), dict(nattr, _register=False))
        else:
            cls._local_constraints = getattr(cls, '_constraints', [])
            cls._local_sql_constraints = getattr(cls, '_sql_constraints', [])

        if not getattr(cls, '_original_module', None):
            cls._original_module = cls._module
        # 构造对象
        obj = object.__new__(cls)
        # 初始化对象
        obj.__init__(pool, cr)
        return obj

上面代码很重要,可以看到首先是判断该对象是否有继承父类,如果没有就直接构造,和动态没有什么关系。

如果有继承父类, 就复制父类属性, 这里就是动态构建类的做法。

假如有不同模块,都继承了同一个父类,那么如何保证类成员和属性是否加载完整或覆盖呢? 答案在于这句代码:

1
                parent_model = pool.get(parent_name)

Registry.get 的定义

1
2
3
    def get(self, model_name):
        """ Return a model for a given name or None if it doesn't exist."""
        return self.models.get(model_name)

最后看看obj.init(pool, cr)初始化对象,做了什么动作?

1
2
3
4
5
6
7
8
9
10
11
12
    def __init__(self, pool, cr):
        """ Initialize a model and make it part of the given registry.

        - copy the stored fields' functions in the osv_pool,
        - update the _columns with the fields found in ir_model_fields,
        - ensure there is a many2one for each _inherits'd parent,
        - update the children's _columns,
        - give a chance to each field to initialize itself.

        """
        pool.add(self._name, self)
        self.pool = pool

pool.add(self._name, self) 定义如下:

1
2
3
    def add(self, model_name, model):
        """ Add or replace a model in the registry."""
        self.models[model_name] = model

到这里应该很非常清楚,Registry.models 保存了对象的 model 信息。这样多个对象继承同一父类时,按照加载顺序先后动态构建相关的类。

至此,OpenERP 启动时动态加载模块分析完成。如模块安装、升级、卸载等, 则是通过 signal_registry_change 和 check_registry_signaling 处理,重新载入 Registry, 然后重新构建 OpenERP 对象。

0条评论

Openerp Web PDF Report Preview & Print 模块升级至 7.0

Openerp Web PDF Report Preview & Print

下载地址: https://github.com/buke/openerp-web-pdf-preview-print

简介:

将OpenERP 的PDF报表打印下载功能,改为直接在浏览器中预览打印。

  • For IE, 需要安装 Adobe Reader。
  • For Firefox 19 + , 神马都不用安装。
  • For Chrome, 神马都不用安装。

以上在windows 上测试通过。如果浏览器阻止了弹出窗口,请点允许弹出窗口。

系统要求:

  • OpenERP 7.0

注:

  1. openerp-7.0-20130222-002152 下测试通过
  2. 之前版本可能会出现以下错误:

TypeError: this.get_action_manager(…) is undefined on Firefox

TypeError: Cannot call method ‘get_title’ of undefined on Chrome / IE

解决办法有2种

F5 刷新页面重新加载 或者 升级OpenERP 版本

0条评论

Tryton 相关软件简介及名字背后的神话

Tryton 是一个MVC三层架构、高性能的通用应用ERP平台,基于 GPL-3 协议发布。其主要特点是模块化设计、可扩展性和安全。2008 年,Tryton 从 TinyERP 4.2 分支fork,并发布了1.0版本。不过到现在为止,Tryton 基本上重构了全部底层代码。到2012年9月,Tryton 已推出 2.6 版本。

Tryton的名字来源于Triton,传说中Triton是希腊神话中海之信使,海王波塞冬和海后安菲特里忒的儿子。另外海王星的命名体系也是来源于希腊海神,Triton 就是海卫一的名字。有意思的是 Tryton 相关的软件,大多也是希腊海中诸神命名。

下面介绍下 Tryton 相关软件和名字来源:

1、Tryton

简介:gtk 客户端

名字来源:Triton ,希腊神话中海之信使,海王波塞冬和海后安菲特里忒的儿子。和他老爸一样,他也带着三叉戟,此外他还带着一个海螺号角,传说大海中可怕的声音就是这个号角发出。

同时也是海卫一的名字。

2、Trytond

简介:Tryton 后台守护进程,Trytond 是 Tryton daemon 的缩写。

3、trytond_xxx-2.6.0

简介:trytond 模块,XXX 是模块名字,2.6.0 是版本号

4、Neso

简介:tryton 可独立运行的客户端+服务端。windows 下使用sqlite 数据库,在单用户情况下使用。

名字来源:海洋仙女涅瑞伊得斯Nereides之一,仙女涅索Neso一名意为【住在岛上】,源自古希腊语的nesos‘岛屿’。如印度尼西亚Indonesia则意为【印度群岛】。

同时也是海卫十三的名字。

5、Proteus

简介:tryton 客户端API库,可以像类库一样读取 tryton server。

名字来源:Proteus 海神波塞冬之子,他负责放牧Poseidon的一群海豹。相比而言,Proteus是海神中最善于变幻的一位,他曾经被从特洛伊战场返航的Menelaus紧紧抓住,为了摆脱这个人,Proteus变成了狮子、虎豹、野猪、流水、熊熊烈火和风等。

同时也是海卫八的名字。

6、SAO

简介:tryton web 客户端(尚在开发中 http://hg.tryton.org/sandbox/sao)。

名字来源:海洋仙女涅瑞伊得斯Nereides之一,仙女 Sao 被认为是拯救水手,保障水手安全的海中仙女。 同时也是海卫十一的名字。

7、Nereid

简介:整合Flask 的web 框架,使用tryton 为后台。由 openlabs.co.in 开发。

名字来源:海洋仙女涅瑞伊德Nereides,Nereid为其单数,Nereides为海神涅柔斯Nereus与大洋神女多里斯Doris的五十个女儿。 同时也是海卫二的名字。

下一个是谁?

海王星 Neptunus (Neptunus 是罗马神话中 波塞冬 的名字) 海王星有13颗已知的天然卫星

海卫一 Triton

海卫二 Nereid

海卫三 Naiad

海卫四 Thalassa

海卫五 Despina

海卫六 Galatea

海卫七 Larissa

海卫八 Proteus

海卫九 Halimede

海卫十 Psamathe

海卫十一 Sao

海卫十二 Laomedeia

海卫十三 Neso

目前为止,Tryton 的相关软件都以海王星的卫星命名,猜猜看下一个是那个?

参考资料: 海王星的卫星 http://zh.wikipedia.org/wiki/%E6%B5%B7%E7%8E%8B%E6%98%9F%E7%9A%84%E5%8D%AB%E6%98%9F

果壳 稻草人语

http://www.guokr.com/blog/431784/

http://www.guokr.com/blog/380892/

http://www.guokr.com/blog/101070/

0条评论