Odoo的定义了自己的一套ORM系统。其中的一个重要组成部分就是字段的处理。这部分的内容都在odoo.field中。和其他模块一样,也大量使用了python的特殊语法,如:元类、__slots__、特殊函数,等。
元类:odoo.fields.MetaField 所有的字段类型的元类。如果一个字段类型使用了这个类,那么在创建这个类型时元类会扫描类中是否有_slots属性。如果有的话,会将_slots中的东西放到__slots__中。然后在初始化这个类型的时候,会把这个新创建的类型放在MetaField的by_type字典中。
注:__slots__的作用是用来存放类实例中的属性。默认,python中的实例是存放在__dict__中的;如果声明了__slots__就不会创建__dict__;__slots__应该比 __dict__节省空间。
class MetaField(type): """ Metaclass for field classes. """ by_type = {} def __new__(meta, name, bases, attrs): """ Combine the ``_slots`` dict from parent classes, and determine ``__slots__`` for them on the new class. """ base_slots = {} for base in reversed(bases): base_slots.update(getattr(base, '_slots', ())) slots = dict(base_slots) slots.update(attrs.get('_slots', ())) attrs['__slots__'] = set(slots) - set(base_slots) attrs['_slots'] = slots return type.__new__(meta, name, bases, attrs) def __init__(cls, name, bases, attrs): super(MetaField, cls).
model是odoo中最重要的部分之一。主要负责各种功能的实现,crm之类的业务模块中的功能姑且不论,页面渲染、工作流引擎、定时任务等核心的功能,也都是基于模型来实现。
元类的基础:odoo.api.Meta 检查要创建的类中的所有函数,然后根据各函数_api属性进行特殊处理。处理完成后再创建该类型。
class Meta(type): """ Metaclass that automatically decorates traditional-style methods by guessing their API. It also implements the inheritance of the :func:`returns` decorators. """ def __new__(meta, name, bases, attrs): # dummy parent class to catch overridden methods decorated with 'returns' parent = type.__new__(meta, name, bases, {}) for key, value in attrs.items(): if not key.startswith('__') and callable(value): # make the method inherit from decorators value = propagate(getattr(parent, key, None), value) # guess calling convention if none is given if not hasattr(value, '_api'): try: value = guess(value) except TypeError: pass if (getattr(value, '_api', None) or '').
Web请求 web请求的包装是在接收请求时,在odoo.http.Root中处理的。请求主要分为
json请求:主要用来处理json请求、rpc请求。 http请求:主要用来处理页面访问请求。 请求的基类:odoo.http.WebRequest 所有请求的基类,定义了请求处理过程中都可能会用到的一些属性:如csrf、db、registry、session等等。
同时WebQuest还使用了__enter__、__exit__。这样当使用with request:这样当表达式时,会将当前request放到werkzeug.local.LocalStack中。方便从任何地方使用odoo.http.request获取当前请求。
具体处理请求的endpoint是通过set_handler传入的。_call_function会调用endpoint来获得返回结果。但是调用_call_function的dispath是由子类来实现的,基类中没有。
_request_stack = werkzeug.local.LocalStack() request = _request_stack() class WebRequest(object): ... @property def registry(self): return odoo.registry(self.db) if self.db else None @property def db(self): return self.session.db if not self.disable_db else None def csrf_token(self, time_limit=3600): ... ... def _call_function(self, *args, **kwargs): """ Generates and returns a CSRF token for the current session ... def validate_csrf(self, csrf): ... def __enter__(self): _request_stack.push(self) return self def __exit__(self, exc_type, exc_value, traceback): _request_stack.
上一篇odoo的模块管理,主要是odoo的模块加载。这一篇来看看模块到底是什么。
抄袭官网文档:
Both server and client extensions are packaged as modules which are optionally loaded in a database. Odoo modules can either add brand new business logic to an Odoo system, or alter and extend existing business logic: a module can be created to add your country's accounting rules to Odoo's generic accounting support, while the next module adds support for real-time visualisation of a bus fleet. Everything in Odoo thus starts and ends with modules.
之前一篇记录了odoo web server的大概情况,以及简单的启动流程、模块加载的情况。深入研究会发现odoo的所有功能都是基于模块制作的,所以本篇开始研究odoo的模块。首先研究下模块是如何进行管理的。负责管理模块的代码主要存放在odoo.modules包里。
模型的注册机 odoo.modules.registry.Registry Registry的用途是存放模型名、模型类对应关系。是一个深度定制化的类。注意,model、module的区别。
此类继承了collections.Mapping,因此它的对像可以按照字典方式来使用 这个类也是一个自产自销的类:创建自己的实例,然后将实例放到自己的类属性中。 类函数/属性registries用来存放已创建的Registry。 装饰器lazy_classproperty是一个神器的东西,它把一个函数变成了一个属性:当函数第一次执行时,获得函数的返回值,然后将返回值设置为类中的一个属性(注意那个setattr函数)。__get__方法会在Registry.registry的时候执行。
class lazy_property(object): def __init__(self, fget): self.fget = fget class lazy_classproperty(lazy_property): def __get__(self, obj, cls): val = self.fget(cls) setattr(cls, self.fget.__name__, val) return val 使用__new__而不是__init__来创建对象:首先尝试从registries中获取已经生成的对象,失败后创建新对象。之前的python的数据结构中曾经探讨过__init__和__new__的区别。
Registry对象在new()里生成:手动生成、手动初始化、存放到registries中、加载所有的模块到self.models字典中(odoo.modules.load_modules函数)。在加载模块的过程中,还需要模块中导出模型(load函数)、完善模型(setup_models),根据模型建表、建约束(init_models)。
注意LRU是Least Recently Used 近期最少使用算法。这里是一个python实现的功能模块。内部是一个字典。
class Registry(Mapping): @lazy_classproperty def registries(cls): """ A mapping from database names to registries. """ size = config.get('registry_lru_size', None) ... return LRU(size) def __new__(cls, db_name): """ Return the registry for the given database name.
odoo的web服务器实现都在一个包里odoo.service.server。为了提升服务器的性能,提供了3种不同的web服务器,分别使用了thread、gevent、process。而在web服务器里,odoo使用werkzeug这套wsgi库,实现端口监听、请求处理等。
服务器的启动 在odoo.service.server包中,入口函数是位于文件末尾的start函数,它的行为如下:
定义全局变量server 加载所谓的server wide module(默认的全局模块只有web,web_kanban) 根据配置创建不同的server 创建文件监视(为了实现模块的动态加载) 启动server 从中可以看到具体处理请求的程序是odoo.service.wsgi_server.application,而默认的server是基于线程的。
def start(preload=None, stop=False): """ Start the odoo http server and cron processor. """ global server load_server_wide_modules() if odoo.evented: server = GeventServer(odoo.service.wsgi_server.application) elif config['workers']: server = PreforkServer(odoo.service.wsgi_server.application) else: server = ThreadedServer(odoo.service.wsgi_server.application) watcher = None if 'reload' in config['dev_mode']: if watchdog: watcher = FSWatcher() watcher.start() else: _logger.warning("'watchdog' module not installed. Code autoreload feature is disabled") if 'werkzeug' in config['dev_mode']: server.
Odoo命令都在odoo.cli包中
元类:CommandType commands = {} class CommandType(type): def __init__(cls, name, bases, attrs): super(CommandType, cls).__init__(name, bases, attrs) name = getattr(cls, name, cls.__name__.lower()) cls.name = name if name != 'command': commands[name] = cls 所有使用了这个元类的类,都会注册到包内的全局字典commands中:类名为key,类为value
所有命令的基类:Command class Command(object): """Subclass this class to define new odoo subcommands """ __metaclass__ = CommandType def run(self, args): pass 可以看到基类使用CommandType作为元类。这意味着所有的子类都会注册到全局字典中。
同时还定义了个函数run。这算是预定义了要实现功能的函数。
命令的实现 以下命令全部都是Command的子类,所以都使用了原类CommandType,并实现了父类的run函数。
odoo.cli.command.Help 根据其它命令中的内容,生成帮助信息,并输出。
odoo.cli.deploy.Deploy 将本地的一个模块部署到指定的服务器上 需要给定本地模块的地址,和远程服务器地址 会将本地模块压缩成zip,然后通过http登录服务器并上传
odoo.cli.scaffold.Scaffold 用来生成一个模块的骨架。主要为了方便做二次开发。
默认使用的模版文件就在odoo.cli包内的template目录里
odoo.cli.shell.Shell 启动odoo,然后使用ipython、ptpython、bpython等创建一个交互环境。可以查询运行中的odoo的一些信息。
个人认为,这个也是方便开发调试的一个工具
这篇纯粹吹水
odoo是一套开源的,由python实现的ERP系统。说是系统,更像是一个平台,甚至可以说是生态系统(就像 appstore)。
首先,它的功能是通过自由组合的各种插件来实现的。其自带的插件就由已经实现了以下功能:客户管理、财务管理、进销存管理、员工管理、审批流程定制,等等。基本上已经覆盖了大部分的企业需求(可能不一定符合特定用户的特定需求,但是它有)。
其次,它为开源社区提供了插件交易的一个市场。各种收费的免费的插件通过这个市场共享。大幅度的扩展了它的应用范围。受益于此,获得了大量的插件:有免费的、方便汇率更新的小型功能插件,也有收费的提供酒店管理的定制化插件。
最后,它还不定时的组织线上线下的活动,来进行进行客户培训、产品推广、技术交流(虽然都在国外)。
最后的最后,完全开源,方便定制化。
总之,个人感觉它可以算是中小企业的福音了。
然后最近准备开始深入研究下这个系统,就像之前研究openstack一样,从源码开始,这个总不会比openstack还复杂吧!!!
以下就直接抄它官网的文字介绍了。
我们认为商业软件应以简单的结构解决复杂的需求。我们的任务是提供直观、功能全面、紧密集成、升级无忧、面向每种业务、每一用户均可平稳运行的软件。 我们的目标是提供一系列易用业务应用程序,形成完整的一套工具,以满足任何业务需求。我们让数百万公司可轻松访问其运营和括大业务所需的软件。 在 Odoo,我们已开发了 30 种主要应用,均会定期更新。此外,我们的社区包括 1,500 多名活跃成员,已另外贡献了 4,500 多款应用,可涵盖大量业务需求。 Odoo 具有“预置型”产品,是全球安装最多的商业软件。从初创公司(1 名用户)到大型企业(300,000 多位用户),全球有 2,000,000 多名用户在使用这款软件。 Odoo 的开源模式让我们可利用无数开发人员和业务专家,在短短数年内,打造数百款应用。 具有强大的技术基础,Odoo 的结构非常独特。其具有 一流的可用性,堪比所有 app。 Odoo 所做的可用性改善会自动应用于我们充分集成的所有应用上。 采用这种方式,Odoo 比其他解决方案发展更快。
本文基于Python 3.5.2官方文档中数据模型部分完成
1.对象,值 和 类型 Python把数据抽象为Objects。Python程序中所有的数据都体现为对象,或者对象之间的关系。(从某种程度上讲,为了与冯.诺伊曼的存储程序型计算机模型相一致,代码同样也体现为对象。)
每个对象都有一个标识,一个类型和一个值。对象一旦创建,它的标识就不会变更;你可以理解为它就像对象在内存中的地址。is操作用来比较两个对象的标识;id()函数返回一个整数来表示它的标识。
CPython实现细节:id(x)返回x在内存的存放地址。
对象的类型决定了对象支持哪些操作(比如,这个对象有长度吗?),同时也定义了对象中可能存在哪些值。type()函数返回一个对象的类型(类型也是一个对象)。就像它的标识,一个对象的类型也是不可变的。
一些对象的值是可修改的。可以修改的的对象被称为可变的to be mutable;创建后不能修改的对象被称为不可变的immutable。(不可变容器对象的值如果是一个可变对象的引用,那么当后者的值发生改变的时候,前者的值也会改变;但是这个容器仍然被认为是不可变的,因为容器内部包含的对象都没有发生变化。所以,不可变性immutability并不意味着拥有一个不可修改的值unchangeable,这很微妙)。一个对象是否可变取决于它的类型;比如:数字、字符串、元组是不可变的,字典和数组是可变的。
对象不会明确的被销毁;只有当它们不可达的时候,它们才有可能被垃圾回收。只要可达的对象不要被回收掉,如何实现延迟垃圾回收,并统一释放他们,这就是垃圾回收的实现质量问题了。
CPython实现细节:CPython现在使用引用计数机制和(可选的)循环垃圾延迟检测机制,对象一旦不可达就尽量收集,但是无法保证能回收循环引用的垃圾。查看gc模块的文档,来了解更多关于如何回收循环引用垃圾的信息。其他的实现可能不一样,而且CPtyhon也可能会改变策略。不要指望对象不可达以后,就会马上会被回收掉(所以你总是应该明确的关闭文件)。
注意跟踪调试工具会让一些本该回收掉的对象始终可达。同时使用try...except处理异常也会使对象始终可达。
一些对象包含对”外部”资源的引用。我们知道当这些对象被垃圾回收后这些资源也会被释放,但是因为垃圾回收不能保证这些对像会被回收,所以这些对象会提供一个明确方式来释放资源,通常是close()函数。强烈建议在程序中明确的关闭这样的对象。可以用try...finally语句和with语句来方便的执行它。
一些对象包含对其他对象的应用;这些对象叫做容器containers。这样的例子有元组、数组和字段。应用是容器的值的一部分。大部分的情况下,当我们说起容器中的值,指的是值,而不是容器内对象的标识;而当我们说容器的可变性,指的是容器中当前对象的标识。所以,一个不可变容器(比如元组)包含一个可变对象的引用,它的值是可变的,当那个可变对象发生变更的时候。
类型几乎影响了对象的所有行为。在一些场景下甚至影响对象标识的重要性:对于不可变类型,计算新值的操作可能实际返回的是一个已存在的,拥有相同类型和值的对象的引用;而对于可变对象这是不允许的。比如,执行a=1; b=1,a 和 b 可能(也可能不是)指向同一个包含1的对象,取决于具体实现; 但是执行c=[];d[],c 和 d 保证指向两个完全不同的,独立的,新创建的空数组。(注意,c = d = [] 将同一个对象分配给了c 和 d 。)
2.标准类型层级 下边是Python中内建类型列表。扩展模块(由C,Java,或其他语言实现的)会定义附加的类型。未来版本的 Python 可能会在此类型层次中增加新的类型 (例如:有理数,高效存储的整数数组等),不过这些类型通常是在标准库中定义的。
以下个别类型描述中可能有介绍特殊属性的段落,它们是供实现访问的,不作为一般用途。这些定义在未来有可能发生改变:
None
这个类型只具有一个值,并且这种类型也只有一个对象,这个对象可以通过内建名字None访问,在许多场合里它表示无值,例如,没有显式返回值的函数会返回None。这个对象的真值为假。
NotImplemented
这个类型只具有一个值,并且这种类型也只有一个对象。这个对象可以通过内建名字NotImplemented访问。如果操作数没有对应实现,数值方法和复杂比较方法rich comparison method应该返回这个值 (解释器会尝试反射操作,或者其它操作,根据具体的操作)。它的真值为真。
Ellipsis
这个类型只具有一个值,并且这种类型也只有一个对象。这个对象可以通过字面值 … 或者内建名字 Ellipsis 访问。它的真值为真。
numbers.Number
它们由数值型字面值产生,或者是算术运算符和内建数学函数的返回值。数值型对象是不可变 的,即一旦创建,其值就不可改变。Python 数值型和数学上的数字关系当然是非常密切的,但也受到计算机数值表达能力的限制。
Python 区分整数,浮点数和复数:
numbers.Integral
描述了数学上的整数集 (正负数).
有两类整数:
Integers (int)
结合官方的文档,还有《The way to go》先来熟悉一下golang的代码结构。以下来自官方文档
Go programs are constructed by linking together packages. A package in turn is constructed from one or more source files that together declare constants, types, variables and functions belonging to the package and which are accessible in all files of the same package. Those elements may be exported and used in another package. Each source file consists of a package clause defining the package to which it belongs, followed by a possibly empty set of import declarations that declare packages whose contents it wishes to use, followed by a possibly empty set of declarations of functions, types, variables, and constants.