ooor 1.4.2 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +10 -70
- data/lib/app/models/base64.rb +1 -1
- data/lib/app/models/common_service.rb +21 -20
- data/lib/app/models/db_service.rb +22 -20
- data/lib/app/models/ooor_client.rb +41 -0
- data/lib/app/models/open_object_resource.rb +325 -411
- data/lib/app/models/relation.rb +145 -0
- data/lib/app/models/type_casting.rb +119 -0
- data/lib/app/models/uml.rb +173 -150
- data/lib/app/ui/action_window.rb +82 -8
- data/lib/app/ui/client_base.rb +26 -29
- data/lib/app/ui/form_model.rb +63 -62
- data/lib/app/ui/menu.rb +12 -11
- data/lib/ooor.rb +90 -77
- data/spec/ooor_spec.rb +41 -34
- metadata +49 -46
data/README.md
CHANGED
@@ -3,10 +3,10 @@ OOOR - OpenObject On Rails
|
|
3
3
|
|
4
4
|
<table>
|
5
5
|
<tr>
|
6
|
-
<td><a href="http://github.com/rvalyi/ooor" title="OOOR - OpenObject On Rails"><img src="http://
|
6
|
+
<td width="159px"><a href="http://github.com/rvalyi/ooor" title="OOOR - OpenObject On Rails"><img src="http://akretion.s3.amazonaws.com/assets/ooor_m.jpg" width="159px" height="124px" /></a></td>
|
7
7
|
<td><b>BY</b></td>
|
8
|
-
<td><a href="http://www.akretion.com" title="Akretion - open source to spin the world"><img src="http://
|
9
|
-
<td>
|
8
|
+
<td width="320px"><a href="http://www.akretion.com" title="Akretion - open source to spin the world"><img src="http://akretion.s3.amazonaws.com/assets/logo.png" width="320px" height="154px" /></a></td>
|
9
|
+
<td width="285px">
|
10
10
|
OOOR stands for OpenObject On Rails. OpenObject is the RAD framework behind OpenERP,
|
11
11
|
the ERP that doesn't hurt, just like Rails is "web development that doesn't hurt".
|
12
12
|
So OOOR exposes seamlessly your OpenOpbject application, to your custom Rails application.
|
@@ -85,6 +85,8 @@ Trying it simply
|
|
85
85
|
If you have Java 1.6+ installed, then the easiest way to tryout OOOR might be to download the [TerminatOOOR zip](http://github.com/rvalyi/terminatooor/downloads)
|
86
86
|
and double-click on jruby-ooor.jar or launch it by command line with java -jar jruby-ooor.jar: it will launch an OOOR console with helpful auto-completion (hit 'tab') on OpenERP business objects.
|
87
87
|
|
88
|
+
You can read [an introduction to OOOR on Akretion's blog.](http://www.akretion.com/en/blog/2010/01/18/introducing-ooor---openobject-on-rails-drivingrequesting-your-openerp-became-a-child-play/)
|
89
|
+
|
88
90
|
|
89
91
|
|
90
92
|
Installation
|
@@ -105,7 +107,7 @@ In all case, you first need to install the ooor gem:
|
|
105
107
|
Let's test OOOR in an irb console (irb command):
|
106
108
|
$ require 'rubygems'
|
107
109
|
$ require 'ooor'
|
108
|
-
$ Ooor.new(
|
110
|
+
$ Ooor.new(:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin')
|
109
111
|
This should load all your OpenERP models into Ruby proxy Activeresource objects. Of course there are option to load only some models.
|
110
112
|
Let's try to retrieve the user with id 1:
|
111
113
|
$ ResUsers.find(1)
|
@@ -115,27 +117,7 @@ Let's try to retrieve the user with id 1:
|
|
115
117
|
|
116
118
|
### (J)Ruby on Rails application:
|
117
119
|
|
118
|
-
|
119
|
-
Inside the Rails::Initializer.run do |config| statement, paste the following gem dependency:
|
120
|
-
|
121
|
-
$ config.gem "ooor"
|
122
|
-
|
123
|
-
Now, you should also create a ooor.yml config file in your config directory
|
124
|
-
You can copy/paste [the default ooor.yml from the OOOR gem](http://github.com/rvalyi/ooor/blob/master/ooor.yml)
|
125
|
-
and then adapt it to your OpenERP server environment.
|
126
|
-
If you set the 'bootstrap' parameter to true, OpenERP models will be loaded at the Rails startup.
|
127
|
-
That the easiest option to get started while you might not want that in production.
|
128
|
-
|
129
|
-
Then just start your Rails application, your OpenERP models will be loaded as you'll see in the Rails log.
|
130
|
-
You can then use all the OOOR API upon all loaded OpenERP models in your regular Rails code (see API usage section).
|
131
|
-
A good way to start playing with OOOR is inside the console, using:
|
132
|
-
$ ruby script/console #or jruby script/console on JRuby of course
|
133
|
-
|
134
|
-
Note: when boostraping Ooor in a Rails application, the default Ooor instance is stored in the Ooor.default_ooor variable.
|
135
|
-
So for instance you can know all loaded models doing Ooor.default_ooor variable.loaded_models; this is used by [OooREST](http://github.com/rvalyi/ooorest) to register all the REST controllers.
|
136
|
-
|
137
|
-
Enabling REST HTTP routes to your OpenERP models:
|
138
|
-
The REST Controller layer of OOOR has been moved as a thin separate gem called [OooREST](http://github.com/rvalyi/ooorest).
|
120
|
+
Please read details [https://github.com/rvalyi/ooor/wiki/(J)Ruby-on-Rails-application](here)
|
139
121
|
|
140
122
|
|
141
123
|
API usage
|
@@ -243,7 +225,8 @@ On Change methods:
|
|
243
225
|
|
244
226
|
Note: currently OOOR doesn't deal with the View layer, or has a very limited support for forms for the wizards.
|
245
227
|
So, it's not possible so far for OOOR to know an on_change signature. Because of this, the on_change syntax is bit awkward
|
246
|
-
as you will see
|
228
|
+
as you will see (fortunately OpenERP SA announced they will fix that on_change API in subsequent v6 OpenERP releases):
|
229
|
+
you need to explicitely tell the on_change name, the parameter name that changed, the new value and finally
|
247
230
|
enfore the on_change syntax (looking at the OpenERP model code or view or XML/RPC logs will help you to find out). But
|
248
231
|
ultimately it works:
|
249
232
|
|
@@ -340,47 +323,4 @@ In this case [CampToCamp](http://www.camptocamp.com/) used the famous [Cucumber
|
|
340
323
|
FAQ
|
341
324
|
------------
|
342
325
|
|
343
|
-
|
344
|
-
|
345
|
-
An easy is to use your GTK client and start it with the -l debug_rpc (or alternatively -l debug_rpc_answer) option.
|
346
|
-
For non *nix users, you can alternatively start your server with the --log-level=debug_rpc option (you can also set this option in your hidden OpenERP server config file in your user directory).
|
347
|
-
Then create indents in the log before doing some action and watch your logs carefully. OOOR will allow you to do the same easily from Ruby/Rails.
|
348
|
-
|
349
|
-
### How can I load/reload my OpenERP models into my Ruby application?
|
350
|
-
|
351
|
-
You can load/reload your models at any time (even in console), creating a new Ooor instance that will override the class definitions:
|
352
|
-
$ Ooor.new({:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
|
353
|
-
or using a config YAML file instead:
|
354
|
-
$ Ooor.new("config/ooor.yml")
|
355
|
-
|
356
|
-
### Do I need to load all the OpenERP models in my Ruby application?
|
357
|
-
|
358
|
-
You can load only some OpenERP models (not all), which is faster and better in term of memory/security:
|
359
|
-
$ Ooor.reload!({:models => [res.partner, product.template, product.product], :url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
|
360
|
-
|
361
|
-
### Isn't OOOR slow?
|
362
|
-
|
363
|
-
You might think that proxying a Python based server through a Rails app using XML/RPC would be dog slow. Well, not too much actually.
|
364
|
-
I did some load testing using a Centrino Duo laptop (dual core at 1.6 GHz), and I was able to approach 10 requests/sec, to display some OpenERP resource as XML through HTTP.
|
365
|
-
That should be enough for what it's meant too. Heavily loaded application might cache things at the Rails level if that's too slow, but I don't think so.
|
366
|
-
|
367
|
-
Also notice that using JRuby I could serve some 20% faster.
|
368
|
-
|
369
|
-
### JRuby compatibility
|
370
|
-
|
371
|
-
Yes Ooor is fully JRuby compatible. It's even somewhat 20% faster using Java6 + last JRuby.
|
372
|
-
This might be espcially interresting if you plan to mix Java libraries with OpenERP in the same web appication.
|
373
|
-
|
374
|
-
### Can I extend the OOOR models?
|
375
|
-
|
376
|
-
Yes you can perfectly do that. Basically an OpenERP model get a basic ActiveResource model. For instance: product.product is mapped to the ProductProduct ActiveResource model.
|
377
|
-
But using the Ruby open classes features, you can asbolutely re-open the ProductProduct class and add features of your own.
|
378
|
-
In you app/model directory, create a product_product.rb file with inside, the redéfinition of ProductProduct, for instance:
|
379
|
-
|
380
|
-
$ class ProductProduct < OpenObjectResource
|
381
|
-
$ def foo
|
382
|
-
$ "bar"
|
383
|
-
$ end
|
384
|
-
$ end
|
385
|
-
|
386
|
-
Now a ProductProduct resource got a method foo, returning "bar".
|
326
|
+
Please read the [FAQ here](https://github.com/rvalyi/ooor/wiki/FAQ)
|
data/lib/app/models/base64.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# OOOR: Open Object On Rails
|
2
|
-
# Copyright (C) 2009-
|
2
|
+
# Copyright (C) 2009-2011 Akretion LTDA (<http://www.akretion.com>).
|
3
3
|
# Author: Raphaël Valyi
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# OOOR: Open Object On Rails
|
2
|
-
# Copyright (C) 2009-
|
2
|
+
# Copyright (C) 2009-2011 Akretion LTDA (<http://www.akretion.com>).
|
3
3
|
# Author: Raphaël Valyi
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
@@ -16,27 +16,28 @@
|
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
18
|
#proxies all 'common' class of OpenERP server/bin/service/web_service.py properly
|
19
|
-
module
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
module Ooor
|
20
|
+
module CommonService
|
21
|
+
def global_login(user, password)
|
22
|
+
@config[:username] = user
|
23
|
+
@config[:password] = password
|
24
|
+
@config[:user_id] = OpenObjectResource.client(@base_url + "/common").call("login", @config[:database], user, password)
|
25
|
+
rescue Exception => error
|
26
|
+
@logger.error """login to OpenERP server failed:
|
27
|
+
#{error.inspect}
|
28
|
+
Are your sure the server is started? Are your login parameters correct? Can this server ping the OpenERP server?
|
29
|
+
login XML/RPC url was #{@config[:url].gsub(/\/$/,'') + "/common"}"""
|
30
|
+
raise
|
31
|
+
end
|
32
32
|
|
33
|
-
|
33
|
+
def login(user, password); global_login(user, password); end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
#we generate methods handles for use in auto-completion tools such as jirb_swing
|
36
|
+
[:ir_get, :ir_set, :ir_del, :about, :logout, :timezone_get, :get_available_updates, :get_migration_scripts, :get_server_environment, :login_message, :check_connectivity].each do |meth|
|
37
|
+
self.instance_eval do
|
38
|
+
define_method meth do |*args|
|
39
|
+
OpenObjectResource.client(@base_url + "/common").call(meth.to_s, *args)
|
40
|
+
end
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# OOOR: Open Object On Rails
|
2
|
-
# Copyright (C) 2009-
|
2
|
+
# Copyright (C) 2009-2011 Akretion LTDA (<http://www.akretion.com>).
|
3
3
|
# Author: Raphaël Valyi
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
@@ -16,28 +16,30 @@
|
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
18
|
#proxies all 'db' class of OpenERP server/bin/service/web_service.py properly
|
19
|
-
module
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
module Ooor
|
20
|
+
module DbService
|
21
|
+
def create(password=@config[:db_password], db_name='ooor_db', demo=true, lang='en_US', user_password=@config[:password] || 'admin')
|
22
|
+
process_id = OpenObjectResource.client(@base_url + "/db").call("create", password, db_name, demo, lang, user_password)
|
23
|
+
@config[:database] = db_name
|
24
|
+
@config[:username] = 'admin'
|
25
|
+
@config[:passowrd] = user_password
|
26
|
+
while get_progress('admin', process_id) == [0, []]
|
27
|
+
@logger.info "..."
|
28
|
+
sleep(0.5)
|
29
|
+
end
|
30
|
+
load_models()
|
28
31
|
end
|
29
|
-
load_models()
|
30
|
-
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
def drop(password=@config[:db_password], db_name='ooor_db')
|
34
|
+
OpenObjectResource.client(@base_url + "/db").call("drop", password, db_name)
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
#we generate methods handles for use in auto-completion tools such as jirb_swing
|
38
|
+
[:get_progress, :dump, :restore, :rename, :db_exist, :list, :change_admin_password, :list_lang, :server_version, :migrate_databases].each do |meth|
|
39
|
+
self.instance_eval do
|
40
|
+
define_method meth do |*args|
|
41
|
+
OpenObjectResource.client(@base_url + "/db").call(meth.to_s, *args)
|
42
|
+
end
|
41
43
|
end
|
42
44
|
end
|
43
45
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# OOOR: Open Object On Rails
|
2
|
+
# Copyright (C) 2009-2011 Akretion LTDA (<http://www.akretion.com>).
|
3
|
+
# Author: Raphaël Valyi
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require 'xmlrpc/client'
|
19
|
+
|
20
|
+
module Ooor
|
21
|
+
class OOORClient < XMLRPC::Client
|
22
|
+
def call2(method, *args)
|
23
|
+
request = create().methodCall(method, *args)
|
24
|
+
data = (["<?xml version='1.0' encoding='UTF-8'?>\n"] + do_rpc(request, false).lines.to_a[1..-1]).join #encoding is not defined by OpenERP and can lead to bug with Ruby 1.9
|
25
|
+
parser().parseMethodResponse(data)
|
26
|
+
rescue RuntimeError => e
|
27
|
+
begin
|
28
|
+
#extracts the eventual error log from OpenERP response as OpenERP doesn't enforce carefully*
|
29
|
+
#the XML/RPC spec, see https://bugs.launchpad.net/openerp/+bug/257581
|
30
|
+
openerp_error_hash = eval("#{ e }".gsub("wrong fault-structure: ", ""))
|
31
|
+
rescue SyntaxError
|
32
|
+
raise e
|
33
|
+
end
|
34
|
+
if openerp_error_hash.is_a? Hash
|
35
|
+
raise RuntimeError.new "\n\n*********** OpenERP Server ERROR ***********\n#{openerp_error_hash["faultCode"]}\n#{openerp_error_hash["faultString"]}********************************************\n."
|
36
|
+
else
|
37
|
+
raise e
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# OOOR: Open Object On Rails
|
2
|
-
# Copyright (C) 2009-
|
2
|
+
# Copyright (C) 2009-2011 Akretion LTDA (<http://www.akretion.com>).
|
3
3
|
# Author: Raphaël Valyi
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
@@ -15,490 +15,404 @@
|
|
15
15
|
# You should have received a copy of the GNU Affero General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
|
-
require 'xmlrpc/client'
|
19
18
|
require 'rubygems'
|
20
19
|
require 'active_resource'
|
21
20
|
require 'app/ui/form_model'
|
22
21
|
require 'app/models/uml'
|
22
|
+
require 'app/models/ooor_client'
|
23
|
+
require 'app/models/type_casting'
|
24
|
+
require 'app/models/relation'
|
25
|
+
|
26
|
+
module Ooor
|
27
|
+
class OpenObjectResource < ActiveResource::Base
|
28
|
+
#PREDEFINED_INHERITS = {'product.product' => 'product_tmpl_id'}
|
29
|
+
#include ActiveModel::Validations
|
30
|
+
include UML
|
31
|
+
include TypeCasting
|
32
|
+
|
33
|
+
# ******************** class methods ********************
|
34
|
+
class << self
|
35
|
+
|
36
|
+
cattr_accessor :logger
|
37
|
+
attr_accessor :openerp_id, :info, :access_ids, :name, :openerp_model, :field_ids, :state, #model class attributes associated to the OpenERP ir.model
|
38
|
+
:fields, :fields_defined, :many2one_associations, :one2many_associations, :many2many_associations, :polymorphic_m2o_associations, :associations_keys,
|
39
|
+
:database, :user_id, :scope_prefix, :ooor, :association
|
40
|
+
|
41
|
+
def class_name_from_model_key(model_key=self.openerp_model)
|
42
|
+
model_key.split('.').collect {|name_part| name_part.capitalize}.join
|
43
|
+
end
|
23
44
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
attr_accessor :openerp_id, :info, :access_ids, :name, :openerp_model, :field_ids, :state, #model class attributes assotiated to the OpenERP ir.model
|
32
|
-
:fields, :fields_defined, :many2one_relations, :one2many_relations, :many2many_relations, :polymorphic_m2o_relations, :relations_keys,
|
33
|
-
:database, :user_id, :scope_prefix, :ooor
|
34
|
-
|
35
|
-
def class_name_from_model_key(model_key=self.openerp_model)
|
36
|
-
model_key.split('.').collect {|name_part| name_part.capitalize}.join
|
37
|
-
end
|
38
|
-
|
39
|
-
#similar to Object#const_get but for OpenERP model key
|
40
|
-
def const_get(model_key)
|
41
|
-
klass_name = class_name_from_model_key(model_key)
|
42
|
-
klass = (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_defined?(klass_name) ? (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_get(klass_name) : @ooor.define_openerp_model({'model' => model_key}, self.scope_prefix)
|
43
|
-
klass.reload_fields_definition()
|
44
|
-
klass
|
45
|
-
end
|
45
|
+
#similar to Object#const_get but for OpenERP model key
|
46
|
+
def const_get(model_key)
|
47
|
+
klass_name = class_name_from_model_key(model_key)
|
48
|
+
klass = (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_defined?(klass_name) ? (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_get(klass_name) : @ooor.define_openerp_model({'model' => model_key}, self.scope_prefix)
|
49
|
+
klass.reload_fields_definition()
|
50
|
+
klass
|
51
|
+
end
|
46
52
|
|
47
|
-
|
48
|
-
|
49
|
-
|
53
|
+
def create(attributes = {}, context={}, default_get_list=false, reload=true)
|
54
|
+
self.new(attributes, default_get_list, context).tap { |resource| resource.save(context, reload) }
|
55
|
+
end
|
50
56
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
57
|
+
def reload_fields_definition(force = false)
|
58
|
+
if force or not @fields_defined
|
59
|
+
@fields_defined = true
|
60
|
+
@fields = {}
|
61
|
+
rpc_execute("fields_get").each do |k, field|
|
62
|
+
case field['type']
|
63
|
+
when 'many2one'
|
64
|
+
@many2one_associations[k] = field
|
65
|
+
when 'one2many'
|
66
|
+
@one2many_associations[k] = field
|
67
|
+
when 'many2many'
|
68
|
+
@many2many_associations[k] = field
|
69
|
+
when 'reference'
|
70
|
+
@polymorphic_m2o_associations[k] = field
|
71
|
+
else
|
72
|
+
# if ['integer', 'int8'].index(field['type'])
|
73
|
+
# self.send :validates_numericality_of, k, :only_integer => true
|
74
|
+
# elsif field['type'] == 'float'
|
75
|
+
# self.send :validates_numericality_of, k
|
76
|
+
# elsif field['type'] == 'char'
|
77
|
+
# self.send :validates_length_of, k, :maximum => field['size'] || 128
|
78
|
+
# end
|
79
|
+
@fields[k] = field if field['name'] != 'id'
|
80
|
+
end
|
81
|
+
# if field["required"]
|
82
|
+
# if field['type'] == 'many2one'
|
83
|
+
# next if PREDEFINED_INHERITS[self.openerp_model] == k
|
84
|
+
# end
|
85
|
+
# self.send :validates_presence_of, k
|
86
|
+
# end
|
67
87
|
end
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
88
|
+
@associations_keys = @many2one_associations.keys + @one2many_associations.keys + @many2many_associations.keys + @polymorphic_m2o_associations.keys
|
89
|
+
(@fields.keys + @associations_keys).each do |meth| #generates method handlers for auto-completion tools such as jirb_swing
|
90
|
+
unless self.respond_to?(meth)
|
91
|
+
self.instance_eval do
|
92
|
+
define_method meth do |*args|
|
93
|
+
self.send :method_missing, *[meth, *args]
|
94
|
+
end
|
75
95
|
end
|
76
96
|
end
|
77
97
|
end
|
98
|
+
logger.debug "#{fields.size} fields loaded in model #{self.name}"
|
78
99
|
end
|
79
|
-
logger.info "#{fields.size} fields loaded in model #{self.class}"
|
80
100
|
end
|
81
|
-
end
|
82
101
|
|
83
|
-
|
102
|
+
# ******************** remote communication ********************
|
84
103
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
rpc_execute_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, method, *args)
|
102
|
-
end
|
103
|
-
|
104
|
-
#corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
|
105
|
-
def rpc_execute_with_all(db, uid, pass, obj, method, *args)
|
106
|
-
clean_request_args!(args)
|
107
|
-
logger.debug "rpc_execute_with_all: rpc_method: 'execute', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.inspect}, obj: #{obj.inspect}, method: #{method}, *args: #{args.inspect}"
|
108
|
-
try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("execute", db, uid, pass, obj, method, *args)) }
|
109
|
-
end
|
110
|
-
|
111
|
-
#corresponding method for OpenERP osv.exec_workflow(self, db, uid, obj, method, *args)
|
112
|
-
def rpc_exec_workflow(action, *args)
|
113
|
-
rpc_exec_workflow_with_object(@openerp_model, action, *args)
|
114
|
-
end
|
104
|
+
#OpenERP search method
|
105
|
+
def search(domain=[], offset=0, limit=false, order=false, context={}, count=false)
|
106
|
+
rpc_execute('search', to_openerp_domain(domain), offset, limit, order, context, count)
|
107
|
+
end
|
108
|
+
|
109
|
+
def relation; @relation ||= Relation.new(self); end
|
110
|
+
def where(opts, *rest); relation.where(opts, *rest); end
|
111
|
+
def all(*args); relation.all(*args); end
|
112
|
+
def limit(value); relation.limit(value); end
|
113
|
+
def order(value); relation.order(value); end
|
114
|
+
def offset(value); relation.offset(value); end
|
115
|
+
|
116
|
+
def client(url)
|
117
|
+
@clients ||= {}
|
118
|
+
@clients[url] ||= OOORClient.new2(url, nil, 900)
|
119
|
+
end
|
115
120
|
|
116
|
-
|
117
|
-
|
118
|
-
|
121
|
+
#corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
|
122
|
+
def rpc_execute(method, *args)
|
123
|
+
rpc_execute_with_object(@openerp_model, method, *args)
|
124
|
+
end
|
119
125
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("exec_workflow", db, uid, pass, obj, action, *args)) }
|
124
|
-
end
|
126
|
+
def rpc_execute_with_object(object, method, *args)
|
127
|
+
rpc_execute_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, method, *args)
|
128
|
+
end
|
125
129
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
logger.debug "
|
131
|
-
|
130
|
+
#corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
|
131
|
+
def rpc_execute_with_all(db, uid, pass, obj, method, *args)
|
132
|
+
clean_request_args!(args)
|
133
|
+
reload_fields_definition()
|
134
|
+
logger.debug "OOOR RPC: rpc_method: 'execute', db: #{db}, uid: #{uid}, pass: #, obj: #{obj}, method: #{method}, *args: #{args.inspect}"
|
135
|
+
cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("execute", db, uid, pass, obj, method, *args))
|
132
136
|
end
|
133
|
-
params = {'model' => @openerp_model, 'form' => form, 'report_type' => report_type}
|
134
|
-
params.merge!({'id' => ids[0], 'ids' => ids}) if ids
|
135
|
-
logger.debug "rpc_execute_with_all: rpc_method: 'execute old_wizard_step' #{wizard_id}, #{params.inspect}, #{step}, #{context}"
|
136
|
-
[wizard_id, try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("execute", @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], wizard_id, params, step, context)) }]
|
137
|
-
end
|
138
137
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
rescue RuntimeError => e
|
144
|
-
begin
|
145
|
-
openerp_error_hash = eval("#{ e }".gsub("wrong fault-structure: ", ""))
|
146
|
-
rescue SyntaxError
|
147
|
-
raise e
|
148
|
-
end
|
149
|
-
raise e unless openerp_error_hash.is_a? Hash
|
150
|
-
logger.error "*********** OpenERP Server ERROR:\n#{openerp_error_hash["faultString"]}***********"
|
151
|
-
raise RuntimeError.new('OpenERP server error')
|
152
|
-
end
|
138
|
+
#corresponding method for OpenERP osv.exec_workflow(self, db, uid, obj, method, *args)
|
139
|
+
def rpc_exec_workflow(action, *args)
|
140
|
+
rpc_exec_workflow_with_object(@openerp_model, action, *args)
|
141
|
+
end
|
153
142
|
|
154
|
-
|
155
|
-
|
156
|
-
args[-1] = @ooor.global_context.merge(args[-1])
|
157
|
-
elsif args.is_a?(Array)
|
158
|
-
args += [@ooor.global_context]
|
143
|
+
def rpc_exec_workflow_with_object(object, action, *args)
|
144
|
+
rpc_exec_workflow_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, action, *args)
|
159
145
|
end
|
160
|
-
cast_request_to_openerp!(args[-2]) if args[-2].is_a? Hash
|
161
|
-
end
|
162
146
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
map[k] = v.to_f
|
169
|
-
elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:sec) && v.respond_to?(:year)#really ensure that's a datetime type
|
170
|
-
map[k] = "#{v.year}-#{v.month}-#{v.day} #{v.hour}:#{v.min}:#{v.sec}"
|
171
|
-
elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:day) && v.respond_to?(:year)#really ensure that's a date type
|
172
|
-
map[k] = "#{v.year}-#{v.month}-#{v.day}"
|
173
|
-
end
|
147
|
+
def rpc_exec_workflow_with_all(db, uid, pass, obj, action, *args)
|
148
|
+
clean_request_args!(args)
|
149
|
+
reload_fields_definition()
|
150
|
+
logger.debug "OOOR RPC: 'exec_workflow', db: #{db}, uid: #{uid}, pass: #, obj: #{obj}, action: #{action}, *args: #{args.inspect}"
|
151
|
+
cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("exec_workflow", db, uid, pass, obj, action, *args))
|
174
152
|
end
|
175
|
-
end
|
176
153
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
case self.fields[k]['type']
|
184
|
-
when 'datetime'
|
185
|
-
map[k] = Time.parse(v)
|
186
|
-
when 'date'
|
187
|
-
map[k] = Date.parse(v)
|
188
|
-
end
|
189
|
-
end
|
154
|
+
def old_wizard_step(wizard_name, ids, step='init', wizard_id=nil, form={}, context={}, report_type='pdf')
|
155
|
+
context = @ooor.global_context.merge(context)
|
156
|
+
cast_request_to_openerp!(form)
|
157
|
+
unless wizard_id
|
158
|
+
logger.debug "OOOR RPC: 'create old_wizard_step' #{wizard_name}"
|
159
|
+
wizard_id = cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("create", @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], wizard_name))
|
190
160
|
end
|
161
|
+
params = {'model' => @openerp_model, 'form' => form, 'report_type' => report_type}
|
162
|
+
params.merge!({'id' => ids[0], 'ids' => ids}) if ids
|
163
|
+
logger.debug "OOOR RPC: 'execute old_wizard_step' #{wizard_id}, #{params.inspect}, #{step}, #{context}"
|
164
|
+
[wizard_id, cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("execute", @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], wizard_id, params, step, context))]
|
191
165
|
end
|
192
166
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
self.cast_map_to_ruby!(answer)
|
197
|
-
else
|
198
|
-
answer
|
167
|
+
def method_missing(method_symbol, *arguments)
|
168
|
+
raise RuntimeError.new("Invalid RPC method: #{method_symbol}") if [:type!, :allowed!].index(method_symbol)
|
169
|
+
self.rpc_execute(method_symbol.to_s, *arguments)
|
199
170
|
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def method_missing(method_symbol, *arguments)
|
203
|
-
raise RuntimeError.new("Invalid RPC method: #{method_symbol}") if [:type!, :allowed!].index(method_symbol)
|
204
|
-
self.rpc_execute(method_symbol.to_s, *arguments)
|
205
|
-
end
|
206
171
|
|
207
172
|
|
208
|
-
|
173
|
+
# ******************** finders low level implementation ********************
|
174
|
+
private
|
209
175
|
|
210
|
-
|
176
|
+
def find_every(options)
|
177
|
+
domain = options[:domain]
|
178
|
+
context = options[:context] || {}
|
179
|
+
prefix_options, domain = split_options(options[:params]) unless domain
|
180
|
+
ids = rpc_execute('search', to_openerp_domain(domain), options[:offset] || 0, options[:limit] || false, options[:order] || false, context)
|
181
|
+
!ids.empty? && ids[0].is_a?(Integer) && find_single(ids, options) || []
|
182
|
+
end
|
211
183
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
184
|
+
#actually finds many resources specified with scope = ids_array
|
185
|
+
def find_single(scope, options)
|
186
|
+
fields = options[:fields] || options[:only] || []
|
187
|
+
context = options[:context] || {}
|
216
188
|
prefix_options, query_options = split_options(options[:params])
|
217
|
-
|
218
|
-
|
219
|
-
|
189
|
+
is_collection = true
|
190
|
+
scope = [scope] and is_collection = false if !scope.is_a? Array
|
191
|
+
scope.map! do |item|
|
192
|
+
if item.is_a?(String) && item.to_i == 0#triggers ir_model_data absolute reference lookup
|
193
|
+
tab = item.split(".")
|
194
|
+
domain = [['name', '=', tab[-1]]]
|
195
|
+
domain += [['module', '=', tab[-2]]] if tab[-2]
|
196
|
+
ir_model_data = const_get('ir.model.data').find(:first, :domain => domain)
|
197
|
+
ir_model_data && ir_model_data.res_id && search([['id', '=', ir_model_data.res_id]])[0]
|
198
|
+
else
|
199
|
+
item
|
200
|
+
end
|
201
|
+
end.reject! {|item| !item}
|
202
|
+
records = rpc_execute('read', scope, fields, context)
|
203
|
+
records = records.sort_by {|r| scope.index(r["id"])} #TODO use sort_by! in Ruby 1.9
|
204
|
+
active_resources = []
|
205
|
+
records.each do |record|
|
206
|
+
r = {}
|
207
|
+
record.each_pair do |k,v|
|
208
|
+
r[k.to_sym] = v
|
209
|
+
end
|
210
|
+
active_resources << instantiate_record(r, prefix_options, context)
|
220
211
|
end
|
212
|
+
unless is_collection
|
213
|
+
return active_resources[0]
|
214
|
+
end
|
215
|
+
return active_resources
|
221
216
|
end
|
222
|
-
ids = rpc_execute('search', domain, options[:offset] || 0, options[:limit] || false, options[:order] || false, context)
|
223
|
-
!ids.empty? && ids[0].is_a?(Integer) && find_single(ids, options) || []
|
224
|
-
end
|
225
217
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
prefix_options, query_options = split_options(options[:params])
|
231
|
-
is_collection = true
|
232
|
-
scope = [scope] and is_collection = false if !scope.is_a? Array
|
233
|
-
scope.map! do |item|
|
234
|
-
if item.is_a?(String) && item.to_i == 0#triggers ir_model_data absolute reference lookup
|
235
|
-
tab = item.split(".")
|
236
|
-
domain = [['name', '=', tab[-1]]]
|
237
|
-
domain += [['module', '=', tab[-2]]] if tab[-2]
|
238
|
-
ir_model_data = const_get('ir.model.data').find(:first, :domain => domain)
|
239
|
-
ir_model_data && ir_model_data.res_id && search([['id', '=', ir_model_data.res_id]])[0]
|
240
|
-
else
|
241
|
-
item
|
242
|
-
end
|
243
|
-
end.reject! {|item| !item}
|
244
|
-
records = rpc_execute('read', scope, fields, context)
|
245
|
-
active_resources = []
|
246
|
-
records.each do |record|
|
247
|
-
r = {}
|
248
|
-
record.each_pair do |k,v|
|
249
|
-
r[k.to_sym] = v
|
218
|
+
#overriden because loading default fields is all the rage but we don't want them when reading a record
|
219
|
+
def instantiate_record(record, prefix_options = {}, context = {})
|
220
|
+
new(record, [], context).tap do |resource|
|
221
|
+
resource.prefix_options = prefix_options
|
250
222
|
end
|
251
|
-
active_resources << instantiate_record(r, prefix_options, context)
|
252
223
|
end
|
253
|
-
unless is_collection
|
254
|
-
return active_resources[0]
|
255
|
-
end
|
256
|
-
return active_resources
|
257
|
-
end
|
258
224
|
|
259
|
-
#overriden because loading default fields is all the rage but we don't want them when reading a record
|
260
|
-
def instantiate_record(record, prefix_options = {}, context = {})
|
261
|
-
new(record, [], context).tap do |resource|
|
262
|
-
resource.prefix_options = prefix_options
|
263
|
-
end
|
264
225
|
end
|
265
226
|
|
266
|
-
|
267
|
-
|
227
|
+
self.name = "OpenObjectResource"
|
228
|
+
|
229
|
+
|
230
|
+
# ******************** instance methods ********************
|
268
231
|
|
269
|
-
|
232
|
+
attr_accessor :associations, :loaded_associations, :ir_model_data_id, :object_session
|
270
233
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
def object_uid;object_session[:user_id] || self.class.user_id || self.class.ooor.config[:user_id]; end
|
275
|
-
def object_pass; object_session[:password] || self.class.password || self.class.ooor.config[:password]; end
|
276
|
-
|
277
|
-
#try to wrap the object context inside the query.
|
278
|
-
def rpc_execute(method, *args)
|
279
|
-
if args[-1].is_a? Hash
|
280
|
-
args[-1] = self.class.ooor.global_context.merge(object_session[:context]).merge(args[-1])
|
281
|
-
elsif args.is_a?(Array)
|
282
|
-
args += [self.class.ooor.global_context.merge(object_session[:context])]
|
283
|
-
end
|
284
|
-
self.class.rpc_execute_with_all(object_db, object_uid, object_pass, self.class.openerp_model, method, *args)
|
285
|
-
end
|
234
|
+
def object_db; object_session[:database] || self.class.database || self.class.ooor.config[:database]; end
|
235
|
+
def object_uid;object_session[:user_id] || self.class.user_id || self.class.ooor.config[:user_id]; end
|
236
|
+
def object_pass; object_session[:password] || self.class.password || self.class.ooor.config[:password]; end
|
286
237
|
|
287
|
-
|
288
|
-
|
289
|
-
|
238
|
+
#try to wrap the object context inside the query.
|
239
|
+
def rpc_execute(method, *args)
|
240
|
+
if args[-1].is_a? Hash
|
241
|
+
args[-1] = self.class.ooor.global_context.merge(object_session[:context]).merge(args[-1])
|
242
|
+
elsif args.is_a?(Array)
|
243
|
+
args += [self.class.ooor.global_context.merge(object_session[:context])]
|
244
|
+
end
|
245
|
+
self.class.rpc_execute_with_all(object_db, object_uid, object_pass, self.class.openerp_model, method, *args)
|
290
246
|
end
|
291
247
|
|
292
|
-
def
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
248
|
+
def reload_from_record!(record) load(record.attributes, record.associations) end
|
249
|
+
|
250
|
+
def load(attributes, associations={})#an attribute might actually be a association too, will be determined here
|
251
|
+
self.class.reload_fields_definition()
|
252
|
+
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
253
|
+
@prefix_options, attributes = split_options(attributes)
|
254
|
+
@associations = associations
|
255
|
+
@attributes = {}
|
256
|
+
@loaded_associations = {}
|
257
|
+
attributes.each do |key, value|
|
258
|
+
skey = key.to_s
|
259
|
+
if self.class.associations_keys.index(skey) || value.is_a?(Array)
|
260
|
+
associations[skey] = value #the association because we want the method to load the association through method missing
|
261
|
+
else
|
262
|
+
case value
|
263
|
+
when Hash
|
264
|
+
resource = find_or_create_resource_for(key) #TODO check!
|
265
|
+
@attributes[skey] = resource@attributes[skey].new(value)
|
266
|
+
else
|
267
|
+
@attributes[skey] = value
|
299
268
|
end
|
300
269
|
end
|
301
|
-
elsif many2many_relations[k]
|
302
|
-
return v = [[6, 0, v]]
|
303
270
|
end
|
271
|
+
self
|
304
272
|
end
|
305
273
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|| !v.is_a?(Array)
|
311
|
-
new_rel = self.cast_relation(k, v, self.class.one2many_relations, self.class.many2many_relations)
|
312
|
-
if new_rel #matches a known o2m or m2m
|
313
|
-
@relations[k] = new_rel
|
314
|
-
else
|
315
|
-
self.class.many2one_relations.each do |k2, field| #try to cast the relation to an inherited o2m or m2m:
|
316
|
-
linked_class = self.class.const_get(field['relation'])
|
317
|
-
new_rel = self.cast_relation(k, v, linked_class.one2many_relations, linked_class.many2many_relations)
|
318
|
-
@relations[k] = new_rel and break if new_rel
|
319
|
-
end
|
320
|
-
end
|
274
|
+
def load_association(model_key, ids, *arguments)
|
275
|
+
options = arguments.extract_options!
|
276
|
+
related_class = self.class.const_get(model_key)
|
277
|
+
related_class.send :find, ids, :fields => options[:fields] || options[:only] || [], :context => options[:context] || {}
|
321
278
|
end
|
322
|
-
end
|
323
279
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
280
|
+
def available_fields
|
281
|
+
msg = "\n*** AVAILABLE FIELDS ON OBJECT #{self.class.name} ARE: ***"
|
282
|
+
msg << "\n\n" << self.class.fields.sort {|a,b| a[1]['type']<=>b[1]['type']}.map {|i| "#{i[1]['type']} --- #{i[0]}"}.join("\n")
|
283
|
+
msg << "\n\n" << self.class.many2one_associations.map {|k, v| "many2one --- #{v['relation']} --- #{k}"}.join("\n")
|
284
|
+
msg << "\n\n" << self.class.one2many_associations.map {|k, v| "one2many --- #{v['relation']} --- #{k}"}.join("\n")
|
285
|
+
msg << "\n\n" << self.class.many2many_associations.map {|k, v| "many2many --- #{v['relation']} --- #{k}"}.join("\n")
|
286
|
+
msg << "\n\n" << self.class.polymorphic_m2o_associations.map {|k, v| "polymorphic_m2o --- #{v['relation']} --- #{k}"}.join("\n")
|
287
|
+
end
|
288
|
+
|
289
|
+
#takes care of reading OpenERP default field values.
|
290
|
+
def initialize(attributes = {}, default_get_list=false, context={})
|
291
|
+
@attributes = {}
|
292
|
+
@prefix_options = {}
|
293
|
+
@ir_model_data_id = attributes.delete(:ir_model_data_id)
|
294
|
+
@object_session = {}
|
295
|
+
@object_session[:user_id] = context.delete :user_id
|
296
|
+
@object_session[:database] = context.delete :database
|
297
|
+
@object_session[:password] = context.delete :password
|
298
|
+
@object_session[:context] = context
|
299
|
+
if default_get_list == []
|
300
|
+
load(attributes)
|
337
301
|
else
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
@attributes[skey] = resource@attributes[skey].new(value)
|
342
|
-
else
|
343
|
-
@attributes[skey] = value
|
344
|
-
end
|
302
|
+
self.class.reload_fields_definition()
|
303
|
+
attributes = rpc_execute("default_get", default_get_list || self.class.fields.keys + self.class.associations_keys, @object_session[:context]).symbolize_keys!.merge(attributes.symbolize_keys!)
|
304
|
+
load(attributes)
|
345
305
|
end
|
346
306
|
end
|
347
|
-
self
|
348
|
-
end
|
349
|
-
|
350
|
-
def load_relation(model_key, ids, *arguments)
|
351
|
-
options = arguments.extract_options!
|
352
|
-
related_class = self.class.const_get(model_key)
|
353
|
-
related_class.send :find, ids, :fields => options[:fields] || [], :context => options[:context] || {}
|
354
|
-
end
|
355
307
|
|
356
|
-
|
357
|
-
|
358
|
-
msg << "\n\n" << self.class.fields.sort {|a,b| a[1]['type']<=>b[1]['type']}.map {|i| "#{i[1]['type']} --- #{i[0]}"}.join("\n")
|
359
|
-
msg << "\n\n" << self.class.many2one_relations.map {|k, v| "many2one --- #{v['relation']} --- #{k}"}.join("\n")
|
360
|
-
msg << "\n\n" << self.class.one2many_relations.map {|k, v| "one2many --- #{v['relation']} --- #{k}"}.join("\n")
|
361
|
-
msg << "\n\n" << self.class.many2many_relations.map {|k, v| "many2many --- #{v['relation']} --- #{k}"}.join("\n")
|
362
|
-
msg << "\n\n" << self.class.polymorphic_m2o_relations.map {|k, v| "polymorphic_m2o --- #{v['relation']} --- #{k}"}.join("\n")
|
363
|
-
self.class.logger.debug msg
|
364
|
-
end
|
365
|
-
|
366
|
-
def to_openerp_hash!
|
367
|
-
cast_relations_to_openerp!
|
368
|
-
@attributes.reject {|key, value| key == 'id'}.merge(@relations)
|
369
|
-
end
|
370
|
-
|
371
|
-
#takes care of reading OpenERP default field values.
|
372
|
-
def initialize(attributes = {}, default_get_list=false, context={})
|
373
|
-
@attributes = {}
|
374
|
-
@prefix_options = {}
|
375
|
-
@ir_model_data_id = attributes.delete(:ir_model_data_id)
|
376
|
-
@object_session = {}
|
377
|
-
@object_session[:user_id] = context.delete :user_id
|
378
|
-
@object_session[:database] = context.delete :database
|
379
|
-
@object_session[:password] = context.delete :password
|
380
|
-
@object_session[:context] = context
|
381
|
-
if default_get_list == []
|
382
|
-
load(attributes)
|
383
|
-
else
|
384
|
-
self.class.reload_fields_definition()
|
385
|
-
load(rpc_execute("default_get", default_get_list || self.class.fields.keys + self.class.relations_keys, @object_session[:context]).symbolize_keys!.merge(attributes.symbolize_keys!))
|
308
|
+
def save(context={}, reload=true)
|
309
|
+
new? ? create(context, reload) : update(context, reload)
|
386
310
|
end
|
387
|
-
end
|
388
|
-
|
389
|
-
def save(context={}, reload=true)
|
390
|
-
new? ? create(context, reload) : update(context, reload)
|
391
|
-
end
|
392
|
-
|
393
|
-
#compatible with the Rails way but also supports OpenERP context
|
394
|
-
def create(context={}, reload=true)
|
395
|
-
self.id = rpc_execute('create', to_openerp_hash!, context)
|
396
|
-
IrModelData.create(:model => self.class.openerp_model, :module => @ir_model_data_id[0], :name=> @ir_model_data_id[1], :res_id => self.id) if @ir_model_data_id
|
397
|
-
reload_from_record!(self.class.find(self.id, :context => context)) if reload
|
398
|
-
end
|
399
|
-
|
400
|
-
#compatible with the Rails way but also supports OpenERP context
|
401
|
-
def update(context={}, reload=true)
|
402
|
-
rpc_execute('write', [self.id], to_openerp_hash!, context)
|
403
|
-
reload_from_record!(self.class.find(self.id, :context => context)) if reload
|
404
|
-
end
|
405
|
-
|
406
|
-
#compatible with the Rails way but also supports OpenERP context
|
407
|
-
def destroy(context={})
|
408
|
-
rpc_execute('unlink', [self.id], context)
|
409
|
-
end
|
410
311
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
def call(method, *args) rpc_execute(method, *args) end
|
312
|
+
#compatible with the Rails way but also supports OpenERP context
|
313
|
+
def create(context={}, reload=true)
|
314
|
+
self.id = rpc_execute('create', to_openerp_hash!, context)
|
315
|
+
IrModelData.create(:model => self.class.openerp_model, :module => @ir_model_data_id[0], :name=> @ir_model_data_id[1], :res_id => self.id) if @ir_model_data_id
|
316
|
+
reload_from_record!(self.class.find(self.id, :context => context)) if reload
|
317
|
+
end
|
418
318
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
self.class.logger.info result["warning"]["title"]
|
424
|
-
self.class.logger.info result["warning"]["message"]
|
319
|
+
#compatible with the Rails way but also supports OpenERP context
|
320
|
+
def update(context={}, reload=true)
|
321
|
+
rpc_execute('write', [self.id], to_openerp_hash!, context)
|
322
|
+
reload_from_record!(self.class.find(self.id, :context => context)) if reload
|
425
323
|
end
|
426
|
-
load(@attributes.merge({field_name => field_value}).merge(result["value"]), @relations)
|
427
|
-
end
|
428
324
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
end
|
325
|
+
#compatible with the Rails way but also supports OpenERP context
|
326
|
+
def destroy(context={})
|
327
|
+
rpc_execute('unlink', [self.id], context)
|
328
|
+
end
|
434
329
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
330
|
+
#OpenERP copy method, load persisted copied Object
|
331
|
+
def copy(defaults={}, context={})
|
332
|
+
self.class.find(rpc_execute('copy', self.id, defaults, context), :context => context)
|
333
|
+
end
|
439
334
|
|
440
|
-
|
335
|
+
#Generic OpenERP rpc method call
|
336
|
+
def call(method, *args) rpc_execute(method, *args) end
|
441
337
|
|
338
|
+
#Generic OpenERP on_change method
|
339
|
+
def on_change(on_change_method, field_name, field_value, *args)
|
340
|
+
result = self.class.rpc_execute_with_all(object_db, object_uid, object_pass, self.class.openerp_model, on_change_method, self.id && [id] || [], *args) #OpenERP doesn't accept context systematically in on_change events unfortunately
|
341
|
+
if result["warning"]
|
342
|
+
self.class.logger.info result["warning"]["title"]
|
343
|
+
self.class.logger.info result["warning"]["message"]
|
344
|
+
end
|
345
|
+
load(@attributes.merge({field_name => field_value}).merge(result["value"]), @associations)
|
346
|
+
end
|
442
347
|
|
443
|
-
|
348
|
+
#wrapper for OpenERP exec_workflow Business Process Management engine
|
349
|
+
def wkf_action(action, context={}, reload=true)
|
350
|
+
self.class.rpc_exec_workflow_with_all(object_db, object_uid, object_pass, self.class.openerp_model, action, self.id) #FIXME looks like OpenERP exec_workflow doesn't accept context but it might be a bug
|
351
|
+
reload_from_record!(self.class.find(self.id, :context => context)) if reload
|
352
|
+
end
|
444
353
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
load_relation(self.class.many2one_relations[method_name]['relation'], @relations[method_name].is_a?(Integer) && @relations[method_name] || @relations[method_name][0], *arguments)
|
449
|
-
elsif self.class.one2many_relations.has_key?(method_name)
|
450
|
-
load_relation(self.class.one2many_relations[method_name]['relation'], @relations[method_name], *arguments)
|
451
|
-
elsif self.class.many2many_relations.has_key?(method_name)
|
452
|
-
load_relation(self.class.many2many_relations[method_name]['relation'], @relations[method_name], *arguments)
|
453
|
-
elsif self.class.polymorphic_m2o_relations.has_key?(method_name)
|
454
|
-
values = @relations[method_name].split(',')
|
455
|
-
load_relation(values[0], values[1].to_i, *arguments)
|
456
|
-
else
|
457
|
-
false
|
354
|
+
def old_wizard_step(wizard_name, step='init', wizard_id=nil, form={}, context={})
|
355
|
+
result = self.class.old_wizard_step(wizard_name, [self.id], step, wizard_id, form, {})
|
356
|
+
FormModel.new(wizard_name, result[0], nil, nil, result[1], [self], self.class.ooor.global_context)
|
458
357
|
end
|
459
|
-
|
358
|
+
|
359
|
+
def log(message, context={}) rpc_execute('log', id, message, context) end
|
360
|
+
|
361
|
+
def type() method_missing(:type) end #skips deprecated Object#type method
|
460
362
|
|
461
|
-
|
462
|
-
method_name
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
return
|
363
|
+
# fakes associations like much like ActiveRecord according to the cached OpenERP data model
|
364
|
+
def relationnal_result(method_name, *arguments)
|
365
|
+
self.class.reload_fields_definition()
|
366
|
+
if self.class.many2one_associations.has_key?(method_name)
|
367
|
+
load_association(self.class.many2one_associations[method_name]['relation'], @associations[method_name].is_a?(Integer) && @associations[method_name] || @associations[method_name][0], *arguments)
|
368
|
+
elsif self.class.one2many_associations.has_key?(method_name)
|
369
|
+
load_association(self.class.one2many_associations[method_name]['relation'], @associations[method_name], *arguments) || []
|
370
|
+
elsif self.class.many2many_associations.has_key?(method_name)
|
371
|
+
load_association(self.class.many2many_associations[method_name]['relation'], @associations[method_name], *arguments) || []
|
372
|
+
elsif self.class.polymorphic_m2o_associations.has_key?(method_name)
|
373
|
+
values = @associations[method_name].split(',')
|
374
|
+
load_association(values[0], values[1].to_i, *arguments)
|
375
|
+
else
|
376
|
+
false
|
476
377
|
end
|
477
|
-
know_fields = self.class.fields.keys + self.class.many2one_relations.collect {|k, field| self.class.const_get(field['relation']).fields.keys}.flatten
|
478
|
-
@attributes[method_key] = arguments[0] and return if know_fields.index(method_key)
|
479
378
|
end
|
379
|
+
|
380
|
+
def method_missing(method_symbol, *arguments)
|
381
|
+
method_name = method_symbol.to_s
|
382
|
+
is_assign = method_name.end_with?('=')
|
383
|
+
method_key = method_name.sub('=', '')
|
384
|
+
self.class.reload_fields_definition()
|
480
385
|
|
481
|
-
|
482
|
-
|
386
|
+
if attributes.has_key?(method_key)
|
387
|
+
return super
|
388
|
+
elsif @loaded_associations.has_key?(method_name)
|
389
|
+
@loaded_associations[method_name]
|
390
|
+
elsif @associations.has_key?(method_name)
|
391
|
+
result = relationnal_result(method_name, *arguments)
|
392
|
+
@loaded_associations[method_name] = result and return result if result
|
393
|
+
elsif self.class.fields.has_key?(method_key) || self.class.associations_keys.index(method_name) #unloaded field/association
|
394
|
+
load(rpc_execute('read', [id], [method_key], *arguments)[0] || {})
|
395
|
+
return method_missing(method_key, *arguments)
|
396
|
+
elsif is_assign
|
397
|
+
known_associations = self.class.associations_keys + self.class.many2one_associations.collect {|k, field| self.class.const_get(field['relation']).associations_keys}.flatten
|
398
|
+
if known_associations.index(method_key)
|
399
|
+
@associations[method_key] = arguments[0]
|
400
|
+
@loaded_associations[method_key] = arguments[0]
|
401
|
+
return
|
402
|
+
end
|
403
|
+
know_fields = self.class.fields.keys + self.class.many2one_associations.collect {|k, field| self.class.const_get(field['relation']).fields.keys}.flatten
|
404
|
+
@attributes[method_key] = arguments[0] and return if know_fields.index(method_key)
|
405
|
+
elsif id #it's an action
|
406
|
+
arguments += [{}] unless arguments.last.is_a?(Hash)
|
407
|
+
rpc_execute(method_key, [id], *arguments) #we assume that's an action
|
408
|
+
else
|
409
|
+
super
|
410
|
+
end
|
483
411
|
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
result = relationnal_result(method_name, *arguments)
|
488
|
-
@loaded_relations[method_name] = result and return result if result
|
489
|
-
|
490
|
-
if id
|
491
|
-
arguments += [{}] unless arguments.last.is_a?(Hash)
|
492
|
-
rpc_execute(method_key, [id], *arguments) #we assume that's an action
|
493
|
-
else
|
494
|
-
super
|
412
|
+
rescue RuntimeError => e
|
413
|
+
e.message << "\n" + available_fields if e.message.index("AttributeError")
|
414
|
+
raise e
|
495
415
|
end
|
496
416
|
|
497
|
-
rescue RuntimeError
|
498
|
-
raise
|
499
|
-
rescue NoMethodError
|
500
|
-
display_available_fields
|
501
|
-
raise
|
502
417
|
end
|
503
|
-
|
504
|
-
end
|
418
|
+
end
|