ooor 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|