In Odoo Qweb report, you probably want to change the time to something else, like add few hours, or change the timezone, or so on. To do that, you would need to use ‘t-esc’ template attribute of Odoo. Here is an example of how to add 6 hours to the original timestamp and then convert the time to string to show the value according to the user preference:
You are seeing a time in the Odoo form view, which is using the correct timezone, but when you try to download the report of the same form view data, you see, the timezone is changed to UTC or something else. How to fix this?
The issue appears when the user has configured a different timezone or has not, while the Odoo system uses a central timezone. Make sure to set the timezone for the user to same as the one Odoo uses. You may do so, from
Odoo >> Settings >> Users >> Select User >> Edit >> Preference >> Set Timezone
If this does not solve the problem, then the problem probably appears because Odoobot is set to use a different timezone, and your report is generated using ‘sudo()’ function. To set the timezone for Odoobot
Odoo >> Settings >> Users >> From the Filters, select ‘Inactive Users’ >> Click on Odoobot >> Edit >> Preference >> Set the Timezone
Installing Odoo 15 along with the CentOS 7 and the latest PGSQL repo has changed pretty a lot. I will try to cover solutions to a few errors along with the straightforward steps on installing Odoo 15 in CentOS 7.
First Step First
Update your CentOS 7 installation and install Epel-release
yum update -y
yum install epel-release
Install Python 3.8
We will use Python 3.8 for Odoo 15. We will use Software Collection Repository or SCL to install our Python binary. You may find details of SCL here:
Once done, you can now install Python 3.8 using the following:
yum install rh-python38 -y
Also, install python38-devel as Python.h is used to compile psycopg2 and python-ldap package. From Odoo 15, you need this to get going:
yum install rh-python38-python-devel -y
Note: The above is used to resolve an error like the following
fatal error: Python.h: No such file or directory
Now, we will install a few prerequisites to install Odoo 15. One difference between the old version installed and the new is that you need to load GCC-c+ now along with the GCC compiler. Otherwise, you will see an error like the following:
gcc: error trying to exec ‘cc1plus’: execvp: No such file or directory
So, to install the pre-requisites, run the following:
Once done, now can you initiate the PostgreSQL and start the database server
systemctl start postgresql-13.service
systemctl enable postgresql-13.service
# create the postgres user odoo
su - postgres -c "createuser -s odoo"
Brilliant, now, one more additional thing we need to resolve. With the latest Postgresql 13, you might still not be able to use the libpq. You need to install it manually. Otherwise, you will see an error like the following:
fatal error: libpq-fe.h: No such file or directory
To resolve this error, you need to install these libraries manually with the following command:
yum install libpq5 libpq5-devel -y
Remember to install libpq5-devel as the source of the libpq would be used to compile psycopg2.
Now, let’s move to the next step of installing wkhtmltox. The version for wkhtmltox has remained the same for pretty long. The following shall work till now:
yum localinstall wkhtmltox-0.12.6-1.centos7.x86_64.rpm
This specific tool is used to generate reports in Odoo, without this, you might not be able to use pdf/html reports using Qweb in Odoo.
Final Step: Install and Configure Odoo 15
We will here download the source from Github and install all the dependent packages. First, we switch to the user odoo
su - odoo
Now, clone the git repo for Odoo 15 to a folder odoo15 using the following:
Once done, now, we can enable python3.8 and create a virtual environment for our Odoo installation. First, enable the Python3.8 using scl:
scl enable rh-python38 bash
Now create a virtual environment for our Odoo15 installation:
python3 -m venv odoo15-venv
Activate the virtual environment we just created
Now, we upgrade the pip and install wheel package:
pip install --upgrade pip
pip3 install wheel
Now, before we can install the requirements file using pip3 package installer, here is an error you will face when compiling psycopg2
Error: pg_config executable not found.
Now the problem is understandable, pg_config file is usually placed under the binary folder of pgsql which is:
For some reason, our installer fails to identify this. To solve the issue, we first, load this in the $PATH variable before running pip3 for requirements.
Now, you can run the pip3 installer:
pip3 install -r odoo15/requirements.txt
This shall be complete without any error if you have solved the solutions I had given above. If any of them are missed, you should double-check all the mentioned errors above.
Now exit the venv:
deactivate && exit ; exit
Now, the first step for configuration, edit the /etc/odoo.conf file
Paste the following:
; This is the password that allows database operations:
admin_passwd = set_the_password_to_create_odoo_database
db_host = False
db_port = False
db_user = odoo
db_password = False
addons_path = /opt/odoo/odoo15/addons
; You can enable log file with uncommenting the next line
; logfile = /var/log/odoo15/odoo.log
Replace ‘set_the_password_to_create_odoo_database’ with the one you want to use to allow odoo installer to create the database for you.
Odoo15 Service File
Now, we will create a service file to start/stop/restart our Odoo 15 installation.
stock_picking is one of the core model for Odoo if you using Odoo for ‘Inventory’. Now, if you use ‘Sales’ module, then stock_picking would be extended with a new field for model and column for database table, naming ‘sale_id’. This can be used to detect if the picking is originated from a sale order or not. But if you install ‘Purchase’ module, then stock_picking model is extended with ‘purchase_id’ like the ‘sale_id’ for purchases, but the database isn’t expanded with a column like ‘Sale’ module.
What does this mean?
This means, if you use Odoo ORM, only then, you may use purchase_id of a stock_picking. An example could be like the following. Let’s say, we would like to pick the pickings that originated from purchase orders, aka, GRN, we could use something like this:
This works, only if you are not trying to make a report from a huge lot of pickings, purchase orders and sale orders, when you want to use SQL statement to produce efficient joins and generate the report quickly.
Let me demonstrate what I meant
We know, stock_picking has a field called sale_id and also this also belongs to the database column as well. Hence, to get all the pickings belongs to sale order, we may first use the ORM:
query = """select * from stock_picking where sale_id is not null"""
result = self.env.cr.fetchall()
Now, the second example is not only faster, but also, it allows you to extend the facility further to use joins or select specific field of a table result, which is only possible using ‘read’ Odoo ORM method, again, domain specification is not permissible like it is available in ‘search’.
We are able to do things like the following with the sql:
query = """select sale_order.name, stock_picking.name from stock_picking left join on stock_picking.sale_id = sale_order.id where stock_picking.sale_id is not null"""
result = self.env.cr.fetchall()
This would give you a result of each sale order with it’s picking name. To produce a result like the above using ORM is costly as it follows ‘N+1’ algorithm, hence inefficient in making reports or scaling the software.
Now, we understand, we are able to use such field and make the reports efficient using SQL as sale_id is distinctively available in the database. But what if you want to check how the product has been purchased, and then sold? Then, we also need purchase_order model to connect to our above query, right? But unfortunately, as ‘Purchase’ module doesn’t add a column purchase_id, we are unable to use this directly.
So, how can we still use purchase_id in the SQL Query to generate report in Odoo?
First, we need to see, how purchase_id is added in Odoo.
purchase_id is added in stock_picking model in the ‘purchase_stock’ module. If you open the following file:
you may see, how purchase_id is defined as related Many2one field:
A related field in Odoo, is like a pointer, a syntactic sugar of foreign key for less used fields. If the field is highly used, this might cause performance issue, as Odoo has to do multiple lookups unlike direct lookup for a related field. Now, get to the point, purchase_id is related to ‘move_lines.purchase_line_id.order_id’. This is a long relation. Let me go one by one:
move_lines : stock_picking has an One2many relation with stock.move model, that derives the available moves for the picking.
purchase_line_id: Each move line derived from a purchase order line, and while doing so, it keeps the ID of the purchase order line in a foreign key of stock.move model, namely purchase_line_id.
order_id: Each purchase_order_line has a foreign key with the purchase.order model kept in order_id field.
Now, we know, how the purchase_id derives the purchase_order id using the following relation:
Picking > Moves > Purchase Order Line > Purchase Order
Now we can use the following kind of relation for detecting purchase order from stock picking:
select purchase_order.name, stock_picking.name from stock_picking left join stock_move on stock_move.picking_id = stock_picking.id left join purchase_order_line on purchase_order_line.id = stock_move.purchase_line_id left join purchase_order on purchase_order.id = purchase_order_line.order_id where stock_move.purchase_line_id is not null group by stock_picking.name, purchase_order.name
Here, we are able to get the picking and purchase in relation with one query. This concept can be used to derive many data, like, let’s say, you would like to see, how many of your products are purchased, then, sold and returned, all can be done in few queries, without having N+1 problem.
When looking at the html report in Odoo, locale fonts look ok, but if you download the Qweb report to print in pdf format, it prints gibberish. How to fix that?
Odoo uses a templating engine for reporting called ‘Qweb’. Qweb can be used to generate two types of reports. One is HTML and the other is PDF. Odoo primarily uses Qweb engine to generate the HTML code. After that, it uses a tool called ‘wkhtmltopdf’ to convert the report to pdf and make it printable. Now when, we look at the HTML version of the report, fonts are shown based on Unicode supported browsers or the fonts you have installed on your computer. But when you try to convert this to PDF using wkhtmltopdf, that tool has to have exclusive access to those fonts to be able to convert them from HTML to pdf for you. As wkhtmlpdf command runs in the server you have installed Odoo, hence, you would need to install the font package in the server.
In my case, I required to install Bengali fonts. For CentOS, it is available under the lohit package, that contains several indian fonts including bengali. To install bengali font package in CentOS 7, use the following command:
yum install lohit-bengali* -y
Once done, your wkhtmltopdf should be able to read the bengali fonts from your html/qweb templates and able to convert them to PDF for you.
res.users and res.partners tables are two base tables of odoo. If you would like to inherit and extend them, remember, you can’t do it from the Odoo user view. The reason is, when you do an upgrade from the user view, it has to be something that works over base module, not base module itself. Hence, you will get a 500 error or internal server error when trying to upgrade the module.
We will make a simple module for res.users to extend the model to add a field called ‘access_token’ for each user, and generate a key automatically when a user is added.
I will only post the model file and the view file here. I expect you already know how to write an Odoo module.
This is my res_users.py file
from odoo import api, fields, models
import string, random
_inherit = 'res.users'
access_token = fields.Text(string='API Access Token', default=False)
def create(self, vals):
key = ''.join(random.choices(string.ascii_lowercase + string.digits, k = 48))
user_id = super(res_users_ex, self).create(vals)
user_id.access_token = key
The next one is the binary of odoo with it’s location, which should be odoo-bin. With the parameter -d, you give your database name, and with the parameter -u, you need to give your module name. After you run the command, you should see no ‘Error’ or Red marked line in your console. If not, it shall be upgraded. Now do control + c, and start your odoo again to see the new fields being visible in your Users tab.
There are times, when you might suddenly see your Odoo is shutdown automatically, without warning. Once you enable to logging, you could see an error like the following:
virtual real time limit (151/120s) reached.
or in full details like the following
2021-04-22 06:46:44,054 32685 WARNING ? odoo.service.server: Thread <Thread(odoo.service.http.request.140015617943296, started 140015617943296)> virtual real time limit (151/120s) reached.
2021-04-22 06:46:44,054 32685 INFO ? odoo.service.server: Dumping stacktrace of limit exceeding threads before reloading
2021-04-22 06:46:44,060 32685 INFO ? odoo.tools.misc:
# Thread: <Thread(odoo.service.http.request.140015617943296, started 140015617943296)> (db:n/a) (uid:n/a) (url:n/a)
File: "/opt/rh/rh-python36/root/usr/lib64/python3.6/threading.py", line 884, in _bootstrap
File: "/opt/rh/rh-python36/root/usr/lib64/python3.6/threading.py", line 916, in _bootstrap_inner
File: "/opt/rh/rh-python36/root/usr/lib64/python3.6/threading.py", line 864, in run
File: "/opt/rh/rh-python36/root/usr/lib64/python3.6/socketserver.py", line 654, in process_request_thread
File: "/opt/rh/rh-python36/root/usr/lib64/python3.6/socketserver.py", line 364, in finish_request
self.RequestHandlerClass(request, client_address, self)
File: "/opt/rh/rh-python36/root/usr/lib64/python3.6/socketserver.py", line 724, in __init__
File: "/opt/odoo/odoo14-venv/lib64/python3.6/site-packages/werkzeug/serving.py", line 329, in handle
rv = BaseHTTPRequestHandler.handle(self)
File: "/opt/rh/rh-python36/root/usr/lib64/python3.6/http/server.py", line 418, in handle
File: "/opt/odoo/odoo14-venv/lib64/python3.6/site-packages/werkzeug/serving.py", line 360, in handle_one_request
self.raw_requestline = self.rfile.readline()
File: "/opt/rh/rh-python36/root/usr/lib64/python3.6/socket.py", line 586, in readinto
2021-04-22 06:46:44,060 32685 INFO ? odoo.service.server: Initiating server reload
This is because Odoo is killing zombie processes and probably mistakenly crashing your Odoo completely while doing so. The parameter that is used for this purpose, can be found in Odoo documentation:
Prevents the worker from taking longer than <limit> seconds to process a request. If the limit is exceeded, the worker is killed.
Differs from --limit-time-cpu in that this is a “wall time” limit including e.g. SQL queries.
Defaults to 120.
You may start your service command with something like –limit-time-real 100000 to avoid Odoo from auto killing processes. A command could look like the following if you edit your service file located at:
There are two ways to see the Odoo Logs. One is rough and can be used to see the latest Odoo logs, it’s the Journal tools. You may do this using the following if your Odoo service is installed as odoo13 for example
journalctl -u odoo13
Note: If you are having trouble primarily in installing Odoo properly, you may check the following:
The other way, is the enable logging to a file. This has to be enabled from the odoo.conf file which is located under /etc/ folder. First we open the /etc/odoo.conf file:
Now, search to see if you have a directive called ‘logfile’. If you don’t, you may add the following to /etc/odoo.conf:
logfile = /var/log/odoo13/odoo.log
If you already have the directive, but commented out, like this:
; logfile = /var/log/odoo13/odoo.log
You may remove the ‘;’ in front of the logfile directive and save the file. Now you may restart your Odoo instance to allow odoo log the information to the file /var/log/odoo13/odoo.log
systemctl restart odoo13
If the restart showing some errors, probably because it is failing to put permission to odoo13 folder. You may try the following:
Even though, if you have defined your routes properly, you are seeing an error of the following:
"debug": "Traceback (most recent call last):\n File \"/opt/odoo/odoo12/odoo/http.py\", line 656, in _handle_exception\n return super(JsonRequest, self)._handle_exception(exception)\n File \"/opt/odoo/odoo12/odoo/http.py\", line 314, in _handle_exception\n raise pycompat.reraise(type(exception), exception, sys.exc_info())\n File \"/opt/odoo/odoo12/odoo/tools/pycompat.py\", line 87, in reraise\n raise value\n File \"/opt/odoo/odoo12/odoo/http.py\", line 1460, in _dispatch_nodb\n func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()\n File \"/opt/odoo/odoo12-venv/lib64/python3.5/site-packages/werkzeug/routing.py\", line 1563, in match\n raise NotFound()\nwerkzeug.exceptions.NotFound: 404: Not Found\n",
"message": "404: Not Found",
"message": "404: Not Found"
This error doesn’t return when you use the http.route as type ‘http’ or default, which is still http, but returns when you use the type ‘json’. One of the cause why the error return is that, you have multiple Odoo databases and Odoo is failing to detect the usable database for the type json. For the type, http, Odoo usually can predict what to use, while for the type json, it can not. For such cases, you would need to use the ‘db-filter’ to add the default database to load for Odoo on the odoo-bin command. If you are using the systemd service file, append the line with the following:
where ‘my_prod’ is your database name.
So the service ExecStart would look like the following:
This blog post goes to my ‘Dirty Hack’ series, where I try to open the gross hacking attempts I practically use in several of my projects.
Odoo is my favorite piece of framework. Even though it has evolved as OpenERP, but the extend ability that Odoo gives, probably can take it anywhere, any other framework unable to, without massive change to their base architecture. This goes to the pros of Odoo, while the biggest cons of Odoo, is that it is not owned/developed by a major ‘English’ speaking company/people. If you go through the Odoo documentation, you can clearly understand the difference I am talking about. One key reason, Odoo is not often seen everywhere, probably because of poor documentation. Most of the things are not explained in details, as Odoo is a large piece of software, makes it difficult for developers to dig it down and extend.
Odoo has it’s own scheduling system, which they call ‘Scheduled Actions’. Odoo manages a model ‘ir.cron’ to manage the Scheduled Actions. If you look at the model details from (on debug mode) Odoo >> Settings >> Technical >> Database Structure >> Model you will see, there is no field it uses to detect if the scheduled action is running or stopped. It has a ‘state’ property though, which does not mean the status like many other odoo models. Here is a output of state property from Odoo shell:
That says, it is telling you what kind of operation it is going to execute, here it is saying, this scheduled action is executing a ‘Python Code’ here.
There are times, when you might require to determine whether the scheduled action is running or not. In my case, it was to track down the some syncing issues, like to find out whether the syncing is doing it’s job properly through some custom methods. As Odoo doesn’t provide a way to do this, I had to find a dirty way to do it. Here is how I did it.
Odoo runs in Postgres SQL, which is basically an open source version of Oracle like Object Oriented Database system. Like many other OODS, Postgres also provide an operation called the following:
FOR UPDATE NOWAIT
From the oracle documentation, it means:
Oracle provides the FOR UPDATE NOWAIT clause in SQL syntax to allow the developer to lock a set of Oracle rows for the duration of a transaction.
When the scheduled action runs, it locks the specific row of the ir_cron table, of course it would because it has to update that specific row with nextcall and other field data. So if a cron is running, and you try to put a lock with another process, will basically fail, which means the cron is running, otherwise the opposite is true. Viola, that should work for us, isn’t it? Simple! Then again, you have to remember, the lock will put in place for the amount of time you specify, or you have to throw an early rollback (why not a commit? to be in safety to avoid any unwanted data being committed to the database during that session). Here is the simple method to do the whole process:
def _check_if_cron_running(self): cron_id = self.env['ir.cron'].browse(18) """ assuming the cron id is 18, you can do any kind of search, like searching by name of the scheduled action etc """ if cron_id: try: self._cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % cron_id._table, [tuple(cron_id.ids)], log_exceptions=False) """ self._cr.execute is used to execute a direct sql command, each ir.cron model data will have 'ids' to tell you which ids are selected, and _table to tell you what is the table name for the model, here it should return ir_cron """ self._cr.rollback() # we need to rollback and give the database cursor back to the other process ASAP _logger.info("log and operate whatever here, this section is reached when the scheduled action is stopped or not running") except psycopg2.OperationalError: self._cr.rollback() # we need to rollback the errors and give control to the other process _logger.info("log and operate whatever here, this section is reached when the scheduled action is running")
Remember, this is dirty, you are hitting direct SQL, on a near around 500 model based framework. So, please take care of your things before placing this in production. Rest assured, keep using Odoo for future! A brilliant piece of art is Odoo!