~zaber/openobject-client/main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
from optparse import OptionParser
from types import ListType
import ConfigParser
import os
import re
import sys
import xmlrpclib

'''
Save a file like this in:

~/.zerp_config


----[ snip start ]-----------------------

[server]
connection: localhost

[server localhost]
username: user
password: pass
database: my-database-foo
rpc_url: http://localhost:8069/xmlrpc

[server localhost2]
username: user
password: pass
database: my-database-foo2
rpc_url: http://localhost:8069/xmlrpc


[server live]
prompt: This is the live server!
username: user
password: pass
database: live
rpc_url: https://liveserver:8069/xmlrpc

----[ snip end ]-----------------------

And choose which server is active via the "connection" parameter
'''

class client(object):
    def __init__(self,
                 connection=None,
                 config=None,
                 no_getopts=False,
                 getopts=None,
                 getopt_additional_options=None,
                 trim_fetches=False):
        """ Read configuration and open RPC connections 

        Alternative configuration can be loaded with the "connection" argument
        eg. client(connection="live")

        Using a completely different config altogether can also be achieved by using 
        the "config" argument.
        eg. client(keyword={
                      username: user
                      password: pass
                      database: my-database-foo2
                      rpc_url: http://localhost:8069/xmlrpc
                    })

        Any scripts that are currently using this client can take advantage of the
        commandline options to choose between server profiles. 

        eg. script.py -h

        eg. script.py -c localhost

        Note that any parameter based calls will override any commandline options

        To add functionality to getopt such as information on what the script does, 
        instantiate the client class with parameters such as "getopts" and/or 
        "getopt_additional_options".

        eg. c = client(getopts={
                          'usage':"usage: %prog [options] DATABASE1 DATABASE2 ..."
                      })

        The above example will produce a message like the following if the script 
        is called with -h:

            Usage: migrate.py [options] DATABASE1 DATABASE2 ...

            Options:
              -h, --help            show this help message and exit
              -l, --list            List available connections
              -c CONNECTION, --connection=CONNECTION
                                    Which connection to use

        Using "getopt_additional_options" will rack additional options as described
        here: http://docs.python.org/library/optparse.html#tutorial

        Set "no_getopts" to true to avoid any invocation of the getopt subroutines.
        
        Set trim_fetches to True to return raw ids instead of (id, name) tuples
        on many2one fields.
        """

        if not no_getopts:
            self.parse_options(getopts,getopt_additional_options)

        if config == None:
            if connection == None and self.options.connection:
                connection = self.options.connection
            config = self.load_config(connection)

        username = config.get('username') #the user
        self.pwd = config.get('password') #the password of the user
        self.dbname = config.get('database') #the database
        rpc_url = config.get('rpc_url')
        self.rpc_url = rpc_url
        self.trim_fetches = trim_fetches

        # Get the user_id
        try:
            sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
            self.user_id = sock_common.login(self.dbname, username, self.pwd)
            self.sock = xmlrpclib.ServerProxy(rpc_url+'/object')
        except IOError:
            _, ex, traceback = sys.exc_info()
            message = "Connecting to '%s': %s." % (config['connection'],
                                                   ex.strerror)
            raise IOError, (ex.errno, message), traceback

        prompt = config.get('prompt')
        if prompt:
            print prompt
            print 'Type Enter to continue or Ctrl-C to abort.'
            sys.stdin.readline()

    def parse_options(self,getopts=None,getopt_additional_options=None):
        """ Subroutine to parse the commandline for options such as
            the selection of which server profile to use.
        """
        if getopts == None: getopts = {}
        parser = OptionParser(**getopts)

        parser.add_option(
            "-l", "--list", action="store_true", dest="list_connections",
            help="List available connections")

        parser.add_option(
            "-c", "--connection", dest="connection",
            help="Which connection to use")

        if getopt_additional_options:
            for options in getopt_additional_options:
                parser.add_option(*options)

        (self.options, self.args) = parser.parse_args()

        if self.options.list_connections:
            config = ConfigParser.ConfigParser()
            config.read(os.path.expanduser('~/.zerp_config'))
            default = config.get('server','connection')
            print "Groups in current configuration:\n"
            for section_name in sorted(config.sections()):
                match = re.search( "^server (.+)", section_name )
                if not match: continue
                profile_name = match.group(1)
                print "  ", "*" if default == profile_name else "-",
                print match.group(1)
            print "\nprofile marked with '*' is the current default"
            print
            sys.exit()

    def load_config(self,connection=None):
        defaults = {'prompt': ''}
        config = ConfigParser.ConfigParser(defaults)
        config.read(os.path.expanduser('~/.zerp_config'))
        config_connection = connection or config.get('server','connection')
        server_conn_key = "server " + (config_connection)
        config = dict(config.items(server_conn_key))
        config['connection'] = config_connection
        return config


    def search(self,
               model, 
               constraints, 
               span_start=0, 
               span_limit=100000, 
               order_by='',
               context=None):
        """ Get a list of ids for records that match the constraints.
        
        product_ids = client.search(
            'product.product', 
            ['|', ('name', 'ilike', 'base assy'),
             ('name', 'ilike', 'LSM Motor Assy')])
        """
        
        if context is None:
            context = {}
        context.setdefault('lang', u'en_CA')
        context.setdefault('tz', False)
        
        search_ids = self.execute(
            model,
            'search', 
            constraints, 
            span_start, 
            span_limit, 
            order_by, 
            context)
        return search_ids

    def fetch(self, model, ids, fields):
        """ Fetch fields for a specific set of records.
        
        client.fetch('stock.location', [1, 2, 3, 4], ['name'])
        
        If trim_fetches is true, then many2one fields will just have raw ids.
        """
        
        records = self.execute(
            model, 
            'read',
            ids,
            fields)
        if self.trim_fetches:
            for record in records:
                for name, value in record.iteritems():
                    if isinstance(value, ListType):
                        record[name] = value[0]
        return records or []

    def search_fetch(
        self,
        model, 
        constraints, 
        fields, 
        span_start=0, 
        span_limit=100000, 
        order_by='',
        context=None):
        """ Combine search and fetch in one step.
        
        product_names = client.search(
            'product.product', 
            ['|', ('name', 'ilike', 'base assy'),
             ('name', 'ilike', 'LSM Motor Assy')],
            ['name'])
        """
        
        search_ids = self.search(
            model, 
            constraints, 
            span_start, 
            span_limit, 
            order_by,
            context)
        if len(search_ids) == 0:
            return []
        return self.fetch(model, search_ids, fields)

    def update(self, model, ids, to_set):
        """ Update records.
        
        client.update(
            'stock.location', 
            [11], 
            {'name': 'doomed location'})
        """
        
        res = self.execute(
            model, 
            'write',
            ids,
            to_set)
        return res

    def create(self, model, fields):
        """ Create a new record.
        
        Returns the new record's id.
        
        client.create(
            'stock.location.path', 
            {
                'product_id': 1234,
                'location_from_id': 11,
                'location_dest_id': 23,
                'auto': 'transparent'
            })
        """
        rec_id = self.execute(
            model,
            'create',
            fields)
        return rec_id
    
    def unlink(self, model, ids):
        """ Delete a set of records.
        
        client.unlink('stock.move', [1, 2, 3, 4])
        """
        
        return self.execute(
            model,
            'unlink',
            ids)

    def execute(self, model, method, *args):
        """ Lets you execute any method on an object.
        
        For example, this will check availability on packing list id 1234:
        client.execute('stock.picking', 'action_assign', [1234])
        """
        return self.sock.execute(
            self.dbname,
            self.user_id,
            self.pwd,
            model,
            method,
            *args)

    def exec_workflow(self, model, signal, res_id):
        """ Lets you trigger the workflow on a record.
        
        For example, this will cancel purchase order 1234:
        client.exec_workflow('purchase.order', 'purchase_cancel', 1234)
        """
        return self.sock.exec_workflow(
            self.dbname,
            self.user_id,
            self.pwd,
            model,
            signal,
            res_id)