Making webkit report work on OpenERP 7.0

wget http://wkhtmltopdf.googlecode.com/files/wkhtmltopdf-0.11.0_rc1-static-amd64.tar.bz2
wget http://wkhtmltopdf.googlecode.com/files/wkhtmltopdf-0.11.0_rc1-static-i386.tar.bz2

Then you have to install it, so you have to write this on the terminal:

tar xvjf wkhtmltopdf-0.11.0_rc1-static-amd64.tar.bz2
sudo chown root:root wkhtmltopdf-amd64
sudo mv wkhtmltopdf-amd64 /usr/local/bin/wkhtmltopdf
sudo chmod +x /usr/local/bin/wkhtmltopdf

Go to Settings>Technical>Parameters>System Parameters

And add this:

Key: webkit_path
Value: /usr/local/bin/wkhtmltopdf

If still error coming, then do-
sudo aptitude install ia32-libs

Handling too many schedulers in OpenERP

If there are too many schedulers running on a single instance, it leads to lot of concurrent update error. In order to speed up the process and minimize concurrent update error, we can do the following

  1. Assign different user to each schedulers. Make sure this user have sufficient rights to run the function.
  2. We can make schedulers run on different instance. For eg., we can set up different instances 8069, 8169, 8269 and so on. Next we can group schedulers based on their functionality and run it on separate instance.

Now, how do we make sure that an instance runs only on the configured instance. For eg., if I have 3 schedulers A, B and C which should  run on 8069, 8169 and 8269 respectively. In order to achieve this, we need to do the following

    • Add a new field- port_no in ir.cron class- 'port_no': fields.char('Port Number', size=4). Make sure this field is visible in view where user can configure the desired port.
    • Modify _run_job() to check if the scheduler is getting called on right instance.
       
      def _check_execute_cron(self, cr, uid, job):
          port_no = job.get('port_no',False)
          xmlrpc_port = tools.config.options['xmlrpc_port']
          execute_cron = True
          if port_no and xmlrpc_port:
              execute_cron = False
              if int(port_no) == int(xmlrpc_port):
                  execute_cron = True
          return execute_cron
      
      def _run_job(self, cr, job, now):
      """
      Inherited to check if the cron is running on specified port
      """
          try:
              execute_cron = self._check_execute_cron(cr, job['user_id'], job)
              if execute_cron:
                  nextcall = datetime.strptime(job['nextcall'], DEFAULT_SERVER_DATETIME_FORMAT)
                  numbercall = job['numbercall']
      
                  ok = False
                  while nextcall < now and numbercall:
                      if numbercall > 0:
                          numbercall -= 1
                      if not ok or job['doall']:
                          self._callback(cr, job['user_id'], job['model'], job['function'], job['args'], job['id'])
                       nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
                       ok = True
                  addsql = ''
                  if not numbercall:
                       addsql = ', active=False'
                  cr.execute("UPDATE ir_cron SET nextcall=%s, numbercall=%s"+addsql+" WHERE id=%s",
                              (nextcall.strftime(DEFAULT_SERVER_DATETIME_FORMAT), numbercall, job['id']))
      
                  if numbercall:
                     # Reschedule our own main cron thread if necessary.
                     # This is really needed if this job runs longer than its rescheduling period.
                     nextcall = calendar.timegm(nextcall.timetuple())
                     openerp.cron.schedule_wakeup(nextcall, cr.dbname)
          finally:
             cr.commit()
             cr.close()
             openerp.cron.release_thread_slot()

Reports in OpenERP using Views

Here, by reports I mean to combine more than one table to give user some useful information.

One way of doing it is to put related field in an object to replicate other object’s data. For eg., if I want to show sale.order Reference No. and Order Date fields in sale.order.line object, then I can make 2 related fields in sale.order.line to capture the data. But this will unnecessarily increase your DB size. So, a better option is to use views.

Views is same as what we define at database level.  Apart from providing summary information, views can be used to show data of 2 or more tables in one screen. Now, lets see how we can do this in OpenERP-

Step 1- Creating a class

Create a class with _auto=False. This ensures PostgreSQL table is not generated.

Step 2- Defining columns

Column names should be same as what we will define in views.

Step 3- Providing View definition

In def init(), we need to provide view definition such that the select clause has field names which exactly matches the name defined in _columns.

For sample, check addons/sale/report/sale_report.py.

Threads in OpenERP

Threading in OpenERP can be very useful especially on Gunicorn instance as it helps in accomplishing a task ‘n’ times faster where n is the number of concurrent threads running.

Step 1- Define custom Thread class


import threading
class SampleThread (threading.Thread):
   def __init__(self, cr, uid, arg1, arg2, context=None):
      self.cr = cr
      self.uid = uid
      self.arg1 = arg1
      self.arg2 = arg2
      self.context = context
      threading.Thread.__init__(self)

   def run(self):
      self.cr = pooler.get_db(self.cr.dbname).cursor()
      my_instance = pooler.get_pool(self.cr.dbname).get('sale.order')
      my_instance._do_action(self.cr, self.uid, self.arg1, self.arg2, self.context)

Step 2- Calling Threads

Threads can now be called from main function-

def _call_threads(self,cr,uid,ids,context={})
   threads = []
   for i in range(0,no_of_process):
      thread = SampleThread(cr,uid,arg1,arg2,context)
      threads += [thread]
      thread.start()
     .
     .
     // Remaining Code

no_of_process is the number of concurrent threads we wish to run.

Step 3- Wait for the threads to terminate

We now need to ensure that all the threads have completed, so that the remaining code in the _call_threads function executes. For that, we do-

for thread in threads:
   thread.join()

If we don’t do this, then the calling thread and called threads executes simultaneously.

Date Range Search in OpenERP 6.1

In older version, when we add date field to search view it used to give range of date filter. However, in 6.1 it only allows to select the exact date.

So, for range filter we need to do the following workaround. For example, adding a date range for field ‘Date’ in sales order, we need to add two dummy function fields-
‘date_order_from’:fields.function(lambda *a,**k:{}, method=True, type=’date’,string=”Date from”),
‘date_order_to’:fields.function(lambda *a,**k:{}, method=True, type=’date’,string=”Date to”),

And then in the search view, give-

And now you have a date range filter in your search view…

Running Schedulers when OpenERP is running with Gunicorn

Since, the main cron thread is simply not running at all when using Gunicorn, we can make a new instance of our server and make it run on a different port. This new instance will be a normal one without gunicorn and can be used specifically for Schedulers or Cron jobs.

We can also have multiple instances of Gunicorn based on the requirement. One instance can be reserved for top-level management and second one for all other users.

Inheriting from mulitple class in OpenERP

This kind of scenario comes up when we want same set of fields to be repeated in core modules.

For eg., if I want order status page to be displayed in Sales and Purchase Order both. So, I define a order_status object which holds the common fields. Then I will inherit sale_order and purchase_order objects and also inherit order_status object.

Now, the next question is to how to assign more than one object to _inherit attribute. The trick is to define multiple class in list as follows-

class sale_order(osv.osv):
  _name='sale.order'
  _inherit=['sale.order','order.status']

 


class purchase_order(osv.osv):
  _name='purchase.order'
  _inherit=['purchase.order','order.status']

Upgrades in OpenERP 6.1 as compared to 6.0.x

Here are some of the changes to be kept in mind while switching to this improved and smarter version. This is for developers switching to 6.1 version from 6.0.x-

View:

  • The web is integrated in server, so we just have to start the server and web automatically starts at http://localhost:8069 by default.
  • By defaut the session of web does not time out just like in GTK client
  • Unlike in 6.0.x, one2many field does not save in database even if Save & New or Save & Close button is clicked. To actually Save it in database, one have to click on main Save button of parent object.
  • If a menu is right clicked to open in new tab or window it does not open that menu. Rather it takes you to the home screen.
  • Remembers the last database accessed. This removes the pain of selecting the right database each time you log in, specially when you have multiple test databases.

Coding:

  • Now, one menu cannot be clicked to open an action and also to open its child menus. If a menu has child menus, then it cannot have action assigned to it.
  • many2many declaration is simplified. The relation name and relation columns need not be specified explicitly. Only this is enough- ‘invoice_ids’: fields.many2many(‘account.invoice’,string=’Invoices’)
  • For circular reference, there is no need to split classes in two. Example taken from OpenERP 6.1 Release Notes- In v6.0, we used to write the following code:

    class res_groups(osv.osv):

    _name = ‘res.groups’

    _columns = {

    ‘name’: fields.char(‘Name’),

    }

    res_groups()


    class res_users(osv.osv):

    _name = ‘res.users’

    _columns = {

    ‘name’: fields.char(‘Name’),

    ‘group_ids’: fields.many2many(‘Groups’, ‘res.groups’),

    }

    res_users()


    class res_groups2(osv.osv):

    _inherit= ‘res.groups’

    _columns = {

    ‘user_ids’: fields.many2many(‘Users’, ‘res.users’),

    }

    res_groups2()


    As of 6.1, this code can be reduced to:

    class res_groups(osv.osv):

    _name = ‘res.groups’

    _columns = {

    ‘name’: fields.char(‘Name’),

    ‘user_ids’: fields.many2many(‘Users’, ‘res.users’),

    }

    class res_users(osv.osv):

    _name = ‘res.users’

    _columns = {

    ‘name’: fields.char(‘Name’),

    ‘group_ids’: fields.many2many(‘Groups’, ‘res.groups’),

    }

  • The debug mode can be turned on by simply appending ?debug to the OpenERP URL.

Sorting and Filtering of functional fields in OpenERP

As per OpenERP policy, sorting and filtering of functional field will not be available in version 6.0.

So, as a workaround I found a way out. For all those functional fields which requires sorting and filtering we make a duplicate normal field (if the functional field is of type float, we create a new field of type fields.float). And keep the duplicate normal field in tree view and the actual functional field in form view. Now, to copy the value of functional field into duplicate normal field there are 2 strategies.

1. Accurate Value Method

The value of functional field is calculated when the value in other fields change. So, we can inherit create() and write() methods of that object and update our new duplicate normal field appropriately.

2. Approximate Value Method

We can run a cron job which calls the function of functional field and updates the duplicate normal field. Though, this method does not show the accurate value in tree view, but the user can see the exact value in form view. This method should be used when the overhead of create() and write() method is too expensive.