ooor 1.9.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.md +23 -71
  2. data/Rakefile +5 -0
  3. data/bin/ooor +1 -0
  4. data/lib/ooor.rb +87 -129
  5. data/lib/ooor/associations.rb +64 -0
  6. data/lib/ooor/base.rb +218 -0
  7. data/lib/{app/models → ooor}/base64.rb +0 -0
  8. data/lib/ooor/connection.rb +37 -0
  9. data/lib/ooor/errors.rb +120 -0
  10. data/lib/ooor/field_methods.rb +153 -0
  11. data/lib/{app → ooor}/helpers/core_helpers.rb +2 -2
  12. data/lib/ooor/locale.rb +13 -0
  13. data/lib/ooor/mini_active_resource.rb +94 -0
  14. data/lib/ooor/model_registry.rb +19 -0
  15. data/lib/ooor/naming.rb +73 -0
  16. data/lib/ooor/rack.rb +114 -0
  17. data/lib/ooor/railtie.rb +41 -0
  18. data/lib/ooor/reflection.rb +537 -0
  19. data/lib/ooor/reflection_ooor.rb +92 -0
  20. data/lib/{app/models → ooor}/relation.rb +61 -22
  21. data/lib/ooor/relation/finder_methods.rb +113 -0
  22. data/lib/ooor/report.rb +53 -0
  23. data/lib/{app/models → ooor}/serialization.rb +18 -6
  24. data/lib/ooor/services.rb +133 -0
  25. data/lib/ooor/session.rb +120 -0
  26. data/lib/ooor/session_handler.rb +63 -0
  27. data/lib/ooor/transport.rb +34 -0
  28. data/lib/ooor/transport/json_client.rb +53 -0
  29. data/lib/ooor/transport/xml_rpc_client.rb +15 -0
  30. data/lib/ooor/type_casting.rb +193 -0
  31. data/lib/ooor/version.rb +8 -0
  32. data/spec/helpers/test_helper.rb +11 -0
  33. data/spec/install_nightly.sh +17 -0
  34. data/spec/ooor_spec.rb +197 -79
  35. data/spec/requirements.txt +19 -0
  36. metadata +58 -20
  37. data/lib/app/models/client_xmlrpc.rb +0 -34
  38. data/lib/app/models/open_object_resource.rb +0 -486
  39. data/lib/app/models/services.rb +0 -47
  40. data/lib/app/models/type_casting.rb +0 -134
  41. data/lib/app/models/uml.rb +0 -210
  42. data/lib/app/ui/action_window.rb +0 -96
  43. data/lib/app/ui/client_base.rb +0 -36
  44. data/lib/app/ui/form_model.rb +0 -88
  45. data/ooor.yml +0 -27
@@ -0,0 +1,120 @@
1
+ require 'ooor/services'
2
+
3
+ module Ooor
4
+ class Session < SimpleDelegator
5
+ include Transport
6
+
7
+ attr_accessor :web_session, :connection, :id
8
+
9
+ def common(); @common_service ||= CommonService.new(self); end
10
+ def db(); @db_service ||= DbService.new(self); end
11
+ def object(); @object_service ||= ObjectService.new(self); end
12
+ def report(); @report_service ||= ReportService.new(self); end
13
+
14
+ def initialize(connection, web_session, id)
15
+ super(connection)
16
+ @connection = connection
17
+ @web_session = web_session || {}
18
+ @id = id || web_session[:session_id]
19
+ end
20
+
21
+ def [](key)
22
+ @session[key]
23
+ end
24
+
25
+ def []=(key, value)
26
+ @session[key] = value
27
+ end
28
+
29
+ def global_login(options)
30
+ config.merge!(options)
31
+ load_models(config[:models], options[:reload])
32
+ end
33
+
34
+ def session_context(context={})
35
+ connection_session.merge(web_session.slice('lang', 'tz')).merge(context) # not just lang and tz?
36
+ end
37
+
38
+ def const_get(model_key, lang=nil);
39
+ if config[:aliases]
40
+ if lang && alias_data = config[:aliases][lang]
41
+ openerp_model = alias_data[model_key] || model_key
42
+ elsif alias_data = config[:aliases][connection_session['lang'] || :en_US]
43
+ openerp_model = alias_data[model_key] || model_key
44
+ else
45
+ openerp_model = model_key
46
+ end
47
+ else
48
+ openerp_model = model_key
49
+ end
50
+ define_openerp_model(model: openerp_model, scope_prefix: config[:scope_prefix], generate_constants: config[:generate_constants])
51
+ end
52
+
53
+ def[](model_key) #TODO invert: define method here and use []
54
+ const_get(model_key)
55
+ end
56
+
57
+ def load_models(model_names=config[:models], reload=config[:reload])
58
+ helper_paths.each do |dir|
59
+ Dir[dir].each { |file| require file }
60
+ end
61
+ domain = model_names ? [['model', 'in', model_names]] : []
62
+ search_domain = domain - [1]
63
+ model_ids = object.object_service(:execute, "ir.model", :search, search_domain, 0, false, false, {}, false)
64
+ models_records = object.object_service(:execute, "ir.model", :read, model_ids, ['model', 'name']) #TODO use search_read
65
+ models_records.each do |opts|
66
+ options = HashWithIndifferentAccess.new(opts.merge(scope_prefix: config[:scope_prefix], reload: reload, generate_constants: config[:generate_constants]))
67
+ define_openerp_model(options)
68
+ end
69
+ end
70
+
71
+ def set_model_template!(klass, options)
72
+ template = Ooor.model_registry.get_template(config, options[:model])
73
+ if template
74
+ klass.t = template
75
+ klass.one2many_associations.keys.each do |meth|
76
+ klass.define_nested_attributes_method(meth)
77
+ end
78
+ else
79
+ template = Ooor::ModelTemplate.new
80
+ template.openerp_model = options[:model]
81
+ template.openerp_id = options[:id]
82
+ template.description = options[:name]
83
+ template.state = options[:state]
84
+ template.many2one_associations = {}
85
+ template.one2many_associations = {}
86
+ template.many2many_associations = {}
87
+ template.polymorphic_m2o_associations = {}
88
+ template.associations_keys = []
89
+ klass.t = template
90
+ end
91
+ end
92
+
93
+ def define_openerp_model(options) #TODO param to tell if we define constants or not
94
+ if !models[options[:model]] || options[:reload]# || !scope.const_defined?(model_class_name)
95
+ scope_prefix = options[:scope_prefix]
96
+ scope = scope_prefix ? Object.const_get(scope_prefix) : Object
97
+ model_class_name = class_name_from_model_key(options[:model])
98
+ logger.debug "registering #{model_class_name}"
99
+ klass = Class.new(Base)
100
+ set_model_template!(klass, options)
101
+ klass.name = model_class_name
102
+ klass.scope_prefix = scope_prefix
103
+ klass.connection = self
104
+ if options[:generate_constants] && (options[:reload] || !scope.const_defined?(model_class_name))
105
+ scope.const_set(model_class_name, klass)
106
+ end
107
+ (Ooor.extensions[options[:model]] || []).each do |block|
108
+ klass.class_eval(&block)
109
+ end
110
+ models[options[:model]] = klass
111
+ end
112
+ models[options[:model]]
113
+ end
114
+
115
+ def models; @models ||= {}; end
116
+
117
+ def logger; Ooor.logger; end
118
+
119
+ end
120
+ end
@@ -0,0 +1,63 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'ooor/session'
3
+ require 'ooor/connection'
4
+
5
+ module Ooor
6
+ autoload :SecureRandom, 'securerandom'
7
+ class SessionHandler
8
+ def connection_spec(config)
9
+ HashWithIndifferentAccess.new(config.slice(:url, :username, :password, :database, :scope_prefix, :helper_paths)) #TODO should really password be part of it?
10
+ end
11
+
12
+ def retrieve_session(config, id=nil, web_session={})
13
+ id ||= SecureRandom.hex(16)
14
+ if config[:reload] || !s = sessions[id]
15
+ create_new_session(config, web_session, id)
16
+ else
17
+ s.tap {|s| s.web_session.merge!(web_session)} #TODO merge config also?
18
+ end
19
+ end
20
+
21
+ def create_new_session(config, web_session, id=nil)
22
+ c_spec = connection_spec(config)
23
+ if connections[c_spec]
24
+ Ooor::Session.new(connections[c_spec], web_session, id)
25
+ else
26
+ Ooor::Session.new(create_new_connection(config, c_spec), web_session, id).tap do |s|
27
+ connections[c_spec] = s.connection
28
+ end
29
+ end
30
+ end
31
+
32
+ def register_session(session)
33
+ if session.config[:session_sharing]
34
+ spec = session.web_session[:session_id]
35
+ else
36
+ spec= session.id
37
+ end
38
+ set_web_session(spec, session.web_session)
39
+ sessions[spec] = session
40
+ end
41
+
42
+ def create_new_connection(config, spec)
43
+ config = Ooor.default_config.merge(config) if Ooor.default_config.is_a? Hash
44
+ Connection.new(config)
45
+ end
46
+
47
+ def reset!
48
+ @sessions = {}
49
+ @connections = {}
50
+ end
51
+
52
+ def get_web_session(key)
53
+ Ooor.cache.read(key)
54
+ end
55
+
56
+ def set_web_session(key, web_session)
57
+ Ooor.cache.write(key, web_session)
58
+ end
59
+
60
+ def sessions; @sessions ||= {}; end
61
+ def connections; @connections ||= {}; end
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ # OOOR: OpenObject On Ruby
2
+ # Copyright (C) 2013 Akretion LTDA (<http://www.akretion.com>).
3
+ # Author: Raphaël Valyi
4
+ # Licensed under the MIT license, see MIT-LICENSE file
5
+
6
+ require 'active_support/dependencies/autoload'
7
+
8
+ module Ooor
9
+ module Transport
10
+ extend ActiveSupport::Autoload
11
+ autoload :XmlRpcClient
12
+ autoload :JsonClient
13
+
14
+ def get_client(type, url)
15
+ case type
16
+ when :json
17
+ @json_clients ||= {}
18
+ @json_clients[url] ||= JsonClient.new(url: url)#, timeout: config[:rpc_timeout] || 900) #TODO timeout doesn't work depending on Faraday versions?
19
+ when :xml
20
+ @xml_clients ||= {}
21
+ @xml_clients[url] ||= XmlRpcClient.new2(url, nil, config[:rpc_timeout] || 900)
22
+ end
23
+ end
24
+
25
+ def base_url
26
+ @base_url ||= config[:url] = "#{config[:url].gsub(/\/$/,'').chomp('/xmlrpc')}/xmlrpc"
27
+ end
28
+
29
+ def base_jsonrpc2_url
30
+ @base_jsonrpc2_url ||= config[:url].gsub(/\/$/,'').chomp('/xmlrpc')
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ require 'faraday'
2
+
3
+ module Ooor
4
+ module Transport
5
+ module JsonClient
6
+ module OeAdapter # NOTE use a middleware here?
7
+
8
+ def oe_service(session_info, service, obj, method, *args)
9
+ if service == :exec_workflow
10
+ url = '/web/dataset/exec_workflow'
11
+ params = {"model"=>obj, "id"=>args[0], "signal"=>method}
12
+ elsif service == :execute
13
+ url = '/web/dataset/call_kw'
14
+ if (i = Ooor.irregular_context_position(method)) && args.size < i
15
+ kwargs = {"context"=> args[i]}
16
+ else
17
+ kwargs = {}
18
+ end
19
+ params = {"model"=>obj, "method"=> method, "kwargs"=> kwargs, "args"=>args}#, "context"=>context}
20
+ else
21
+ url = "/web/dataset/#{service}"
22
+ params = args[0].merge({"model"=>obj})
23
+ end
24
+ oe_request(session_info, url, params, method, *args)
25
+ end
26
+
27
+ def oe_request(session_info, url, params, method, *args)
28
+ if session_info[:sid] # required on v7 but forbidden in v8
29
+ params.merge!({"session_id" => session_info[:session_id]})
30
+ end
31
+ response = JSON.parse(post do |req|
32
+ req.headers['Cookie'] = session_info[:cookie]
33
+ req.url url
34
+ req.headers['Content-Type'] = 'application/json'
35
+ req.body = {"jsonrpc"=>"2.0","method"=>"call", "params" => params, "id"=>"r42"}.to_json
36
+ end.body)
37
+ if response["error"]
38
+ faultCode = response["error"]['data']['fault_code'] || response["error"]['data']['debug']
39
+ raise OpenERPServerError.build(faultCode, response["error"]['message'], method, *args)
40
+ else
41
+ response["result"]
42
+ end
43
+ end
44
+ end
45
+
46
+ Faraday::Connection.send :include, OeAdapter
47
+
48
+ def self.new(url = nil, options = nil)
49
+ Faraday.new(url, options) # TODO use middlewares
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ require 'xmlrpc/client'
2
+
3
+ module Ooor
4
+ module Transport
5
+ class XmlRpcClient < XMLRPC::Client
6
+ def call2(method, *args)
7
+ request = create().methodCall(method, *args)
8
+ 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
9
+ parser().parseMethodResponse(data)
10
+ rescue RuntimeError => e
11
+ raise OpenERPServerError.create_from_trace(e, method, *args)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,193 @@
1
+ # OOOR: OpenObject On Ruby
2
+ # Copyright (C) 2009-2012 Akretion LTDA (<http://www.akretion.com>).
3
+ # Author: Raphaël Valyi
4
+ # Licensed under the MIT license, see MIT-LICENSE file
5
+
6
+ module Ooor
7
+ module TypeCasting
8
+ extend ActiveSupport::Concern
9
+
10
+ OPERATORS = ["=", "!=", "<=", "<", ">", ">=", "=?", "=like", "=ilike", "like", "not like", "ilike", "not ilike", "in", "not in", "child_of"]
11
+
12
+ module ClassMethods
13
+
14
+ def openerp_string_domain_to_ruby(string_domain) #FIXME: used? broken?
15
+ eval(string_domain.gsub('(', '[').gsub(')',']'))
16
+ end
17
+
18
+ def to_openerp_domain(domain)
19
+ if domain.is_a?(Hash)
20
+ return domain.map{|k,v| [k.to_s, '=', v]}
21
+ elsif domain == []
22
+ return []
23
+ elsif domain.is_a?(Array) && !domain.last.is_a?(Array)
24
+ return [domain]
25
+ else
26
+ return domain
27
+ end
28
+ end
29
+
30
+ def to_rails_type(type)
31
+ case type.to_sym
32
+ when :char
33
+ :string
34
+ when :binary
35
+ :file
36
+ when :many2one
37
+ :belongs_to
38
+ when :one2many
39
+ :has_many
40
+ when :many2many
41
+ :has_and_belongs_to_many
42
+ else
43
+ type.to_sym
44
+ end
45
+ end
46
+
47
+ def value_to_openerp(v)
48
+ if v == nil || v == ""
49
+ return false
50
+ elsif !v.is_a?(Integer) && !v.is_a?(Float) && v.is_a?(Numeric) && v.respond_to?(:to_f)
51
+ return v.to_f
52
+ elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:sec) && v.respond_to?(:year)#really ensure that's a datetime type
53
+ return "%d-%02d-%02d %02d:%02d:%02d" % [v.year, v.month, v.day, v.hour, v.min, v.sec]
54
+ elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:day) && v.respond_to?(:year)#really ensure that's a date type
55
+ return "%d-%02d-%02d" % [v.year, v.month, v.day]
56
+ elsif v == "false" #may happen with OOORBIT
57
+ return false
58
+ elsif v.respond_to?(:read)
59
+ return Base64.encode64(v.read())
60
+ else
61
+ v
62
+ end
63
+ end
64
+
65
+ def cast_request_to_openerp(request)
66
+ if request.is_a?(Array)
67
+ request.map { |item| cast_request_to_openerp(item) }
68
+ elsif request.is_a?(Hash)
69
+ request2 = {}
70
+ request.each do |k, v|
71
+
72
+ if k.to_s.end_with?("_attributes")
73
+ attrs = []
74
+ if v.is_a?(Hash)
75
+ v.each do |key, val|
76
+ if !val["_destroy"].empty?
77
+ attrs << [2, val[:id] || val['id']]
78
+ elsif val[:id] || val['id']
79
+ attrs << [1, val[:id] || val['id'], cast_request_to_openerp(val)]
80
+ else
81
+ attrs << [0, 0, cast_request_to_openerp(val)]
82
+ end
83
+ end
84
+ end
85
+
86
+ request2[k.to_s.gsub("_attributes", "")] = attrs
87
+ else
88
+ request2[k] = cast_request_to_openerp(v)
89
+ end
90
+ end
91
+ request2
92
+
93
+ else
94
+ value_to_openerp(request)
95
+ end
96
+ end
97
+
98
+ def cast_answer_to_ruby!(answer)
99
+ def cast_map_to_ruby!(map)
100
+ map.each do |k, v|
101
+ if self.t.fields[k] && v.is_a?(String) && !v.empty?
102
+ case self.t.fields[k]['type']
103
+ when 'datetime'
104
+ map[k] = DateTime.parse(v)
105
+ when 'date'
106
+ map[k] = Date.parse(v)
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ if answer.is_a?(Array)
113
+ answer.each {|item| self.cast_map_to_ruby!(item) if item.is_a? Hash}
114
+ elsif answer.is_a?(Hash)
115
+ self.cast_map_to_ruby!(answer)
116
+ else
117
+ answer
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ def to_openerp_hash(attributes=@attributes, associations=@associations)
124
+ associations = cast_relations_to_openerp(associations)
125
+ blacklist = %w[id write_date create_date write_ui create_ui]
126
+ r = {}
127
+ attributes.reject {|k, v| blacklist.index(k)}.merge(associations).each do |k, v|
128
+ if k.end_with?("_id") && !self.class.associations_keys.index(k) && self.class.associations_keys.index(k.gsub(/_id$/, ""))
129
+ r[k.gsub(/_id$/, "")] = v && v.to_i || v
130
+ else
131
+ r[k] = v
132
+ end
133
+ end
134
+ r
135
+ end
136
+
137
+ def cast_relations_to_openerp(associations=@associations)
138
+ associations2 = {}
139
+ associations.each do |k, v|
140
+ if k.match(/_ids$/) && !self.class.associations_keys.index(k) && self.class.associations_keys.index(rel = k.gsub(/_ids$/, ""))
141
+ if v.is_a? Array
142
+ v.reject! {|i| i == ''}.map! {|i| i.to_i}
143
+ end
144
+ associations2[rel] = v
145
+ elsif v.is_a?(Array) && (v.size == 0 or v[1].is_a?(String)) #reject non assigned many2one or empty list
146
+ next
147
+ else
148
+ if k.end_with?("_ids") && v.is_a?(String)
149
+ v = v.split(",").map{|i| i.to_i}
150
+ end
151
+ associations2[k] = v
152
+ end
153
+ end
154
+
155
+ associations2.each do |k, v| #see OpenERP awkward associations API
156
+ #already casted, possibly before server error!
157
+ next if (v.is_a?(Array) && v.size == 1 && v[0].is_a?(Array)) \
158
+ || self.class.many2one_associations[k] \
159
+ || !v.is_a?(Array)
160
+ new_rel = self.cast_relation(k, v, self.class.one2many_associations, self.class.many2many_associations)
161
+ if new_rel #matches a known o2m or m2m
162
+ associations2[k] = new_rel
163
+ else
164
+ self.class.many2one_associations.each do |k2, field| #try to cast the association to an inherited o2m or m2m:
165
+ linked_class = self.class.const_get(field['relation'])
166
+ new_rel = self.cast_relation(k, v, linked_class.one2many_associations, linked_class.many2many_associations)
167
+ associations2[k] = new_rel and break if new_rel
168
+ end
169
+ end
170
+ end
171
+ associations2
172
+ end
173
+
174
+ def cast_relation(k, v, one2many_associations, many2many_associations)
175
+ if one2many_associations[k]
176
+ return v.collect do |value|
177
+ if value.is_a?(Base) #on the fly creation as in the GTK client
178
+ [0, 0, value.to_openerp_hash]
179
+ else
180
+ if value.is_a?(Hash)
181
+ [0, 0, value]
182
+ else
183
+ [1, value, {}]
184
+ end
185
+ end
186
+ end
187
+ elsif many2many_associations[k]
188
+ return v = [[6, 0, v]]
189
+ end
190
+ end
191
+
192
+ end
193
+ end