diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv
index 25df6e117d0..6113f17c5aa 100644
--- a/openerp/addons/base/security/ir.model.access.csv
+++ b/openerp/addons/base/security/ir.model.access.csv
@@ -113,3 +113,4 @@
"access_ir_needaction_mixin","ir_needaction_mixin","model_ir_needaction_mixin",,1,1,1,1
"access_res_font_all","res_res_font all","model_res_font",,1,0,0,0
"access_res_font_group_user","res_res_font group_user","model_res_font","group_user",1,1,1,1
+"access_ir_logging","ir_logging admin","model_ir_logging","group_erp_manager",1,1,1,1
diff --git a/openerp/addons/base/static/img/country_flags/ad.png b/openerp/addons/base/static/img/country_flags/ad.png
new file mode 100644
index 00000000000..3806c28c6d8
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ad.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ae.png b/openerp/addons/base/static/img/country_flags/ae.png
new file mode 100644
index 00000000000..4d5c0a3a657
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ae.png differ
diff --git a/openerp/addons/base/static/img/country_flags/af.png b/openerp/addons/base/static/img/country_flags/af.png
new file mode 100644
index 00000000000..299482a1fad
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/af.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ag.png b/openerp/addons/base/static/img/country_flags/ag.png
new file mode 100644
index 00000000000..29ec9e8ad1e
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ag.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ai.png b/openerp/addons/base/static/img/country_flags/ai.png
new file mode 100644
index 00000000000..343a6dfc294
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ai.png differ
diff --git a/openerp/addons/base/static/img/country_flags/al.png b/openerp/addons/base/static/img/country_flags/al.png
new file mode 100644
index 00000000000..0d1b5f87cb3
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/al.png differ
diff --git a/openerp/addons/base/static/img/country_flags/am.png b/openerp/addons/base/static/img/country_flags/am.png
new file mode 100644
index 00000000000..8023ad7a4b2
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/am.png differ
diff --git a/openerp/addons/base/static/img/country_flags/an.png b/openerp/addons/base/static/img/country_flags/an.png
new file mode 100644
index 00000000000..701d55fbda9
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/an.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ao.png b/openerp/addons/base/static/img/country_flags/ao.png
new file mode 100644
index 00000000000..b599322efb8
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ao.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ar.png b/openerp/addons/base/static/img/country_flags/ar.png
new file mode 100644
index 00000000000..943ebb2be73
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ar.png differ
diff --git a/openerp/addons/base/static/img/country_flags/as.png b/openerp/addons/base/static/img/country_flags/as.png
new file mode 100644
index 00000000000..068d34174b6
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/as.png differ
diff --git a/openerp/addons/base/static/img/country_flags/at.png b/openerp/addons/base/static/img/country_flags/at.png
new file mode 100644
index 00000000000..758147d4bbb
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/at.png differ
diff --git a/openerp/addons/base/static/img/country_flags/au.png b/openerp/addons/base/static/img/country_flags/au.png
new file mode 100644
index 00000000000..6e864267043
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/au.png differ
diff --git a/openerp/addons/base/static/img/country_flags/aw.png b/openerp/addons/base/static/img/country_flags/aw.png
new file mode 100644
index 00000000000..0f362bbb3fc
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/aw.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ax.png b/openerp/addons/base/static/img/country_flags/ax.png
new file mode 100644
index 00000000000..e546ec92d5b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ax.png differ
diff --git a/openerp/addons/base/static/img/country_flags/az.png b/openerp/addons/base/static/img/country_flags/az.png
new file mode 100644
index 00000000000..334567372f2
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/az.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ba.png b/openerp/addons/base/static/img/country_flags/ba.png
new file mode 100644
index 00000000000..2abf146fc11
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ba.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bb.png b/openerp/addons/base/static/img/country_flags/bb.png
new file mode 100644
index 00000000000..8fbafd7d12a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bb.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bd.png b/openerp/addons/base/static/img/country_flags/bd.png
new file mode 100644
index 00000000000..1754328d655
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bd.png differ
diff --git a/openerp/addons/base/static/img/country_flags/be.png b/openerp/addons/base/static/img/country_flags/be.png
new file mode 100644
index 00000000000..42e4ff638bc
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/be.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bf.png b/openerp/addons/base/static/img/country_flags/bf.png
new file mode 100644
index 00000000000..bf69a1318e8
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bf.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bg.png b/openerp/addons/base/static/img/country_flags/bg.png
new file mode 100644
index 00000000000..dbe55253e01
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bh.png b/openerp/addons/base/static/img/country_flags/bh.png
new file mode 100644
index 00000000000..50944f1ee6f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bh.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bi.png b/openerp/addons/base/static/img/country_flags/bi.png
new file mode 100644
index 00000000000..6a04c66cdbb
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bi.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bj.png b/openerp/addons/base/static/img/country_flags/bj.png
new file mode 100644
index 00000000000..aac7f881956
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bj.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bl.png b/openerp/addons/base/static/img/country_flags/bl.png
new file mode 100644
index 00000000000..8fdf4c45717
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bl.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bm.png b/openerp/addons/base/static/img/country_flags/bm.png
new file mode 100644
index 00000000000..47753a3c677
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bn.png b/openerp/addons/base/static/img/country_flags/bn.png
new file mode 100644
index 00000000000..97aa098d88f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bo.png b/openerp/addons/base/static/img/country_flags/bo.png
new file mode 100644
index 00000000000..44a066b1601
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bo.png differ
diff --git a/openerp/addons/base/static/img/country_flags/br.png b/openerp/addons/base/static/img/country_flags/br.png
new file mode 100644
index 00000000000..7231f543ffb
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/br.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bs.png b/openerp/addons/base/static/img/country_flags/bs.png
new file mode 100644
index 00000000000..d7482884694
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bs.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bt.png b/openerp/addons/base/static/img/country_flags/bt.png
new file mode 100644
index 00000000000..7f889b3c4b7
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bt.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bw.png b/openerp/addons/base/static/img/country_flags/bw.png
new file mode 100644
index 00000000000..f3bcf5663ef
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bw.png differ
diff --git a/openerp/addons/base/static/img/country_flags/by.png b/openerp/addons/base/static/img/country_flags/by.png
new file mode 100644
index 00000000000..fe37c575ac4
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/by.png differ
diff --git a/openerp/addons/base/static/img/country_flags/bz.png b/openerp/addons/base/static/img/country_flags/bz.png
new file mode 100644
index 00000000000..bbb9903a237
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/bz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ca.png b/openerp/addons/base/static/img/country_flags/ca.png
new file mode 100644
index 00000000000..12807f498ec
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ca.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cc.png b/openerp/addons/base/static/img/country_flags/cc.png
new file mode 100644
index 00000000000..84ccc4ecaeb
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cc.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cd.png b/openerp/addons/base/static/img/country_flags/cd.png
new file mode 100644
index 00000000000..e96bce99ea7
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cd.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cf.png b/openerp/addons/base/static/img/country_flags/cf.png
new file mode 100644
index 00000000000..366d4cb55e5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cf.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cg.png b/openerp/addons/base/static/img/country_flags/cg.png
new file mode 100644
index 00000000000..e049d47c408
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ch.png b/openerp/addons/base/static/img/country_flags/ch.png
new file mode 100644
index 00000000000..815b6664b49
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ch.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ci.png b/openerp/addons/base/static/img/country_flags/ci.png
new file mode 100644
index 00000000000..61e92e63a9d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ci.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ck.png b/openerp/addons/base/static/img/country_flags/ck.png
new file mode 100644
index 00000000000..fb487762817
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ck.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cl.png b/openerp/addons/base/static/img/country_flags/cl.png
new file mode 100644
index 00000000000..5ad2daaf25c
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cl.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cm.png b/openerp/addons/base/static/img/country_flags/cm.png
new file mode 100644
index 00000000000..80a639ce7dc
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cn.png b/openerp/addons/base/static/img/country_flags/cn.png
new file mode 100644
index 00000000000..5b28787b636
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/co.png b/openerp/addons/base/static/img/country_flags/co.png
new file mode 100644
index 00000000000..53609723452
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/co.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cr.png b/openerp/addons/base/static/img/country_flags/cr.png
new file mode 100644
index 00000000000..40ee573607b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cu.png b/openerp/addons/base/static/img/country_flags/cu.png
new file mode 100644
index 00000000000..31c2bf7dd0c
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cu.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cv.png b/openerp/addons/base/static/img/country_flags/cv.png
new file mode 100644
index 00000000000..d4fd1c54552
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cv.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cw.png b/openerp/addons/base/static/img/country_flags/cw.png
new file mode 100644
index 00000000000..71fb770b24c
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cw.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cx.png b/openerp/addons/base/static/img/country_flags/cx.png
new file mode 100644
index 00000000000..d093d2e8b3b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cx.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cy.png b/openerp/addons/base/static/img/country_flags/cy.png
new file mode 100644
index 00000000000..abbf15ce0e3
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cy.png differ
diff --git a/openerp/addons/base/static/img/country_flags/cz.png b/openerp/addons/base/static/img/country_flags/cz.png
new file mode 100644
index 00000000000..bd3cacfe4bb
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/cz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/de.png b/openerp/addons/base/static/img/country_flags/de.png
new file mode 100644
index 00000000000..a479fee6662
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/de.png differ
diff --git a/openerp/addons/base/static/img/country_flags/dj.png b/openerp/addons/base/static/img/country_flags/dj.png
new file mode 100644
index 00000000000..9ac6493eff3
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/dj.png differ
diff --git a/openerp/addons/base/static/img/country_flags/dk.png b/openerp/addons/base/static/img/country_flags/dk.png
new file mode 100644
index 00000000000..d2882e82a16
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/dk.png differ
diff --git a/openerp/addons/base/static/img/country_flags/dm.png b/openerp/addons/base/static/img/country_flags/dm.png
new file mode 100644
index 00000000000..f70973854b4
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/dm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/do.png b/openerp/addons/base/static/img/country_flags/do.png
new file mode 100644
index 00000000000..24614878d56
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/do.png differ
diff --git a/openerp/addons/base/static/img/country_flags/dz.png b/openerp/addons/base/static/img/country_flags/dz.png
new file mode 100644
index 00000000000..86c293aebef
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/dz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ec.png b/openerp/addons/base/static/img/country_flags/ec.png
new file mode 100644
index 00000000000..15ba2c3f2e9
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ec.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ee.png b/openerp/addons/base/static/img/country_flags/ee.png
new file mode 100644
index 00000000000..fbf210ef42e
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ee.png differ
diff --git a/openerp/addons/base/static/img/country_flags/eg.png b/openerp/addons/base/static/img/country_flags/eg.png
new file mode 100644
index 00000000000..0d8132a03a9
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/eg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/er.png b/openerp/addons/base/static/img/country_flags/er.png
new file mode 100644
index 00000000000..c89d5a95644
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/er.png differ
diff --git a/openerp/addons/base/static/img/country_flags/es.png b/openerp/addons/base/static/img/country_flags/es.png
new file mode 100644
index 00000000000..ea384d4d516
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/es.png differ
diff --git a/openerp/addons/base/static/img/country_flags/et.png b/openerp/addons/base/static/img/country_flags/et.png
new file mode 100644
index 00000000000..d2d1e4255ee
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/et.png differ
diff --git a/openerp/addons/base/static/img/country_flags/fi.png b/openerp/addons/base/static/img/country_flags/fi.png
new file mode 100644
index 00000000000..195226a5042
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/fi.png differ
diff --git a/openerp/addons/base/static/img/country_flags/fj.png b/openerp/addons/base/static/img/country_flags/fj.png
new file mode 100644
index 00000000000..daee24a3746
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/fj.png differ
diff --git a/openerp/addons/base/static/img/country_flags/fk.png b/openerp/addons/base/static/img/country_flags/fk.png
new file mode 100644
index 00000000000..f202be3118f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/fk.png differ
diff --git a/openerp/addons/base/static/img/country_flags/fm.png b/openerp/addons/base/static/img/country_flags/fm.png
new file mode 100644
index 00000000000..c157b518251
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/fm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/fo.png b/openerp/addons/base/static/img/country_flags/fo.png
new file mode 100644
index 00000000000..eceef2d8d73
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/fo.png differ
diff --git a/openerp/addons/base/static/img/country_flags/fr.png b/openerp/addons/base/static/img/country_flags/fr.png
new file mode 100644
index 00000000000..8fdf4c45717
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/fr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ga.png b/openerp/addons/base/static/img/country_flags/ga.png
new file mode 100644
index 00000000000..d4a2b71f2d5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ga.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gb.png b/openerp/addons/base/static/img/country_flags/gb.png
new file mode 100644
index 00000000000..bf90e9b8b9c
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gb.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gd.png b/openerp/addons/base/static/img/country_flags/gd.png
new file mode 100644
index 00000000000..807755f284f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gd.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ge.png b/openerp/addons/base/static/img/country_flags/ge.png
new file mode 100644
index 00000000000..6e813b3b310
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ge.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gg.png b/openerp/addons/base/static/img/country_flags/gg.png
new file mode 100644
index 00000000000..20f3b15c0fd
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gh.png b/openerp/addons/base/static/img/country_flags/gh.png
new file mode 100644
index 00000000000..5eb077085c7
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gh.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gi.png b/openerp/addons/base/static/img/country_flags/gi.png
new file mode 100644
index 00000000000..79dbd3333d5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gi.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gl.png b/openerp/addons/base/static/img/country_flags/gl.png
new file mode 100644
index 00000000000..f7e82893f1a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gl.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gm.png b/openerp/addons/base/static/img/country_flags/gm.png
new file mode 100644
index 00000000000..8c539e2d23c
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gn.png b/openerp/addons/base/static/img/country_flags/gn.png
new file mode 100644
index 00000000000..c0d4c72c87a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gq.png b/openerp/addons/base/static/img/country_flags/gq.png
new file mode 100644
index 00000000000..375a7b78f62
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gq.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gr.png b/openerp/addons/base/static/img/country_flags/gr.png
new file mode 100644
index 00000000000..7c4471b86e2
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gs.png b/openerp/addons/base/static/img/country_flags/gs.png
new file mode 100644
index 00000000000..b0269d14acf
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gs.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gt.png b/openerp/addons/base/static/img/country_flags/gt.png
new file mode 100644
index 00000000000..6d6998c4c6c
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gt.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gu.png b/openerp/addons/base/static/img/country_flags/gu.png
new file mode 100644
index 00000000000..847d59520f5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gu.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gw.png b/openerp/addons/base/static/img/country_flags/gw.png
new file mode 100644
index 00000000000..88ba1d8a531
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gw.png differ
diff --git a/openerp/addons/base/static/img/country_flags/gy.png b/openerp/addons/base/static/img/country_flags/gy.png
new file mode 100644
index 00000000000..de7bef45285
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/gy.png differ
diff --git a/openerp/addons/base/static/img/country_flags/hk.png b/openerp/addons/base/static/img/country_flags/hk.png
new file mode 100644
index 00000000000..62f6453b8b7
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/hk.png differ
diff --git a/openerp/addons/base/static/img/country_flags/hn.png b/openerp/addons/base/static/img/country_flags/hn.png
new file mode 100644
index 00000000000..e1201738ca7
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/hn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/hr.png b/openerp/addons/base/static/img/country_flags/hr.png
new file mode 100644
index 00000000000..5e24e9a52d4
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/hr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ht.png b/openerp/addons/base/static/img/country_flags/ht.png
new file mode 100644
index 00000000000..58ec58e41be
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ht.png differ
diff --git a/openerp/addons/base/static/img/country_flags/hu.png b/openerp/addons/base/static/img/country_flags/hu.png
new file mode 100644
index 00000000000..e1f237f802d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/hu.png differ
diff --git a/openerp/addons/base/static/img/country_flags/id.png b/openerp/addons/base/static/img/country_flags/id.png
new file mode 100644
index 00000000000..1dc8207c288
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/id.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ie.png b/openerp/addons/base/static/img/country_flags/ie.png
new file mode 100644
index 00000000000..fb8cf9382f1
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ie.png differ
diff --git a/openerp/addons/base/static/img/country_flags/il.png b/openerp/addons/base/static/img/country_flags/il.png
new file mode 100644
index 00000000000..c4c4f21e128
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/il.png differ
diff --git a/openerp/addons/base/static/img/country_flags/im.png b/openerp/addons/base/static/img/country_flags/im.png
new file mode 100644
index 00000000000..6befe9efe3a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/im.png differ
diff --git a/openerp/addons/base/static/img/country_flags/in.png b/openerp/addons/base/static/img/country_flags/in.png
new file mode 100644
index 00000000000..a0daccd9397
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/in.png differ
diff --git a/openerp/addons/base/static/img/country_flags/io.png b/openerp/addons/base/static/img/country_flags/io.png
new file mode 100644
index 00000000000..a112651851b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/io.png differ
diff --git a/openerp/addons/base/static/img/country_flags/iq.png b/openerp/addons/base/static/img/country_flags/iq.png
new file mode 100644
index 00000000000..1095c9d312a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/iq.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ir.png b/openerp/addons/base/static/img/country_flags/ir.png
new file mode 100644
index 00000000000..8b9c5fac4d0
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ir.png differ
diff --git a/openerp/addons/base/static/img/country_flags/iran.png b/openerp/addons/base/static/img/country_flags/iran.png
new file mode 100644
index 00000000000..9ca5513ace0
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/iran.png differ
diff --git a/openerp/addons/base/static/img/country_flags/is.png b/openerp/addons/base/static/img/country_flags/is.png
new file mode 100644
index 00000000000..c5d0ffb1ec3
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/is.png differ
diff --git a/openerp/addons/base/static/img/country_flags/it.png b/openerp/addons/base/static/img/country_flags/it.png
new file mode 100644
index 00000000000..1cd74ec0ee0
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/it.png differ
diff --git a/openerp/addons/base/static/img/country_flags/je.png b/openerp/addons/base/static/img/country_flags/je.png
new file mode 100644
index 00000000000..ca62fe471e3
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/je.png differ
diff --git a/openerp/addons/base/static/img/country_flags/jm.png b/openerp/addons/base/static/img/country_flags/jm.png
new file mode 100644
index 00000000000..7458372eea8
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/jm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/jo.png b/openerp/addons/base/static/img/country_flags/jo.png
new file mode 100644
index 00000000000..b5e88a3c251
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/jo.png differ
diff --git a/openerp/addons/base/static/img/country_flags/jp.png b/openerp/addons/base/static/img/country_flags/jp.png
new file mode 100644
index 00000000000..21f38c35847
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/jp.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ke.png b/openerp/addons/base/static/img/country_flags/ke.png
new file mode 100644
index 00000000000..57ef9e3f20b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ke.png differ
diff --git a/openerp/addons/base/static/img/country_flags/kg.png b/openerp/addons/base/static/img/country_flags/kg.png
new file mode 100644
index 00000000000..1b550f6cfaa
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/kg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/kh.png b/openerp/addons/base/static/img/country_flags/kh.png
new file mode 100644
index 00000000000..299ad93e08e
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/kh.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ki.png b/openerp/addons/base/static/img/country_flags/ki.png
new file mode 100644
index 00000000000..cf730409013
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ki.png differ
diff --git a/openerp/addons/base/static/img/country_flags/km.png b/openerp/addons/base/static/img/country_flags/km.png
new file mode 100644
index 00000000000..df5c343fce5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/km.png differ
diff --git a/openerp/addons/base/static/img/country_flags/kn.png b/openerp/addons/base/static/img/country_flags/kn.png
new file mode 100644
index 00000000000..9ecfa0ab9c4
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/kn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/kp.png b/openerp/addons/base/static/img/country_flags/kp.png
new file mode 100644
index 00000000000..14db96ff065
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/kp.png differ
diff --git a/openerp/addons/base/static/img/country_flags/kr.png b/openerp/addons/base/static/img/country_flags/kr.png
new file mode 100644
index 00000000000..8b66b25f75d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/kr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/kw.png b/openerp/addons/base/static/img/country_flags/kw.png
new file mode 100644
index 00000000000..33c04a64f70
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/kw.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ky.png b/openerp/addons/base/static/img/country_flags/ky.png
new file mode 100644
index 00000000000..84279c83bcf
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ky.png differ
diff --git a/openerp/addons/base/static/img/country_flags/kz.png b/openerp/addons/base/static/img/country_flags/kz.png
new file mode 100644
index 00000000000..f756682c74f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/kz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/la.png b/openerp/addons/base/static/img/country_flags/la.png
new file mode 100644
index 00000000000..27d4e2be994
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/la.png differ
diff --git a/openerp/addons/base/static/img/country_flags/lb.png b/openerp/addons/base/static/img/country_flags/lb.png
new file mode 100644
index 00000000000..8df863b7ca1
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/lb.png differ
diff --git a/openerp/addons/base/static/img/country_flags/lc.png b/openerp/addons/base/static/img/country_flags/lc.png
new file mode 100644
index 00000000000..15af8fee7ff
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/lc.png differ
diff --git a/openerp/addons/base/static/img/country_flags/li.png b/openerp/addons/base/static/img/country_flags/li.png
new file mode 100644
index 00000000000..f86197e9af6
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/li.png differ
diff --git a/openerp/addons/base/static/img/country_flags/lk.png b/openerp/addons/base/static/img/country_flags/lk.png
new file mode 100644
index 00000000000..46bab54aabc
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/lk.png differ
diff --git a/openerp/addons/base/static/img/country_flags/lr.png b/openerp/addons/base/static/img/country_flags/lr.png
new file mode 100644
index 00000000000..3f2cc830120
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/lr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ls.png b/openerp/addons/base/static/img/country_flags/ls.png
new file mode 100644
index 00000000000..6b6d6b17531
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ls.png differ
diff --git a/openerp/addons/base/static/img/country_flags/lt.png b/openerp/addons/base/static/img/country_flags/lt.png
new file mode 100644
index 00000000000..a25f6a31aae
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/lt.png differ
diff --git a/openerp/addons/base/static/img/country_flags/lu.png b/openerp/addons/base/static/img/country_flags/lu.png
new file mode 100644
index 00000000000..2899c7bbba1
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/lu.png differ
diff --git a/openerp/addons/base/static/img/country_flags/lv.png b/openerp/addons/base/static/img/country_flags/lv.png
new file mode 100644
index 00000000000..674a7c6c9b0
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/lv.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ly.png b/openerp/addons/base/static/img/country_flags/ly.png
new file mode 100644
index 00000000000..0e4eb2a5627
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ly.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ma.png b/openerp/addons/base/static/img/country_flags/ma.png
new file mode 100644
index 00000000000..b43ea8c8597
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ma.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mc.png b/openerp/addons/base/static/img/country_flags/mc.png
new file mode 100644
index 00000000000..119460a5e7f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mc.png differ
diff --git a/openerp/addons/base/static/img/country_flags/md.png b/openerp/addons/base/static/img/country_flags/md.png
new file mode 100644
index 00000000000..a05ba191843
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/md.png differ
diff --git a/openerp/addons/base/static/img/country_flags/me.png b/openerp/addons/base/static/img/country_flags/me.png
new file mode 100644
index 00000000000..aead438a92f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/me.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mg.png b/openerp/addons/base/static/img/country_flags/mg.png
new file mode 100644
index 00000000000..75242401c76
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mh.png b/openerp/addons/base/static/img/country_flags/mh.png
new file mode 100644
index 00000000000..4cd0d244a49
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mh.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mk.png b/openerp/addons/base/static/img/country_flags/mk.png
new file mode 100644
index 00000000000..f313b092e4c
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mk.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ml.png b/openerp/addons/base/static/img/country_flags/ml.png
new file mode 100644
index 00000000000..eb5579c08d5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ml.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mm.png b/openerp/addons/base/static/img/country_flags/mm.png
new file mode 100644
index 00000000000..6723bbb3b03
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mn.png b/openerp/addons/base/static/img/country_flags/mn.png
new file mode 100644
index 00000000000..607caac7659
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mo.png b/openerp/addons/base/static/img/country_flags/mo.png
new file mode 100644
index 00000000000..f64c674be59
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mo.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mp.png b/openerp/addons/base/static/img/country_flags/mp.png
new file mode 100644
index 00000000000..3453b270cc0
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mp.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mq.png b/openerp/addons/base/static/img/country_flags/mq.png
new file mode 100644
index 00000000000..54ba3e92b83
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mq.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mr.png b/openerp/addons/base/static/img/country_flags/mr.png
new file mode 100644
index 00000000000..76b032b47b8
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ms.png b/openerp/addons/base/static/img/country_flags/ms.png
new file mode 100644
index 00000000000..4f291816856
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ms.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mt.png b/openerp/addons/base/static/img/country_flags/mt.png
new file mode 100644
index 00000000000..dbdcbf9d503
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mt.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mu.png b/openerp/addons/base/static/img/country_flags/mu.png
new file mode 100644
index 00000000000..6eae60e796b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mu.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mv.png b/openerp/addons/base/static/img/country_flags/mv.png
new file mode 100644
index 00000000000..6d2597d66b1
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mv.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mw.png b/openerp/addons/base/static/img/country_flags/mw.png
new file mode 100644
index 00000000000..f2f3454334b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mw.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mx.png b/openerp/addons/base/static/img/country_flags/mx.png
new file mode 100644
index 00000000000..4c5c32d0589
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mx.png differ
diff --git a/openerp/addons/base/static/img/country_flags/my.png b/openerp/addons/base/static/img/country_flags/my.png
new file mode 100644
index 00000000000..7ee43cc1925
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/my.png differ
diff --git a/openerp/addons/base/static/img/country_flags/mz.png b/openerp/addons/base/static/img/country_flags/mz.png
new file mode 100644
index 00000000000..4d47b29b02b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/mz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/na.png b/openerp/addons/base/static/img/country_flags/na.png
new file mode 100644
index 00000000000..0e09fd2f12a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/na.png differ
diff --git a/openerp/addons/base/static/img/country_flags/nc.png b/openerp/addons/base/static/img/country_flags/nc.png
new file mode 100644
index 00000000000..5e9b0ca9f73
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/nc.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ne.png b/openerp/addons/base/static/img/country_flags/ne.png
new file mode 100644
index 00000000000..e96acdedbc0
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ne.png differ
diff --git a/openerp/addons/base/static/img/country_flags/nf.png b/openerp/addons/base/static/img/country_flags/nf.png
new file mode 100644
index 00000000000..2bb62238166
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/nf.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ng.png b/openerp/addons/base/static/img/country_flags/ng.png
new file mode 100644
index 00000000000..052ec0e9761
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ng.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ni.png b/openerp/addons/base/static/img/country_flags/ni.png
new file mode 100644
index 00000000000..4e455f6777e
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ni.png differ
diff --git a/openerp/addons/base/static/img/country_flags/nl.png b/openerp/addons/base/static/img/country_flags/nl.png
new file mode 100644
index 00000000000..1b5938306f5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/nl.png differ
diff --git a/openerp/addons/base/static/img/country_flags/no.png b/openerp/addons/base/static/img/country_flags/no.png
new file mode 100644
index 00000000000..bd2840d96c1
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/no.png differ
diff --git a/openerp/addons/base/static/img/country_flags/np.png b/openerp/addons/base/static/img/country_flags/np.png
new file mode 100644
index 00000000000..4c4beda56fe
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/np.png differ
diff --git a/openerp/addons/base/static/img/country_flags/nr.png b/openerp/addons/base/static/img/country_flags/nr.png
new file mode 100644
index 00000000000..923bea03faf
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/nr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/nu.png b/openerp/addons/base/static/img/country_flags/nu.png
new file mode 100644
index 00000000000..1f745218efa
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/nu.png differ
diff --git a/openerp/addons/base/static/img/country_flags/nz.png b/openerp/addons/base/static/img/country_flags/nz.png
new file mode 100644
index 00000000000..6d669ae696e
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/nz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/om.png b/openerp/addons/base/static/img/country_flags/om.png
new file mode 100644
index 00000000000..5c61b15a0a5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/om.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pa.png b/openerp/addons/base/static/img/country_flags/pa.png
new file mode 100644
index 00000000000..b4857d13574
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pa.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pe.png b/openerp/addons/base/static/img/country_flags/pe.png
new file mode 100644
index 00000000000..14f7e1c86d3
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pe.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pf.png b/openerp/addons/base/static/img/country_flags/pf.png
new file mode 100644
index 00000000000..db134fc81c4
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pf.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pg.png b/openerp/addons/base/static/img/country_flags/pg.png
new file mode 100644
index 00000000000..56e85161956
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ph.png b/openerp/addons/base/static/img/country_flags/ph.png
new file mode 100644
index 00000000000..e74d917eb3d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ph.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pk.png b/openerp/addons/base/static/img/country_flags/pk.png
new file mode 100644
index 00000000000..adea341ccec
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pk.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pl.png b/openerp/addons/base/static/img/country_flags/pl.png
new file mode 100644
index 00000000000..4d0f9d19f65
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pl.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pm.png b/openerp/addons/base/static/img/country_flags/pm.png
new file mode 100644
index 00000000000..b34299ce47b
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pn.png b/openerp/addons/base/static/img/country_flags/pn.png
new file mode 100644
index 00000000000..890c9593342
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pr.png b/openerp/addons/base/static/img/country_flags/pr.png
new file mode 100644
index 00000000000..5e2c7c6078a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pt.png b/openerp/addons/base/static/img/country_flags/pt.png
new file mode 100644
index 00000000000..5fe2ae2cf63
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pt.png differ
diff --git a/openerp/addons/base/static/img/country_flags/pw.png b/openerp/addons/base/static/img/country_flags/pw.png
new file mode 100644
index 00000000000..fe8ef26d2e6
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/pw.png differ
diff --git a/openerp/addons/base/static/img/country_flags/py.png b/openerp/addons/base/static/img/country_flags/py.png
new file mode 100644
index 00000000000..0a04748f182
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/py.png differ
diff --git a/openerp/addons/base/static/img/country_flags/qa.png b/openerp/addons/base/static/img/country_flags/qa.png
new file mode 100644
index 00000000000..35c44fc076d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/qa.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ro.png b/openerp/addons/base/static/img/country_flags/ro.png
new file mode 100644
index 00000000000..0cb3f29f11d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ro.png differ
diff --git a/openerp/addons/base/static/img/country_flags/rs.png b/openerp/addons/base/static/img/country_flags/rs.png
new file mode 100644
index 00000000000..6f7d83de2e5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/rs.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ru.png b/openerp/addons/base/static/img/country_flags/ru.png
new file mode 100644
index 00000000000..1a99c506234
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ru.png differ
diff --git a/openerp/addons/base/static/img/country_flags/rw.png b/openerp/addons/base/static/img/country_flags/rw.png
new file mode 100644
index 00000000000..2abba014617
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/rw.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sa.png b/openerp/addons/base/static/img/country_flags/sa.png
new file mode 100644
index 00000000000..6c106a540a1
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sa.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sb.png b/openerp/addons/base/static/img/country_flags/sb.png
new file mode 100644
index 00000000000..a23c5bc58ba
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sb.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sc.png b/openerp/addons/base/static/img/country_flags/sc.png
new file mode 100644
index 00000000000..d1acc28403e
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sc.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sd.png b/openerp/addons/base/static/img/country_flags/sd.png
new file mode 100644
index 00000000000..e7694590811
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sd.png differ
diff --git a/openerp/addons/base/static/img/country_flags/se.png b/openerp/addons/base/static/img/country_flags/se.png
new file mode 100644
index 00000000000..a30498df565
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/se.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sg.png b/openerp/addons/base/static/img/country_flags/sg.png
new file mode 100644
index 00000000000..16bb5445c58
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sh.png b/openerp/addons/base/static/img/country_flags/sh.png
new file mode 100644
index 00000000000..cb48a6a2e2f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sh.png differ
diff --git a/openerp/addons/base/static/img/country_flags/si.png b/openerp/addons/base/static/img/country_flags/si.png
new file mode 100644
index 00000000000..b749349245d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/si.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sk.png b/openerp/addons/base/static/img/country_flags/sk.png
new file mode 100644
index 00000000000..1609f2c8d9a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sk.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sl.png b/openerp/addons/base/static/img/country_flags/sl.png
new file mode 100644
index 00000000000..ef67f96c22a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sl.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sm.png b/openerp/addons/base/static/img/country_flags/sm.png
new file mode 100644
index 00000000000..b515c509030
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sn.png b/openerp/addons/base/static/img/country_flags/sn.png
new file mode 100644
index 00000000000..2cb1d0a36c3
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/so.png b/openerp/addons/base/static/img/country_flags/so.png
new file mode 100644
index 00000000000..d6772bdd702
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/so.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sr.png b/openerp/addons/base/static/img/country_flags/sr.png
new file mode 100644
index 00000000000..9be5dc7b952
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ss.png b/openerp/addons/base/static/img/country_flags/ss.png
new file mode 100644
index 00000000000..a07a513e189
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ss.png differ
diff --git a/openerp/addons/base/static/img/country_flags/st.png b/openerp/addons/base/static/img/country_flags/st.png
new file mode 100644
index 00000000000..b3abd9852f3
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/st.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sv.png b/openerp/addons/base/static/img/country_flags/sv.png
new file mode 100644
index 00000000000..4685c62e305
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sv.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sx.png b/openerp/addons/base/static/img/country_flags/sx.png
new file mode 100644
index 00000000000..d8d40f75924
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sx.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sy.png b/openerp/addons/base/static/img/country_flags/sy.png
new file mode 100644
index 00000000000..a45853957cc
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sy.png differ
diff --git a/openerp/addons/base/static/img/country_flags/sz.png b/openerp/addons/base/static/img/country_flags/sz.png
new file mode 100644
index 00000000000..49b2a9360c1
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/sz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tc.png b/openerp/addons/base/static/img/country_flags/tc.png
new file mode 100644
index 00000000000..078b226810d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tc.png differ
diff --git a/openerp/addons/base/static/img/country_flags/td.png b/openerp/addons/base/static/img/country_flags/td.png
new file mode 100644
index 00000000000..2e8603a6012
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/td.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tf.png b/openerp/addons/base/static/img/country_flags/tf.png
new file mode 100644
index 00000000000..945c7afb0b1
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tf.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tg.png b/openerp/addons/base/static/img/country_flags/tg.png
new file mode 100644
index 00000000000..3154c5cb830
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/th.png b/openerp/addons/base/static/img/country_flags/th.png
new file mode 100644
index 00000000000..f39ce45eeee
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/th.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tj.png b/openerp/addons/base/static/img/country_flags/tj.png
new file mode 100644
index 00000000000..15d59605186
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tj.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tk.png b/openerp/addons/base/static/img/country_flags/tk.png
new file mode 100644
index 00000000000..b191458e187
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tk.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tl.png b/openerp/addons/base/static/img/country_flags/tl.png
new file mode 100644
index 00000000000..0031a4e41f9
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tl.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tm.png b/openerp/addons/base/static/img/country_flags/tm.png
new file mode 100644
index 00000000000..6699276a8ed
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tn.png b/openerp/addons/base/static/img/country_flags/tn.png
new file mode 100644
index 00000000000..04587942bb7
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/to.png b/openerp/addons/base/static/img/country_flags/to.png
new file mode 100644
index 00000000000..51a1556d5e9
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/to.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tp.png b/openerp/addons/base/static/img/country_flags/tp.png
new file mode 100644
index 00000000000..2ec85c15394
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tp.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tr.png b/openerp/addons/base/static/img/country_flags/tr.png
new file mode 100644
index 00000000000..ebf2a31e807
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tr.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tt.png b/openerp/addons/base/static/img/country_flags/tt.png
new file mode 100644
index 00000000000..e49c3682a50
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tt.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tv.png b/openerp/addons/base/static/img/country_flags/tv.png
new file mode 100644
index 00000000000..ab10c93d5d7
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tv.png differ
diff --git a/openerp/addons/base/static/img/country_flags/tz.png b/openerp/addons/base/static/img/country_flags/tz.png
new file mode 100644
index 00000000000..d4e99a62df7
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/tz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ua.png b/openerp/addons/base/static/img/country_flags/ua.png
new file mode 100644
index 00000000000..57cf957010d
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ua.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ug.png b/openerp/addons/base/static/img/country_flags/ug.png
new file mode 100644
index 00000000000..908784213f0
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ug.png differ
diff --git a/openerp/addons/base/static/img/country_flags/us.png b/openerp/addons/base/static/img/country_flags/us.png
new file mode 100644
index 00000000000..801ef4e8abb
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/us.png differ
diff --git a/openerp/addons/base/static/img/country_flags/uy.png b/openerp/addons/base/static/img/country_flags/uy.png
new file mode 100644
index 00000000000..7af80710a3f
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/uy.png differ
diff --git a/openerp/addons/base/static/img/country_flags/uz.png b/openerp/addons/base/static/img/country_flags/uz.png
new file mode 100644
index 00000000000..54840bd935c
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/uz.png differ
diff --git a/openerp/addons/base/static/img/country_flags/va.png b/openerp/addons/base/static/img/country_flags/va.png
new file mode 100644
index 00000000000..28933fb7e04
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/va.png differ
diff --git a/openerp/addons/base/static/img/country_flags/vc.png b/openerp/addons/base/static/img/country_flags/vc.png
new file mode 100644
index 00000000000..62b9091dc9a
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/vc.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ve.png b/openerp/addons/base/static/img/country_flags/ve.png
new file mode 100644
index 00000000000..7ab81205f84
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ve.png differ
diff --git a/openerp/addons/base/static/img/country_flags/vg.png b/openerp/addons/base/static/img/country_flags/vg.png
new file mode 100644
index 00000000000..9a33f8cf9e6
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/vg.png differ
diff --git a/openerp/addons/base/static/img/country_flags/vi.png b/openerp/addons/base/static/img/country_flags/vi.png
new file mode 100644
index 00000000000..b6361f49e57
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/vi.png differ
diff --git a/openerp/addons/base/static/img/country_flags/vn.png b/openerp/addons/base/static/img/country_flags/vn.png
new file mode 100644
index 00000000000..25753977ec5
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/vn.png differ
diff --git a/openerp/addons/base/static/img/country_flags/vu.png b/openerp/addons/base/static/img/country_flags/vu.png
new file mode 100644
index 00000000000..124c7cf7b56
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/vu.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ws.png b/openerp/addons/base/static/img/country_flags/ws.png
new file mode 100644
index 00000000000..787354394c8
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ws.png differ
diff --git a/openerp/addons/base/static/img/country_flags/ye.png b/openerp/addons/base/static/img/country_flags/ye.png
new file mode 100644
index 00000000000..07c4c8ef1fb
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/ye.png differ
diff --git a/openerp/addons/base/static/img/country_flags/za.png b/openerp/addons/base/static/img/country_flags/za.png
new file mode 100644
index 00000000000..dc4fb4df547
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/za.png differ
diff --git a/openerp/addons/base/static/img/country_flags/zm.png b/openerp/addons/base/static/img/country_flags/zm.png
new file mode 100644
index 00000000000..6275c83148e
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/zm.png differ
diff --git a/openerp/addons/base/static/img/country_flags/zw.png b/openerp/addons/base/static/img/country_flags/zw.png
new file mode 100644
index 00000000000..de98aa7f4d8
Binary files /dev/null and b/openerp/addons/base/static/img/country_flags/zw.png differ
diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py
index 263cb0adf23..bbc5b21d1fb 100644
--- a/openerp/addons/base/tests/test_base.py
+++ b/openerp/addons/base/tests/test_base.py
@@ -9,6 +9,7 @@ class test_base(common.TransactionCase):
super(test_base,self).setUp()
self.res_partner = self.registry('res.partner')
self.res_users = self.registry('res.users')
+ self.res_partner_title = self.registry('res.partner.title')
# samples use effective TLDs from the Mozilla public suffix
# list at http://publicsuffix.org
@@ -285,27 +286,99 @@ class test_base(common.TransactionCase):
def test_60_read_group(self):
cr, uid = self.cr, self.uid
- for user_data in [
- {'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend'},
- {'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend'},
- {'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper'},
- {'name': 'Nab', 'login': 'nab', 'color': 2, 'function': '5$ Wrench'},
- ]:
- self.res_users.create(cr, uid, user_data)
+ title_sir = self.res_partner_title.create(cr, uid, {'name': 'Sir', 'domain': 'contact'})
+ title_lady = self.res_partner_title.create(cr, uid, {'name': 'Lady', 'domain': 'contact'})
+ test_users = [
+ {'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend', 'date': '2015-03-28', 'title': title_lady},
+ {'name': 'Alice', 'login': 'alice2', 'color': 0, 'function': 'Friend', 'date': '2015-01-28', 'title': title_lady},
+ {'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend', 'date': '2015-03-02', 'title': title_sir},
+ {'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper', 'date': '2015-03-20', 'title': title_lady},
+ {'name': 'Nab', 'login': 'nab', 'color': -3, 'function': '5$ Wrench', 'date': '2014-09-10', 'title': title_sir},
+ {'name': 'Nab', 'login': 'nab-she', 'color': 6, 'function': '5$ Wrench', 'date': '2014-01-02', 'title': title_lady},
+ ]
+ ids = [self.res_users.create(cr, uid, u) for u in test_users]
+ domain = [('id', 'in', ids)]
- groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color', 'function'], groupby='function')
- self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
+ # group on local char field without domain and without active_test (-> empty WHERE clause)
+ groups_data = self.res_users.read_group(cr, uid, [], fields=['login'], groupby=['login'], orderby='login DESC', context={'active_test': False})
+ self.assertGreater(len(groups_data), 6, "Incorrect number of results when grouping on a field")
+
+ # group on local char field with limit
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['login'], groupby=['login'], orderby='login DESC', limit=3, offset=3)
+ self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field with limit")
+ self.assertEqual(['bob', 'alice2', 'alice'], [g['login'] for g in groups_data], 'Result mismatch')
+
+ # group on inherited char field, aggregate on int field (second groupby ignored on purpose)
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color', 'function'], groupby=['function', 'login'])
+ self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
+ self.assertEqual(['5$ Wrench', 'Eavesdropper', 'Friend'], [g['function'] for g in groups_data], 'incorrect read_group order')
for group_data in groups_data:
- self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values")
- self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
+ self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values")
+ self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
- groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color'], groupby='name', orderby='name DESC, color asc')
- self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
- self.assertEqual([user['name'] for user in groups_data], ['Eve', 'Bob', 'Alice'], 'Incorrect ordering of the list')
+ # group on inherited char field, reverse order
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='name DESC')
+ self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
+
+ # group on int field, default ordering
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['color'], groupby='color')
+ self.assertEqual([-3, 0, 1, 2, 3, 6], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
+
+ # multi group, second level is int field, should still be summed in first level grouping
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby=['name', 'color'], orderby='name DESC')
+ self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
+ self.assertEqual([3, 3, 2, 1], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
+
+ # group on inherited char field, multiple orders with directions
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='color DESC, name')
+ self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
+ self.assertEqual(['Eve', 'Nab', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
+ self.assertEqual([1, 2, 1, 2], [g['name_count'] for g in groups_data], 'Incorrect number of results')
+
+ # group on inherited date column (res_partner.date) -> Year-Month, default ordering
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'])
+ self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
+ self.assertEqual(['January 2014', 'September 2014', 'January 2015', 'March 2015'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
+ self.assertEqual([1, 1, 1, 3], [g['date_count'] for g in groups_data], 'Incorrect number of results')
+
+ # group on inherited date column (res_partner.date) -> Year-Month, custom order
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'], orderby='date DESC')
+ self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
+ self.assertEqual(['March 2015', 'January 2015', 'September 2014', 'January 2014'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
+ self.assertEqual([3, 1, 1, 1], [g['date_count'] for g in groups_data], 'Incorrect number of results')
+
+ # group on inherited many2one (res_partner.title), default order
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'])
+ self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
+ # m2o is returned as a (id, label) pair
+ self.assertEqual([(title_lady, 'Lady'), (title_sir, 'Sir')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
+ self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
+ self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
+
+ # group on inherited many2one (res_partner.title), reversed natural order
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="title desc")
+ self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
+ # m2o is returned as a (id, label) pair
+ self.assertEqual([(title_sir, 'Sir'), (title_lady, 'Lady')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
+ self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
+ self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
+
+ # group on inherited many2one (res_partner.title), multiple orders with m2o in second position
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="color desc, title desc")
+ self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
+ # m2o is returned as a (id, label) pair
+ self.assertEqual([(title_lady, 'Lady'), (title_sir, 'Sir')], [g['title'] for g in groups_data], 'Incorrect ordering of the result')
+ self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
+ self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
+
+ # group on inherited many2one (res_partner.title), ordered by other inherited field (color)
+ groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby='color')
+ self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
+ # m2o is returned as a (id, label) pair
+ self.assertEqual([(title_sir, 'Sir'), (title_lady, 'Lady')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
+ self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
+ self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
- groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve', 'nab'))], fields=['function', 'color'], groupby='function', orderby='color ASC')
- self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
- self.assertEqual(groups_data, sorted(groups_data, key=lambda x: x['color']), 'Incorrect ordering of the list')
class test_partner_recursion(common.TransactionCase):
diff --git a/openerp/addons/base/tests/test_fields.py b/openerp/addons/base/tests/test_fields.py
index 47ffcd2f5b1..ce34bceb014 100644
--- a/openerp/addons/base/tests/test_fields.py
+++ b/openerp/addons/base/tests/test_fields.py
@@ -179,3 +179,51 @@ class TestPropertyField(common.TransactionCase):
self.partner.write(cr, alice, [partner_id], {'property_country': country_be})
self.assertEqual(self.partner.browse(cr, alice, partner_id).property_country.id, country_be, "Alice does not see the value he has set on the property field")
self.assertEqual(self.partner.browse(cr, bob, partner_id).property_country.id, country_fr, "Changes made by Alice have overwritten Bob's value")
+
+
+class TestHtmlField(common.TransactionCase):
+
+ def setUp(self):
+ super(TestHtmlField, self).setUp()
+ self.partner = self.registry('res.partner')
+
+ def test_00_sanitize(self):
+ cr, uid, context = self.cr, self.uid, {}
+ old_columns = self.partner._columns
+ self.partner._columns = dict(old_columns)
+ self.partner._columns.update({
+ 'comment': fields.html('Secure Html', sanitize=False),
+ })
+ some_ugly_html = """Oops this should maybe be sanitized
+% if object.some_field and not object.oriented:
+
+ % if object.other_field:
+
+ ${object.mako_thing}
+ |
+ |
+ % endif
+
+%if object.dummy_field:
+ Youpie
+%endif"""
+
+ pid = self.partner.create(cr, uid, {
+ 'name': 'Raoul Poilvache',
+ 'comment': some_ugly_html,
+ }, context=context)
+ partner = self.partner.browse(cr, uid, pid, context=context)
+ self.assertEqual(partner.comment, some_ugly_html, 'Error in HTML field: content was sanitized but field has sanitize=False')
+
+ self.partner._columns.update({
+ 'comment': fields.html('Unsecure Html', sanitize=True),
+ })
+ self.partner.write(cr, uid, [pid], {
+ 'comment': some_ugly_html,
+ }, context=context)
+ partner = self.partner.browse(cr, uid, pid, context=context)
+ # sanitize should have closed tags left open in the original html
+ self.assertIn('
', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
+ self.assertIn('', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
+
+ self.partner._columns = old_columns
diff --git a/openerp/addons/base/tests/test_ir_sequence.py b/openerp/addons/base/tests/test_ir_sequence.py
index bc428d7f647..e925fffa3b1 100644
--- a/openerp/addons/base/tests/test_ir_sequence.py
+++ b/openerp/addons/base/tests/test_ir_sequence.py
@@ -8,6 +8,7 @@
# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence
# This assume an existing database.
import psycopg2
+import psycopg2.errorcodes
import unittest2
import openerp
@@ -111,11 +112,11 @@ class test_ir_sequence_no_gap(unittest2.TestCase):
cr0 = cursor()
cr1 = cursor()
cr1._default_log_exceptions = False # Prevent logging a traceback
- msg_re = '^could not obtain lock on row in relation "ir_sequence"$'
- with self.assertRaisesRegexp(psycopg2.OperationalError, msg_re):
+ with self.assertRaises(psycopg2.OperationalError) as e:
n0 = registry('ir.sequence').next_by_code(cr0, ADMIN_USER_ID, 'test_sequence_type_2', {})
assert n0
n1 = registry('ir.sequence').next_by_code(cr1, ADMIN_USER_ID, 'test_sequence_type_2', {})
+ self.assertEqual(e.exception.pgcode, psycopg2.errorcodes.LOCK_NOT_AVAILABLE, msg="postgresql returned an incorrect errcode")
cr0.close()
cr1.close()
diff --git a/openerp/addons/base/tests/test_qweb.py b/openerp/addons/base/tests/test_qweb.py
index 71beaab605a..ca1503a0c24 100644
--- a/openerp/addons/base/tests/test_qweb.py
+++ b/openerp/addons/base/tests/test_qweb.py
@@ -14,7 +14,8 @@ class TestQWebTField(common.TransactionCase):
self.engine = self.registry('ir.qweb')
def context(self, values):
- return ir_qweb.QWebContext(self.cr, self.uid, values)
+ return ir_qweb.QWebContext(
+ self.cr, self.uid, values, context={'inherit_branding': True})
def test_trivial(self):
field = document.createElement('span')
diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py
index 6780996ed6e..06030a84792 100644
--- a/openerp/addons/base/tests/test_views.py
+++ b/openerp/addons/base/tests/test_views.py
@@ -1,6 +1,8 @@
# -*- encoding: utf-8 -*-
from functools import partial
+import unittest2
+
from lxml import etree as ET
from lxml.builder import E
@@ -8,6 +10,24 @@ from openerp.tests import common
Field = E.field
+class ViewCase(common.TransactionCase):
+ def setUp(self):
+ super(ViewCase, self).setUp()
+ self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual)
+
+ def assertTreesEqual(self, n1, n2, msg=None):
+ self.assertEqual(n1.tag, n2.tag)
+ self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg)
+ self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg)
+
+ # Because lxml uses ordereddicts in which order is important to
+ # equality (!?!?!?!)
+ self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
+
+ for c1, c2 in zip(n1, n2):
+ self.assertTreesEqual(c1, c2, msg)
+
+
class TestNodeLocator(common.BaseCase):
"""
The node locator returns None when it can not find a node, and the first
@@ -98,7 +118,7 @@ class TestNodeLocator(common.BaseCase):
E.foo(attr='1', version='3'))
self.assertIsNone(node)
-class TestViewInheritance(common.TransactionCase):
+class TestViewInheritance(ViewCase):
def arch_for(self, name, view_type='form', parent=None):
""" Generates a trivial view of the specified ``view_type``.
@@ -206,7 +226,7 @@ class TestViewInheritance(common.TransactionCase):
self.View.default_view(
self.cr, self.uid, model=self.model, view_type='graph'))
-class TestApplyInheritanceSpecs(common.TransactionCase):
+class TestApplyInheritanceSpecs(ViewCase):
""" Applies a sequence of inheritance specification nodes to a base
architecture. IO state parameters (cr, uid, model, context) are used for
error reporting
@@ -230,8 +250,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(Field(name="replacement"), string="Title")))
+ self.base_arch,
+ E.form(Field(name="replacement"), string="Title"))
def test_delete(self):
spec = Field(name="target", position="replace")
@@ -241,8 +261,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(string="Title")))
+ self.base_arch,
+ E.form(string="Title"))
def test_insert_after(self):
spec = Field(
@@ -254,12 +274,12 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(
+ self.base_arch,
+ E.form(
Field(name="target"),
Field(name="inserted"),
string="Title"
- )))
+ ))
def test_insert_before(self):
spec = Field(
@@ -271,11 +291,11 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(
+ self.base_arch,
+ E.form(
Field(name="inserted"),
Field(name="target"),
- string="Title")))
+ string="Title"))
def test_insert_inside(self):
default = Field(Field(name="inserted"), name="target")
@@ -289,13 +309,13 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(
+ self.base_arch,
+ E.form(
Field(
Field(name="inserted"),
Field(name="inserted 2"),
name="target"),
- string="Title")))
+ string="Title"))
def test_unpack_data(self):
spec = E.data(
@@ -310,15 +330,15 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(
+ self.base_arch,
+ E.form(
Field(
Field(name="inserted 0"),
Field(name="inserted 1"),
Field(name="inserted 2"),
Field(name="inserted 3"),
name="target"),
- string="Title")))
+ string="Title"))
def test_invalid_position(self):
spec = Field(
@@ -350,18 +370,18 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
self.base_arch,
spec, None)
-class TestApplyInheritedArchs(common.TransactionCase):
+class TestApplyInheritedArchs(ViewCase):
""" Applies a sequence of modificator archs to a base view
"""
-class TestViewCombined(common.TransactionCase):
+class TestViewCombined(ViewCase):
"""
Test fallback operations of View.read_combined:
* defaults mapping
* ?
"""
-class TestNoModel(common.TransactionCase):
+class TestNoModel(ViewCase):
def test_create_view_nomodel(self):
View = self.registry('ir.ui.view')
view_id = View.create(self.cr, self.uid, {
@@ -411,13 +431,11 @@ class TestNoModel(common.TransactionCase):
'value': translated_text,
})
sarch = View.translate_qweb(self.cr, self.uid, None, self.arch, 'fr_FR')
-
- self.text_para.text = translated_text
- self.assertEqual(
- ET.tostring(sarch, encoding='utf-8'),
- ET.tostring(self.arch, encoding='utf-8'))
-class TestTemplating(common.TransactionCase):
+ self.text_para.text = translated_text
+ self.assertEqual(sarch, self.arch)
+
+class TestTemplating(ViewCase):
def setUp(self):
import openerp.modules
super(TestTemplating, self).setUp()
@@ -473,7 +491,126 @@ class TestTemplating(common.TransactionCase):
second.get('data-oe-id'),
"second should come from the extension view")
-class test_views(common.TransactionCase):
+ def test_branding_distribute_inner(self):
+ """ Checks that the branding is correctly distributed within a view
+ extension
+ """
+ Views = self.registry('ir.ui.view')
+ id = Views.create(self.cr, self.uid, {
+ 'name': "Base view",
+ 'type': 'qweb',
+ 'arch': """
+
+ """
+ })
+ id2 = Views.create(self.cr, self.uid, {
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': id,
+ 'arch': """
+ -
+ bar
+
+ """
+ })
+
+ arch_string = Views.read_combined(
+ self.cr, self.uid, id, fields=['arch'],
+ context={'inherit_branding': True})['arch']
+
+ arch = ET.fromstring(arch_string)
+ Views.distribute_branding(arch)
+
+ self.assertEqual(
+ arch,
+ E.root(
+ E.item(
+ E.content("bar", {
+ 't-att-href': "foo",
+ 'data-oe-model': 'ir.ui.view',
+ 'data-oe-id': str(id2),
+ 'data-oe-field': 'arch',
+ 'data-oe-xpath': '/xpath/item/content[1]',
+ }), {
+ 'order': '2',
+ 'data-oe-source-id': str(id)
+ }),
+ E.item({
+ 'order': '1',
+ 'data-oe-model': 'ir.ui.view',
+ 'data-oe-id': str(id),
+ 'data-oe-field': 'arch',
+ 'data-oe-xpath': '/root[1]/item[1]'
+ })
+ )
+ )
+
+ def test_esc_no_branding(self):
+ Views = self.registry('ir.ui.view')
+ id = Views.create(self.cr, self.uid, {
+ 'name': "Base View",
+ 'type': 'qweb',
+ 'arch': """
+
+ """,
+ })
+
+ arch_string = Views.read_combined(
+ self.cr, self.uid, id, fields=['arch'],
+ context={'inherit_branding': True})['arch']
+ arch = ET.fromstring(arch_string)
+ Views.distribute_branding(arch)
+
+ self.assertEqual(arch, E.root(E.item(E.span({'t-esc': "foo"}))))
+
+ def test_ignore_unbrand(self):
+ Views = self.registry('ir.ui.view')
+ id = Views.create(self.cr, self.uid, {
+ 'name': "Base view",
+ 'type': 'qweb',
+ 'arch': """
+ -
+
+
+ """
+ })
+ id2 = Views.create(self.cr, self.uid, {
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': id,
+ 'arch': """
+ -
+ bar
+
+ """
+ })
+
+ arch_string = Views.read_combined(
+ self.cr, self.uid, id, fields=['arch'],
+ context={'inherit_branding': True})['arch']
+
+ arch = ET.fromstring(arch_string)
+ Views.distribute_branding(arch)
+
+ self.assertEqual(
+ arch,
+ E.root(
+ E.item(
+ {'t-ignore': 'true', 'order': '1'},
+ E.t({'t-esc': 'foo'}),
+ E.item(
+ {'order': '2', 'data-oe-source-id': str(id)},
+ E.content(
+ {'t-att-href': 'foo'},
+ "bar")
+ )
+ )
+ ),
+ "t-ignore should apply to injected sub-view branding, not just to"
+ " the main view's"
+ )
+
+class test_views(ViewCase):
def test_nonexistent_attribute_removal(self):
Views = self.registry('ir.ui.view')
@@ -589,16 +726,17 @@ class test_views(common.TransactionCase):
})
self.assertEqual(view['type'], 'form')
self.assertEqual(
- ET.tostring(ET.fromstring(
+ ET.fromstring(
view['arch'],
parser=ET.XMLParser(remove_blank_text=True)
- )),
- '')
+ ),
+ E.form(
+ E.p("Replacement data"),
+ E.footer(
+ E.button(name="action_next", type="object", string="New button"),
+ thing="bob"
+ ),
+ string="Replacement title", version="7.0"))
def test_view_inheritance_divergent_models(self):
Views = self.registry('ir.ui.view')
@@ -656,14 +794,13 @@ class test_views(common.TransactionCase):
})
self.assertEqual(view['type'], 'form')
self.assertEqual(
- ET.tostring(ET.fromstring(
+ ET.fromstring(
view['arch'],
parser=ET.XMLParser(remove_blank_text=True)
- )),
- '')
-
+ ),
+ E.form(
+ E.p("Replacement data"),
+ E.footer(
+ E.button(name="action_next", type="object", string="New button")),
+ string="Replacement title", version="7.0"
+ ))
diff --git a/openerp/addons/test_converter/tests/test_html.py b/openerp/addons/test_converter/tests/test_html.py
index 2f0896990ea..3ef6566e829 100644
--- a/openerp/addons/test_converter/tests/test_html.py
+++ b/openerp/addons/test_converter/tests/test_html.py
@@ -112,11 +112,14 @@ class TestCurrencyExport(TestExport):
'widget': 'monetary',
'display_currency': 'c2'
}
+ context = dict(inherit_branding=True)
converted = converter.to_html(
self.cr, self.uid, 'value', obj, options,
doc.createElement('span'),
{'field': 'obj.value', 'field-options': json.dumps(options)},
- '', ir_qweb.QWebContext(self.cr, self.uid, {'obj': obj, 'c2': dest, }))
+ '', ir_qweb.QWebContext(self.cr, self.uid, {'obj': obj, 'c2': dest, }),
+ context=context,
+ )
return converted
def test_currency_post(self):
diff --git a/openerp/addons/test_impex/tests/test_export.py b/openerp/addons/test_impex/tests/test_export.py
index 26219b36d3b..995d69471d6 100644
--- a/openerp/addons/test_impex/tests/test_export.py
+++ b/openerp/addons/test_impex/tests/test_export.py
@@ -309,8 +309,9 @@ class test_m2o(CreatorCase):
def test_external_id(self):
integer_id = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
- # __export__.$class.$id
- external_id = u'__export__.export_many2one_%d' % integer_id
+ # Expecting the m2o target model name in the external id,
+ # not this model's name
+ external_id = u'__export__.export_integer_%d' % integer_id
self.assertEqual(
self.export(integer_id, fields=['value/id']),
[[external_id]])
diff --git a/openerp/cli/server.py b/openerp/cli/server.py
index 42d920da9aa..e7ba695f19c 100644
--- a/openerp/cli/server.py
+++ b/openerp/cli/server.py
@@ -127,7 +127,6 @@ def main(args):
check_root_user()
openerp.tools.config.parse_config(args)
check_postgres_user()
- openerp.netsvc.init_logger()
report_configuration()
config = openerp.tools.config
diff --git a/openerp/http.py b/openerp/http.py
index 1750478a836..5f37afbc116 100644
--- a/openerp/http.py
+++ b/openerp/http.py
@@ -23,6 +23,7 @@ import urlparse
import warnings
import babel.core
+import psutil
import psycopg2
import simplejson
import werkzeug.contrib.sessions
@@ -35,7 +36,7 @@ import werkzeug.wsgi
import openerp
from openerp.service import security, model as service_model
-import openerp.tools
+from openerp.tools.func import lazy_property
_logger = logging.getLogger(__name__)
@@ -50,6 +51,70 @@ request = _request_stack()
A global proxy that always redirect to the current request object.
"""
+def replace_request_password(args):
+ # password is always 3rd argument in a request, we replace it in RPC logs
+ # so it's easier to forward logs for diagnostics/debugging purposes...
+ if len(args) > 2:
+ args = list(args)
+ args[2] = '*'
+ return tuple(args)
+
+def dispatch_rpc(service_name, method, params):
+ """ Handle a RPC call.
+
+ This is pure Python code, the actual marshalling (from/to XML-RPC) is done
+ in a upper layer.
+ """
+ try:
+ rpc_request = logging.getLogger(__name__ + '.rpc.request')
+ rpc_response = logging.getLogger(__name__ + '.rpc.response')
+ rpc_request_flag = rpc_request.isEnabledFor(logging.DEBUG)
+ rpc_response_flag = rpc_response.isEnabledFor(logging.DEBUG)
+ if rpc_request_flag or rpc_response_flag:
+ start_time = time.time()
+ start_rss, start_vms = 0, 0
+ start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
+ if rpc_request and rpc_response_flag:
+ openerp.netsvc.log(rpc_request, logging.DEBUG, '%s.%s' % (service_name, method), replace_request_password(params))
+
+ threading.current_thread().uid = None
+ threading.current_thread().dbname = None
+ if service_name == 'common':
+ dispatch = openerp.service.common.dispatch
+ elif service_name == 'db':
+ dispatch = openerp.service.db.dispatch
+ elif service_name == 'object':
+ dispatch = openerp.service.model.dispatch
+ elif service_name == 'report':
+ dispatch = openerp.service.report.dispatch
+ else:
+ dispatch = openerp.service.wsgi_server.rpc_handlers.get(service_name)
+ result = dispatch(method, params)
+
+ if rpc_request_flag or rpc_response_flag:
+ end_time = time.time()
+ end_rss, end_vms = 0, 0
+ end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info()
+ logline = '%s.%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (service_name, method, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024)
+ if rpc_response_flag:
+ openerp.netsvc.log(rpc_response, logging.DEBUG, logline, result)
+ else:
+ openerp.netsvc.log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1)
+
+ return result
+ except (openerp.osv.orm.except_orm, openerp.exceptions.AccessError, \
+ openerp.exceptions.AccessDenied, openerp.exceptions.Warning, \
+ openerp.exceptions.RedirectWarning):
+ raise
+ except openerp.exceptions.DeferredException, e:
+ _logger.exception(openerp.tools.exception_to_unicode(e))
+ openerp.tools.debugger.post_mortem(openerp.tools.config, e.traceback)
+ raise
+ except Exception, e:
+ _logger.exception(openerp.tools.exception_to_unicode(e))
+ openerp.tools.debugger.post_mortem(openerp.tools.config, sys.exc_info())
+ raise
+
def local_redirect(path, query=None, keep_hash=False, forward_debug=True, code=303):
url = path
if not query:
@@ -133,6 +198,10 @@ class WebRequest(object):
self.auth_method = None
self._cr_cm = None
self._cr = None
+
+ # prevents transaction commit, use when you catch an exception during handling
+ self._failed = None
+
# set db/uid trackers - they're cleaned up at the WSGI
# dispatching phase in openerp.service.wsgi_server.application
if self.db:
@@ -180,10 +249,13 @@ class WebRequest(object):
_request_stack.pop()
if self._cr:
- # Dont commit test cursors
- if not openerp.tests.common.release_test_cursor(self.session_id):
- if exc_type is None:
+ # Dont close test cursors
+ if not openerp.tests.common.release_test_cursor(self._cr):
+ if exc_type is None and not self._failed:
self._cr.commit()
+ else:
+ # just to be explicit - happens at close() anyway
+ self._cr.rollback()
self._cr.close()
# just to be sure no one tries to re-use the request
self.disable_db = True
@@ -209,20 +281,19 @@ class WebRequest(object):
# Backward for 7.0
if self.endpoint.first_arg_is_req:
args = (request,) + args
+
# Correct exception handling and concurency retry
@service_model.check
def checked_call(___dbname, *a, **kw):
+ # The decorator can call us more than once if there is an database error. In this
+ # case, the request cursor is unusable. Rollback transaction to create a new one.
+ if self._cr and not openerp.tools.config['test_enable']:
+ self._cr.rollback()
return self.endpoint(*a, **kw)
- # FIXME: code and rollback management could be cleaned
- try:
- if self.db:
- return checked_call(self.db, *args, **kwargs)
- return self.endpoint(*args, **kwargs)
- except Exception:
- if self._cr:
- self._cr.rollback()
- raise
+ if self.db:
+ return checked_call(self.db, *args, **kwargs)
+ return self.endpoint(*args, **kwargs)
@property
def debug(self):
@@ -362,21 +433,25 @@ class JsonRequest(WebRequest):
response['id'] = self.jsonrequest.get('id')
response["result"] = self._call_function(**self.params)
except AuthenticationError, e:
- _logger.exception("Exception during JSON request handling.")
+ _logger.exception("JSON-RPC AuthenticationError in %s.", self.httprequest.path)
se = serialize_exception(e)
error = {
'code': 100,
'message': "OpenERP Session Invalid",
'data': se
}
+ self._failed = e # prevent tx commit
except Exception, e:
- _logger.exception("Exception during JSON request handling.")
+ # Mute test cursor error for runbot
+ if not (openerp.tools.config['test_enable'] and isinstance(e, psycopg2.OperationalError)):
+ _logger.exception("JSON-RPC Exception in %s.", self.httprequest.path)
se = serialize_exception(e)
error = {
'code': 200,
'message': "OpenERP Server Error",
'data': se
}
+ self._failed = e # prevent tx commit
if error:
response["error"] = error
@@ -618,7 +693,7 @@ class SessionExpiredException(Exception):
class Service(object):
"""
.. deprecated:: 8.0
- Use ``openerp.netsvc.dispatch_rpc()`` instead.
+ Use ``dispatch_rpc()`` instead.
"""
def __init__(self, session, service_name):
self.session = session
@@ -626,7 +701,7 @@ class Service(object):
def __getattr__(self, method):
def proxy_method(*args):
- result = openerp.netsvc.dispatch_rpc(self.service_name, method, args)
+ result = dispatch_rpc(self.service_name, method, args)
return result
return proxy_method
@@ -699,7 +774,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
HTTP_HOST=wsgienv['HTTP_HOST'],
REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
)
- uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env])
+ uid = dispatch_rpc('common', 'authenticate', [db, login, password, env])
else:
security.check(db, uid, password)
self.db = db
@@ -733,7 +808,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
self.setdefault("uid", None)
self.setdefault("login", None)
self.setdefault("password", None)
- self.setdefault("context", {'tz': "UTC", "uid": None})
+ self.setdefault("context", {})
def get_context(self):
"""
@@ -803,14 +878,14 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
def send(self, service_name, method, *args):
"""
.. deprecated:: 8.0
- Use ``openerp.netsvc.dispatch_rpc()`` instead.
+ Use ``dispatch_rpc()`` instead.
"""
- return openerp.netsvc.dispatch_rpc(service_name, method, args)
+ return dispatch_rpc(service_name, method, args)
def proxy(self, service):
"""
.. deprecated:: 8.0
- Use ``openerp.netsvc.dispatch_rpc()`` instead.
+ Use ``dispatch_rpc()`` instead.
"""
return Service(self, service)
@@ -1003,22 +1078,25 @@ class Root(object):
path = openerp.tools.config.session_dir
_logger.debug('HTTP sessions stored in: %s', path)
self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession)
+ self._loaded = False
- # TODO should we move this to ir.http so that only configured modules are served ?
- _logger.info("HTTP Configuring static files")
- self.load_addons()
-
+ @lazy_property
+ def nodb_routing_map(self):
_logger.info("Generating nondb routing")
- self.nodb_routing_map = routing_map([''] + openerp.conf.server_wide_modules, True)
+ return routing_map([''] + openerp.conf.server_wide_modules, True)
def __call__(self, environ, start_response):
""" Handle a WSGI request
"""
+ if not self._loaded:
+ self._loaded = True
+ self.load_addons()
return self.dispatch(environ, start_response)
def load_addons(self):
- """ Load all addons from addons patch containg static files and
+ """ Load all addons from addons path containing static files and
controllers and configure them. """
+ # TODO should we move this to ir.http so that only configured modules are served ?
statics = {}
for addons_path in openerp.modules.module.ad_paths:
@@ -1032,12 +1110,16 @@ class Root(object):
_logger.debug("Loading %s", module)
if 'openerp.addons' in sys.modules:
m = __import__('openerp.addons.' + module)
+ else:
+ m = None
addons_module[module] = m
addons_manifest[module] = manifest
statics['/%s/static' % module] = path_static
- app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, statics)
- self.dispatch = DisableCacheMiddleware(app)
+ if statics:
+ _logger.info("HTTP Configuring static files")
+ app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, statics)
+ self.dispatch = DisableCacheMiddleware(app)
def setup_session(self, httprequest):
# recover or create session
@@ -1163,7 +1245,7 @@ class Root(object):
return request.registry['ir.http'].routing_map()
def db_list(force=False, httprequest=None):
- dbs = openerp.netsvc.dispatch_rpc("db", "list", [force])
+ dbs = dispatch_rpc("db", "list", [force])
return db_filter(dbs, httprequest=httprequest)
def db_filter(dbs, httprequest=None):
@@ -1208,18 +1290,10 @@ class CommonController(Controller):
@route('/jsonrpc', type='json', auth="none")
def jsonrpc(self, service, method, args):
""" Method used by client APIs to contact OpenERP. """
- return openerp.netsvc.dispatch_rpc(service, method, args)
+ return dispatch_rpc(service, method, args)
- @route('/gen_session_id', type='json', auth="none")
- def gen_session_id(self):
- nsession = root.session_store.new()
- return nsession.sid
-
-root = None
-
-def wsgi_postload():
- global root
- root = Root()
- openerp.service.wsgi_server.register_wsgi_handler(root)
+# register main wsgi handler
+root = Root()
+openerp.service.wsgi_server.register_wsgi_handler(root)
# vim:et:ts=4:sw=4:
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index 7ca0a6785d6..5635e42e780 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -42,6 +42,7 @@ from openerp import SUPERUSER_ID
from openerp.tools.translate import _
from openerp.modules.module import initialize_sys_path, \
load_openerp_module, init_module_models, adapt_version
+from module import runs_post_install
_logger = logging.getLogger(__name__)
_test_logger = logging.getLogger('openerp.tests')
@@ -187,6 +188,10 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
# Yamel test
report.record_result(load_test(module_name, idref, mode))
# Python tests
+ ir_http = registry['ir.http']
+ if hasattr(ir_http, '_routing_map'):
+ # Force routing map to be rebuilt between each module test suite
+ del(ir_http._routing_map)
report.record_result(openerp.modules.module.run_unit_tests(module_name, cr.dbname))
processed_modules.append(package.name)
@@ -421,8 +426,13 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
for model in registry.models.values():
model._register_hook(cr)
+ # STEP 9: Run the post-install tests
+ cr.commit()
+ if openerp.tools.config['test_enable']:
+ cr.execute("SELECT name FROM ir_module_module WHERE state='installed'")
+ for module_name in cr.fetchall():
+ report.record_result(openerp.modules.module.run_unit_tests(module_name[0], cr.dbname, position=runs_post_install))
finally:
cr.close()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/modules/module.py b/openerp/modules/module.py
index 554b38f06cf..82e86e83ce2 100644
--- a/openerp/modules/module.py
+++ b/openerp/modules/module.py
@@ -38,7 +38,6 @@ import openerp.release as release
from openerp.tools.safe_eval import safe_eval as eval
_logger = logging.getLogger(__name__)
-_test_logger = logging.getLogger('openerp.tests')
# addons path as a list
ad_paths = []
@@ -170,13 +169,15 @@ def get_module_icon(module):
return ('/' + module + '/') + '/'.join(iconpath)
return '/base/' + '/'.join(iconpath)
-def load_information_from_description_file(module):
+def load_information_from_description_file(module, mod_path=None):
"""
:param module: The name of the module (sale, purchase, ...)
+ :param mod_path: Physical path of module, if not providedThe name of the module (sale, purchase, ...)
"""
- terp_file = get_module_resource(module, '__openerp__.py')
- mod_path = get_module_path(module)
+ if not mod_path:
+ mod_path = get_module_path(module)
+ terp_file = opj(mod_path, '__openerp__.py')
if terp_file:
info = {}
if os.path.isfile(terp_file):
@@ -340,7 +341,8 @@ def get_test_modules(module):
# Use a custom stream object to log the test executions.
class TestStream(object):
- def __init__(self):
+ def __init__(self, logger_name='openerp.tests'):
+ self.logger = logging.getLogger(logger_name)
self.r = re.compile(r'^-*$|^ *... *$|^ok$')
def flush(self):
pass
@@ -352,34 +354,10 @@ class TestStream(object):
if not first:
c = '` ' + c
first = False
- _test_logger.info(c)
+ self.logger.info(c)
current_test = None
-def run_unit_tests(module_name, dbname):
- """
- :returns: ``True`` if all of ``module_name``'s tests succeeded, ``False``
- if any of them failed.
- :rtype: bool
- """
- global current_test
- current_test = module_name
- mods = get_test_modules(module_name)
- r = True
- for m in mods:
- tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m))
- suite = unittest2.TestSuite(itertools.ifilter(runs_at_install, tests))
- _logger.info('module %s: running test %s.', module_name, m.__name__)
-
- result = unittest2.TextTestRunner(verbosity=2, stream=TestStream()).run(suite)
-
- if not result.wasSuccessful():
- r = False
- _logger.error("Module %s: %d failures, %d errors",
- module_name, len(result.failures), len(result.errors))
- current_test = None
- return r
-
def runs_at(test, hook, default):
# by default, tests do not run post install
test_runs = getattr(test, hook, default)
@@ -396,6 +374,30 @@ def runs_at(test, hook, default):
runs_at_install = functools.partial(runs_at, hook='at_install', default=True)
runs_post_install = functools.partial(runs_at, hook='post_install', default=False)
+def run_unit_tests(module_name, dbname, position=runs_at_install):
+ """
+ :returns: ``True`` if all of ``module_name``'s tests succeeded, ``False``
+ if any of them failed.
+ :rtype: bool
+ """
+ global current_test
+ current_test = module_name
+ mods = get_test_modules(module_name)
+ r = True
+ for m in mods:
+ tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m))
+ suite = unittest2.TestSuite(itertools.ifilter(position, tests))
+ _logger.info('running %s tests.', m.__name__)
+
+ result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite)
+
+ if not result.wasSuccessful():
+ r = False
+ _logger.error("Module %s: %d failures, %d errors",
+ module_name, len(result.failures), len(result.errors))
+ current_test = None
+ return r
+
def unwrap_suite(test):
"""
Attempts to unpack testsuites (holding suites or cases) in order to
diff --git a/openerp/netsvc.py b/openerp/netsvc.py
index 755f6513b25..d762eb5006f 100644
--- a/openerp/netsvc.py
+++ b/openerp/netsvc.py
@@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2012 OpenERP SA ()
+# Copyright (C) 2004-2014 OpenERP SA ()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -19,23 +19,29 @@
#
##############################################################################
-
import logging
import logging.handlers
import os
+import pprint
import release
import sys
import threading
-import time
-import types
-from pprint import pformat
-import psutil
-import tools
+import psycopg2
+
import openerp
+import sql_db
+import tools
_logger = logging.getLogger(__name__)
+def log(logger, level, prefix, msg, depth=None):
+ indent=''
+ indent_after=' '*len(prefix)
+ for line in (prefix + pprint.pformat(msg, depth=depth)).split('\n'):
+ logger.log(level, indent+line)
+ indent=indent_after
+
def LocalService(name):
"""
The openerp.netsvc.LocalService() function is deprecated. It still works
@@ -62,6 +68,36 @@ def LocalService(name):
with registry.cursor() as cr:
return registry['ir.actions.report.xml']._lookup_report(cr, name[len('report.'):])
+class PostgreSQLHandler(logging.Handler):
+ """ PostgreSQL Loggin Handler will store logs in the database, by default
+ the current database, can be set using --log-db=DBNAME
+ """
+ def emit(self, record):
+ ct = threading.current_thread()
+ ct_db = getattr(ct, 'dbname', None)
+ ct_uid = getattr(ct, 'uid', None)
+ dbname = tools.config['log_db'] or ct_db
+ if dbname:
+ cr = None
+ try:
+ cr = sql_db.db_connect(dbname).cursor()
+ msg = unicode(record.msg)
+ traceback = getattr(record, 'exc_text', '')
+ if traceback:
+ msg = "%s\n%s" % (msg, traceback)
+ level = logging.getLevelName(record.levelno)
+ val = (ct_uid, ct_uid, 'server', dbname, record.name, level, msg, record.pathname, record.lineno, record.funcName)
+ cr.execute("""
+ INSERT INTO ir_logging(create_date, write_date, create_uid, write_uid, type, dbname, name, level, message, path, line, func)
+ VALUES (NOW() at time zone 'UTC', NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
+ """, val )
+ cr.commit()
+ except Exception, e:
+ pass
+ finally:
+ if cr:
+ cr.close()
+
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10)
#The background is set with 40 plus the number of the color, and the foreground with 30
#These are the sequences need to get colored ouput
@@ -89,7 +125,13 @@ class ColoredFormatter(DBFormatter):
record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
return DBFormatter.format(self, record)
+_logger_init = False
def init_logger():
+ global _logger_init
+ if _logger_init:
+ return
+ _logger_init = True
+
from tools.translate import resetlocale
resetlocale()
@@ -101,7 +143,7 @@ def init_logger():
if os.name == 'nt':
handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version))
else:
- handler = logging.handlers.SysLogHandler('/dev/log')
+ handler = logging.handlers.SysLogHandler()
format = '%s %s' % (release.description, release.version) \
+ ':%(dbname)s:%(levelname)s:%(name)s:%(message)s'
@@ -109,11 +151,12 @@ def init_logger():
# LogFile Handler
logf = tools.config['logfile']
try:
+ # We check we have the right location for the log files
dirname = os.path.dirname(logf)
if dirname and not os.path.isdir(dirname):
os.makedirs(dirname)
if tools.config['logrotate'] is not False:
- handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30)
+ handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30)
elif os.name == 'posix':
handler = logging.handlers.WatchedFileHandler(logf)
else:
@@ -129,15 +172,23 @@ def init_logger():
# behind Apache with mod_wsgi, handler.stream will have type mod_wsgi.Log,
# which has no fileno() method. (mod_wsgi.Log is what is being bound to
# sys.stderr when the logging.StreamHandler is being constructed above.)
- if isinstance(handler, logging.StreamHandler) \
- and hasattr(handler.stream, 'fileno') \
- and os.isatty(handler.stream.fileno()):
+ def is_a_tty(stream):
+ return hasattr(stream, 'fileno') and os.isatty(stream.fileno())
+
+ if isinstance(handler, logging.StreamHandler) and is_a_tty(handler.stream):
formatter = ColoredFormatter(format)
else:
formatter = DBFormatter(format)
handler.setFormatter(formatter)
- # Configure handlers
+ logging.getLogger().addHandler(handler)
+
+ if tools.config['log_db']:
+ postgresqlHandler = PostgreSQLHandler()
+ postgresqlHandler.setLevel(logging.WARNING)
+ logging.getLogger().addHandler(postgresqlHandler)
+
+ # Configure loggers levels
pseudo_config = PSEUDOCONFIG_MAPPER.get(tools.config['log_level'], [])
logconfig = tools.config['log_handler']
@@ -147,26 +198,22 @@ def init_logger():
loggername, level = logconfig_item.split(':')
level = getattr(logging, level, logging.INFO)
logger = logging.getLogger(loggername)
- logger.handlers = []
logger.setLevel(level)
- logger.addHandler(handler)
- if loggername != '':
- logger.propagate = False
for logconfig_item in logging_configurations:
_logger.debug('logger level set: "%s"', logconfig_item)
DEFAULT_LOG_CONFIGURATION = [
'openerp.workflow.workitem:WARNING',
- 'openerp.netsvc.rpc.request:INFO',
- 'openerp.netsvc.rpc.response:INFO',
+ 'openerp.http.rpc.request:INFO',
+ 'openerp.http.rpc.response:INFO',
'openerp.addons.web.http:INFO',
'openerp.sql_db:INFO',
':INFO',
]
PSEUDOCONFIG_MAPPER = {
- 'debug_rpc_answer': ['openerp:DEBUG','openerp.netsvc.rpc.request:DEBUG', 'openerp.netsvc.rpc.response:DEBUG'],
- 'debug_rpc': ['openerp:DEBUG','openerp.netsvc.rpc.request:DEBUG'],
+ 'debug_rpc_answer': ['openerp:DEBUG','openerp.http.rpc.request:DEBUG', 'openerp.http.rpc.response:DEBUG'],
+ 'debug_rpc': ['openerp:DEBUG','openerp.http.rpc.request:DEBUG'],
'debug': ['openerp:DEBUG'],
'debug_sql': ['openerp.sql_db:DEBUG'],
'info': [],
@@ -175,99 +222,4 @@ PSEUDOCONFIG_MAPPER = {
'critical': ['openerp:CRITICAL'],
}
-# A alternative logging scheme for automated runs of the
-# server intended to test it.
-def init_alternative_logger():
- class H(logging.Handler):
- def emit(self, record):
- if record.levelno > 20:
- print record.levelno, record.pathname, record.msg
- handler = H()
- # Add the handler to the 'openerp' logger.
- logger = logging.getLogger('openerp')
- logger.addHandler(handler)
- logger.setLevel(logging.ERROR)
-
-def replace_request_password(args):
- # password is always 3rd argument in a request, we replace it in RPC logs
- # so it's easier to forward logs for diagnostics/debugging purposes...
- if len(args) > 2:
- args = list(args)
- args[2] = '*'
- return tuple(args)
-
-def log(logger, level, prefix, msg, depth=None):
- indent=''
- indent_after=' '*len(prefix)
- for line in (prefix+pformat(msg, depth=depth)).split('\n'):
- logger.log(level, indent+line)
- indent=indent_after
-
-def dispatch_rpc(service_name, method, params):
- """ Handle a RPC call.
-
- This is pure Python code, the actual marshalling (from/to XML-RPC) is done
- in a upper layer.
- """
- try:
- rpc_request = logging.getLogger(__name__ + '.rpc.request')
- rpc_response = logging.getLogger(__name__ + '.rpc.response')
- rpc_request_flag = rpc_request.isEnabledFor(logging.DEBUG)
- rpc_response_flag = rpc_response.isEnabledFor(logging.DEBUG)
- if rpc_request_flag or rpc_response_flag:
- start_time = time.time()
- start_rss, start_vms = 0, 0
- start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
- if rpc_request and rpc_response_flag:
- log(rpc_request,logging.DEBUG,'%s.%s'%(service_name,method), replace_request_password(params))
-
- threading.current_thread().uid = None
- threading.current_thread().dbname = None
- if service_name == 'common':
- dispatch = openerp.service.common.dispatch
- elif service_name == 'db':
- dispatch = openerp.service.db.dispatch
- elif service_name == 'object':
- dispatch = openerp.service.model.dispatch
- elif service_name == 'report':
- dispatch = openerp.service.report.dispatch
- else:
- dispatch = openerp.service.wsgi_server.rpc_handlers.get(service_name)
- result = dispatch(method, params)
-
- if rpc_request_flag or rpc_response_flag:
- end_time = time.time()
- end_rss, end_vms = 0, 0
- end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info()
- logline = '%s.%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (service_name, method, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024)
- if rpc_response_flag:
- log(rpc_response,logging.DEBUG, logline, result)
- else:
- log(rpc_request,logging.DEBUG, logline, replace_request_password(params), depth=1)
-
- return result
- except openerp.osv.orm.except_orm:
- raise
- except openerp.exceptions.AccessError:
- raise
- except openerp.exceptions.AccessDenied:
- raise
- except openerp.exceptions.Warning:
- raise
- except openerp.exceptions.RedirectWarning:
- raise
- except openerp.exceptions.DeferredException, e:
- _logger.exception(tools.exception_to_unicode(e))
- post_mortem(e.traceback)
- raise
- except Exception, e:
- _logger.exception(tools.exception_to_unicode(e))
- post_mortem(sys.exc_info())
- raise
-
-def post_mortem(info):
- if tools.config['debug_mode'] and isinstance(info[2], types.TracebackType):
- import pdb
- pdb.post_mortem(info[2])
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py
index 478f982cd69..d5d0ee0fa30 100644
--- a/openerp/osv/fields.py
+++ b/openerp/osv/fields.py
@@ -236,15 +236,24 @@ class char(_column):
class text(_column):
_type = 'text'
+
class html(text):
_type = 'html'
_symbol_c = '%s'
- def _symbol_f(x):
- if x is None or x == False:
+
+ def _symbol_set_html(self, value):
+ if value is None or value is False:
return None
- return html_sanitize(x)
-
- _symbol_set = (_symbol_c, _symbol_f)
+ if not self._sanitize:
+ return value
+ return html_sanitize(value)
+
+ def __init__(self, string='unknown', sanitize=True, **args):
+ super(html, self).__init__(string=string, **args)
+ self._sanitize = sanitize
+ # symbol_set redefinition because of sanitize specific behavior
+ self._symbol_f = self._symbol_set_html
+ self._symbol_set = (self._symbol_c, self._symbol_f)
import __builtin__
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 3126507608f..64fa06783ab 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -75,7 +75,7 @@ _schema = logging.getLogger(__name__ + '.schema')
# List of etree._Element subclasses that we choose to ignore when parsing XML.
from openerp.tools import SKIPPED_ELEMENT_TYPES
-regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
+regex_order = re.compile('^( *([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
AUTOINIT_RECALCULATE_STORED_FIELDS = 1000
@@ -1124,7 +1124,7 @@ class BaseModel(object):
def _get_xml_id(self, cr, uid, r):
model_data = self.pool.get('ir.model.data')
- data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
+ data_ids = model_data.search(cr, uid, [('model', '=', r._model._name), ('res_id', '=', r['id'])])
if len(data_ids):
d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
if d['module']:
@@ -1134,13 +1134,13 @@ class BaseModel(object):
else:
postfix = 0
while True:
- n = self._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
+ n = r._model._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
if not model_data.search(cr, uid, [('name', '=', n)]):
break
postfix += 1
model_data.create(cr, SUPERUSER_ID, {
'name': n,
- 'model': self._name,
+ 'model': r._model._name,
'res_id': r['id'],
'module': '__export__',
})
@@ -2185,36 +2185,42 @@ class BaseModel(object):
r['__fold'] = folded.get(r[groupby] and r[groupby][0], False)
return result
- def _read_group_generate_order_by(self, orderby, aggregated_fields, groupby, query):
+ def _read_group_prepare(self, orderby, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type=None):
"""
- Generates the ORDER BY sql clause for the read group method. Adds the missing JOIN clause
+ Prepares the GROUP BY and ORDER BY terms for the read_group method. Adds the missing JOIN clause
to the query if order should be computed against m2o field.
:param orderby: the orderby definition in the form "%(field)s %(order)s"
:param aggregated_fields: list of aggregated fields in the query
:param groupby: the current groupby field name
- :param query: the query object used to construct the query afterwards
+ :param qualified_groupby_field: the fully qualified SQL name for the grouped field
+ :param osv.Query query: the query under construction
+ :param groupby_type: the type of the grouped field
+ :return: (groupby_terms, orderby_terms)
"""
- orderby_list = []
- ob = []
- for order_splits in orderby.split(','):
- order_split = order_splits.split()
- orderby_field = order_split[0]
- fields = openerp.osv.fields
- if isinstance(self._all_columns[orderby_field].column, (fields.date, fields.datetime)):
- continue
- orderby_dir = len(order_split) == 2 and order_split[1].upper() == 'ASC' and 'ASC' or 'DESC'
- if orderby_field == groupby:
- orderby_item = self._generate_order_by(order_splits, query).replace('ORDER BY ', '')
- if orderby_item:
- orderby_list.append(orderby_item)
- ob += [obi.split()[0] for obi in orderby_item.split(',')]
- elif orderby_field in aggregated_fields:
- orderby_list.append('%s %s' % (orderby_field,orderby_dir))
+ orderby_terms = []
+ groupby_terms = [qualified_groupby_field] if groupby else []
+ if not orderby:
+ return groupby_terms, orderby_terms
- if orderby_list:
- return ' ORDER BY %s' % (','.join(orderby_list)), ob and ','.join(ob) or ''
- else:
- return '', ''
+ self._check_qorder(orderby)
+ for order_part in orderby.split(','):
+ order_split = order_part.split()
+ order_field = order_split[0]
+ if order_field == groupby:
+ if groupby_type == 'many2one':
+ order_clause = self._generate_order_by(order_part, query).replace('ORDER BY ', '')
+ if order_clause:
+ orderby_terms.append(order_clause)
+ groupby_terms += [order_term.split()[0] for order_term in order_clause.split(',')]
+ else:
+ orderby_terms.append(order_part)
+ elif order_field in aggregated_fields:
+ orderby_terms.append(order_part)
+ else:
+ # Cannot order by a field that will not appear in the results (needs to be grouped or aggregated)
+ _logger.warn('%s: read_group order by `%s` ignored, cannot sort on empty columns (not grouped/aggregated)',
+ self._name, order_part)
+ return groupby_terms, orderby_terms
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
"""
@@ -2275,9 +2281,9 @@ class BaseModel(object):
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
fget = self.fields_get(cr, uid, fields)
- flist = ''
- group_count = group_by = groupby
group_by_params = {}
+ select_terms = []
+ groupby_type = None
if groupby:
if fget.get(groupby):
groupby_type = fget[groupby]['type']
@@ -2305,12 +2311,9 @@ class BaseModel(object):
timezone = context.get('tz', 'UTC')
qualified_groupby_field = "timezone('%s', timezone('UTC',%s))" % (timezone, qualified_groupby_field)
qualified_groupby_field = "date_trunc('%s', %s)" % (interval, qualified_groupby_field)
- flist = "%s as %s " % (qualified_groupby_field, groupby)
elif groupby_type == 'boolean':
qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
- flist = "%s as %s " % (qualified_groupby_field, groupby)
- else:
- flist = qualified_groupby_field
+ select_terms.append("%s as %s " % (qualified_groupby_field, groupby))
else:
# Don't allow arbitrary values, as this would be a SQL injection vector!
raise except_orm(_('Invalid group_by'),
@@ -2318,34 +2321,48 @@ class BaseModel(object):
aggregated_fields = [
f for f in fields
- if f not in ('id', 'sequence')
+ if f not in ('id', 'sequence', groupby)
if fget[f]['type'] in ('integer', 'float')
if (f in self._all_columns and getattr(self._all_columns[f].column, '_classic_write'))]
for f in aggregated_fields:
group_operator = fget[f].get('group_operator', 'sum')
- if flist:
- flist += ', '
qualified_field = self._inherits_join_calc(f, query)
- flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
+ select_terms.append("%s(%s) AS %s" % (group_operator, qualified_field, f))
- order = orderby or groupby
- orderby_clause = ''
- ob = ''
- if order:
- orderby_clause, ob = self._read_group_generate_order_by(order, aggregated_fields, groupby, query)
-
- gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
+ order = orderby or groupby or ''
+ groupby_terms, orderby_terms = self._read_group_prepare(order, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type)
from_clause, where_clause, where_clause_params = query.get_sql()
- where_clause = where_clause and ' WHERE ' + where_clause
- limit_str = limit and ' limit %d' % limit or ''
- offset_str = offset and ' offset %d' % offset or ''
if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
- group_count = '_'
- cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + (ob and ',') + ob + orderby_clause + limit_str + offset_str, where_clause_params)
- alldata = {}
- groupby = group_by
+ count_field = '_'
+ else:
+ count_field = groupby
+ prefix_terms = lambda prefix, terms: (prefix + " " + ",".join(terms)) if terms else ''
+ prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else ''
+
+ query = """
+ SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s_count
+ %(extra_fields)s
+ FROM %(from)s
+ %(where)s
+ %(groupby)s
+ %(orderby)s
+ %(limit)s
+ %(offset)s
+ """ % {
+ 'table': self._table,
+ 'count_field': count_field,
+ 'extra_fields': prefix_terms(',', select_terms),
+ 'from': from_clause,
+ 'where': prefix_term('WHERE', where_clause),
+ 'groupby': prefix_terms('GROUP BY', groupby_terms),
+ 'orderby': prefix_terms('ORDER BY', orderby_terms),
+ 'limit': prefix_term('LIMIT', int(limit) if limit else None),
+ 'offset': prefix_term('OFFSET', int(offset) if limit else None),
+ }
+ cr.execute(query, where_clause_params)
+ alldata = {}
fetched_data = cr.dictfetchall()
data_ids = []
@@ -2356,8 +2373,6 @@ class BaseModel(object):
data_ids.append(r['id'])
del r['id']
-
-
if groupby:
data = self.read(cr, uid, data_ids, [groupby], context=context)
# restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small):
@@ -2847,7 +2862,7 @@ class BaseModel(object):
msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
_schema.debug(msg, self._table, k, f._type)
- if isinstance(f, fields.many2one):
+ if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
dest_model = self.pool[f._obj]
if dest_model._table != 'ir_actions':
self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete)
@@ -2882,7 +2897,7 @@ class BaseModel(object):
todo_end.append((order, self._update_store, (f, k)))
# and add constraints if needed
- if isinstance(f, fields.many2one):
+ if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
if f._obj not in self.pool:
raise except_orm('Programming Error', 'There is no reference available for %s' % (f._obj,))
dest_model = self.pool[f._obj]
diff --git a/openerp/service/db.py b/openerp/service/db.py
index f48a1e6e91b..bb97e72f992 100644
--- a/openerp/service/db.py
+++ b/openerp/service/db.py
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
-
-import base64
-import contextlib
+from contextlib import closing
+from functools import wraps
import logging
import os
+import shutil
import threading
import traceback
-from contextlib import contextmanager, closing
+import tempfile
+import zipfile
+
+import psycopg2
import openerp
from openerp import SUPERUSER_ID
@@ -28,7 +31,8 @@ def _initialize_db(id, db_name, demo, lang, user_password):
self_actions[id]['progress'] = 0
db = openerp.sql_db.db_connect(db_name)
with closing(db.cursor()) as cr:
- openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by RegistryManager.new().
+ # TODO this should be removed as it is done by RegistryManager.new().
+ openerp.modules.db.initialize(cr)
openerp.tools.config['lang'] = lang
cr.commit()
@@ -55,14 +59,13 @@ def _initialize_db(id, db_name, demo, lang, user_password):
self_actions[id]['traceback'] = traceback.format_exc()
def dispatch(method, params):
- if method in [ 'create', 'get_progress', 'drop', 'dump',
- 'restore', 'rename',
- 'change_admin_password', 'migrate_databases',
- 'create_database', 'duplicate_database' ]:
+ if method in ['create', 'get_progress', 'drop', 'dump', 'restore', 'rename',
+ 'change_admin_password', 'migrate_databases',
+ 'create_database', 'duplicate_database']:
passwd = params[0]
params = params[1:]
security.check_super(passwd)
- elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
+ elif method in ['db_exist', 'list', 'list_lang', 'server_version']:
# params = params
# No security check for these methods
pass
@@ -78,9 +81,9 @@ def _create_empty_database(name):
cr.execute("SELECT datname FROM pg_database WHERE datname = %s",
(name,))
if cr.fetchall():
- raise openerp.exceptions.Warning(" %s database already exists!" % name )
+ raise openerp.exceptions.Warning("database %r already exists!" % (name,))
else:
- cr.autocommit(True) # avoid transaction block
+ cr.autocommit(True) # avoid transaction block
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
def exp_create(db_name, demo, lang, user_password='admin'):
@@ -96,7 +99,7 @@ def exp_create(db_name, demo, lang, user_password='admin'):
_logger.info('CREATE DATABASE %s', db_name.lower())
create_thread = threading.Thread(target=_initialize_db,
- args=(id, db_name, demo, lang, user_password))
+ args=(id, db_name, demo, lang, user_password))
create_thread.start()
self_actions[id]['thread'] = create_thread
return id
@@ -121,14 +124,19 @@ def exp_duplicate_database(db_original_name, db_name):
openerp.sql_db.close_db(db_original_name)
db = openerp.sql_db.db_connect('postgres')
with closing(db.cursor()) as cr:
- cr.autocommit(True) # avoid transaction block
+ cr.autocommit(True) # avoid transaction block
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
+
+ from_fs = openerp.tools.config.filestore(db_original_name)
+ to_fs = openerp.tools.config.filestore(db_name)
+ if os.path.exists(from_fs) and not os.path.exists(to_fs):
+ shutil.copy(from_fs, to_fs)
return True
def exp_get_progress(id):
if self_actions[id]['thread'].isAlive():
# return openerp.modules.init_progress[db_name]
- return min(self_actions[id].get('progress', 0),0.95), []
+ return min(self_actions[id].get('progress', 0), 0.95), []
else:
clean = self_actions[id]['clean']
if clean:
@@ -140,9 +148,9 @@ def exp_get_progress(id):
self_actions.pop(id)
return 1.0, users
else:
- e = self_actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
- self_actions.pop(id)
- raise Exception, e
+ a = self_actions.pop(id)
+ exc, tb = a['exception'], a['traceback']
+ raise Exception, exc, tb
def exp_drop(db_name):
if db_name not in exp_list(True):
@@ -152,18 +160,17 @@ def exp_drop(db_name):
db = openerp.sql_db.db_connect('postgres')
with closing(db.cursor()) as cr:
- cr.autocommit(True) # avoid transaction block
+ cr.autocommit(True) # avoid transaction block
# Try to terminate all other connections that might prevent
# dropping the database
try:
-
# PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
# http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
FROM pg_stat_activity
- WHERE datname = %%s AND
+ WHERE datname = %%s AND
%(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
(db_name,))
except Exception:
@@ -176,96 +183,146 @@ def exp_drop(db_name):
raise Exception("Couldn't drop database %s: %s" % (db_name, e))
else:
_logger.info('DROP DB: %s', db_name)
+
+ fs = openerp.tools.config.filestore(db_name)
+ if os.path.exists(fs):
+ shutil.rmtree(fs)
return True
-@contextlib.contextmanager
-def _set_pg_password_in_environment():
+def _set_pg_password_in_environment(func):
""" On systems where pg_restore/pg_dump require an explicit
password (i.e. when not connecting via unix sockets, and most
importantly on Windows), it is necessary to pass the PG user
password in the environment or in a special .pgpass file.
- This context management method handles setting
+ This decorator handles setting
:envvar:`PGPASSWORD` if it is not already
set, and removing it afterwards.
See also http://www.postgresql.org/docs/8.4/static/libpq-envars.html
-
+
.. note:: This is not thread-safe, and should never be enabled for
SaaS (giving SaaS users the super-admin password is not a good idea
anyway)
"""
- if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
- yield
- else:
- os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
- try:
- yield
- finally:
- del os.environ['PGPASSWORD']
-
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
+ return func(*args, **kwargs)
+ else:
+ os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
+ try:
+ return func(*args, **kwargs)
+ finally:
+ del os.environ['PGPASSWORD']
+ return wrapper
def exp_dump(db_name):
- with _set_pg_password_in_environment():
- cmd = ['pg_dump', '--format=c', '--no-owner']
+ with tempfile.TemporaryFile() as t:
+ dump_db(db_name, t)
+ t.seek(0)
+ return t.read().encode('base64')
+
+@_set_pg_password_in_environment
+def dump_db(db, stream):
+ """Dump database `db` into file-like object `stream`"""
+ with openerp.tools.osutil.tempdir() as dump_dir:
+ registry = openerp.modules.registry.RegistryManager.get(db)
+ with registry.cursor() as cr:
+ filestore = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
+ if os.path.exists(filestore):
+ shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
+
+ dump_file = os.path.join(dump_dir, 'dump.sql')
+ cmd = ['pg_dump', '--format=p', '--no-owner', '--file=' + dump_file]
if openerp.tools.config['db_user']:
cmd.append('--username=' + openerp.tools.config['db_user'])
if openerp.tools.config['db_host']:
cmd.append('--host=' + openerp.tools.config['db_host'])
if openerp.tools.config['db_port']:
cmd.append('--port=' + str(openerp.tools.config['db_port']))
- cmd.append(db_name)
+ cmd.append(db)
- stdin, stdout = openerp.tools.exec_pg_command_pipe(*tuple(cmd))
- stdin.close()
- data = stdout.read()
- res = stdout.close()
+ if openerp.tools.exec_pg_command(*cmd):
+ _logger.error('DUMP DB: %s failed! Please verify the configuration of the database '
+ 'password on the server. You may need to create a .pgpass file for '
+ 'authentication, or specify `db_password` in the server configuration '
+ 'file.', db)
+ raise Exception("Couldn't dump database")
- if not data or res:
- _logger.error(
- 'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '
- 'You may need to create a .pgpass file for authentication, or specify `db_password` in the '
- 'server configuration file.\n %s', db_name, data)
- raise Exception, "Couldn't dump database"
- _logger.info('DUMP DB successful: %s', db_name)
+ openerp.tools.osutil.zip_dir(dump_dir, stream, include_dir=False)
- return base64.encodestring(data)
+ _logger.info('DUMP DB successful: %s', db)
-def exp_restore(db_name, data):
- with _set_pg_password_in_environment():
- if exp_db_exist(db_name):
- _logger.warning('RESTORE DB: %s already exists', db_name)
- raise Exception, "Database already exists"
+def exp_restore(db_name, data, copy=False):
+ data_file = tempfile.NamedTemporaryFile(delete=False)
+ try:
+ data_file.write(data.decode('base64'))
+ data_file.close()
+ restore_db(db_name, data_file.name, copy=copy)
+ finally:
+ os.unlink(data_file.name)
+ return True
- _create_empty_database(db_name)
+@_set_pg_password_in_environment
+def restore_db(db, dump_file, copy=False):
+ assert isinstance(db, basestring)
+ if exp_db_exist(db):
+ _logger.warning('RESTORE DB: %s already exists', db)
+ raise Exception("Database already exists")
- cmd = ['pg_restore', '--no-owner']
+ _create_empty_database(db)
+
+ filestore_path = None
+ with openerp.tools.osutil.tempdir() as dump_dir:
+ if zipfile.is_zipfile(dump_file):
+ # v8 format
+ with zipfile.ZipFile(dump_file, 'r') as z:
+ # only extract known members!
+ filestore = [m for m in z.namelist() if m.startswith('filestore/')]
+ z.extractall(dump_dir, ['dump.sql'] + filestore)
+
+ if filestore:
+ filestore_path = os.path.join(dump_dir, 'filestore')
+
+ pg_cmd = 'psql'
+ pg_args = ['-q', '-f', os.path.join(dump_dir, 'dump.sql')]
+
+ else:
+ # <= 7.0 format (raw pg_dump output)
+ pg_cmd = 'pg_restore'
+ pg_args = ['--no-owner', dump_file]
+
+ args = []
if openerp.tools.config['db_user']:
- cmd.append('--username=' + openerp.tools.config['db_user'])
+ args.append('--username=' + openerp.tools.config['db_user'])
if openerp.tools.config['db_host']:
- cmd.append('--host=' + openerp.tools.config['db_host'])
+ args.append('--host=' + openerp.tools.config['db_host'])
if openerp.tools.config['db_port']:
- cmd.append('--port=' + str(openerp.tools.config['db_port']))
- cmd.append('--dbname=' + db_name)
- args2 = tuple(cmd)
+ args.append('--port=' + str(openerp.tools.config['db_port']))
+ args.append('--dbname=' + db)
+ pg_args = args + pg_args
- buf=base64.decodestring(data)
- if os.name == "nt":
- tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
- file(tmpfile, 'wb').write(buf)
- args2=list(args2)
- args2.append(tmpfile)
- args2=tuple(args2)
- stdin, stdout = openerp.tools.exec_pg_command_pipe(*args2)
- if not os.name == "nt":
- stdin.write(base64.decodestring(data))
- stdin.close()
- res = stdout.close()
- if res:
- raise Exception, "Couldn't restore database"
- _logger.info('RESTORE DB: %s', db_name)
+ if openerp.tools.exec_pg_command(pg_cmd, *pg_args):
+ raise Exception("Couldn't restore database")
- return True
+ registry = openerp.modules.registry.RegistryManager.new(db)
+ with registry.cursor() as cr:
+ if copy:
+ # if it's a copy of a database, force generation of a new dbuuid
+ registry['ir.config_parameter'].init(cr, force=True)
+ if filestore_path:
+ filestore_dest = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
+ shutil.move(filestore_path, filestore_dest)
+
+ if openerp.tools.config['unaccent']:
+ try:
+ with cr.savepoint():
+ cr.execute("CREATE EXTENSION unaccent")
+ except psycopg2.Error:
+ pass
+
+ _logger.info('RESTORE DB: %s', db)
def exp_rename(old_name, new_name):
openerp.modules.registry.RegistryManager.delete(old_name)
@@ -273,15 +330,21 @@ def exp_rename(old_name, new_name):
db = openerp.sql_db.db_connect('postgres')
with closing(db.cursor()) as cr:
- cr.autocommit(True) # avoid transaction block
+ cr.autocommit(True) # avoid transaction block
try:
cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
_logger.info('RENAME DB: %s -> %s', old_name, new_name)
except Exception, e:
_logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e)
raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
+
+ old_fs = openerp.tools.config.filestore(old_name)
+ new_fs = openerp.tools.config.filestore(new_name)
+ if os.path.exists(old_fs) and not os.path.exists(new_fs):
+ shutil.move(old_fs, new_fs)
return True
+@openerp.tools.mute_logger('openerp.sql_db')
def exp_db_exist(db_name):
## Not True: in fact, check if connection to database is possible. The database may exists
return bool(openerp.sql_db.db_connect(db_name))
diff --git a/openerp/service/http_server.py b/openerp/service/http_server.py
deleted file mode 100644
index e25e75dba62..00000000000
--- a/openerp/service/http_server.py
+++ /dev/null
@@ -1,178 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright P. Christeas 2008-2010
-# Copyright 2010 OpenERP SA. (http://www.openerp.com)
-#
-#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsibility of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# guarantees and support are strongly advised to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-###############################################################################
-
-
-""" This module offers the family of HTTP-based servers. These are not a single
- class/functionality, but a set of network stack layers, implementing
- extendable HTTP protocols.
-
- The OpenERP server defines a single instance of a HTTP server, listening at
- the standard 8069, 8071 ports (well, it is 2 servers, and ports are
- configurable, of course). This "single" server then uses a `MultiHTTPHandler`
- to dispatch requests to the appropriate channel protocol, like the XML-RPC,
- static HTTP, DAV or other.
-"""
-
-import base64
-import posixpath
-import urllib
-import os
-import logging
-
-from websrv_lib import *
-import openerp.tools as tools
-
-try:
- import fcntl
-except ImportError:
- fcntl = None
-
-try:
- from ssl import SSLError
-except ImportError:
- class SSLError(Exception): pass
-
-_logger = logging.getLogger(__name__)
-
-# TODO delete this for 6.2, it is still needed for 6.1.
-class HttpLogHandler:
- """ helper class for uniform log handling
- Please define self._logger at each class that is derived from this
- """
- _logger = None
-
- def log_message(self, format, *args):
- self._logger.debug(format % args) # todo: perhaps other level
-
- def log_error(self, format, *args):
- self._logger.error(format % args)
-
- def log_exception(self, format, *args):
- self._logger.exception(format, *args)
-
- def log_request(self, code='-', size='-'):
- self._logger.debug('"%s" %s %s',
- self.requestline, str(code), str(size))
-
-class StaticHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler):
- _logger = logging.getLogger(__name__)
-
- _HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD'] }
-
- def __init__(self,request, client_address, server):
- HTTPHandler.__init__(self,request,client_address,server)
- document_root = tools.config.get('static_http_document_root', False)
- assert document_root, "Please specify static_http_document_root in configuration, or disable static-httpd!"
- self.__basepath = document_root
-
- def translate_path(self, path):
- """Translate a /-separated PATH to the local filename syntax.
-
- Components that mean special things to the local file system
- (e.g. drive or directory names) are ignored. (XXX They should
- probably be diagnosed.)
-
- """
- # abandon query parameters
- path = path.split('?',1)[0]
- path = path.split('#',1)[0]
- path = posixpath.normpath(urllib.unquote(path))
- words = path.split('/')
- words = filter(None, words)
- path = self.__basepath
- for word in words:
- if word in (os.curdir, os.pardir): continue
- path = os.path.join(path, word)
- return path
-
-def init_static_http():
- if not tools.config.get('static_http_enable', False):
- return
-
- document_root = tools.config.get('static_http_document_root', False)
- assert document_root, "Document root must be specified explicitly to enable static HTTP service (option --static-http-document-root)"
-
- base_path = tools.config.get('static_http_url_prefix', '/')
-
- reg_http_service(base_path, StaticHTTPHandler)
-
- _logger.info("Registered HTTP dir %s for %s", document_root, base_path)
-
-import security
-
-class OpenERPAuthProvider(AuthProvider):
- """ Require basic authentication."""
- def __init__(self,realm='OpenERP User'):
- self.realm = realm
- self.auth_creds = {}
- self.auth_tries = 0
- self.last_auth = None
-
- def authenticate(self, db, user, passwd, client_address):
- try:
- uid = security.login(db,user,passwd)
- if uid is False:
- return False
- return user, passwd, db, uid
- except Exception,e:
- _logger.debug("Fail auth: %s" % e )
- return False
-
- def checkRequest(self,handler,path, db=False):
- auth_str = handler.headers.get('Authorization',False)
- try:
- if not db:
- db = handler.get_db_from_path(path)
- except Exception:
- if path.startswith('/'):
- path = path[1:]
- psp= path.split('/')
- if len(psp)>1:
- db = psp[0]
- else:
- #FIXME!
- _logger.info("Wrong path: %s, failing auth" %path)
- raise AuthRejectedExc("Authorization failed. Wrong sub-path.")
- if self.auth_creds.get(db):
- return True
- if auth_str and auth_str.startswith('Basic '):
- auth_str=auth_str[len('Basic '):]
- (user,passwd) = base64.decodestring(auth_str).split(':')
- _logger.info("Found user=\"%s\", passwd=\"***\" for db=\"%s\"", user, db)
- acd = self.authenticate(db,user,passwd,handler.client_address)
- if acd != False:
- self.auth_creds[db] = acd
- self.last_auth = db
- return True
- if self.auth_tries > 5:
- _logger.info("Failing authorization after 5 requests w/o password")
- raise AuthRejectedExc("Authorization failed.")
- self.auth_tries += 1
- raise AuthRequiredExc(atype='Basic', realm=self.realm)
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/service/server.py b/openerp/service/server.py
index 3d02215bbe6..f9db66a28ef 100644
--- a/openerp/service/server.py
+++ b/openerp/service/server.py
@@ -9,7 +9,10 @@ import os.path
import platform
import psutil
import random
-import resource
+if os.name == 'posix':
+ import resource
+else:
+ resource = None
import select
import signal
import socket
@@ -638,6 +641,8 @@ class Worker(object):
raise
def process_limit(self):
+ if resource is None:
+ return
# If our parent changed sucide
if self.ppid != os.getppid():
_logger.info("Worker (%s) Parent changed", self.pid)
@@ -846,7 +851,8 @@ def load_test_file_py(registry, test_file):
stream = openerp.modules.module.TestStream()
result = unittest2.TextTestRunner(verbosity=2, stream=stream).run(suite)
success = result.wasSuccessful()
- registry._assertion_report.report_result(success)
+ if hasattr(registry._assertion_report,'report_result'):
+ registry._assertion_report.report_result(success)
if not success:
_logger.error('%s: at least one error occurred in a test', test_file)
diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py
index 506a07d0295..5e72ba570b5 100644
--- a/openerp/service/wsgi_server.py
+++ b/openerp/service/wsgi_server.py
@@ -72,7 +72,7 @@ def xmlrpc_return(start_response, service, method, params, string_faultcode=Fals
# This also mimics SimpleXMLRPCDispatcher._marshaled_dispatch() for
# exception handling.
try:
- result = openerp.netsvc.dispatch_rpc(service, method, params)
+ result = openerp.http.dispatch_rpc(service, method, params)
response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None)
except Exception, e:
if string_faultcode:
@@ -164,153 +164,6 @@ def wsgi_xmlrpc(environ, start_response):
params, method = xmlrpclib.loads(data)
return xmlrpc_return(start_response, service, method, params, string_faultcode)
-def wsgi_webdav(environ, start_response):
- pi = environ['PATH_INFO']
- if environ['REQUEST_METHOD'] == 'OPTIONS' and pi in ['*','/']:
- return return_options(environ, start_response)
- elif pi.startswith('/webdav'):
- http_dir = websrv_lib.find_http_service(pi)
- if http_dir:
- path = pi[len(http_dir.path):]
- if path.startswith('/'):
- environ['PATH_INFO'] = path
- else:
- environ['PATH_INFO'] = '/' + path
- return http_to_wsgi(http_dir)(environ, start_response)
-
-def return_options(environ, start_response):
- # Microsoft specific header, see
- # http://www.ibm.com/developerworks/rational/library/2089.html
- if 'Microsoft' in environ.get('User-Agent', ''):
- options = [('MS-Author-Via', 'DAV')]
- else:
- options = []
- options += [('DAV', '1 2'), ('Allow', 'GET HEAD PROPFIND OPTIONS REPORT')]
- start_response("200 OK", [('Content-Length', str(0))] + options)
- return []
-
-def http_to_wsgi(http_dir):
- """
- Turn a BaseHTTPRequestHandler into a WSGI entry point.
-
- Actually the argument is not a bare BaseHTTPRequestHandler but is wrapped
- (as a class, so it needs to be instanciated) in a HTTPDir.
-
- This code is adapted from wbsrv_lib.MultiHTTPHandler._handle_one_foreign().
- It is a temporary solution: the HTTP sub-handlers (in particular the
- document_webdav addon) have to be WSGIfied.
- """
- def wsgi_handler(environ, start_response):
-
- headers = {}
- for key, value in environ.items():
- if key.startswith('HTTP_'):
- key = key[5:].replace('_', '-').title()
- headers[key] = value
- if key == 'CONTENT_LENGTH':
- key = key.replace('_', '-').title()
- headers[key] = value
- if environ.get('Content-Type'):
- headers['Content-Type'] = environ['Content-Type']
-
- path = urllib.quote(environ.get('PATH_INFO', ''))
- if environ.get('QUERY_STRING'):
- path += '?' + environ['QUERY_STRING']
-
- request_version = 'HTTP/1.1' # TODO
- request_line = "%s %s %s\n" % (environ['REQUEST_METHOD'], path, request_version)
-
- class Dummy(object):
- pass
-
- # Let's pretend we have a server to hand to the handler.
- server = Dummy()
- server.server_name = environ['SERVER_NAME']
- server.server_port = int(environ['SERVER_PORT'])
-
- # Initialize the underlying handler and associated auth. provider.
- con = openerp.service.websrv_lib.noconnection(environ['wsgi.input'])
- handler = http_dir.instanciate_handler(con, environ['REMOTE_ADDR'], server)
-
- # Populate the handler as if it is called by a regular HTTP server
- # and the request is already parsed.
- handler.wfile = StringIO.StringIO()
- handler.rfile = environ['wsgi.input']
- handler.headers = headers
- handler.command = environ['REQUEST_METHOD']
- handler.path = path
- handler.request_version = request_version
- handler.close_connection = 1
- handler.raw_requestline = request_line
- handler.requestline = request_line
-
- # Handle authentication if there is an auth. provider associated to
- # the handler.
- if hasattr(handler, 'auth_provider'):
- try:
- handler.auth_provider.checkRequest(handler, path)
- except websrv_lib.AuthRequiredExc, ae:
- # Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the
- # authorisation features of HTTP/1.1
- if request_version != 'HTTP/1.1' and ('Darwin/9.' not in handler.headers.get('User-Agent', '')):
- start_response("403 Forbidden", [])
- return []
- start_response("401 Authorization required", [
- ('WWW-Authenticate', '%s realm="%s"' % (ae.atype,ae.realm)),
- # ('Connection', 'keep-alive'),
- ('Content-Type', 'text/html'),
- ('Content-Length', 4), # len(self.auth_required_msg)
- ])
- return ['Blah'] # self.auth_required_msg
- except websrv_lib.AuthRejectedExc,e:
- start_response("403 %s" % (e.args[0],), [])
- return []
-
- method_name = 'do_' + handler.command
-
- # Support the OPTIONS method even when not provided directly by the
- # handler. TODO I would prefer to remove it and fix the handler if
- # needed.
- if not hasattr(handler, method_name):
- if handler.command == 'OPTIONS':
- return return_options(environ, start_response)
- start_response("501 Unsupported method (%r)" % handler.command, [])
- return []
-
- # Finally, call the handler's method.
- try:
- method = getattr(handler, method_name)
- method()
- # The DAV handler buffers its output and provides a _flush()
- # method.
- getattr(handler, '_flush', lambda: None)()
- response = parse_http_response(handler.wfile.getvalue())
- response_headers = response.getheaders()
- body = response.read()
- start_response(str(response.status) + ' ' + response.reason, response_headers)
- return [body]
- except (websrv_lib.AuthRejectedExc, websrv_lib.AuthRequiredExc):
- raise
- except Exception, e:
- start_response("500 Internal error", [])
- return []
-
- return wsgi_handler
-
-def parse_http_response(s):
- """ Turn a HTTP response string into a httplib.HTTPResponse object."""
- class DummySocket(StringIO.StringIO):
- """
- This is used to provide a StringIO to httplib.HTTPResponse
- which, instead of taking a file object, expects a socket and
- uses its makefile() method.
- """
- def makefile(self, *args, **kw):
- return self
- response = httplib.HTTPResponse(DummySocket(s))
- response.begin()
- return response
-
# WSGI handlers registered through the register_wsgi_handler() function below.
module_handlers = []
# RPC endpoints registered through the register_rpc_endpoint() function below.
@@ -342,7 +195,7 @@ def application_unproxied(environ, start_response):
del threading.current_thread().dbname
# Try all handlers until one returns some result (i.e. not None).
- wsgi_handlers = [wsgi_xmlrpc, wsgi_webdav]
+ wsgi_handlers = [wsgi_xmlrpc]
wsgi_handlers += module_handlers
for handler in wsgi_handlers:
result = handler(environ, start_response)
diff --git a/openerp/sql_db.py b/openerp/sql_db.py
index 694ae02b3d9..59841656d8c 100644
--- a/openerp/sql_db.py
+++ b/openerp/sql_db.py
@@ -148,7 +148,7 @@ class Cursor(object):
def check(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
- if self.__closed:
+ if self._closed:
msg = 'Unable to use a closed cursor.'
if self.__closer:
msg += ' It was closed at %s, line %s' % self.__closer
@@ -165,7 +165,7 @@ class Cursor(object):
self.sql_log = _logger.isEnabledFor(logging.DEBUG)
self.sql_log_count = 0
- self.__closed = True # avoid the call of close() (by __del__) if an exception
+ self._closed = True # avoid the call of close() (by __del__) if an exception
# is raised by any of the following initialisations
self._pool = pool
self.dbname = dbname
@@ -180,7 +180,7 @@ class Cursor(object):
self.__caller = frame_codeinfo(currentframe(),2)
else:
self.__caller = False
- self.__closed = False # real initialisation value
+ self._closed = False # real initialisation value
self.autocommit(False)
self.__closer = False
@@ -189,7 +189,7 @@ class Cursor(object):
self.cache = {}
def __del__(self):
- if not self.__closed and not self._cnx.closed:
+ if not self._closed and not self._cnx.closed:
# Oops. 'self' has not been closed explicitly.
# The cursor will be deleted by the garbage collector,
# but the database connection is not put back into the connection
@@ -302,7 +302,7 @@ class Cursor(object):
# collected as fast as they should). The problem is probably due in
# part because browse records keep a reference to the cursor.
del self._obj
- self.__closed = True
+ self._closed = True
# Clean the underlying connection.
self._cnx.rollback()
diff --git a/openerp/tests/common.py b/openerp/tests/common.py
index b68bbfe20df..a53a8d82240 100644
--- a/openerp/tests/common.py
+++ b/openerp/tests/common.py
@@ -13,7 +13,11 @@ import subprocess
import threading
import time
import unittest2
+import urllib2
import xmlrpclib
+from datetime import datetime, timedelta
+
+import werkzeug
import openerp
@@ -45,10 +49,9 @@ def acquire_test_cursor(session_id):
cr._test_lock.acquire()
return cr
-def release_test_cursor(session_id):
+def release_test_cursor(cr):
if openerp.tools.config['test_enable']:
- cr = HTTP_SESSION.get(session_id)
- if cr:
+ if hasattr(cr, '_test_lock'):
cr._test_lock.release()
return True
return False
@@ -150,7 +153,7 @@ class SingleTransactionCase(BaseCase):
class HttpCase(TransactionCase):
- """ Transactionnal HTTP TestCase with a phantomjs helper.
+ """ Transactionnal HTTP TestCase with url_open and phantomjs helpers.
"""
def __init__(self, methodName='runTest'):
@@ -171,11 +174,17 @@ class HttpCase(TransactionCase):
self.cr._test_lock = threading.RLock()
HTTP_SESSION[self.session_id] = self.cr
-
def tearDown(self):
del HTTP_SESSION[self.session_id]
super(HttpCase, self).tearDown()
+ def url_open(self, url, data=None, timeout=10):
+ opener = urllib2.build_opener()
+ opener.addheaders.append(('Cookie', 'session_id=%s' % self.session_id))
+ if url.startswith('/'):
+ url = "http://localhost:%s%s" % (PORT, url)
+ return opener.open(url, data, timeout)
+
def phantom_poll(self, phantom, timeout):
""" Phantomjs Test protocol.
@@ -187,11 +196,12 @@ class HttpCase(TransactionCase):
Other lines are relayed to the test log.
"""
- t0 = time.time()
+ t0 = datetime.now()
+ td = timedelta(seconds=timeout)
buf = bytearray()
while True:
# timeout
- self.assertLess(time.time(), t0 + timeout,
+ self.assertLess(datetime.now() - t0, td,
"PhantomJS tests should take less than %s seconds" % timeout)
# read a byte
@@ -202,7 +212,8 @@ class HttpCase(TransactionCase):
# OSError, and no errno/strerror/filename, only a pair of
# unnamed arguments (matching errno and strerror)
err, _ = e.args
- if err == errno.EINTR: continue
+ if err == errno.EINTR:
+ continue
raise
if ready:
@@ -214,24 +225,24 @@ class HttpCase(TransactionCase):
# process lines
if '\n' in buf:
line, buf = buf.split('\n', 1)
-
line = str(line)
- if 'CoreText' in line:
- continue
+
+ # relay everything from console.log, even 'ok' or 'error...' lines
+ _logger.info("phantomjs: %s", line)
+
if line == "ok":
break
if line.startswith("error"):
line_ = line[6:]
- try: line_ = json.loads(line_)
- except ValueError: pass
+ # when error occurs the execution stack may be sent as as JSON
+ try:
+ line_ = json.loads(line_)
+ except ValueError:
+ pass
self.fail(line_ or "phantomjs test failed")
- try: line = json.loads(line)
- except ValueError: pass
- _logger.info("phantomjs: %s", line)
-
def phantom_run(self, cmd, timeout):
- _logger.debug('executing `%s`', ' '.join(cmd))
+ _logger.info('phantom_run executing %s', ' '.join(cmd))
try:
phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError:
@@ -242,6 +253,7 @@ class HttpCase(TransactionCase):
# kill phantomjs if phantom.exit() wasn't called in the test
if phantom.poll() is None:
phantom.terminate()
+ _logger.info("phantom_run execution finished")
def phantom_jsfile(self, jsfile, timeout=30, **kw):
options = {
diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js
index 1cdfeabce76..5a6a9696cc0 100644
--- a/openerp/tests/phantomtest.js
+++ b/openerp/tests/phantomtest.js
@@ -2,28 +2,20 @@
function waitFor (ready, callback, timeout, timeoutMessageCallback) {
timeout = timeout || 10000;
- var start = new Date().getTime();
- var condition = ready();
- var interval = setInterval(function() {
- if ((new Date().getTime() - start < timeout) && !condition ) {
- condition = ready();
+ var start = new Date;
+
+ (function waitLoop() {
+ if(new Date - start > timeout) {
+ console.log('error', timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms");
+ phantom.exit(1);
+ } else if (ready()) {
+ callback();
} else {
- if(!condition) {
- var message = timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms";
- console.log("Waiting for " + ready);
- error(message);
- } else {
- clearInterval(interval);
- callback();
- }
+ setTimeout(waitLoop, 250);
}
- }, 250);
+ }());
}
-function error(message) {
- console.log('error', message);
- phantom.exit(1);
-}
function PhantomTest() {
var self = this;
this.options = JSON.parse(phantom.args[phantom.args.length-1]);
@@ -54,10 +46,12 @@ function PhantomTest() {
}));
msg.push('(leaf frame on top)')
}
- error(JSON.stringify(msg.join('\n')));
+ console.log('error', JSON.stringify(msg.join('\n')));
+ phantom.exit(1);
};
this.page.onAlert = function(message) {
- error(message);
+ console.log('error', message);
+ phantom.exit(1);
};
this.page.onConsoleMessage = function(message) {
console.log(message);
@@ -83,7 +77,8 @@ function PhantomTest() {
if(!found) {
console.log('Injecting', src, 'needed for', need);
if(!self.page.injectJs(src)) {
- error("Cannot inject " + src);
+ console.log('error', "Cannot inject " + src);
+ phantom.exit(1);
}
}
}
@@ -93,8 +88,9 @@ function PhantomTest() {
self.page.evaluate(function () {
var message = ("Timeout\nhref: " + window.location.href
+ "\nreferrer: " + document.referrer
- + "\n\n" + document.body.innerHTML).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*");
- error(message);
+ + "\n\n" + (document.body && document.body.innerHTML)).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*");
+ console.log('error', message);
+ phantom.exit(1);
});
}, self.timeout);
@@ -103,33 +99,38 @@ function PhantomTest() {
// ----------------------------------------------------
this.run = function(url_path, code, ready) {
if(self.options.login) {
- qp = [];
+ var qp = [];
qp.push('db=' + self.options.db);
qp.push('login=' + self.options.login);
qp.push('key=' + self.options.password);
qp.push('redirect=' + encodeURIComponent(url_path));
- var url_path = "/login?" + qp.join('&');
+ url_path = "/login?" + qp.join('&');
}
var url = self.origin + url_path;
self.page.open(url, function(status) {
if (status !== 'success') {
- error("failed to load " + url)
+ console.log('error', "failed to load " + url);
+ phantom.exit(1);
} else {
console.log('loaded', url, status);
// process ready
waitFor(function() {
- return self.page.evaluate(function (ready) {
+ console.log("PhantomTest.run: wait for condition: " + ready);
+ return self.page.evaluate(function (ready) {
var r = false;
try {
+ console.log("page.evaluate eval expr:", ready);
r = !!eval(ready);
} catch(ex) {
- console.log("waiting for " + ready);
}
+ console.log("page.evaluate eval result:", r);
return r;
}, ready);
// run test
}, function() {
+ console.log("PhantomTest.run: condition statified, executing: " + code);
self.page.evaluate(function (code) { return eval(code); }, code);
+ console.log("PhantomTest.run: execution launched, waiting for console.log('ok')...");
});
}
});
diff --git a/openerp/tools/__init__.py b/openerp/tools/__init__.py
index 03e2eda038a..c927b2fc87a 100644
--- a/openerp/tools/__init__.py
+++ b/openerp/tools/__init__.py
@@ -35,7 +35,7 @@ from yaml_import import *
from sql import *
from float_utils import *
from mail import *
-
+from debugger import *
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/tools/config.py b/openerp/tools/config.py
index b6d239d75a8..6b21bda8b4f 100644
--- a/openerp/tools/config.py
+++ b/openerp/tools/config.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+#openerp.loggers.handlers. -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
@@ -76,9 +76,10 @@ class configmanager(object):
}
# Not exposed in the configuration file.
- self.blacklist_for_save = set(
- ['publisher_warranty_url', 'load_language', 'root_path',
- 'init', 'save', 'config', 'update', 'stop_after_init'])
+ self.blacklist_for_save = set([
+ 'publisher_warranty_url', 'load_language', 'root_path',
+ 'init', 'save', 'config', 'update', 'stop_after_init'
+ ])
# dictionary mapping option destination (keys in self.options) to MyOptions.
self.casts = {}
@@ -87,7 +88,10 @@ class configmanager(object):
self.config_file = fname
self.has_ssl = check_ssl()
- self._LOGLEVELS = dict([(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x)) for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')])
+ self._LOGLEVELS = dict([
+ (getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
+ for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')
+ ])
version = "%s %s" % (release.description, release.version)
self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption)
@@ -148,19 +152,11 @@ class configmanager(object):
parser.add_option_group(group)
# WEB
- # TODO move to web addons after MetaOption merge
group = optparse.OptionGroup(parser, "Web interface Configuration")
group.add_option("--db-filter", dest="dbfilter", default='.*',
help="Filter listed database", metavar="REGEXP")
parser.add_option_group(group)
- # Static HTTP
- group = optparse.OptionGroup(parser, "Static HTTP service")
- group.add_option("--static-http-enable", dest="static_http_enable", action="store_true", my_default=False, help="enable static HTTP service for serving plain HTML files")
- group.add_option("--static-http-document-root", dest="static_http_document_root", help="specify the directory containing your static HTML files (e.g '/var/www/')")
- group.add_option("--static-http-url-prefix", dest="static_http_url_prefix", help="specify the URL root prefix where you want web browsers to access your static HTML files (e.g '/')")
- parser.add_option_group(group)
-
# Testing Group
group = optparse.OptionGroup(parser, "Testing Configuration")
group.add_option("--test-file", dest="test_file", my_default=False,
@@ -179,16 +175,20 @@ class configmanager(object):
group.add_option("--logrotate", dest="logrotate", action="store_true", my_default=False, help="enable logfile rotation")
group.add_option("--syslog", action="store_true", dest="syslog", my_default=False, help="Send the log to the syslog server")
group.add_option('--log-handler', action="append", default=DEFAULT_LOG_HANDLER, my_default=DEFAULT_LOG_HANDLER, metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")')
- group.add_option('--log-request', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.request:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.request:DEBUG')
- group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG')
- group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG')
+ group.add_option('--log-request', action="append_const", dest="log_handler", const="openerp.http.rpc.request:DEBUG", help='shortcut for --log-handler=openerp.http.rpc.request:DEBUG')
+ group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.http.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.http.rpc.response:DEBUG')
+ group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.http:DEBUG", help='shortcut for --log-handler=openerp.http:DEBUG')
group.add_option('--log-sql', action="append_const", dest="log_handler", const="openerp.sql_db:DEBUG", help='shortcut for --log-handler=openerp.sql_db:DEBUG')
+ group.add_option('--log-db', dest='log_db', help="Logging database", my_default=False)
# For backward-compatibility, map the old log levels to something
# quite close.
- levels = ['info', 'debug_rpc', 'warn', 'test', 'critical',
- 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset']
- group.add_option('--log-level', dest='log_level', type='choice', choices=levels,
- my_default='info', help='specify the level of the logging. Accepted values: ' + str(levels) + ' (deprecated option).')
+ levels = [
+ 'info', 'debug_rpc', 'warn', 'test', 'critical',
+ 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset'
+ ]
+ group.add_option('--log-level', dest='log_level', type='choice',
+ choices=levels, my_default='info',
+ help='specify the level of the logging. Accepted values: %s (deprecated option).' % (levels,))
parser.add_option_group(group)
@@ -252,7 +252,8 @@ class configmanager(object):
# Advanced options
group = optparse.OptionGroup(parser, "Advanced options")
- group.add_option('--auto-reload', dest='auto_reload', action='store_true', my_default=False, help='enable auto reload')
+ if os.name == 'posix':
+ group.add_option('--auto-reload', dest='auto_reload', action='store_true', my_default=False, help='enable auto reload')
group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode')
group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
help="stop the server after its initialization")
@@ -274,27 +275,28 @@ class configmanager(object):
help="Use the unaccent function provided by the database when available.")
parser.add_option_group(group)
- group = optparse.OptionGroup(parser, "Multiprocessing options")
- # TODO sensible default for the three following limits.
- group.add_option("--workers", dest="workers", my_default=0,
- help="Specify the number of workers, 0 disable prefork mode.",
- type="int")
- group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=2048 * 1024 * 1024,
- help="Maximum allowed virtual memory per worker, when reached the worker be reset after the current request (default 671088640 aka 640MB).",
- type="int")
- group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=2560 * 1024 * 1024,
- help="Maximum allowed virtual memory per worker, when reached, any memory allocation will fail (default 805306368 aka 768MB).",
- type="int")
- group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60,
- help="Maximum allowed CPU time per request (default 60).",
- type="int")
- group.add_option("--limit-time-real", dest="limit_time_real", my_default=120,
- help="Maximum allowed Real time per request (default 120).",
- type="int")
- group.add_option("--limit-request", dest="limit_request", my_default=8192,
- help="Maximum number of request to be processed per worker (default 8192).",
- type="int")
- parser.add_option_group(group)
+ if os.name == 'posix':
+ group = optparse.OptionGroup(parser, "Multiprocessing options")
+ # TODO sensible default for the three following limits.
+ group.add_option("--workers", dest="workers", my_default=0,
+ help="Specify the number of workers, 0 disable prefork mode.",
+ type="int")
+ group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=2048 * 1024 * 1024,
+ help="Maximum allowed virtual memory per worker, when reached the worker be reset after the current request (default 671088640 aka 640MB).",
+ type="int")
+ group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=2560 * 1024 * 1024,
+ help="Maximum allowed virtual memory per worker, when reached, any memory allocation will fail (default 805306368 aka 768MB).",
+ type="int")
+ group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60,
+ help="Maximum allowed CPU time per request (default 60).",
+ type="int")
+ group.add_option("--limit-time-real", dest="limit_time_real", my_default=120,
+ help="Maximum allowed Real time per request (default 120).",
+ type="int")
+ group.add_option("--limit-request", dest="limit_request", my_default=8192,
+ help="Maximum number of request to be processed per worker (default 8192).",
+ type="int")
+ parser.add_option_group(group)
# Copy all optparse options (i.e. MyOption) into self.options.
for group in parser.option_groups:
@@ -303,9 +305,10 @@ class configmanager(object):
self.options[option.dest] = option.my_default
self.casts[option.dest] = option
- self.parse_config(None, False)
+ # generate default config
+ self._parse_config()
- def parse_config(self, args=None, complete=True):
+ def parse_config(self, args=None):
""" Parse the configuration file (if any) and the command-line
arguments.
@@ -319,10 +322,12 @@ class configmanager(object):
Typical usage of this method:
openerp.tools.config.parse_config(sys.argv[1:])
-
- :param complete: this is a hack used in __init__(), leave it to True.
-
"""
+ self._parse_config(args)
+ openerp.netsvc.init_logger()
+ openerp.modules.module.initialize_sys_path()
+
+ def _parse_config(self, args=None):
if args is None:
args = []
opt, args = self.parser.parse_args(args)
@@ -381,8 +386,7 @@ class configmanager(object):
'db_maxconn', 'import_partial', 'addons_path',
'xmlrpc', 'syslog', 'without_demo', 'timezone',
'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs',
- 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix',
- 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level'
+ 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level', 'log_db'
]
for arg in keys:
@@ -405,12 +409,22 @@ class configmanager(object):
'list_db', 'xmlrpcs', 'proxy_mode',
'test_file', 'test_enable', 'test_commit', 'test_report_directory',
'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
- 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request',
- 'auto_reload', 'data_dir',
+ 'data_dir',
]
+ posix_keys = [
+ 'auto_reload', 'workers',
+ 'limit_memory_hard', 'limit_memory_soft',
+ 'limit_time_cpu', 'limit_time_real', 'limit_request',
+ ]
+
+ if os.name == 'posix':
+ keys += posix_keys
+ else:
+ self.options.update(dict.fromkeys(posix_keys, None))
+
+ # Copy the command-line arguments...
for arg in keys:
- # Copy the command-line argument...
if getattr(opt, arg) is not None:
self.options[arg] = getattr(opt, arg)
# ... or keep, but cast, the config file value.
@@ -483,8 +497,6 @@ class configmanager(object):
openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))
else:
openerp.conf.server_wide_modules = ['web','web_kanban']
- if complete:
- openerp.modules.module.initialize_sys_path()
def _generate_pgpassfile(self):
"""
@@ -642,6 +654,9 @@ class configmanager(object):
os.chmod(d, 0700)
return d
+ def filestore(self, dbname):
+ return os.path.join(self['data_dir'], 'filestore', dbname)
+
config = configmanager()
diff --git a/openerp/tools/convert.py b/openerp/tools/convert.py
index 43ee96660bd..a4231529b8f 100644
--- a/openerp/tools/convert.py
+++ b/openerp/tools/convert.py
@@ -857,12 +857,12 @@ form: module.record_id""" % (xml_id,)
def _tag_template(self, cr, el, data_node=None):
# This helper transforms a element into a and forwards it
tpl_id = el.get('id', el.get('t-name', '')).encode('ascii')
- module = self.module
- if '.' in tpl_id:
- module, tpl_id = tpl_id.split('.', 1)
+ full_tpl_id = tpl_id
+ if '.' not in full_tpl_id:
+ full_tpl_id = '%s.%s' % (self.module, tpl_id)
# set the full template name for qweb .
if not (el.get('inherit_id') or el.get('inherit_option_id')):
- el.set('t-name', '%s.%s' % (module, tpl_id))
+ el.set('t-name', full_tpl_id)
el.tag = 't'
else:
el.tag = 'data'
@@ -952,8 +952,9 @@ form: module.record_id""" % (xml_id,)
'url': self._tag_url
}
-def convert_file(cr, module, filename, idref, mode='update', noupdate=False, kind=None, report=None):
- pathname = os.path.join(module, filename)
+def convert_file(cr, module, filename, idref, mode='update', noupdate=False, kind=None, report=None, pathname=None):
+ if pathname is None:
+ pathname = os.path.join(module, filename)
fp = misc.file_open(pathname)
ext = os.path.splitext(filename)[1].lower()
try:
diff --git a/openerp/tools/debugger.py b/openerp/tools/debugger.py
new file mode 100644
index 00000000000..293d4052513
--- /dev/null
+++ b/openerp/tools/debugger.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+# Copyright: 2014 - OpenERP S.A.
+import types
+
+def post_mortem(config, info):
+ if config['debug_mode'] and isinstance(info[2], types.TracebackType):
+ import pdb
+ pdb.post_mortem(info[2])
diff --git a/openerp/tools/func.py b/openerp/tools/func.py
index ca8bba6bd49..89cdf9fb622 100644
--- a/openerp/tools/func.py
+++ b/openerp/tools/func.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL ().
-# Copyright (C) 2010 OpenERP s.a. ().
+# Copyright (C) 2010, 2014 OpenERP s.a. ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -20,11 +20,39 @@
#
##############################################################################
-__all__ = ['synchronized']
+__all__ = ['synchronized', 'lazy_property']
from functools import wraps
from inspect import getsourcefile
+
+class lazy_property(object):
+ """ Decorator for a lazy property of an object, i.e., an object attribute
+ that is determined by the result of a method call evaluated once. To
+ reevaluate the property, simply delete the attribute on the object, and
+ get it again.
+ """
+ def __init__(self, fget):
+ self.fget = fget
+ self.name = fget.__name__
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ value = self.fget(obj)
+ setattr(obj, self.name, value)
+ return value
+
+ @staticmethod
+ def reset_all(obj):
+ """ Reset all lazy properties on the instance `obj`. """
+ cls = type(obj)
+ obj_dict = obj.__dict__
+ for name in obj_dict.keys():
+ if isinstance(getattr(cls, name, None), lazy_property):
+ obj_dict.pop(name)
+
+
def synchronized(lock_attr='_lock'):
def decorator(func):
@wraps(func)
diff --git a/openerp/tools/image.py b/openerp/tools/image.py
index 196ace2da60..08c2fd91f21 100644
--- a/openerp/tools/image.py
+++ b/openerp/tools/image.py
@@ -32,7 +32,7 @@ from random import randint
# Image resizing
# ----------------------------------------
-def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False):
+def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype=None, avoid_if_small=False):
""" Function to resize an image. The image will be resized to the given
size, while keeping the aspect ratios, and holes in the image will be
filled with transparent background. The image will not be stretched if
@@ -58,7 +58,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
height mean an automatically computed value based respectivelly
on height or width of the source image.
:param encoding: the output encoding
- :param filetype: the output filetype
+ :param filetype: the output filetype, by default the source image's
+ :type filetype: str, any PIL image format (supported for creation)
:param avoid_if_small: do not resize if image height and width
are smaller than the expected size.
"""
@@ -68,6 +69,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
return base64_source
image_stream = StringIO.StringIO(base64_source.decode(encoding))
image = Image.open(image_stream)
+ # store filetype here, as Image.new below will lose image.format
+ filetype = filetype or image.format
asked_width, asked_height = size
if asked_width is None:
@@ -95,21 +98,21 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
image.save(background_stream, filetype)
return background_stream.getvalue().encode(encoding)
-def image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG', avoid_if_small=True):
+def image_resize_image_big(base64_source, size=(1204, 1024), encoding='base64', filetype=None, avoid_if_small=True):
""" Wrapper on image_resize_image, to resize images larger than the standard
'big' image size: 1024x1024px.
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
"""
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
-def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype='PNG', avoid_if_small=False):
+def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype=None, avoid_if_small=False):
""" Wrapper on image_resize_image, to resize to the standard 'medium'
image size: 180x180.
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
"""
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
-def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype='PNG', avoid_if_small=False):
+def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype=None, avoid_if_small=False):
""" Wrapper on image_resize_image, to resize to the standard 'small' image
size: 50x50.
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py
index 251905137c0..bb61fe2e7a2 100644
--- a/openerp/tools/misc.py
+++ b/openerp/tools/misc.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL ().
-# Copyright (C) 2010-2013 OpenERP s.a. ().
+# Copyright (C) 2010-2014 OpenERP s.a. ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -83,7 +83,8 @@ def exec_pg_command(name, *args):
raise Exception('Couldn\'t find %s' % name)
args2 = (prog,) + args
- return subprocess.call(args2)
+ with open(os.devnull) as dn:
+ return subprocess.call(args2, stdout=dn, stderr=subprocess.STDOUT)
def exec_pg_command_pipe(name, *args):
prog = find_pg_tool(name)
diff --git a/openerp/tools/osutil.py b/openerp/tools/osutil.py
index b5cff6b2646..94684d34673 100644
--- a/openerp/tools/osutil.py
+++ b/openerp/tools/osutil.py
@@ -23,8 +23,12 @@
Some functions related to the os and os.path module
"""
+from contextlib import contextmanager
import os
from os.path import join as opj
+import shutil
+import tempfile
+import zipfile
if os.name == 'nt':
import ctypes
@@ -61,6 +65,30 @@ def walksymlinks(top, topdown=True, onerror=None):
if not topdown:
yield dirpath, dirnames, filenames
+@contextmanager
+def tempdir():
+ tmpdir = tempfile.mkdtemp()
+ try:
+ yield tmpdir
+ finally:
+ shutil.rmtree(tmpdir)
+
+def zip_dir(path, stream, include_dir=True): # TODO add ignore list
+ path = os.path.normpath(path)
+ len_prefix = len(os.path.dirname(path)) if include_dir else len(path)
+ if len_prefix:
+ len_prefix += 1
+
+ with zipfile.ZipFile(stream, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zipf:
+ for dirpath, dirnames, filenames in os.walk(path):
+ for fname in filenames:
+ bname, ext = os.path.splitext(fname)
+ ext = ext or bname
+ if ext not in ['.pyc', '.pyo', '.swp', '.DS_Store']:
+ path = os.path.normpath(os.path.join(dirpath, fname))
+ if os.path.isfile(path):
+ zipf.write(path, path[len_prefix:])
+
if os.name != 'nt':
getppid = os.getppid
diff --git a/openerp/tools/safe_eval.py b/openerp/tools/safe_eval.py
index b64b401a71c..508a072a3b2 100644
--- a/openerp/tools/safe_eval.py
+++ b/openerp/tools/safe_eval.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
-# Copyright (C) 2004-2012 OpenERP s.a. ().
+# Copyright (C) 2004-2014 OpenERP s.a. ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -225,9 +225,14 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
'False': False,
'None': None,
'str': str,
+ 'unicode': unicode,
'globals': locals,
'locals': locals,
'bool': bool,
+ 'int': int,
+ 'float': float,
+ 'long': long,
+ 'enumerate': enumerate,
'dict': dict,
'list': list,
'tuple': tuple,
@@ -235,15 +240,23 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
'abs': abs,
'min': min,
'max': max,
+ 'sum': sum,
'reduce': reduce,
'filter': filter,
'round': round,
'len': len,
- 'set': set,
'repr': repr,
- 'int': int,
- 'float': float,
+ 'set': set,
+ 'all': all,
+ 'any': any,
+ 'ord': ord,
+ 'chr': chr,
+ 'cmp': cmp,
+ 'divmod': divmod,
+ 'isinstance': isinstance,
'range': range,
+ 'xrange': xrange,
+ 'zip': zip,
}
)
if locals_builtins:
diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py
index 6f5b22f4f53..b7954054604 100644
--- a/openerp/tools/translate.py
+++ b/openerp/tools/translate.py
@@ -458,7 +458,7 @@ def trans_export(lang, modules, buffer, format, cr):
row.setdefault('tnrs', []).append((type, name, res_id))
row.setdefault('comments', set()).update(comments)
- for src, row in grouped_rows.items():
+ for src, row in sorted(grouped_rows.items()):
if not lang:
# translation template, so no translation value
row['translation'] = ''
diff --git a/setup.py b/setup.py
index 05178fb12ac..545b02e9bbd 100644
--- a/setup.py
+++ b/setup.py
@@ -75,7 +75,7 @@ def py2exe_options():
"skip_archive": 1,
"optimize": 0, # keep the assert running, because the integrated tests rely on them.
"dist_dir": 'dist',
- "packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "docutils", "email", "encodings", "imaplib", "jinja2", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "pytz", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
+ "packages": ["HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "docutils", "email", "encodings", "imaplib", "jinja2", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "pytz", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
"excludes" : ["Tkconstants","Tkinter","tcl"],
}
}
@@ -140,6 +140,7 @@ setuptools.setup(
'pyyaml',
'qrcode',
'reportlab', # windows binary pypi.python.org/pypi/reportlab
+ 'requests',
'simplejson',
'unittest2',
'vatnumber',