Published: Thu 08 November 2018
Updated: Sat 23 February 2019
By Arnaud
In Misc .
tags: python odoo
Odoo 12.0 is out since october. I am currently investigating the differences with previous versions to update the instance of the association Les Compagnons du CEP . A lot of changes have been made in a few years but the workflow stays about the same. This article describes my workflow, the backup policy, how a module was used and fixed to restore a missing feature. Finally, the changes in my custom product import function are presented.
Production and debug setup
Agayon.be instance of Odoo runs inside a Docker container on a small VPS. Postgresql is installed as a core package. This setup is great in production but it is difficult to debug some python code with this configuration. The first step is to run Odoo with an IDE (I personally use the excellent Pycharm ).
Running and debugging Odoo
The first step is the creation of the virtualenv. Once the Postgresql instance is ready , you can build the environment.
git clone https://www.github.com/odoo/odoo --depth 1 --branch 12.0
$ virtualenv myenv
$ source myenv/bin/activate
(myenv)$ pip install -r odoo/requirements.txt
(myenv)$ mkdir custom-addons
(myenv)$ chown odoo: odoo11-custom-addons
Edit odoo.conf and then run odoo:
odoo/odoo-bin -c odoo.conf
Merge purchase order
UPDATE: 23/02/2019: The following paragraph is not needed anymore. Odoo 12 restored the automatic merge of purchase orders.
Unfortunately, a major feature has disappeared in version 12.0: automatic merge of purchase order. I don't know why since it seems unrealistic to send each quotation separately to your vendor. Fortunately a free module can be used to perform the merge but it has a critical bug. Some quotation lines are merged even if they concerns different products .
Fix
After forking it, I decided to start by refactoring it. The module is quite small but a lot of code is duplicated. As I try to avoid spaghetti code , it needed to be refactored .I think the new code may be improved but no line is duplicated. Finally, the bug has been fixed .
Backup management
Since a few version, the filestore
is mandatory in Odoo. If it is incoherent with the database, some really annoying errors are raised and the solution is quite tedious. My backup procedure has been updated to avoid losing any data. It is based on the article from zeroheure . The backups are saved with the auto_backup module. Restoring the data is not possible with the web interface because the process reaches the memory limit but it can be performed with the following shell script.
#!/bin/bash
BACKUPLOCATION = "/path/to/backup.zip"
DBNAME = "db_name"
FILESTORE_DIR = "/path/to/filestore"
if [ -z " $FILESTORE_DIR " ] || [ -z " $DBNAME " ] || [ -z " $BACKUPLOCATION " ]
then
echo "verify your variables"
else
cd $BACKUPLOCATION
rm filestore
unzip -q $DBNAME .zip
cp -r filestore $DBNAME
sudo rm -rf $FILESTORE_DIR /$DBNAME
sudo mv $DBNAME $FILESTORE_DIR
sudo chown -R odoo:odoo $FILESTORE_DIR /$DBNAME
dropdb -U odoo $DBNAME
createdb -U odoo $DBNAME
psql $DBNAME --quiet < dump.sql
fi
Wine import with Django website
Version 12.0 needs some minor changes in the code displayed in the previous article .
These modifications includes:
removing state property in the product template.
Adding the invoice_policy and purchase_method in the product template.[ref]See this post and this issue [/ref]
Add a reference to the standard price in the product_supplierinfo dictionary. This value is used in the orders when purchasing wines to suppliers.
def research ( default_code , supplier_code , wine_name ):
# 1) search if default code is used?)
# 2) search if suppliers is in the database
# 3) search if the name is already used
# Retrieve the dataframes. This example comes from a jupyter nootebook.
# df_product and df_sellers are already defined.
# In a real case, we should use class variables.
n_code , df_code = search_df ( df = df_product , col_name = 'default_code' , search_item = default_code ,
search_int = False )
n_supplier , df_suppliers = search_df ( df = df_suppliers , col_name = 'function' ,
search_item = supplier_code , search_int = False )
n_name , df_name = search_df ( df = df_product , col_name = 'name' , search_item = wine_name , search_int = False )
try :
# ids_product = list(df_code['id'])
ids_product = df_code [ 'id' ] . tolist ()
except AttributeError :
ids_product = []
if n_code == 0 and n_name == 0 and n_supplier != 0 :
return 'success' , ids_product , n_supplier , n_name
if n_code != 0 and n_name != 0 :
# Another product uses the same name with another code
return 'e_code_used_same_name' , ids_product , n_supplier , n_name
if n_code != 0 :
# the code is already used
return 'e_code_used' , ids_product , n_supplier , n_name
if n_supplier == 0 :
# Cannot find the supplier
return 'e_missing_seller' , ids_product , n_supplier , n_name
if n_name != 0 :
# A product with the same name and another code exists.
return 'e_code_used_different_name' , ids_product , n_supplier , n_name
def import2odoo ():
route_warehouse0_mto = 1
route_warehouse0_manufacture = 5
[ ... ]
# iterate over all rows, read the cells and assign the wine parameters to variables
# each row correspond to one wine
for row in rows :
seller_name = row [ 0 ]
default_code = row [ 1 ]
name = row [ 2 ]
do_import = row [ 2 ]
comment = row [ 3 ]
name = row [ 4 ]
default_code = row [ 5 ]
standard_price = row [ 6 ]
list_price = row [ 7 ]
seller_code = row [ 8 ]
res_search , ids_product , n_supplier , n_name = research ( default_code , seller_code , name )
if res_search == 'success' and do_import == "1" :
product_template = {
'name' : name ,
'active' : True ,
'standard_price' : standard_price ,
'list_price' : list_price ,
'description' : comment ,
'default_code' : default_code ,
'purchase_ok' : 1 ,
'sale_ok' : 1 ,
'uom_id' : 1 ,
'uom_po_id' : 1 ,
'type' : 'product' ,
'cost_method' : 'standard' ,
'invoice_policy' : 'order' , # ordered quantities
'purchase_method' : 'receive' , # control received quantities (or ordered ones, test to delivery)
'route_ids' : [( 6 , 0 , [ route_warehouse0_mto , route_warehouse0_manufacture ])]
}
# For each wine, a template must be created
template_id = sock . execute ( dbname , uid , pwd , 'product.template' ,
'create' ,
product_template )
# Create the supplier information for the wine
product_supplierinfo = {
'name' : name ,
'product_code' : row [ 1 ], # code for supplier
'product_name' : row [ 15 ], # name for supplier
'min_qty' : 1 ,
'delay' : 300 ,
'product_tmpl_id' : template_id ,
'price' : standard_price ,
}
# Create the supplier information for the wine
product_supplierinfo_id = sock . execute ( dbname , uid , pwd ,
'product.supplierinfo' ,
'create' , product_supplierinfo )
logging . info ( "Wine {} : {} has been added" . format ( default_code , name ))
[ ... ]
# Here we take into account the exceptions and several cases: the wine is already present, the seller is missing etc.
Conclusions
After using Odoo in production for 4 years, I still love it. My users are happy to use it daily and the improvements over the year are impressive. Like any other alive project, some changes may break the workflow but Odoo and it's community make it relatively simple to adapt with plugins. Odoo 12 is strong and I look forward to use it for the next 3 years :-).
Links