ooor 1.1.0 → 1.2.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 +44 -56
- data/lib/app/models/open_object_resource.rb +157 -113
- data/lib/app/models/open_object_ui.rb +4 -3
- data/lib/app/models/uml.rb +180 -0
- data/lib/ooor.rb +81 -75
- metadata +3 -3
- data/lib/app/controllers/open_objects_controller.rb +0 -139
data/README.md
CHANGED
@@ -76,7 +76,7 @@ OOOR also extends ActiveResource a bit with special request parameters (like :do
|
|
76
76
|
Installation
|
77
77
|
------------
|
78
78
|
|
79
|
-
You can use OOOR in a standalone (J)Ruby application, or in a Rails application.
|
79
|
+
You can use OOOR in a standalone (J)Ruby application, or in a Rails application, it only depends on the activeresource gem.
|
80
80
|
For both example we assume that you already started some OpenERP server on localhost, with XML/RPC on port 8069 (default),
|
81
81
|
with a database called 'mybase', with username 'admin' and password 'admin'.
|
82
82
|
|
@@ -91,8 +91,7 @@ In all case, you first need to install the ooor gem:
|
|
91
91
|
Let's test OOOR in an irb console (irb command):
|
92
92
|
$ require 'rubygems'
|
93
93
|
$ require 'ooor'
|
94
|
-
$
|
95
|
-
$ Ooor.reload!({:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
|
94
|
+
$ Ooor.new({:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
|
96
95
|
This should load all your OpenERP models into Ruby proxy Activeresource objects. Of course there are option to load only some models.
|
97
96
|
Let's try to retrieve the user with id 1:
|
98
97
|
$ ResUsers.find(1)
|
@@ -116,13 +115,11 @@ You can then use all the OOOR API upon all loaded OpenERP models in your regular
|
|
116
115
|
A good way to start playing with OOOR is inside the console, using:
|
117
116
|
$ ruby script/console #or jruby script/console on JRuby of course
|
118
117
|
|
119
|
-
|
120
|
-
|
121
|
-
$ OpenObjectsController.load_all_controllers(map)
|
122
|
-
|
123
|
-
Or only enable the route to some specific model instead (here partners):
|
124
|
-
$ map.resources :res_partner
|
118
|
+
Note: when boostraping Ooor in a Rails application, the default Ooor instance is stored in the OOOR constant.
|
119
|
+
So for instance you can know all loaded models doing OOOR.all_loaded_models; this is used by [OooREST](http://github.com/rvalyi/ooorest) to register all the REST controllers.
|
125
120
|
|
121
|
+
Enabling REST HTTP routes to your OpenERP models:
|
122
|
+
The REST Controller layer of OOOR has been moved as a thin separate gem called [OooREST](http://github.com/rvalyi/ooorest).
|
126
123
|
|
127
124
|
|
128
125
|
API usage
|
@@ -140,15 +137,15 @@ Basic finders:
|
|
140
137
|
$ ProductProduct.find(:last)
|
141
138
|
|
142
139
|
|
143
|
-
OpenERP domain support:
|
140
|
+
OpenERP domain support (same as OpenERP):
|
144
141
|
|
145
142
|
$ ResPartner.find(:all, :domain=>[['supplier', '=', 1],['active','=',1]])
|
146
|
-
More
|
143
|
+
More subtle now, remember OpenERP use a kind of inverse polish notation for complex domains,
|
147
144
|
here we look for a product in category 1 AND which name is either 'PC1' OR 'PC2':
|
148
145
|
$ ProductProduct.find(:all, :domain=>[['categ_id','=',1],'|',['name', '=', 'PC1'],['name','=','PC2']])
|
149
146
|
|
150
147
|
|
151
|
-
OpenERP context support:
|
148
|
+
OpenERP context support (same as OpenERP):
|
152
149
|
|
153
150
|
$ ProductProduct.find(1, :context => {:my_key => 'value'})
|
154
151
|
|
@@ -166,10 +163,11 @@ Arguments are: domain, offset=0, limit=false, order=false, context={}, count=fal
|
|
166
163
|
|
167
164
|
Relations (many2one, one2many, many2many) support:
|
168
165
|
|
169
|
-
$ SaleOrder.find(1).order_line
|
166
|
+
$ SaleOrder.find(1).order_line #one2many relation
|
170
167
|
$ p = ProductProduct.find(1)
|
171
168
|
$ p.product_tmpl_id #many2one relation
|
172
|
-
$ p.
|
169
|
+
$ p.taxes_id #automagically reads man2many relation inherited via the product_tmpl_id inheritance relation
|
170
|
+
$ p.taxes_id = [1,2] #save a many2many relation, notice how we bypass the awkward OpenERP syntax for many2many (would require [6,0, [1,2]]) ,
|
173
171
|
$ p.save #assigns taxes with id 1 and 2 as sale taxes,
|
174
172
|
see [the official OpenERP documentation](http://doc.openerp.com/developer/5_18_upgrading_server/19_1_upgrading_server.html?highlight=many2many)
|
175
173
|
|
@@ -180,9 +178,6 @@ Inherited relations support:
|
|
180
178
|
|
181
179
|
Please notice that loaded relations are cached (to avoid hitting OpenERP over and over)
|
182
180
|
until the root object is reloaded (after save/update for instance).
|
183
|
-
Currently, save/update doesn't save the whole object graph but only the current object.
|
184
|
-
We might change this in the future to match the way OpenERP clients are working which
|
185
|
-
is supported by the OpenERP ORM, see issue: http://github.com/rvalyi/ooor/issues/#issue/3
|
186
181
|
|
187
182
|
|
188
183
|
Load only specific fields support (faster than loading all fields):
|
@@ -229,26 +224,48 @@ Call workflow:
|
|
229
224
|
|
230
225
|
On Change methods:
|
231
226
|
|
232
|
-
|
233
|
-
|
234
|
-
|
227
|
+
Note: currently OOOR doesn't deal with the View layer, or has a very limited support for forms for the wizards.
|
228
|
+
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
|
229
|
+
as you will see: you need to explicitely tell the on_change name, the parameter name that changed, the new value and finally
|
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
|
231
|
+
ultimately it works:
|
232
|
+
|
233
|
+
$ l = SaleOrderLine.new
|
234
|
+
$ l.on_change('product_id_change', :product_id, 20, 1, 20, 1, false, 1, false, false, 7, 'en_US', true, false, false, false)
|
235
|
+
$ => #<SaleOrderLine:0x7f76118b4348 @prefix_options={}, @relations={"product_uos"=>false, "product_id"=>20, "product_uom"=>1, "tax_id"=>[]}, @loaded_relations={}, @attributes={"name"=>"[TOW1] ATX Mid-size Tower", "product_uos_qty"=>1, "delay"=>1.0, "price_unit"=>37.5, "type"=>"make_to_stock", "th_weight"=>0}>
|
235
236
|
Notice that it reloads the Objects attrs and print warning message accordingly
|
236
237
|
|
237
238
|
|
239
|
+
On the fly one2many object graph update/creation:
|
240
|
+
|
241
|
+
Just like the OpenERP GTK client (and unlike the web client), in OOOR you can pass create/update
|
242
|
+
one2many relation in place directly. For instance:
|
243
|
+
$ so = SaleOrder.new
|
244
|
+
$ so.on_change('onchange_partner_id', :partner_id, 1, 1, false) #auto-complete the address and other data based on the partner
|
245
|
+
$ so.order_line = [SaleOrderLine.new(:name => 'sl1', :product_id => 1, :price_unit => 42, :product_uom => 1)] #create one order line
|
246
|
+
$ so.save
|
247
|
+
$ so.amount_total
|
248
|
+
$ => 42.0
|
249
|
+
|
250
|
+
|
238
251
|
Call aribtrary method:
|
239
252
|
|
240
253
|
$ use static ObjectClass.rpc_execute_with_all method
|
241
254
|
$ or object.call(method_name, args*) #were args is an aribtrary list of arguments
|
242
|
-
|
255
|
+
|
256
|
+
Class methods from are osv.py/orm.py proxied to OpenERP directly (as the web client does):
|
243
257
|
$ ResPartner.name_search('ax', [], 'ilike', {})
|
244
258
|
$ ProductProduct.fields_view_get(132, 'tree', {})
|
245
259
|
|
246
260
|
|
247
|
-
Call old wizards:
|
261
|
+
Call old style wizards:
|
248
262
|
|
249
263
|
$ inv = AccountInvoice.find(4)
|
264
|
+
$ #in case the inv.state is 'draft', do inv.wkf_action('invoice_open')
|
250
265
|
$ wizard = inv.old_wizard_step('account.invoice.pay') #tip: you can inspect the wizard fields, arch and datas
|
251
266
|
$ wizard.reconcile({:journal_id => 6, :name =>"from_rails"}) #if you want to pay all; will give you a reloaded invoice
|
267
|
+
$ inv.state
|
268
|
+
$ => "paid"
|
252
269
|
$ #or if you want a payment with a write off:
|
253
270
|
$ wizard.writeoff_check({"amount" => 12, "journal_id" => 6, "name" =>'from_rails'}) #use the button name as the wizard method
|
254
271
|
$ wizard.reconcile({required missing write off fields...}) #will give you a reloaded invoice because state is 'end'
|
@@ -274,21 +291,7 @@ However you might want to change that. 2 solutions:
|
|
274
291
|
$ In the config yaml file or hash, set the :log_level parameter
|
275
292
|
|
276
293
|
|
277
|
-
|
278
|
-
REST HTTP API:
|
279
|
-
|
280
|
-
$ http://localhost:3000/res_partner
|
281
|
-
$ http://localhost:3000/res_partner.xml
|
282
|
-
$ http://localhost:3000/res_partner.json
|
283
|
-
$ http://localhost:3000/res_partner/2
|
284
|
-
$ http://localhost:3000/res_partner/2.json
|
285
|
-
$ http://localhost:3000/res_partner/2.xml
|
286
|
-
$ http://localhost:3000/res_partner/[2,3,4].xml
|
287
|
-
$ http://localhost:3000/res_partner/[2,3,4].json
|
288
|
-
|
289
|
-
$ TODO http://localhost:3000/res_partner.xml?active=1
|
290
|
-
|
291
|
-
$ TODO http://localhost:3000/res_partner.xml?domain=TODO
|
294
|
+
[Drawing OpenERP UML diagrams with OOOR](http://wiki.github.com/rvalyi/ooor/drawing-openerp-uml-diagrams-with-ooor)
|
292
295
|
|
293
296
|
|
294
297
|
FAQ
|
@@ -302,17 +305,16 @@ Then create indents in the log before doing some action and watch your logs care
|
|
302
305
|
|
303
306
|
### How can I load/reload my OpenERP models into my Ruby application?
|
304
307
|
|
305
|
-
You can load/reload your models at any time (even in console),
|
306
|
-
$ Ooor.
|
308
|
+
You can load/reload your models at any time (even in console), creating a new Ooor instance that will override the class definitions:
|
309
|
+
$ Ooor.new({:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
|
307
310
|
or using a config YAML file instead:
|
308
|
-
$ Ooor.
|
311
|
+
$ Ooor.new("config/ooor.yml")
|
309
312
|
|
310
313
|
### Do I need to load all the OpenERP models in my Ruby application?
|
311
314
|
|
312
315
|
You can load only some OpenERP models (not all), which is faster and better in term of memory/security:
|
313
316
|
$ Ooor.reload!({:models => [res.partner, product.template, product.product], :url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
|
314
317
|
|
315
|
-
|
316
318
|
### Isn't OOOR slow?
|
317
319
|
|
318
320
|
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.
|
@@ -338,18 +340,4 @@ In you app/model directory, create a product_product.rb file with inside, the re
|
|
338
340
|
$ end
|
339
341
|
$ end
|
340
342
|
|
341
|
-
Now a ProductProduct resource got a method foo, returning "bar".
|
342
|
-
|
343
|
-
### Can I extend the OOOR controllers?
|
344
|
-
|
345
|
-
Yes, you can do that, see the "how to extends models" section as it's very similar. Instead, in the app/controllers directory, you'll re-open defined controllers,
|
346
|
-
for the product.product controllers, it means creating a product_product_controller.rb file with:
|
347
|
-
|
348
|
-
$ class ProductProduct < OpenObjectsController
|
349
|
-
$ def foo
|
350
|
-
$ render :text => "bar"
|
351
|
-
$ end
|
352
|
-
$ end
|
353
|
-
|
354
|
-
Now, if you register that new method in your route.rb file GET /product_product/1/foo will render "bar" on your browser screen.
|
355
|
-
You could instead just customize the existing CRUD methods so you don't need to regiter any other route in route.rb.
|
343
|
+
Now a ProductProduct resource got a method foo, returning "bar".
|
@@ -1,30 +1,29 @@
|
|
1
1
|
require 'xmlrpc/client'
|
2
|
-
require '
|
2
|
+
require 'active_resource'
|
3
3
|
require 'app/models/open_object_ui'
|
4
|
+
require 'app/models/uml'
|
4
5
|
|
5
6
|
#TODO implement passing session credentials to RPC methods (concurrent access of different user credentials in Rails)
|
6
7
|
|
7
8
|
class OpenObjectResource < ActiveResource::Base
|
9
|
+
include UML
|
8
10
|
|
9
11
|
# ******************** class methods ********************
|
10
12
|
class << self
|
11
13
|
|
12
14
|
cattr_accessor :logger
|
13
15
|
attr_accessor :openerp_id, :info, :access_ids, :name, :openerp_model, :field_ids, :state, #model class attributes assotiated to the OpenERP ir.model
|
14
|
-
:
|
15
|
-
:openerp_database, :user_id
|
16
|
+
:fields, :fields_defined, :many2one_relations, :one2many_relations, :many2many_relations,
|
17
|
+
:openerp_database, :user_id, :scope_prefix, :ooor
|
16
18
|
|
17
|
-
def class_name_from_model_key(model_key)
|
18
|
-
model_key.split('.').collect {|name_part| name_part
|
19
|
+
def class_name_from_model_key(model_key=self.openerp_model)
|
20
|
+
self.scope_prefix + model_key.split('.').collect {|name_part| name_part.capitalize}.join
|
19
21
|
end
|
20
22
|
|
21
23
|
def reload_fields_definition(force = false)
|
22
|
-
if self
|
23
|
-
fields = IrModelFields.find(@field_ids)
|
24
|
+
if not (self.to_s.match('IrModel') || self.to_s.match('IrModelFields')) and (force or not @fields_defined)#TODO have a way to force reloading @field_ids too eventually
|
25
|
+
fields = Object.const_get(self.scope_prefix + 'IrModelFields').find(@field_ids)
|
24
26
|
@fields = {}
|
25
|
-
@many2one_relations = {}
|
26
|
-
@one2many_relations = {}
|
27
|
-
@many2many_relations = {}
|
28
27
|
fields.each do |field|
|
29
28
|
case field.attributes['ttype']
|
30
29
|
when 'many2one'
|
@@ -37,38 +36,11 @@ class OpenObjectResource < ActiveResource::Base
|
|
37
36
|
@fields[field.attributes['name']] = field
|
38
37
|
end
|
39
38
|
end
|
40
|
-
logger.info "#{fields.size} fields loaded"
|
39
|
+
logger.info "#{fields.size} fields loaded in model #{self.class}"
|
41
40
|
end
|
42
|
-
@
|
41
|
+
@fields_defined = true
|
43
42
|
end
|
44
43
|
|
45
|
-
def define_openerp_model(arg, url, database, user_id, pass, binding)
|
46
|
-
param = (arg.is_a? OpenObjectResource) ? arg.attributes.merge(arg.relations) : {'model' => arg}
|
47
|
-
model_key = param['model']
|
48
|
-
Ooor.all_loaded_models.push(model_key)
|
49
|
-
model_class_name = class_name_from_model_key(model_key)
|
50
|
-
logger.info "registering #{model_class_name} as a Rails ActiveResource Model wrapper for OpenObject #{model_key} model"
|
51
|
-
definition = "
|
52
|
-
class #{model_class_name} < OpenObjectResource
|
53
|
-
self.site = '#{url || Ooor.base_url}'
|
54
|
-
self.user = #{user_id}
|
55
|
-
self.password = #{pass || false}
|
56
|
-
self.openerp_database = '#{database}'
|
57
|
-
self.openerp_model = '#{model_key}'
|
58
|
-
self.openerp_id = #{param['id'] || false}
|
59
|
-
self.info = '#{(param['info'] || '').gsub("'",' ')}'
|
60
|
-
self.name = '#{param['name']}'
|
61
|
-
self.state = '#{param['state']}'
|
62
|
-
self.field_ids = #{(param['field_id'] and '[' + param['field_id'].join(',') + ']') || false}
|
63
|
-
self.access_ids = #{(param['access_ids'] and '[' + param['access_ids'].join(',') + ']') || false}
|
64
|
-
self.many2one_relations = {}
|
65
|
-
self.one2many_relations = {}
|
66
|
-
self.many2many_relations = {}
|
67
|
-
end"
|
68
|
-
eval definition, binding
|
69
|
-
end
|
70
|
-
|
71
|
-
|
72
44
|
# ******************** remote communication ********************
|
73
45
|
|
74
46
|
#OpenERP search method
|
@@ -78,12 +50,7 @@ class OpenObjectResource < ActiveResource::Base
|
|
78
50
|
|
79
51
|
def client(url)
|
80
52
|
@clients ||= {}
|
81
|
-
@
|
82
|
-
unless @clientl
|
83
|
-
@client ||= XMLRPC::Client.new2(url)
|
84
|
-
@clients[url] = @client
|
85
|
-
end
|
86
|
-
return @client
|
53
|
+
@clients[url] ||= XMLRPC::Client.new2(url)
|
87
54
|
end
|
88
55
|
|
89
56
|
#corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
|
@@ -92,16 +59,16 @@ class OpenObjectResource < ActiveResource::Base
|
|
92
59
|
end
|
93
60
|
|
94
61
|
def rpc_execute_with_object(object, method, *args)
|
95
|
-
rpc_execute_with_all(@database ||
|
62
|
+
rpc_execute_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, method, *args)
|
96
63
|
end
|
97
64
|
|
98
65
|
#corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
|
99
66
|
def rpc_execute_with_all(db, uid, pass, obj, method, *args)
|
100
67
|
if args[-1].is_a? Hash
|
101
|
-
args[-1] =
|
68
|
+
args[-1] = @ooor.global_context.merge(args[-1])
|
102
69
|
end
|
103
70
|
logger.debug "rpc_execute_with_all: rpc_methods: 'execute', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.inspect}, obj: #{obj.inspect}, method: #{method}, *args: #{args.inspect}"
|
104
|
-
try_with_pretty_error_log { client((@database && @site ||
|
71
|
+
try_with_pretty_error_log { client((@database && @site || @ooor.base_url) + "/object").call("execute", db, uid, pass, obj, method, *args) }
|
105
72
|
end
|
106
73
|
|
107
74
|
#corresponding method for OpenERP osv.exec_workflow(self, db, uid, obj, method, *args)
|
@@ -110,23 +77,23 @@ class OpenObjectResource < ActiveResource::Base
|
|
110
77
|
end
|
111
78
|
|
112
79
|
def rpc_exec_workflow_with_object(object, action, *args)
|
113
|
-
rpc_exec_workflow_with_all(@database ||
|
80
|
+
rpc_exec_workflow_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, action, *args)
|
114
81
|
end
|
115
82
|
|
116
83
|
def rpc_exec_workflow_with_all(db, uid, pass, obj, action, *args)
|
117
84
|
if args[-1].is_a? Hash
|
118
|
-
args[-1] =
|
85
|
+
args[-1] = @ooor.global_context.merge(args[-1])
|
119
86
|
end
|
120
87
|
logger.debug "rpc_execute_with_all: rpc_methods: 'exec_workflow', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.inspect}, obj: #{obj.inspect}, action #{action}, *args: #{args.inspect}"
|
121
|
-
try_with_pretty_error_log { client((@database && @site ||
|
88
|
+
try_with_pretty_error_log { client((@database && @site || @ooor.base_url) + "/object").call("exec_workflow", db, uid, pass, obj, action, *args) }
|
122
89
|
end
|
123
90
|
|
124
91
|
def old_wizard_step(wizard_name, ids, step='init', wizard_id=nil, form={}, context={}, report_type='pdf')
|
125
|
-
context =
|
92
|
+
context = @ooor.global_context.merge(context)
|
126
93
|
unless wizard_id
|
127
|
-
wizard_id = try_with_pretty_error_log { client((@database && @site ||
|
94
|
+
wizard_id = try_with_pretty_error_log { 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) }
|
128
95
|
end
|
129
|
-
[wizard_id, try_with_pretty_error_log { client((@database && @site ||
|
96
|
+
[wizard_id, try_with_pretty_error_log { 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, {'model' => @openerp_model, 'form' => form, 'id' => ids[0], 'report_type' => report_type, 'ids' => ids}, step, context) }]
|
130
97
|
end
|
131
98
|
|
132
99
|
#grab the eventual error log from OpenERP response as OpenERP doesn't enforce carefuly
|
@@ -146,17 +113,13 @@ class OpenObjectResource < ActiveResource::Base
|
|
146
113
|
raise
|
147
114
|
end
|
148
115
|
|
149
|
-
def method_missing(method_symbol, *arguments)
|
150
|
-
return self.rpc_execute(method_symbol.to_s, *arguments)
|
151
|
-
end
|
116
|
+
def method_missing(method_symbol, *arguments) return self.rpc_execute(method_symbol.to_s, *arguments) end
|
152
117
|
|
153
|
-
def load_relation(model_key, ids, *arguments)
|
118
|
+
def load_relation(model_key, ids, scope_prefix, *arguments)
|
154
119
|
options = arguments.extract_options!
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
end
|
159
|
-
relation_model_class = eval class_name_from_model_key(model_key)
|
120
|
+
class_name = class_name_from_model_key(model_key)
|
121
|
+
@ooor.define_openerp_model(model_key, nil, nil, nil, nil, scope_prefix) unless Object.const_defined?(class_name)
|
122
|
+
relation_model_class = Object.const_get(class_name)
|
160
123
|
relation_model_class.send :find, ids, :fields => options[:fields] || [], :context => options[:context] || {}
|
161
124
|
end
|
162
125
|
|
@@ -180,9 +143,7 @@ class OpenObjectResource < ActiveResource::Base
|
|
180
143
|
end
|
181
144
|
|
182
145
|
#TODO, make sense?
|
183
|
-
def find_one
|
184
|
-
raise "Not implemented yet, go on!"
|
185
|
-
end
|
146
|
+
def find_one; raise"Not implemented yet, go on!"; end
|
186
147
|
|
187
148
|
# Find a single resource from the default URL
|
188
149
|
def find_single(scope, options)
|
@@ -216,28 +177,87 @@ class OpenObjectResource < ActiveResource::Base
|
|
216
177
|
|
217
178
|
attr_accessor :relations, :loaded_relations
|
218
179
|
|
219
|
-
def
|
220
|
-
@attributes.each
|
180
|
+
def cast_attributes_to_ruby!
|
181
|
+
@attributes.each do |k, v|
|
182
|
+
if self.class.fields[k]
|
183
|
+
if v.is_a?(String)
|
184
|
+
case self.class.fields[k].ttype
|
185
|
+
when 'datetime'
|
186
|
+
@attributes[k] = Time.parse(v)
|
187
|
+
when 'date'
|
188
|
+
@attributes[k] = Date.parse(v)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
221
193
|
end
|
222
194
|
|
223
|
-
def
|
224
|
-
|
195
|
+
def cast_attributes_to_openerp!
|
196
|
+
@attributes.each do |k, v|
|
197
|
+
@attributes[k] = ((v.is_a? BigDecimal) ? Float(v) : v)
|
198
|
+
if self.class.fields[k]
|
199
|
+
case self.class.fields[k].ttype
|
200
|
+
when 'datetime'
|
201
|
+
@attributes[k] = "#{v.year}-#{v.month}-#{v.day} #{v.hour}:#{v.min}:#{v.sec}" if v.respond_to?(:sec)
|
202
|
+
when 'date'
|
203
|
+
@attributes[k] = "#{v.year}-#{v.month}-#{v.day}" if v.is_a?(Date)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def cast_relations_to_openerp!
|
210
|
+
@relations.reject! do |k, v| #reject non asigned many2one or empty list
|
211
|
+
v.is_a?(Array) && (v.size == 0 or v[1].is_a?(String))
|
212
|
+
end
|
213
|
+
|
214
|
+
def cast_relation(k, v, one2many_relations, many2many_relations)
|
215
|
+
if one2many_relations[k]
|
216
|
+
return v.collect! do |value|
|
217
|
+
if value.is_a?(OpenObjectResource) #on the fly creation as in the GTK client
|
218
|
+
[0, 0, value.to_openerp_hash!]
|
219
|
+
else
|
220
|
+
[1, value, {}]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
elsif many2many_relations[k]
|
224
|
+
return v = [[6, 0, v]]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
@relations.each do |k, v| #see OpenERP awkward relations API
|
229
|
+
new_rel = self.cast_relation(k, v, self.class.one2many_relations, self.class.many2many_relations)
|
230
|
+
if new_rel #matches a known o2m or m2m
|
231
|
+
@relations[k] = new_rel
|
232
|
+
else
|
233
|
+
self.class.many2one_relations.each do |k2, field| #try to cast the relation to na inherited o2m or m2m:
|
234
|
+
class_name = self.class.class_name_from_model_key(field.relation)
|
235
|
+
self.class.ooor.define_openerp_model(field.relation, nil, nil, nil, nil, self.class.scope_prefix) unless Object.const_defined?(class_name)
|
236
|
+
linked_class = Object.const_get(class_name)
|
237
|
+
linked_class.reload_fields_definition unless linked_class.fields_defined
|
238
|
+
new_rel = self.cast_relation(k, v, linked_class.one2many_relations, linked_class.many2many_relations)
|
239
|
+
@relations[k] = new_rel and break if new_rel
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
225
243
|
end
|
226
244
|
|
245
|
+
def reload_from_record!(record) load(record.attributes, record.relations) end
|
246
|
+
|
227
247
|
def load(attributes, relations={})
|
228
|
-
self.class.reload_fields_definition unless self.class.
|
248
|
+
self.class.reload_fields_definition() unless self.class.fields_defined
|
229
249
|
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
230
250
|
@prefix_options, attributes = split_options(attributes)
|
231
251
|
@relations = relations
|
252
|
+
@attributes = {}
|
232
253
|
@loaded_relations = {}
|
233
254
|
attributes.each do |key, value|
|
234
255
|
skey = key.to_s
|
235
|
-
if self.class.many2one_relations.has_key?(skey) || self.class.one2many_relations.has_key?(skey) ||
|
256
|
+
if self.class.many2one_relations.has_key?(skey) || self.class.one2many_relations.has_key?(skey) ||
|
257
|
+
self.class.many2many_relations.has_key?(skey) || value.is_a?(Array)
|
236
258
|
relations[skey] = value #the relation because we want the method to load the association through method missing
|
237
259
|
else
|
238
260
|
case value
|
239
|
-
when self.class.many2one_relations.has_key?(skey) || self.class.one2many_relations.has_key?(skey) || self.class.many2many_relations.has_key?(skey)
|
240
|
-
relations[skey] = value #the relation because we want the method to load the association through method missing
|
241
261
|
when Hash
|
242
262
|
resource = find_or_create_resource_for(key) #TODO check!
|
243
263
|
@attributes[skey] = resource@attributes[skey].new(value)
|
@@ -246,25 +266,40 @@ class OpenObjectResource < ActiveResource::Base
|
|
246
266
|
end
|
247
267
|
end
|
248
268
|
end
|
249
|
-
|
269
|
+
cast_attributes_to_ruby!
|
250
270
|
self
|
251
271
|
end
|
252
272
|
|
253
|
-
|
273
|
+
def display_available_fields
|
274
|
+
self.class.logger.debug ""
|
275
|
+
self.class.logger.debug "*** DIRECTLY AVAILABLE FIELDS ON OBJECT #{self} ARE: ***\n"
|
276
|
+
self.class.fields.sort {|a,b| a[1].ttype<=>b[1].ttype}.each {|i| self.class.logger.debug "#{i[1].ttype} --- #{i[0]}"}
|
277
|
+
self.class.logger.debug ""
|
278
|
+
self.class.many2one_relations.each {|k, v| self.class.logger.debug "many2one --- #{v.relation} --- #{k}"}
|
279
|
+
self.class.logger.debug ""
|
280
|
+
self.class.one2many_relations.each {|k, v| self.class.logger.debug "one2many --- #{v.relation} --- #{k}"}
|
281
|
+
self.class.logger.debug ""
|
282
|
+
self.class.many2many_relations.each {|k, v| self.class.logger.debug "many2many --- #{v.relation} --- #{k}"}
|
283
|
+
self.class.logger.debug ""
|
284
|
+
self.class.logger.debug "YOU CAN ALSO USE THE INHERITED FIELDS FROM THE INHERITANCE MANY2ONE RELATIONS OR THE OBJECT METHODS..."
|
285
|
+
self.class.logger.debug ""
|
286
|
+
end
|
287
|
+
|
288
|
+
def to_openerp_hash!
|
289
|
+
cast_attributes_to_openerp!
|
290
|
+
cast_relations_to_openerp!
|
291
|
+
@attributes.reject {|key, value| key == 'id'}.merge(@relations)
|
292
|
+
end
|
293
|
+
|
294
|
+
#compatible with the Rails way but also supports OpenERP context
|
254
295
|
def create(context={})
|
255
|
-
self.
|
256
|
-
m2o = @relations.reject{|k, v| !self.class.many2one_relations.has_key?(k)}
|
257
|
-
vals = @attributes.merge(m2o.merge(m2o){|k, v| v.is_a?(Array) ? v[0] : v})
|
258
|
-
self.id = self.class.rpc_execute('create', vals, context)
|
296
|
+
self.id = self.class.rpc_execute('create', to_openerp_hash!, context)
|
259
297
|
reload_from_record!(self.class.find(self.id, :context => context))
|
260
298
|
end
|
261
299
|
|
262
|
-
#compatible with the Rails way but also supports OpenERP context
|
300
|
+
#compatible with the Rails way but also supports OpenERP context
|
263
301
|
def update(context={})
|
264
|
-
self.
|
265
|
-
m2o = @relations.reject{|k, v| !self.class.many2one_relations.has_key?(k)}
|
266
|
-
vals = @attributes.reject {|key, value| key == 'id'}.merge(m2o.merge(m2o){|k, v| v.is_a?(Array) ? v[0] : v})
|
267
|
-
self.class.rpc_execute('write', self.id, vals, context)
|
302
|
+
self.class.rpc_execute('write', self.id, to_openerp_hash!, context)
|
268
303
|
reload_from_record!(self.class.find(self.id, :context => context))
|
269
304
|
end
|
270
305
|
|
@@ -274,21 +309,21 @@ class OpenObjectResource < ActiveResource::Base
|
|
274
309
|
end
|
275
310
|
|
276
311
|
#OpenERP copy method, load persisted copied Object
|
277
|
-
def copy(defaults=
|
312
|
+
def copy(defaults={}, context={})
|
278
313
|
self.class.find(self.class.rpc_execute('copy', self.id, defaults, context), :context => context)
|
279
314
|
end
|
280
315
|
|
281
316
|
#Generic OpenERP rpc method call
|
282
|
-
def call(method, *args)
|
283
|
-
self.class.rpc_execute(method, *args)
|
284
|
-
end
|
317
|
+
def call(method, *args) self.class.rpc_execute(method, *args) end
|
285
318
|
|
286
319
|
#Generic OpenERP on_change method
|
287
|
-
def on_change(on_change_method, *args)
|
288
|
-
result = self.class.rpc_execute(on_change_method, *args)
|
289
|
-
|
290
|
-
|
291
|
-
|
320
|
+
def on_change(on_change_method, field_name, field_value, *args)
|
321
|
+
result = self.class.rpc_execute(on_change_method, self.id && [id] || [], *args)
|
322
|
+
if result["warning"]
|
323
|
+
self.class.logger.info result["warning"]["title"]
|
324
|
+
self.class.logger.info result["warning"]["message"]
|
325
|
+
end
|
326
|
+
load({field_name => field_value}.merge(result["value"]))
|
292
327
|
end
|
293
328
|
|
294
329
|
#wrapper for OpenERP exec_workflow Business Process Management engine
|
@@ -299,20 +334,20 @@ class OpenObjectResource < ActiveResource::Base
|
|
299
334
|
|
300
335
|
def old_wizard_step(wizard_name, step='init', wizard_id=nil, form={}, context={})
|
301
336
|
result = self.class.old_wizard_step(wizard_name, [self.id], step, wizard_id, form, {})
|
302
|
-
OpenObjectWizard.new(wizard_name, result[0], result[1], [self])
|
337
|
+
OpenObjectWizard.new(wizard_name, result[0], result[1], [self], self.class.ooor.global_context)
|
303
338
|
end
|
304
339
|
|
305
340
|
|
306
341
|
# ******************** fake associations like much like ActiveRecord according to the cached OpenERP data model ********************
|
307
342
|
|
308
343
|
def relationnal_result(method_name, *arguments)
|
309
|
-
self.class.reload_fields_definition unless self.class.
|
344
|
+
self.class.reload_fields_definition unless self.class.fields_defined
|
310
345
|
if self.class.many2one_relations.has_key?(method_name)
|
311
|
-
self.class.load_relation(self.class.many2one_relations[method_name].relation, @relations[method_name][0], *arguments)
|
346
|
+
self.class.load_relation(self.class.many2one_relations[method_name].relation, @relations[method_name][0], self.class.scope_prefix, *arguments)
|
312
347
|
elsif self.class.one2many_relations.has_key?(method_name)
|
313
|
-
self.class.load_relation(self.class.one2many_relations[method_name].relation, @relations[method_name], *arguments)
|
348
|
+
self.class.load_relation(self.class.one2many_relations[method_name].relation, @relations[method_name], self.class.scope_prefix, *arguments)
|
314
349
|
elsif self.class.many2many_relations.has_key?(method_name)
|
315
|
-
self.class.load_relation(self.class.many2many_relations[method_name].relation, @relations[method_name], *arguments)
|
350
|
+
self.class.load_relation(self.class.many2many_relations[method_name].relation, @relations[method_name], self.class.scope_prefix, *arguments)
|
316
351
|
else
|
317
352
|
false
|
318
353
|
end
|
@@ -320,24 +355,33 @@ class OpenObjectResource < ActiveResource::Base
|
|
320
355
|
|
321
356
|
def method_missing(method_symbol, *arguments)
|
322
357
|
method_name = method_symbol.to_s
|
358
|
+
return super if attributes.has_key?(method_name) or attributes.has_key?(method_name.first(-1))
|
359
|
+
if method_name.end_with?('=')
|
360
|
+
@relations[method_name.sub('=', '')] = *arguments
|
361
|
+
return
|
362
|
+
end
|
323
363
|
return @loaded_relations[method_name] if @loaded_relations.has_key?(method_name)
|
324
|
-
if @relations.has_key?(method_name) and !@relations[method_name]
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
model =
|
335
|
-
|
336
|
-
return
|
364
|
+
return false if @relations.has_key?(method_name) and !@relations[method_name]
|
365
|
+
|
366
|
+
result = relationnal_result(method_name, *arguments)
|
367
|
+
if result
|
368
|
+
@loaded_relations[method_name] = result
|
369
|
+
return result
|
370
|
+
elsif !self.class.many2one_relations.empty? #maybe the relation is inherited or could be inferred from a related field
|
371
|
+
self.class.many2one_relations.each do |k, field|
|
372
|
+
if @relations[k]
|
373
|
+
@loaded_relations[k] ||= self.class.load_relation(field.relation, @relations[k][0], self.class.scope_prefix, *arguments)
|
374
|
+
model = @loaded_relations[k]
|
375
|
+
model.loaded_relations[method_name] ||= model.relationnal_result(method_name, *arguments)
|
376
|
+
return model.loaded_relations[method_name] if model.loaded_relations[method_name]
|
337
377
|
end
|
338
|
-
super
|
339
378
|
end
|
379
|
+
super
|
340
380
|
end
|
381
|
+
|
382
|
+
rescue
|
383
|
+
display_available_fields
|
384
|
+
|
341
385
|
super
|
342
386
|
end
|
343
387
|
|
@@ -10,7 +10,7 @@ end
|
|
10
10
|
class OpenObjectWizard < OpenObjectLayoutedFields
|
11
11
|
attr_accessor :name, :id, :datas, :arch, :fields, :type, :state, :open_object_resources
|
12
12
|
|
13
|
-
def initialize(name, id, data, open_object_resources)
|
13
|
+
def initialize(name, id, data, open_object_resources, wizard_context)
|
14
14
|
super(data['arch'], data['fields'])
|
15
15
|
@name = name
|
16
16
|
@id = id
|
@@ -18,16 +18,17 @@ class OpenObjectWizard < OpenObjectLayoutedFields
|
|
18
18
|
@datas = data['datas'].symbolize_keys!
|
19
19
|
@type = data['type']
|
20
20
|
@state = data['state']
|
21
|
+
@wizard_context = wizard_context
|
21
22
|
end
|
22
23
|
|
23
24
|
def method_missing(method_symbol, *arguments)
|
24
25
|
values = @datas.merge((arguments[0] || {}).symbolize_keys!)
|
25
|
-
context =
|
26
|
+
context = @wizard_context.merge(arguments[1] || {})
|
26
27
|
if @open_object_resources.size == 1
|
27
28
|
open_object_resource = @open_object_resources[0]
|
28
29
|
data = open_object_resource.class.old_wizard_step(@name, [open_object_resource.id], method_symbol, @id, values, context)
|
29
30
|
if data[1]['state'] == 'end'
|
30
|
-
return open_object_resource.
|
31
|
+
return open_object_resource.reload_from_record!(open_object_resource.class.find(open_object_resource.id, :context => context))
|
31
32
|
end
|
32
33
|
@arch = data[1]['arch']
|
33
34
|
@fields = data[1]['fields']
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module UML
|
4
|
+
#usage: UML.print_uml or with options: UML.print_uml(:all, : detailed) or MyOpenObjectResource.print_uml or UML.print_uml([list_of_classes], :all, :detailed)
|
5
|
+
|
6
|
+
def self.included(base) base.extend(ClassMethods) end
|
7
|
+
|
8
|
+
def print_uml(*options)
|
9
|
+
UML.print_uml(@config[:models] && @all_loaded_models.select {|model| @config[:models].index(model.openerp_model)} || @all_loaded_models, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def print_uml(*options)
|
14
|
+
UML.print_uml([self], options) if self.is_a?(OpenObjectResource)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.display_fields(clazz)
|
19
|
+
s = ""
|
20
|
+
clazz.reload_fields_definition if clazz.fields.empty?
|
21
|
+
clazz.fields.sort {|a,b| a[1].ttype <=> b[1].ttype}.each {|i| s << "+ #{i[1].ttype} : #{i[0]}\\l\\n"}
|
22
|
+
s
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.print_uml(classes, *options)
|
26
|
+
options = options[0] if options[0].is_a?(Array)
|
27
|
+
local = (options.index(:all) == nil)
|
28
|
+
detailed = (options.index(:detailed) != nil) || local && (options.index(:nodetail) == nil)
|
29
|
+
|
30
|
+
enabled_targets = classes[0].ooor.config[:models] #defines the scope of the UML for option local
|
31
|
+
m2o_edges = {}
|
32
|
+
o2m_edges = {}
|
33
|
+
m2m_edges = {}
|
34
|
+
#instead of diplaying several relations of the same kind between two nodes which would bloat the graph,
|
35
|
+
#we track them all and factor them on a common multiline edge label:
|
36
|
+
connex_classes = UML.collect_edges(false, local, classes, enabled_targets, m2o_edges, o2m_edges, m2m_edges)
|
37
|
+
#back links from connex classes:
|
38
|
+
connex_classes += UML.collect_edges(true, local, connex_classes - classes, classes, m2o_edges, o2m_edges, m2m_edges)
|
39
|
+
|
40
|
+
File.open('uml.dot', 'w') do |f|
|
41
|
+
f << <<-eos
|
42
|
+
digraph G {
|
43
|
+
fontname = "Bitstream Vera Sans"
|
44
|
+
fontsize = 8
|
45
|
+
label = "*** generated by OOOR by www.akretion.com ***"
|
46
|
+
node [
|
47
|
+
fontname = "Bitstream Vera Sans"
|
48
|
+
fontsize = 16
|
49
|
+
shape = "record"
|
50
|
+
fillcolor=orange
|
51
|
+
style="rounded,filled"
|
52
|
+
]
|
53
|
+
edge [
|
54
|
+
arrowhead = "none"
|
55
|
+
fontname = "Bitstream Vera Sans"
|
56
|
+
fontsize = 9
|
57
|
+
]
|
58
|
+
eos
|
59
|
+
|
60
|
+
#UML nodes definitions
|
61
|
+
((connex_classes - classes) + classes - [IrModel, IrModelFields]).each do |model|
|
62
|
+
f << " #{model} [ label = \"{#{model.name}#{detailed ? '|' + display_fields(model) : ''}}\" ]"
|
63
|
+
end
|
64
|
+
|
65
|
+
#many2one:
|
66
|
+
f << <<-eos
|
67
|
+
edge [
|
68
|
+
headlabel = "1"
|
69
|
+
taillabel = "n"
|
70
|
+
]
|
71
|
+
eos
|
72
|
+
m2o_edges.each do |k, v|
|
73
|
+
reverse_part = v[3].size > 0 ? "\\n/#{v[3].join("\\n")}\"]\n" : "\"]\n"
|
74
|
+
f << "edge [label = \"#{v[2].join("\\n")}#{reverse_part}"
|
75
|
+
f << "#{v[0]} -> #{v[1]}\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
#one2many:
|
79
|
+
f << <<-eos
|
80
|
+
edge [
|
81
|
+
headlabel = "n"
|
82
|
+
taillabel = "1"
|
83
|
+
]
|
84
|
+
eos
|
85
|
+
o2m_edges.each do |k, v|
|
86
|
+
f << "edge [label = \"#{v[3].join("\\n")}\"]\n"
|
87
|
+
f << "#{v[0]} -> #{v[1]}\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
#many2many:
|
91
|
+
f << <<-eos
|
92
|
+
edge [
|
93
|
+
headlabel = "n"
|
94
|
+
taillabel = "n"
|
95
|
+
]
|
96
|
+
eos
|
97
|
+
m2m_edges.each do |k, v|
|
98
|
+
reverse_part = v[3].size > 0 ? "\\n/#{v[3].join("\\n")}\"]\n" : "\"]\n"
|
99
|
+
f << "edge [label = \"#{v[2].join("\\n")}}#{reverse_part}"
|
100
|
+
f << "#{v[0]} -> #{v[1]}\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
f << "}"
|
104
|
+
end
|
105
|
+
|
106
|
+
begin
|
107
|
+
cmd_line1 = "rm uml.png"
|
108
|
+
system(cmd_line1)
|
109
|
+
rescue
|
110
|
+
end
|
111
|
+
cmd_line2 = "dot -Tpng uml.dot -o uml.png"
|
112
|
+
system(cmd_line2)
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.collect_edges(is_reverse, local, classes, enabled_targets, m2o_edges, o2m_edges, m2m_edges)
|
116
|
+
connex_classes = Set.new
|
117
|
+
|
118
|
+
classes.each do |model|
|
119
|
+
model.reload_fields_definition if model.fields.empty?
|
120
|
+
|
121
|
+
#many2one:
|
122
|
+
model.many2one_relations.each do |k, field|
|
123
|
+
target = UML.get_target(is_reverse, local, enabled_targets, field, model)
|
124
|
+
if target
|
125
|
+
connex_classes.add(target)
|
126
|
+
if m2o_edges["#{model}-#{target}"]
|
127
|
+
m2o_edges["#{model}-#{target}"][2] += [k]
|
128
|
+
else
|
129
|
+
m2o_edges["#{model}-#{target}"] = [model, target, [k], []]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
classes.each do |model|
|
137
|
+
#one2many:
|
138
|
+
model.one2many_relations.each do |k, field|
|
139
|
+
target = UML.get_target(is_reverse, local, enabled_targets, field, model)
|
140
|
+
if target
|
141
|
+
connex_classes.add(target)
|
142
|
+
if m2o_edges["#{target}-#{model}"]
|
143
|
+
m2o_edges["#{target}-#{model}"][3] += [k]
|
144
|
+
elsif o2m_edges["#{model}-#{target}"]
|
145
|
+
o2m_edges["#{model}-#{target}"][3] += [k]
|
146
|
+
else
|
147
|
+
o2m_edges["#{model}-#{target}"] = [model, target, [], [k]]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
#many2many:
|
153
|
+
model.many2many_relations.each do |k, field|
|
154
|
+
target = UML.get_target(is_reverse, local, enabled_targets, field, model)
|
155
|
+
if target
|
156
|
+
connex_classes.add(target)
|
157
|
+
if m2m_edges["#{model}-#{target}"]
|
158
|
+
m2m_edges["#{model}-#{target}"][2] += [k]
|
159
|
+
elsif m2m_edges["#{target}-#{model}"]
|
160
|
+
m2m_edges["#{target}-#{model}"][3] += [k]
|
161
|
+
else
|
162
|
+
m2m_edges["#{model}-#{target}"] = [model, target, [k], []]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
connex_classes
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def self.get_target(is_reverse, local, enabled_targets, field, model)
|
174
|
+
if (is_reverse && !local) || (!enabled_targets) || enabled_targets.index(field.relation)
|
175
|
+
target_name = model.class_name_from_model_key(field.relation)
|
176
|
+
return Object.const_defined?(target_name) ? Object.const_get(target_name) : model.ooor.define_openerp_model(field.relation, nil, nil, nil, nil, model.scope_prefix)
|
177
|
+
end
|
178
|
+
return false
|
179
|
+
end
|
180
|
+
end
|
data/lib/ooor.rb
CHANGED
@@ -1,89 +1,95 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'xmlrpc/client'
|
3
|
+
require 'app/models/open_object_resource'
|
4
|
+
require 'app/models/uml'
|
5
|
+
|
6
|
+
class Ooor
|
7
|
+
include UML
|
8
|
+
|
9
|
+
attr_accessor :logger, :config, :all_loaded_models, :base_url, :global_context, :ir_model_class
|
10
|
+
|
11
|
+
#load the custom configuration
|
12
|
+
def self.load_config(config_file=nil, env=nil)
|
13
|
+
config_file ||= defined?(RAILS_ROOT) && "#{RAILS_ROOT}/config/ooor.yml" || 'ooor.yml'
|
14
|
+
@config = YAML.load_file(config_file)[env || 'development']
|
15
|
+
rescue SystemCallError
|
16
|
+
@logger.error """failed to load OOOR yaml configuration file.
|
17
|
+
make sure your app has a #{config_file} file correctly set up
|
18
|
+
if not, just copy/paste the default ooor.yml file from the OOOR Gem
|
19
|
+
to #{RAILS_ROOT}/config/ooor.yml and customize it properly\n\n"""
|
20
|
+
raise
|
21
|
+
end
|
3
22
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
rescue SystemCallError
|
16
|
-
Ooor.logger.error """failed to load OOOR yaml configuration file.
|
17
|
-
make sure your app has a #{config_file} file correctly set up
|
18
|
-
if not, just copy/paste the default ooor.yml file from the OOOR Gem
|
19
|
-
to #{RAILS_ROOT}/config/ooor.yml and customize it properly\n\n"""
|
20
|
-
raise
|
21
|
-
end
|
22
|
-
|
23
|
-
def loaded?
|
24
|
-
Ooor.all_loaded_models.size > 0
|
23
|
+
def global_login(user, password)
|
24
|
+
begin
|
25
|
+
@config[:username] = user
|
26
|
+
@config[:password] = password
|
27
|
+
client = OpenObjectResource.client(@base_url + "/common")
|
28
|
+
OpenObjectResource.try_with_pretty_error_log { client.call("login", @config[:database], user, password)}
|
29
|
+
rescue SocketError => error
|
30
|
+
@logger.error """login to OpenERP server failed:
|
31
|
+
#{error.inspect}
|
32
|
+
Are your sure the server is started? Are your login parameters correct? Can this server ping the OpenERP server?
|
33
|
+
login XML/RPC url was #{@config[:url].gsub(/\/$/,'') + "/common"}"""
|
25
34
|
end
|
35
|
+
end
|
26
36
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
def initialize(config, env=false)
|
38
|
+
@config = config.is_a?(String) ? Ooor.load_config(config, env) : config
|
39
|
+
@config.symbolize_keys!
|
40
|
+
@logger = ((defined?(RAILS_ENV) and RAILS_ENV != "development") ? Rails.logger : Logger.new(STDOUT))
|
41
|
+
@logger.level = config[:log_level] if config[:log_level]
|
42
|
+
@base_url = config[:url].gsub(/\/$/,'')
|
43
|
+
@global_context = config[:global_context] || {}
|
44
|
+
config[:user_id] = global_login(config[:username] || 'admin', config[:password] || 'admin')
|
45
|
+
|
46
|
+
@all_loaded_models = []
|
47
|
+
OpenObjectResource.logger = @logger
|
48
|
+
@ir_model_class = define_openerp_model("ir.model", nil, nil, nil, nil, config[:scope_prefix] || '')
|
49
|
+
define_openerp_model("ir.model.fields", nil, nil, nil, nil, config[:scope_prefix] || '')
|
50
|
+
|
51
|
+
if config[:models] #we load only a customized subset of the OpenERP models
|
52
|
+
models = @ir_model_class.find(:all, :domain => [['model', 'in', config[:models]]])
|
53
|
+
else #we load all the models
|
54
|
+
models = @ir_model_class.find(:all).reject {|model| model.model == "ir.model" || model.model == "ir.model.fields"}
|
39
55
|
end
|
56
|
+
models.each {|openerp_model| define_openerp_model(openerp_model, nil, nil, nil, nil, config[:scope_prefix] || '')}
|
57
|
+
end
|
40
58
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
Ooor.logger.level = Ooor.config[:log_level] if Ooor.config[:log_level]
|
45
|
-
Ooor.base_url = Ooor.config[:url].gsub(/\/$/,'')
|
46
|
-
Ooor.global_context = Ooor.config[:global_context] || {}
|
47
|
-
Ooor.config[:user_id] = global_login(Ooor.config[:username] || 'admin', Ooor.config[:password] || 'admin')
|
48
|
-
|
49
|
-
#*************** load the models
|
50
|
-
|
51
|
-
Ooor.all_loaded_models = []
|
52
|
-
OpenObjectResource.logger = Ooor.logger
|
53
|
-
OpenObjectResource.define_openerp_model("ir.model", nil, nil, nil, nil, Ooor.binding)
|
54
|
-
OpenObjectResource.define_openerp_model("ir.model.fields", nil, nil, nil, nil, Ooor.binding)
|
55
|
-
|
56
|
-
if Ooor.config[:models] #we load only a customized subset of the OpenERP models
|
57
|
-
models = IrModel.find(:all, :domain => [['model', 'in', Ooor.config[:models]]])
|
58
|
-
else #we load all the models
|
59
|
-
models = IrModel.find(:all)
|
60
|
-
end
|
61
|
-
|
62
|
-
models.each {|openerp_model| OpenObjectResource.define_openerp_model(openerp_model, nil, nil, nil, nil, Ooor.binding)}
|
63
|
-
|
64
|
-
# *************** load the models REST controllers
|
65
|
-
if defined?(ActionController)
|
66
|
-
OpenObjectsController.logger = Ooor.logger
|
67
|
-
models.each {|openerp_model| OpenObjectsController.define_openerp_controller(openerp_model.model, Ooor.binding) }
|
68
|
-
end
|
69
|
-
|
59
|
+
def define_openerp_model(arg, url, database, user_id, pass, scope_prefix)
|
60
|
+
if arg.is_a?(String) && arg != 'ir.model' && arg != 'ir.model.fields'
|
61
|
+
arg = @ir_model_class.find(:first, :domain => [['model', '=', arg]])
|
70
62
|
end
|
71
|
-
|
63
|
+
param = (arg.is_a? OpenObjectResource) ? arg.attributes.merge(arg.relations) : {'model' => arg}
|
64
|
+
klass = Class.new(OpenObjectResource)
|
65
|
+
klass.ooor = self
|
66
|
+
klass.site = url || @base_url
|
67
|
+
klass.user = user_id
|
68
|
+
klass.password = pass
|
69
|
+
klass.openerp_database = database
|
70
|
+
klass.openerp_model = param['model']
|
71
|
+
klass.openerp_id = url || param['id']
|
72
|
+
klass.info = (param['info'] || '').gsub("'",' ')
|
73
|
+
klass.name = param['name']
|
74
|
+
klass.state = param['state']
|
75
|
+
klass.field_ids = param['field_id']
|
76
|
+
klass.access_ids = param['access_ids']
|
77
|
+
klass.many2one_relations = {}
|
78
|
+
klass.one2many_relations = {}
|
79
|
+
klass.many2many_relations = {}
|
80
|
+
klass.fields = {}
|
81
|
+
klass.scope_prefix = scope_prefix
|
82
|
+
model_class_name = klass.class_name_from_model_key
|
83
|
+
@logger.info "registering #{model_class_name} as a Rails ActiveResource Model wrapper for OpenObject #{param['model']} model"
|
84
|
+
Object.const_set(model_class_name, klass)
|
85
|
+
@all_loaded_models.push(klass)
|
86
|
+
klass
|
72
87
|
end
|
73
88
|
|
74
89
|
end
|
75
90
|
|
76
|
-
|
77
|
-
Ooor.logger = ((defined?(RAILS_ENV) and RAILS_ENV != "development") ? Rails.logger : Logger.new(STDOUT))
|
78
|
-
Ooor.binding = lambda {}
|
79
|
-
|
80
|
-
require 'app/models/open_object_resource'
|
81
|
-
require 'app/controllers/open_objects_controller'
|
82
|
-
|
83
|
-
|
91
|
+
#Optionnal Rails settings:
|
84
92
|
if defined?(Rails)
|
85
|
-
|
86
|
-
|
87
|
-
Ooor.reload!(false, false, true)
|
88
|
-
end
|
93
|
+
config = Ooor.load_config(false, RAILS_ENV)
|
94
|
+
OOOR = Ooor.new(config) if config['bootstrap']
|
89
95
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ooor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raphael Valyi - www.akretion.com
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-18 00:00:00 -02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -36,7 +36,7 @@ files:
|
|
36
36
|
- lib/ooor.rb
|
37
37
|
- lib/app/models/open_object_resource.rb
|
38
38
|
- lib/app/models/open_object_ui.rb
|
39
|
-
- lib/app/
|
39
|
+
- lib/app/models/uml.rb
|
40
40
|
- ooor.yml
|
41
41
|
has_rdoc: true
|
42
42
|
homepage: http://github.com/rvalyi/ooor
|
@@ -1,139 +0,0 @@
|
|
1
|
-
require 'action_controller'
|
2
|
-
|
3
|
-
class OpenObjectsController < ActionController::Base
|
4
|
-
|
5
|
-
#TODO get lang from URL in before filter
|
6
|
-
#TODO use timezone from HTTP header and put is in context
|
7
|
-
|
8
|
-
# ******************** class methods ********************
|
9
|
-
class << self
|
10
|
-
|
11
|
-
cattr_accessor :logger
|
12
|
-
|
13
|
-
def model_class
|
14
|
-
if defined?(@model_class)
|
15
|
-
@model_class
|
16
|
-
elsif superclass != Object && superclass.model_class
|
17
|
-
superclass.model_class.dup.freeze
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def model_class=(_model_class)
|
22
|
-
@model_class = _model_class
|
23
|
-
end
|
24
|
-
|
25
|
-
def ids_from_param(param)
|
26
|
-
if param.split(',').size > 0
|
27
|
-
return eval param
|
28
|
-
else
|
29
|
-
return param
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def define_openerp_controller(model_key, binding)
|
34
|
-
model_class_name = OpenObjectResource.class_name_from_model_key(model_key)
|
35
|
-
controller_class_name = model_class_name + "Controller"
|
36
|
-
logger.info "registering #{controller_class_name} as a Rails ActiveResource Controller wrapper for OpenObject #{model_key} model"
|
37
|
-
eval "
|
38
|
-
class #{controller_class_name} < OpenObjectsController
|
39
|
-
self.model_class = #{model_class_name}
|
40
|
-
end
|
41
|
-
", binding
|
42
|
-
end
|
43
|
-
|
44
|
-
def load_all_controllers(map)
|
45
|
-
Ooor.all_loaded_models.each do |model|
|
46
|
-
map.resources model.gsub('.', '_').to_sym
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
|
53
|
-
# ******************** instance methods ********************
|
54
|
-
|
55
|
-
# GET /models
|
56
|
-
# GET /models.xml
|
57
|
-
def index
|
58
|
-
@models = self.class.model_class.find(:all)
|
59
|
-
|
60
|
-
respond_to do |format|
|
61
|
-
format.html # index.html.erb
|
62
|
-
format.xml { render :xml => @models }
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# GET /models/1
|
67
|
-
# GET /models/1.xml
|
68
|
-
def show
|
69
|
-
@models = self.class.model_class.find(self.class.ids_from_param(params[:id]))
|
70
|
-
|
71
|
-
respond_to do |format|
|
72
|
-
format.html # show.html.erb
|
73
|
-
format.xml { render :xml => @models }
|
74
|
-
format.json { render :json => @models }
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
# GET /models/new
|
79
|
-
# GET /models/new.xml
|
80
|
-
def new
|
81
|
-
@models = self.class.model_class.new
|
82
|
-
|
83
|
-
respond_to do |format|
|
84
|
-
format.html # new.html.erb
|
85
|
-
format.xml { render :xml => @models }
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# GET /models/1/edit
|
90
|
-
def edit
|
91
|
-
@models = self.class.model_class.find(params[:id])
|
92
|
-
end
|
93
|
-
|
94
|
-
# POST /models
|
95
|
-
# POST /models.xml
|
96
|
-
def create
|
97
|
-
@models = self.class.model_class.new(params[:partners])
|
98
|
-
|
99
|
-
respond_to do |format|
|
100
|
-
if @models.save
|
101
|
-
flash[:notice] = 'Model was successfully created.'
|
102
|
-
format.html { redirect_to(@models) }
|
103
|
-
format.xml { render :xml => @models, :status => :created, :location => @models }
|
104
|
-
else
|
105
|
-
format.html { render :action => "new" }
|
106
|
-
format.xml { render :xml => @models.errors, :status => :unprocessable_entity }
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# PUT /models/1
|
112
|
-
# PUT /models/1.xml
|
113
|
-
def update
|
114
|
-
@models = self.class.model_class.find(params[:id])
|
115
|
-
|
116
|
-
respond_to do |format|
|
117
|
-
if @models.update_attributes(params[:partners])
|
118
|
-
flash[:notice] = 'Partners was successfully updated.'
|
119
|
-
format.html { redirect_to(@models) }
|
120
|
-
format.xml { head :ok }
|
121
|
-
else
|
122
|
-
format.html { render :action => "edit" }
|
123
|
-
format.xml { render :xml => @models.errors, :status => :unprocessable_entity }
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
# DELETE /models/1
|
129
|
-
# DELETE /models/1.xml
|
130
|
-
def destroy
|
131
|
-
@models = self.class.model_class.find(params[:id])
|
132
|
-
@models.destroy
|
133
|
-
|
134
|
-
respond_to do |format|
|
135
|
-
format.html { redirect_to(url_for(:action => index)) }
|
136
|
-
format.xml { head :ok }
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|