A few tips on using the OpenERP XML RPC API

I’ve been developing applications that talk to OpenERP via it’s API for a little while now and I figured it would be worth noting down some general pointers for using it. The API is used by all the UI clients for OpenERP so it definitely allows you to do everything that they do. It’s also got a reasonable amount of access to the data layer so you can do an awful lot via it. From simply creating interfaces to it, to streamlining a process with a slick UI, there are a lot of possibilities with it.

Here are my tips for developing with it,

  1. Run OpenERP with the --log-request and --log-response command line flags to see how OpenERP achieves it’s tasks. It’s also helpful to see what your application is sending.
  2. Don’t be afraid to read the code. Watching OpenERP is a great starting point, but sometimes you need to look at the code to understand what a parameter is for, or how it’s getting the data.
  3. Don’t be afraid to use the debugger. Putting --debug on the command line allows the python debugger to kick in when there is an exception. It also allows you to stick ‘import pdb; pdb.set_trace()’ onto a line in the code you want to investigate and drop into there in the debugger.
  4. Pass the context like you are told to, it makes life easier if you are setup to simply pass it along with your calls (but also be ready to add data to it). The context contains things like the language which is used for translations. You want your applications language to be consistent with any users of OpenERP itself otherwise things will get weird. Also note that some calls require extra information to be passed in via it. It’s not a completely opaque blob that you simply pass about between calls.
  5. Limit the columns you ask for. When you make a read call you can specify which columns you are interested in. Do that from the start or you’ll end up with performance problems later on. Once you have a partner with 1000’s of orders and invoices etc. a simple read of the res.partner will take a significant chunk of time if you aren’t limiting what you read from it.
  6. Don’t be afraid to extend OpenERP. Even if you aren’t a Python developer by trade, if you’re doing serious data modification you’re better off creating a module and calling a method on that than making lots of API calls. The main reason for that is to get everything into a single transaction. The other reason is speed.
  7. Be careful about filtering by calculated fields, they generally get turned into a ‘TRUE’ statement in the SQL. This can really screw things up when you have OR conditions in your filter. Use the --log-sql if you’re unsure what is happening.
  8. False is used for Null/None in the XML RPC
  9. Returning a dictionary from an OpenERP method via the API requires the keys to be strings. i.e you can’t simply return { id: quantity }. You’d need to do { str(id): quantity } to prevent a crash. None is also a no-no for dictionary values. Convert them to False if you want to be consistent with OpenERP.
  10. Formatting of numbers to n decimal places is largely done client side. OpenERP provides you all the info you need, but you need to deal with that yourself.
  11. Beware of gapless ir.sequence sequences in batch jobs. They have a high potential for causing contention.

26 thoughts on “A few tips on using the OpenERP XML RPC API

  1. Hi,
    Could you explain what you mean by “Beware of gapless ir.sequence sequences in batch jobs” ?

    What is it exactly ?

    Regarding the key string requirement (i would say a “bug” ?)… how do you if you don’t modify / add your own module ?

    For instance, if i do a standard “price_get” method (in order to calculate the price of a product)… I face the “key string” problem… and I receive a nice error message from OpenERP !

    I bypass this issue, by using a… JSON query. Am i wrong ?

    Last but not least… in standard… it’s impossible to process queries with a JOIN like in SQL.

    For instance : without adding / modify a module, I dont how to get :

    -the lines of purchases orders that are “confirmed”… but with the flag “invoiced” to false (in the purchase.order table), which would be 1 query in SQL.. but with XMLRPC… i need to get all the purchase orders lines that are “confirmed”, then take their ID, then make a search with those IDs on purchase.order to screen out the flag invoiced…

    It’s a mess.

  2. The gapless sequences ensure that sequence numbers have no gaps. i.e. SO0001, SO0002, SO0003 … Rather than say skipping SO0003. They are only really used on the account moves by default and they ensure that the account movements have consistent sequential names. With the normal sequences if a transaction asks for a number, then needs to rollback because of some other problem, the sequence doesn’t worry about it, that number just gets skipped in the sequence. With the gapless sequences a lock is obtained on the sequence number field so the next process will have to wait until the current transaction is done before it can read it, and therefore if there is a rollback the number won’t be lost. The downside is that if the transaction takes a long time this can block things for quite a while. It also increases the chances of long running processes that depend on them from bailing because of a contention.

    I hadn’t really come across any places where the key string issue appeared to be an issue in the OpenERP code, but you appear to be right that that method does indeed suffer from that problem. You quite often find methods that were never expected to be called by the users of the API but are just about callable, but not won’t quite work. I have generally added extra methods in my own method to call them and make them usable where necessary. The json api might avoid that particular issue.

    The search is quite easy, joins on search terms are done automatically and fairly simply. The query you want is easily done like this,

    server.execute(db, uid, pwd, 'purchase.order.line', 'search', [('order_id.invoiced', '=', '1') ])
    

    As you can see here, you specify the relationship field name ‘order_id’, then the field you want to query on, ‘invoiced’. You can quite often descend through several models and it will do the right thing.

  3. Brillant ! Thanks for the tip. I never saw this on all the documentation. How did you find it ?

    Speaking about doc, that’s quite an issue with OpenERP. It’s really poor. So people have to spend hours and hours perusing websites, blogs (yours !), tidbits, forums, compounded by the problem of version number… it’s exhausting.
    😉

    Anyway, let’s hope once they release version 8, they will have more time and ressources to spend on doc work.

  4. Version 6’s documentation seemed okay if limited, after that it seemed to struggle to keep up. Some times it can be worth looking in the different version of the documentation too as some parts are the same.

    This is the page I normally refer back to when I’m looking for the details on how search works – ORM Methods. Apart from that I just look at what is happening in OpenERP itself by watching the debug log.

  5. Unfortunately I don’t believe it is. If you do find a way I’d be interested to know about it. When I’ve really needed to do things that the ORM can not do I’ve added a method to a module that implements it in SQL.

    • It’s rather weird to allow to make a join search, but not a read. I’ve tried many combinations with the read method, but no success.

      I know that it’s piece of cake to create a custom module that would process SQL queries, but why it’s not available in standard ?

      That’s a short coming, and I’m wondering if it’s not the dark side of OpenERP’s model : “oh people can do it with a custom module… so no need to bother”…

      And eventually, everybody… is recreating the wheel, each time, by themselves, over and over again !

      I mean we are not talking here about a “luxury” item or a “gimmick” function… but rather an essential tool.

      So to go back to my example about purchase_order_line and purchase_order… I can do the search now, but I can’t display -in standard- fields in both table with the IDs I receive.
      😉

      Anyway. End of the rant.

      • Keep an eye on their release notes and roadmaps. I have heard tell of improved ORM’s etc. in the future, but I haven’t really paid too much attention because I only really care about what is current and stable.

    • Within reason yes. Assuming the method hasn’t been marked private (those starting with a _), and has a method signature that is roughly compatible with remote execution (normally starting (self, cr, uid, …)) then it is possible to call most methods. Some methods aren’t expecting to be called via the API and so don’t conform to the standards expected, but you can generally try it and see if it works. Methods that return nothing will cause OpenERP to throw an error (after executing the function) and those that return a dictionary containing numeric keys will also cause OpenERP to throw an error.

  6. I’m using xml-rpc to import products. However the qty_available field can’t be set directly with this method. Do you know a way to manage this? Maybe via the model?

    • That’s a calculated field that is based on movements in the warehouse. In order to affect that figure you need to process stock movements (using the model stock.move).

  7. Hi, I am struggling to get the parameters for certain methods right. I am trying to get them for the create_from_ui method from the pos.order model, which posts payment for a POS sale and then also refund function. Have you worked with these and is there any advice you can provide on how to get these parameters right?

    • This looks like a fairly complex case so I’d suggest a combination of reading the code and looking at when the call is made. It looks like the call is made by javascript so you can probably look at the call in the network traffic in your browser. In Google Chrome for example I use the developer tools by hitting F12 then going to the network tab.

      The code looks like it is expecting a list of dictionaries. My impression is that it wants a data structure like this,

      [
          {
              'data': { 'name': 'somename' },
              'amount_return': 10, # cash being returned
              'pos_session_id': 3,
              'user_id': 3,
              'lines': [
                  # these are pos.order.line items, see that models description
                  # for a list of all the fields.  these should be the minimum required
                  {
                      'qty': 1, # defaults to 1
                      'product_id': 1,
                      'price_unit': 3.3,
                  }
              ],
              'name': 'POS Reference',
              'partner_id': False,
              'to_invoice': True, # introduced in v8
              'statement_ids': [
                  { 
                      'amount': 10,
                      'name': '2014-03-01', # this might be the wrong format, it might also require the time
                      'statement_id': 1,
                      'note': 'some note',
                      'journal_id': 1 
                  },
              ],
          }
      ]
      

      Note the data structure is hand written in python syntax, convert to whatever format you need to send it in. Also note that I’ve pointed you to the trunk version in github, make sure you look at the code from the actual version you are working on. Fundamentally the code hasn’t changed that much between 7.0 and trunk, but it has changed so it’s worth ensuring what you are doing is precisely right for the version you are working with.

  8. Hi,
    I am using the existing POS order creation to create a gapless sequence for my transactions. However, since the volume is high, concurrent updates on the ir_sequence often take place and the transactions roll back.

    Can you please suggest a workaround to handle these locks. I am quite new to openerp and don’t understand how to get around it. If I understand correctly, the default mechanism of ir_sequence uses UPDATE NOWAIT, meaning that it will instantly fail the transaction if there is another concurrent one which has locked the table. I just want it to wait until the previous transaction has committed and then proceed instead.

    I have spent days going through the official openerp forums (in futility), which have discussed resolutions to this problem, but I can’t understand what it is that they did – mostly because the technical discussions were incredibly hardcore.

    Please help me out here.
    Thanks in advance!

    • Odoo (formerly OpenERP) do provide commercial support which might be a far simpler way to get help with this sort of problem so I would be remiss if I didn’t mention that first. Given that you appear to be running a successful business with the software it could well be worth getting a support contract where you can get proper hands on help directly from the people who know the most about the software.

      Onto the problem. Can you afford to turn off the gapless sequence numbers? Is it a legal requirement for that data sequence to be without gaps? If it’s not then simply turn off the gapless feature. It’s only set to that by default on account journal entries. Most of the other sequences use their ‘standard’ algorithm which uses a non locking approach. It is guaranteed to be unique, but not gapless.

      If you do require it to be gapless and you are talking about account journal entries then you have a more fundamental problem. You need to have a central authority on numbers and that also needs to be in the transaction so the technical solution that OpenERP provides is correct. The thing that you need to attack is the length of time each transaction takes to complete. Over time OpenERP, now Odoo has improved performance of the software when it comes to posting accounting transactions and in general so it might be worth checking out the latest version. If you have got this sort of problem I would again recommend some sort of commercial support, either directly from Odoo or a local partner who works with the technical details of OpenERP/Odoo. Their partners page is a good starting point if you want to find someone, but I would suggest that Odoo themselves are likely to be the best point of call for a problem of that nature.

      Sorry about the constant Odoo/OpenERP flipping, with the name change with the latest version and the fact that I don’t know which version you are running I’m not sure the best way to refer to it without causing confusion.

      • Of course I should also mention that one of the ways to attack the speed of the transaction is to simply chuck faster hardware at the problem. We have found that when we needed greater throughput going for a beefy server and avoiding virtualisation (the server was previously virtualised) greatly improved our capacity.

      • Thanks for your prompt response! We are currently using OpenERP 7, and we are attempting to build a custom POS module for testing purposes. As I understand it, Openerp does not provide support for custom modules. We followed your advice and increased the server configuration and found a good improvement! But I would like to handle the lock explicitly to ensure consistency just in case. When you mentioned ‘standard’ algorithm, can I achieve this by simply changing the corresponding value in the ‘implementation’ column in the ir_sequence table?
        Also, if I were to maintain my own sequence table solely for this case, do you think that would help?

      • It is best to change the ‘implementation’ to standard from the user interface. When you do that OpenERP does some house keeping that generally keeps some things sane. In order to get to that option you need to ensure that your administrator user has the ‘technical features’ ticked. With that you will then be able to go to Settings -> Technical Features -> Sequences (roughly from memory).

        Flipping to standard should be all you need to do. It removes all contention from the sequences.

  9. Hello,
    I am tying to call the method attendance_action_change via the execute_kw RPC function to sign employees in or out in Odoo. I am struggling with finding the right parameters.

    this is my code :

    ##Calling Method##
    models = xmlrpclib.ServerProxy(‘{}/xmlrpc/2/object’.format(url))
    models.execute_kw(db, uid, password,
    ‘hr.employee’, ‘attendance_action_change’,
    [”])
    True

    Can you help me with filling in the brackets, please?
    When i run this program i get nothing returned and no errors as well.
    Thanks in advance

    • Odoo have forums were you may get a better answer – https://www.odoo.com/forum/help-1

      Having said that, a quick grep of the source code shows an instance of that call being made which suggests what you should be passing it,

      addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py
      94: emp_obj.attendance_action_change(cr, uid, [emp_id], {‘action’:’sign_out’, ‘action_date’:data.date})
      102: emp_obj.attendance_action_change(cr, uid, [emp_id], {‘action’:’action’, ‘action_date’:data.date})
      158: emp_obj.attendance_action_change(cr, uid, [emp_id], {‘action’:’sign_in’, ‘action_date’:data.date})

      https://github.com/odoo/odoo/blob/8.0/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py

      Fundamentally you need to pass in the employee id’s, then the action and date you want to do that for. It looks like the ‘action’ action will probably check whether you are signed in or out currently to decide which action is appropriate for the scenario.

      • Thank you very much for the fast reply.
        (I am also trying on the Odoo forum but they still have to verify my account.)
        I am still figuring out how to pass those parameters correctly. I tried several ways of writing them..
        [’employee_id’, ‘action’, ‘name’] seems the most correct/logical to me,
        but returns
        “….line 241, in wrapper\n return old_api(self, *args, **kwargs)\nTypeError: attendance_action_change() takes at most 5 arguments (6 given)\n’>”

        https://github.com/odoo/odoo/blob/8.0/addons/hr_attendance/hr_attendance.py

        80: ’employee_id’: fields.many2one(‘hr.employee’, “Employee”, required=True, select=True),

        78: ‘action’: fields.selection([(‘sign_in’, ‘Sign In’), (‘sign_out’, ‘Sign Out’), (‘action’,’Action’)], ‘Action’, required=True),

        77: ‘name’: fields.datetime(‘Date’, required=True, select=1),

        What do you think about that?

  10. This is probably what you need,

    models.execute_kw(db, uid, password, ‘hr.employee’, ‘attendance_action_change’, [employee_id], {'action':'sign_in', 'action_date':'2014-04-01' })

  11. models.execute_kw(DB, uid, PASS,
    ‘hr.employee’, ‘attendance_action_change’, [state[‘id’]], {})

    works! state[‘id’] gives the id number. Seems like I have to use the id instead of the uid.
    Thanks! your answers helped me a lot.

    • models.execute_kw(db, uid, password, ‘hr.attendance’, ‘create’, [{
      ’employee_id’:employeeid,
      ‘action’:’sign_in’,
      ‘name’:currdt.strftime(“%Y-%m-%d %H:%M:%S”)
      }])
      works for me in odoo 9

  12. Hello colinnewell
    i want to update my mrp price on change so i will try that

    my method calling like that
    mrp is a new filed i would like call onchange this filed mrp
    mrp_count is my onchange method name
    models.execute_kw(dbname, uid, pwd, ‘product.template’, ‘mrp_count’, mrp, {})

Leave a reply to colinnewell Cancel reply