Capítulo IX: API Externa

Until now, we have been working with server-side code. However, the Odoo server also provides an external API, which is used by its web client and is also available for other client applications.

In this chapter, we will learn how to use the Odoo external API from our own client programs. For simplicity, we will focus on Python-based clients.

Setting up a Python client

The Odoo API can be accessed externally using two different protocols: XML-RPC and JSON-RPC. Any external program capable of implementing a client for one of these protocols will be able to interact with an Odoo server. To avoid introducing additional programming languages, we will keep using Python to explore the external API.

Until now, we have been running Python code only on the server. This time, we will use Python on the client side, so it’s possible you might need to do some additional setup on your workstation.

To follow the examples in this chapter, you will need to be able to run Python files on your work computer. The Odoo server requires Python 2, but our RPC client can be in any language, so Python 3 will be just fine. However, since some readers may be running the server on the same machine they are working on (hello Ubuntu users!), it will be simpler for everyone to follow if we stick to Python 2.

If you are using Ubuntu or a Macintosh, probably Python is already installed. Open a terminal console, type python, and you should be greeted with something like the following:

Python    2.7.8    (default,    Oct    20    2014,    15:05:29) [GCC    4.9.1]    on    linux2 Type    "help",    "copyright",",    "credits"    or    "license"    for    more    information. 
>>>

Note Windows users can find an installer and also quickly get up to speed. The official installation packages can be found at https://www.python.org/downloads/.

Calling the Odoo API using XML-RPC

The simplest method to access the server is using XML-RPC. We can use the xmlrpclib library from Python’s standard library for this. Remember that we are programming a client in order to connect to a server, so we need an Odoo server instance running to connect to. In our examples, we will assume that an Odoo server instance is running on the same machine (localhost), but you can use any IP address or server name, if the server is running on another machine.

Opening an XML-RPC connection

Let’s get a fist contact with the external API. Start a Python console and type the following:

>>> import xmlrpclib 
>>> srv,    db    =    'http://localhost:8069',    'v8dev' >>>    user,    pwd    =    'admin',    'admin' 
>>>    common    =    xmlrpclib.ServerProxy('%s/xmlrpc/2/common'    %    srv) 
>>>    common.version() {'server_version_info':    [8,    0,    0,    'final',    0],    'server_serie':    '8.0',     'server_version':    '8.0',    'protocol_version':    1}

Here, we import the xmlrpclib library and then set up some variables with the information for the server location and connection credentials. Feel free to adapt these to your specific setup.

Next, we set up access to the server’s public services (not requiring a login), exposed at the /xmlrpc/2/common endpoint. One of the methods that are available is version(), which inspects the server version. We use it to confirm that we can communicate with the server.

Another public method is authenticate(). In fact, this does not create a session, as you might be led to believe. This method just confirms that the username and password are accepted and returns the user ID that should be used in requests instead of the username, as shown here:

>>>    uid    =    common.authenticate(db,    user,    pwd,    {}) 
>>>    print    uid 1

Reading data from the server

With XML-RPC, no session is maintained and the authentication credentials are sent with every request. This adds some overhead to the protocol, but makes it simpler to use. Next, we set up access to the server methods that need a login to be accessed. These are exposed at the /xmlrpc/2/object endpoint, as shown in the following:

>>>    api    =    xmlrpclib.ServerProxy('%s/xmlrpc/2/object'    %    srv) 
>>>    api.execute_kw(db,    uid,    pwd,    'res.partner',    'search_count',        [[]]) 70

Here, we are doing our first access to the server API, performing a count on the Partner records. Methods are called using the execute_kw() method that takes the following arguments: The name of the database to connect to The connection user ID The user password The target model identifier name The method to call A list of positional arguments An optional dictionary with keyword arguments

The preceding example calls the search_count method of the res.partner model with one positional argument, [], and no keyword arguments. The positional argument is a search domain; since we are providing an empty list, it counts all the Partners.

Frequent actions are search and read. When called from the RPC, the search method returns a list of IDs matching a domain. The browse method is not available from the RPC, and read should be used in its place to, given a list of record IDs, retrieve their data, as shown in the following code:

>>>    api.execute_kw(db,    uid,    pwd,    'res.partner',    'search',    [[('country_id',     '=',    'be'),    ('parent_id',    '!=',    False)]]) [43,    42] 
>>>    api.execute_kw(db,    uid,    pwd,    'res.partner',    'read',        [[43]],    {'fields':     ['id',    'name',    'parent_id']}) [{'parent_id':    [7,    'Agrolait'],    'id':    43,    'name':    'Michel    Fletcher'}]

Note that for the read method, we are using one positional argument for the list of IDs, [43], and one keyword argument, fields. We can also notice that relational fields are retrieved as a pair, with the related record’s ID and display name. That’s something to keep in mind when processing the data in your code.

The search and read combination is so frequent that a search_read method is provided to perform both operations in a single step. The same result as the previous two steps can be obtained with the following:

>>>    api.execute_kw(db,    uid,    pwd,    'res.partner',    'search_read',     [[('country_id',    '=',    'be'),    ('parent_id',    '!=',    False)]],    {'fields':     ['id',    'name',    'parent_id']})

The search_read method behaves like read, but expects as first positional argument a domain instead of a list of IDs. It’s worth mentioning that the field argument on read and search_read is not mandatory. If not provided, all fields will be retrieved.

Calling other methods

The remaining model methods are all exposed through RPC, except for those starting with “_” that are considered private. This means that we can use create, write, and unlink to modify data on the server as follows:

>>>    api.execute_kw(db,    uid,    pwd,    'res.partner',    'create',    [{'name':     'Packt'}]) 75 >>>    api.execute_kw(db,    uid,    pwd,    'res.partner',    'write',    [[75],    {'name':     'Packt    Pub'}]) True 
>>>    api.execute_kw(db,    uid,    pwd,    'res.partner',    'read',    [[75],    ['id',     'name']]) [{'id':    75,    'name':    'Packt    Pub'}] >>>    api.execute_kw(db,    uid,    pwd,    'res.partner',    'unlink',    [[75]]) True

One limitation of the XML-RPC protocol is that it does not support None values. The implication is that methods that don’t return anything won’t be usable through XML-RPC, since they are implicitly returning None. This is why methods should always finish with at least a return True statement.

Writing a Notes desktop application Let’s do something interesting with the RPC API. What if users could manage their Odoo to-do tasks directly from their computer’s desktop? Let’s write a simple Python application to do just that, as shown in the following screenshot:

For clarity, we will split it into two files: one concerned to interact with the server backend, note_api.py, and another with the graphical user interface, note_gui.py.

Communication layer with Odoo

We will create a class to set up the connection and store its information. It should expose two methods: get() to retrieve task data and set() to create or update tasks. Select a directory to host the application files and create the note_api.py file. We can start by adding the class constructor, as follows:

import    xmlrpclib class    NoteAPI():                 def    __init__(self,    srv,    db,    user,    pwd):                                 common    =    xmlrpclib.ServerProxy(                                                 '%s/xmlrpc/2/common'    %    srv)                                 self.api    =    xmlrpclib.ServerProxy(                                                 '%s/xmlrpc/2/object'    %    srv)                                 self.uid    =    common.authenticate(db,    user,    pwd,    {})                                 self.pwd    =    pwd                                 self.db    =    db                                 self.model    =    'todo.task'

Here we store in the created object all the information needed to execute calls on a model: the API reference, uid, password, database name, and the model to use. Next we will define a helper method to execute the calls. It takes advantage of the object stored data to provide a smaller function signature, as shown next:

                def    execute(self,    method,    arg_list,    kwarg_dict=None):                                 return    self.api.execute_kw(                                                 self.db,    self.uid,    self.pwd,    self.model,                                                 method,    arg_list,    kwarg_dict    or    {})

Now we can use it to implement the higher level get() and set() methods. The get() method will accept an optional list of IDs to retrieve. If none are listed, all records will be returned, as shown here:

                def    get(self,    ids=None):                                 domain    =    [('id','    in',    ids)]    if    ids    else    []                                 fields    =    ['id',    'name']                                 return    self.execute('search_read',    [domain,    fields])

The set() method will have as arguments the task text to write, and an optional ID. If ID is not provided, a new record will be created. It returns the ID of the record written or created, as shown here:

                def    set(self,    text,    id=None):                                 if    id:                                                 self.execute('write',    [[id],    {'name':    text}])                                 else:                                                 vals    =    {'name':    text,    'user_id':    self.uid}                                                 id    =    self.execute('create',    [vals])
```                                 return    id 
Let’s    end    the    file    with    a    small    piece    of    test    code    that    will    be    executed    if    we    run    the    Python file:

if name == 'main': srv, db = 'http://localhost:8069', 'v8dev' user, pwd = 'admin', 'admin' api = NoteAPI(srv, db, user, pwd) from pprint import pprint pprint(api.get())

If    we    run    the    Python    script,    we    should    see    the    content    of    our    to-do    tasks    printed    out.    Now that    we    have    a    simple    wrapper    around    our    Odoo    backend,    let’s    deal    with    the    desktop    user interface. 


**Creating    the    GUI**

Our    goal    here    was    to    learn    to    write    the    interface    between    an    external    application    and    the Odoo    server,    and    this    was    done    in    the    previous    section.    But    it    would    be    a    shame    not    going the    extra    step    and    actually    making    it    available    to    the    end    user. 
To    keep    the    setup    as    simple    as    possible,    we    will    use    Tkinter    to    implement    the    graphical user    interface.    Since    it    is    part    of    the    standard    library,    it    does    not    require    any    additional installation.    It    is    not    our    goal    to    explain    how    Tkinter    works,    so    we    will    be    short    on explanations    about    it. 

Each    Task    should    have    a    small    yellow    window    on    the    desktop.    These    windows    will    have    a single    Text    widget.    Pressing    *Ctrl*    +    *N*    will    open    a    new    Note,    and    pressing    *Ctrl*    +    *S*    will write    the    content    of    the    current    note    to    the    Odoo    server. 

Now,    alongside    the    note_api.py    file,    create    a    new    note_gui.py    file.    It    will    first    import the    Tkinter    modules    and    widgets    we    will    use,    and    then    the    NoteAPI    class,    as    shown    in    the following: 
from    Tkinter    import    Text,    Tk import    tkMessageBox from    note_api    import    NoteAPI 

Next    we    create    our    own    Text    widget    derived    from    the    Tkinter    one.    When    creating    an instance,    it    will    expect    an    API    reference,    to    use    for    the    save    action,    and    also    the    Task’s    text and    ID,    as    shown    in    the    following:

class NoteText(Text): def init(self, api, text='', id=None): self.master = Tk() self.id = id self.api = api Text.init(self, self.master, bg='#f9f3a9', wrap='word', undo=True) self.bind('', self.create) self.bind('', self.save) if id: self.master.title('#%d' % id) self.delete('1.0', 'end') self.insert('1.0', text) self.master.geometry('220x235') self.pack(fill='both', expand=1)

The    Tk()    constructor    creates    a    new    UI    window    and    the    Text    widget    places    itself    inside    it, so    that    creating    a    new    NoteText    instance    automatically    opens    a    desktop    window. 
Next,    we    will    implement    the    create    and    save    actions.    The    create    action    opens    a    new empty    window,    but    it    will    be    stored    in    the    server    only    when    a    save    action    is    performed,    as shown    in    the    following    code:
            def    create(self,    event=None):                                 NoteText(self.api,    '')                 def    save(self,    event=None): 

                             text    =    self.get('1.0',    'end')                                 self.id    =    self.api.set(text,    self.id)                                 tkMessageBox.showinfo('Info',    'Note    %d    Saved.'    %    self.id) 
The    save    action    can    be    performed    either    on    existing    or    on    new    tasks,    but    there    is    no    need to    worry    about    that    here    since    those    cases    are    already    handled    by    the    set()    method    of 
NoteAPI. 

Finally,    we    will    add    the    code    that    retrieves    and    creates    all    note    windows    when    the    program is    started,    as    shown    in    the    following    code:

if name == 'main': srv, db = 'http://localhost:8069', 'v8dev' user, pwd = 'admin', 'admin' api = NoteAPI(srv, db, user, pwd) for note in api.get(): x = NoteText(api, note['name'], note['id']) x.master.mainloop()

The    last    command    runs    mainloop()    on    the    last    Note    window    created,    to    start    waiting    for window    events. 

This    is    a    very    basic    application,    but    the    point    here    is    to    make    an    example    of    interesting ways    to    leverage    the    Odoo    RPC    API. 


**Introducing    the    ERPpeek    client**

ERPpeek    is    a    versatile    tool    that    can    be    used    both    as    an    interactive    Command-line Interface     (CLI )    and    as    a    Python    library ,    with    a    more    convenient    API    than    the    one provided    by    xmlrpclib.    It    is    available    from    the    PyPi    index    and    can    be    installed    with    the following:

$ pip install -U erppeek

On    a    Unix    system,    if    you    are    installing    it    system    wide,    you    might    need    to    prepend    sudo    to the    command. 


**The    ERPpeek    API**

The    erppeek    library    provides    a    programming    interface,    wrapping    around    xmlrpclib, which    is    similar    to    the    programming    interface    we    have    for    the    server-side    code. 
Our    point    here    is    to    provide    a    glimpse    of    what    ERPpeek    has    to    offer,    and    not    to    provide    a full    explanation    of    all    its    features. 

We    can    start    by    reproducing    our    first    steps    with    xmlrpclib    using    erppeek    as    follows:

import erppeek api = erppeek.Client('http://localhost:8069', 'v8dev', 'admin', 'admin') api.common.version() >>> api.count('res.partner', []) >>> api.search('res.partner', [('country_id', '=', 'be'), ('parent_id', '!=', False)]) >>> api.read('res.partner', [43], ['id', 'name', 'parent_id'])

``` As you can see, the API calls use fewer arguments and are similar to the server-side counterparts.

But ERPpeek doesn’t stop here, and also provides a representation for Models. We have the following two alternative ways to get an instance for a model, either using the model () method or accessing an attribute in camel case:

>>>    m    =    api.model('res.partner') 
>>>    m    =    api.ResPartner

Now we can perform actions on that model as follows:

>>>    m.count([('name',    'like',    'Packt%')]) 1 
>>>    m.search([('name',    'like',    'Packt%')]) [76]

It also provides client-side object representation for records as follows:

>>>    recs    =    m.browse([('name',    'like',    'Packt%')]) 
>>>    recs <RecordList    'res.partner,[76]'> 
>>>    recs.name ['Packt']

As you can see, ERPpeek goes a long way from plain xmlrpclib, and makes it possible to write code that can be reused server side with little or no modification.

The ERPpeek CLI

Not only can erppeek be used as a Python library, it is also a CLI that can be used to perform administrative actions on the server. Where the odoo shell command provided a local interactive session on the host server, erppeek provides a remote interactive session on a client across the network.

Opening a command line, we can have a peek at the options available, as shown in the following:

$    erppeek    --help

Let’s see a sample session as follows:

$    erppeek    --server='http://localhost:8069'    -d    v8dev    -u    admin Usage    (some    commands):                 models(name)                                                                                #    List    models    matching    pattern                 model(name)                                                                                    #    Return    a    Model    instance (...) Password    for    'admin': Logged    in    as    'admin' v8dev    
>>>    model('res.users').count() 3 v8dev    
>>>    rec    =    model('res.partner').browse(43) v8dev    
>>>    rec.name 'Michel    Fletcher'

As you can see, a connection was made to the server, and the execution context provided a reference to the model() method to get model instances and perform actions on them.

The erppeek.Client instance used for the connection is also available through the client variable. Notably, it provides an alternative to the web client to manage the following modules installed:

  • client.modules(): This can search and list modules available or installed

  • client.install(): This performs module installation

  • client.upgrade(): This orders modules to be upgraded

  • client.uninstall(): This uninstalls modules

So, ERPpeek can also provide good service as a remote administration tool for Odoo servers.

Summary

Our goal for this chapter was to learn how the external API works and what it is capable of. We started exploring it using a simple Python XML-RPC client, but the external API can be used from any programming language. In fact, the official docs provide code examples for Java, PHP, and Ruby.

There are a number of libraries to handle XML-RPC or JSON-RPC, some generic and some specific for use with Odoo. We tried not point out any libraries in particular, except for erppeek, since it is not only a proven wrapper for the Odoo/OpenERP XML-RPC but because it is also an invaluable tool for remote server management and inspection.

Until now, we used our Odoo server instances for development and tests. But to have a production grade server, there are additional security and optimization configurations that need to be done. In the next chapter, we will focus on them.

Last updated