[tryton-debian-vcs] tryton-client branch upstream-2.2 created. 735ca6b3aa8a510dcfad732d1e0defe535c6dd6d

Mathias Behrle tryton-debian-vcs at alioth.debian.org
Wed Nov 27 16:51:06 UTC 2013


The following commit has been merged in the upstream-2.2 branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/tryton-client.git;a=commitdiff;h=735ca6b3aa8a510dcfad732d1e0defe535c6dd6d
commit 735ca6b3aa8a510dcfad732d1e0defe535c6dd6d
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Mon Nov 4 14:16:41 2013 +0100

    Adding upstream version 2.2.11.

diff --git a/CHANGELOG b/CHANGELOG
index 91c3772..f13ed51 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+Version 2.2.11 - 2013-11-03
+* Bug fixes (see mercurial logs for details)
+* Sanitize report file extension
+
 Version 2.2.10 - 2013-10-10
 * Bug fixes (see mercurial logs for details)
 
diff --git a/PKG-INFO b/PKG-INFO
index 15fa977..ce9cbba 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.10
+Version: 2.2.11
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index 15fa977..ce9cbba 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.10
+Version: 2.2.11
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/action/main.py b/tryton/action/main.py
index 3c81170..d5df3e8 100644
--- a/tryton/action/main.py
+++ b/tryton/action/main.py
@@ -59,7 +59,8 @@ class Action(object):
         dtemp = tempfile.mkdtemp(prefix='tryton_')
         fp_name = os.path.join(dtemp,
                 name.replace(os.sep, '_').replace(os.altsep or os.sep, '_') \
-                        + os.extsep + type)
+            + os.extsep
+            + type.replace(os.sep, '_').replace(os.altsep or os.sep, '_'))
         with open(fp_name, 'wb') as file_d:
             file_d.write(data)
         if email_print:
diff --git a/tryton/gui/window/view_form/view/form_gtk/one2many.py b/tryton/gui/window/view_form/view/form_gtk/one2many.py
index 21ae30c..99eeebc 100644
--- a/tryton/gui/window/view_form/view/form_gtk/one2many.py
+++ b/tryton/gui/window/view_form/view/form_gtk/one2many.py
@@ -297,9 +297,8 @@ class One2Many(WidgetInterface):
         domain = self.field.domain_get(self.record)
         context = rpc.CONTEXT.copy()
         context.update(self.field.context_get(self.record))
-        domain = domain[:]
-        domain.extend(self.record.expr_eval(self.attrs.get('add_remove'),
-            context))
+        domain = [domain, self.record.expr_eval(self.attrs.get('add_remove'),
+                context)]
         removed_ids = self.field.get_removed_ids(self.record)
 
         try:
diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py
index 1efca22..7c26376 100644
--- a/tryton/gui/window/view_form/view/list.py
+++ b/tryton/gui/window/view_form/view/list.py
@@ -129,7 +129,8 @@ class AdaptModelGroup(gtk.GenericTreeModel):
             # Don't remove record from previous group
             # as the new parent will change the parent
             # This prevents concurrency conflict
-            record.group.record_removed.remove(record)
+            if record in record.group.record_removed:
+                record.group.record_removed.remove(record)
             group.add(record)
             record.modified_fields.setdefault(record.parent_name or 'id')
         group.move(record, 0)
diff --git a/tryton/gui/window/win_export.py b/tryton/gui/window/win_export.py
index 43f2c96..107956e 100644
--- a/tryton/gui/window/win_export.py
+++ b/tryton/gui/window/win_export.py
@@ -370,7 +370,7 @@ class WinExport(object):
                             self.on_row_expanded(self.view1, iter,
                                     self.model1.get_path(iter))
                             iter = self.model1.iter_children(iter)
-                            prefix = parent + '/'
+                            prefix += parent + '/'
                             break
                         else:
                             iter = self.model1.iter_next(iter)
diff --git a/tryton/version.py b/tryton/version.py
index fdefcfd..ae1e1c5 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.10"
+VERSION = "2.2.11"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit 80f7acb500987894809ed10c6582a66f0ef248e1
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Thu Oct 17 12:37:24 2013 +0200

    Adding upstream version 2.2.10.

diff --git a/CHANGELOG b/CHANGELOG
index 7068b22..91c3772 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.10 - 2013-10-10
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.9 - 2013-07-22
 * Bug fixes (see mercurial logs for details)
 
diff --git a/PKG-INFO b/PKG-INFO
index 3b6ecf9..15fa977 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.9
+Version: 2.2.10
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index 3b6ecf9..15fa977 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.9
+Version: 2.2.10
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py
index 7b6ba2d..598b108 100644
--- a/tryton/gui/window/view_form/model/field.py
+++ b/tryton/gui/window/view_form/model/field.py
@@ -55,13 +55,13 @@ class CharField(object):
 
     def domain_get(self, record):
         screen_domain, attr_domain = self.domains_get(record)
-        return localize_domain(screen_domain) + attr_domain
+        return [localize_domain(screen_domain), attr_domain]
 
     def validation_domains(self, record):
         screen_domain, attr_domain = self.domains_get(record)
         if attr_domain:
-            return screen_domain, screen_domain + unlocalize_domain(attr_domain,
-                self.name)
+            return (screen_domain, [screen_domain,
+                    unlocalize_domain(attr_domain, self.name)])
         else:
             return screen_domain, screen_domain
 
@@ -394,7 +394,8 @@ class M2OField(CharField):
 
     def domain_get(self, record):
         screen_domain, attr_domain = self.domains_get(record)
-        return localize_domain(inverse_leaf(screen_domain), self.name) + attr_domain
+        return [localize_domain(inverse_leaf(screen_domain), self.name),
+            attr_domain]
 
     def get_state_attrs(self, record):
         result = super(M2OField, self).get_state_attrs(record)
@@ -685,7 +686,7 @@ class O2MField(CharField):
 
     def domain_get(self, record):
         screen_domain, attr_domain = self.domains_get(record)
-        return localize_domain(inverse_leaf(screen_domain)) + attr_domain
+        return [localize_domain(inverse_leaf(screen_domain)), attr_domain]
 
 
 class M2MField(O2MField):
diff --git a/tryton/version.py b/tryton/version.py
index 6b2b241..fdefcfd 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.9"
+VERSION = "2.2.10"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit 4d3a03d3725807c6e998167b691bebcdc2c3f971
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Wed Aug 7 17:11:57 2013 +0200

    Adding upstream version 2.2.9.

diff --git a/CHANGELOG b/CHANGELOG
index 22700eb..7068b22 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.9 - 2013-07-22
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.8 - 2013-06-09
 * Bug fixes (see mercurial logs for details)
 
diff --git a/PKG-INFO b/PKG-INFO
index 0ed926e..3b6ecf9 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.8
+Version: 2.2.9
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index 0ed926e..3b6ecf9 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.8
+Version: 2.2.9
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/gui/window/view_form/view/list_gtk/editabletree.py b/tryton/gui/window/view_form/view/list_gtk/editabletree.py
index 3c79a7b..701898f 100644
--- a/tryton/gui/window/view_form/view/list_gtk/editabletree.py
+++ b/tryton/gui/window/view_form/view/list_gtk/editabletree.py
@@ -22,8 +22,6 @@ class EditableTreeView(gtk.TreeView):
 
     def on_quit_cell(self, current_record, fieldname, value, callback=None):
         field = current_record[fieldname]
-        if hasattr(field, 'editabletree_entry'):
-            del field.editabletree_entry
         cell = self.cells[fieldname]
 
         # The value has not changed and is valid ... do nothing.
@@ -234,6 +232,11 @@ class EditableTreeView(gtk.TreeView):
                 entry.set_max_length(int(field.attrs.get('size', 0)))
             # store in the record the entry widget to get the value in set_value
             field.editabletree_entry = entry
+
+            def remove_widget(cell):
+                if hasattr(field, 'editabletree_entry'):
+                    del field.editabletree_entry
+            entry.connect('remove-widget', remove_widget)
             record.modified_fields.setdefault(column.name)
             return False
 
diff --git a/tryton/version.py b/tryton/version.py
index 6435eba..6b2b241 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.8"
+VERSION = "2.2.9"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit 805ed7ff8b00af63bfac07d5bb662c42d8c2d0ab
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Tue Jun 11 13:53:09 2013 +0200

    Adding upstream version 2.2.8.

diff --git a/CHANGELOG b/CHANGELOG
index 3933461..22700eb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.8 - 2013-06-09
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.7 - 2013-05-02
 * Bug fixes (see mercurial logs for details)
 
diff --git a/PKG-INFO b/PKG-INFO
index d7b3feb..0ed926e 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.7
+Version: 2.2.8
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index d7b3feb..0ed926e 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.7
+Version: 2.2.8
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/gui/main.py b/tryton/gui/main.py
index 9d5b687..02611bf 100644
--- a/tryton/gui/main.py
+++ b/tryton/gui/main.py
@@ -18,6 +18,7 @@ import tryton.rpc as rpc
 from tryton.config import CONFIG, TRYTON_ICON, PIXMAPS_DIR, DATA_DIR, \
         get_config_dir
 import tryton.common as common
+from tryton.jsonrpc import object_hook
 from tryton.action import Action
 from tryton.exceptions import TrytonServerError, TrytonError
 from tryton.gui.window import Window
@@ -1445,9 +1446,12 @@ class Main(object):
                 limit = json.loads(params.get('limit', 'null'))
                 auto_refresh = json.loads(params.get('auto_refresh', 'false'))
                 name = json.loads(params.get('window_name', 'false'))
-                search_value = json.loads(params.get('search_value', '{}'))
-                domain = json.loads(params.get('domain', '[]'))
-                context = json.loads(params.get('context', '{}'))
+                search_value = json.loads(params.get('search_value', '{}'),
+                    object_hook=object_hook)
+                domain = json.loads(params.get('domain', '[]'),
+                    object_hook=object_hook)
+                context = json.loads(params.get('context', '{}'),
+                    object_hook=object_hook)
             except ValueError:
                 return
             if path:
@@ -1468,13 +1472,15 @@ class Main(object):
             if not wizard:
                 return
             try:
-                data = json.loads(params.get('data', '{}'))
+                data = json.loads(params.get('data', '{}'),
+                    object_hook=object_hook)
                 direct_print = json.loads(params.get('direct_print', 'false'))
                 email_print = json.loads(params.get('email_print', 'false'))
                 email = json.loads(params.get('email', 'null'))
                 name = json.loads(params.get('name', 'false'))
                 window = json.loads(params.get('window', 'false'))
-                context = json.loads(params.get('context', '{}'))
+                context = json.loads(params.get('context', '{}'),
+                    object_hook=object_hook)
             except ValueError:
                 return
             try:
@@ -1489,12 +1495,13 @@ class Main(object):
             if not report:
                 return
             try:
-                data = json.loads(params.get('data'))
+                data = json.loads(params.get('data'), object_hook=object_hook)
                 direct_print = json.loads(params.get('direct_print', 'false'))
                 email_print = json.loads(params.get('email_print', 'false'))
                 email = json.loads(params.get('email', 'null'))
                 name = json.loads(params.get('name', 'false'))
-                context = json.loads(params.get('context', '{}'))
+                context = json.loads(params.get('context', '{}'),
+                    object_hook=object_hook)
             except ValueError:
                 return
             try:
diff --git a/tryton/version.py b/tryton/version.py
index 7a5d714..6435eba 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.7"
+VERSION = "2.2.8"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit 7326b7761d81e41f89eefb6b1dce15cb855a82cd
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Sat May 4 16:48:34 2013 +0200

    Adding upstream version 2.2.7.

diff --git a/CHANGELOG b/CHANGELOG
index 69a750a..3933461 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.7 - 2013-05-02
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.6 - 2013-02-12
 * Bug fixes (see mercurial logs for details)
 
diff --git a/COPYRIGHT b/COPYRIGHT
index 49c6ef7..d30cab4 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,4 +1,4 @@
-Copyright (C) 2010-2012 Nicolas Évrard.
+Copyright (C) 2010-2013 Nicolas Évrard.
 Copyright (C) 2007-2013 Cédric Krier.
 Copyright (C) 2007-2011 Bertrand Chenal.
 Copyright (C) 2008-2013 B2CK SPRL.
diff --git a/PKG-INFO b/PKG-INFO
index f031600..d7b3feb 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.6
+Version: 2.2.7
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index f031600..d7b3feb 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.6
+Version: 2.2.7
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/gui/main.py b/tryton/gui/main.py
index 6b41eab..9d5b687 100644
--- a/tryton/gui/main.py
+++ b/tryton/gui/main.py
@@ -5,7 +5,7 @@ import os
 import sys
 import socket
 import gettext
-from urlparse import urlparse
+from urlparse import urlparse, parse_qsl
 import urllib
 import gobject
 import gtk
@@ -1412,14 +1412,14 @@ class Main(object):
             Main.get_main().refresh_ssl()
 
     def _open_url(self, url):
-        url = urllib.unquote(url)
         urlp = urlparse(url)
         if not urlp.scheme == 'tryton':
             return
         urlp = urlparse('http' + url[6:])
-        hostname, port = (urlp.netloc.split(':', 1)
-                + [CONFIG.defaults['login.port']])[:2]
-        database, path = (urlp.path[1:].split('/', 1) + [None])[:2]
+        hostname, port = map(urllib.unquote,
+            (urlp.netloc.split(':', 1) + [CONFIG.defaults['login.port']])[:2])
+        database, path = map(urllib.unquote,
+            (urlp.path[1:].split('/', 1) + [None])[:2])
         if (not path or
                 hostname != rpc._HOST or
                 int(port) != rpc._PORT or
@@ -1429,8 +1429,8 @@ class Main(object):
         params = {}
         if urlp.params:
             try:
-                params = dict(param.split('=', 1)
-                        for param in urlp.params.split('&'))
+                params.update(dict(parse_qsl(urlp.params,
+                            strict_parsing=True)))
             except ValueError:
                 return
 
diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py
index 1ebde12..7b6ba2d 100644
--- a/tryton/gui/window/view_form/model/field.py
+++ b/tryton/gui/window/view_form/model/field.py
@@ -629,7 +629,7 @@ class O2MField(CharField):
                     to_remove.append(record2)
         for record2 in to_remove:
             record.value[self.name].remove(record2, signal=False,
-                force_remove=True)
+                force_remove=False)
 
         if value and (value.get('add') or value.get('update', [])):
             record.value[self.name].add_fields(fields, signal=False)
diff --git a/tryton/gui/window/view_form/model/record.py b/tryton/gui/window/view_form/model/record.py
index c64ea84..2b6bb18 100644
--- a/tryton/gui/window/view_form/model/record.py
+++ b/tryton/gui/window/view_form/model/record.py
@@ -46,6 +46,11 @@ class Record(SignalEvent):
                         (field.attrs.get('loading', 'eager')
                             for field in self.group.fields.itervalues()),
                         'eager')
+                # Set a valid name for next loaded check
+                for fname, field in self.group.fields.iteritems():
+                    if field.attrs.get('loading', 'eager') == loading:
+                        name = fname
+                        break
             else:
                 loading = self.group.fields[name].attrs.get('loading', 'eager')
             if self in self.group and loading == 'eager':
diff --git a/tryton/gui/window/view_form/view/graph_gtk/bar.py b/tryton/gui/window/view_form/view/graph_gtk/bar.py
index 73b248a..26f5431 100644
--- a/tryton/gui/window/view_form/view/graph_gtk/bar.py
+++ b/tryton/gui/window/view_form/view/graph_gtk/bar.py
@@ -11,6 +11,10 @@ import tryton.rpc as rpc
 
 class Bar(Graph):
 
+    def __init__(self, *args, **kwargs):
+        super(Bar, self).__init__(*args, **kwargs)
+        self.bars = []
+
     def drawGraph(self, cr, width, height):
 
         def drawBar(bar):
diff --git a/tryton/gui/window/view_form/view/list_gtk/parser.py b/tryton/gui/window/view_form/view/list_gtk/parser.py
index 080ba54..b77abb5 100644
--- a/tryton/gui/window/view_form/view/list_gtk/parser.py
+++ b/tryton/gui/window/view_form/view/list_gtk/parser.py
@@ -518,8 +518,8 @@ class M2O(Char):
 
     def value_from_text(self, record, text, callback=None):
         field = record.group.fields[self.field_name]
-        if not text and not field.get_state_attrs(
-                record)['required']:
+        if not text:
+            field.set_client(record, (False, ''))
             if callback:
                 callback()
             return False
@@ -527,11 +527,7 @@ class M2O(Char):
         relation = record[self.field_name].attrs['relation']
         domain = record[self.field_name].domain_get(record)
         context = record[self.field_name].context_get(record)
-        if text:
-            dom = [('rec_name', 'ilike', '%' + text + '%'),
-                    domain]
-        else:
-            dom = domain
+        dom = [('rec_name', 'ilike', '%' + text + '%'), domain]
         args = ('model', relation, 'search', dom, 0, None, None,
                 context)
         try:
@@ -539,7 +535,7 @@ class M2O(Char):
         except TrytonServerError, exception:
             ids = common.process_exception(exception, *args)
             if not ids:
-                field.set_client(record, '???')
+                field.set_client(record, (False, ''))
                 if callback:
                     callback()
                 return
diff --git a/tryton/version.py b/tryton/version.py
index 8c0fe7e..7a5d714 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.6"
+VERSION = "2.2.7"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit 5e522c04291d1bed7beaacfa08069dde6f23a998
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Sun Feb 24 21:45:46 2013 +0100

    Adding upstream version 2.2.6.

diff --git a/CHANGELOG b/CHANGELOG
index 6373ef7..69a750a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.6 - 2013-02-12
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.5 - 2012-12-23
 * Bug fixes (see mercurial logs for details)
 
diff --git a/COPYRIGHT b/COPYRIGHT
index 5db8542..49c6ef7 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,7 +1,7 @@
 Copyright (C) 2010-2012 Nicolas Évrard.
-Copyright (C) 2007-2012 Cédric Krier.
+Copyright (C) 2007-2013 Cédric Krier.
 Copyright (C) 2007-2011 Bertrand Chenal.
-Copyright (C) 2008-2012 B2CK SPRL.
+Copyright (C) 2008-2013 B2CK SPRL.
 Copyright (C) 2008-2011 Udo Spallek.
 Copyright (C) 2008-2011 virtual things - Preisler & Spallek GbR.
 Copyright (C) 2007-2009 Lorenzo Gil Sanchez.
diff --git a/PKG-INFO b/PKG-INFO
index fd6dd20..f031600 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.5
+Version: 2.2.6
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index fd6dd20..f031600 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.5
+Version: 2.2.6
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py
index d63d7e1..1ebde12 100644
--- a/tryton/gui/window/view_form/model/field.py
+++ b/tryton/gui/window/view_form/model/field.py
@@ -660,9 +660,9 @@ class O2MField(CharField):
             return True
         res = True
         for record2 in record.value.get(self.name, []):
-            if not record2.loaded:
+            if not record2.loaded and record2.id >= 0:
                 continue
-            if not record2.validate():
+            if not record2.validate(softvalidation=softvalidation):
                 if not record2.modified:
                     record.value[self.name].remove(record2)
                 else:
diff --git a/tryton/version.py b/tryton/version.py
index fa988ef..8c0fe7e 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.5"
+VERSION = "2.2.6"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit 1b80b4e294ef3e23fd30c7fc3072b776767674ca
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Sun Feb 24 21:43:51 2013 +0100

    Adding upstream version 2.2.5.

diff --git a/CHANGELOG b/CHANGELOG
index 9ce8cd0..6373ef7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.5 - 2012-12-23
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.4 - 2012-11-05
 * Bug fixes (see mercurial logs for details)
 
diff --git a/PKG-INFO b/PKG-INFO
index 1a46c5d..fd6dd20 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.4
+Version: 2.2.5
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/setup.py b/setup.py
index 817acea..833e105 100644
--- a/setup.py
+++ b/setup.py
@@ -233,6 +233,11 @@ if os.name == 'nt':
                 shutil.rmtree(os.path.join(dist_dir, 'share', 'locale', lang))
             shutil.copytree(os.path.join(gtk_dir, 'share', 'locale', lang),
                 os.path.join(dist_dir, 'share', 'locale', lang))
+            if os.path.isdir(os.path.join(os.path.dirname(__file__),
+                        'share', 'locale', lang)):
+                shutil.copytree(os.path.join(os.path.dirname(__file__),
+                        'share', 'locale', lang),
+                    os.path.join(dist_dir, 'share', 'locale', lang))
 
         if os.path.isdir(os.path.join(dist_dir, 'share', 'themes', 'MS-Windows')):
             shutil.rmtree(os.path.join(dist_dir, 'share', 'themes', 'MS-Windows'))
@@ -331,6 +336,11 @@ elif sys.platform == 'darwin':
                 shutil.rmtree(os.path.join(resources_dir, 'share', 'locale', lang))
             shutil.copytree(os.path.join(gtk_dir, 'share', 'locale', lang),
                 os.path.join(resources_dir, 'share', 'locale', lang))
+            if os.path.isdir(os.path.join(os.path.dirname(__file__),
+                        'share', 'locale', lang)):
+                shutil.copytree(os.path.join(os.path.dirname(__file__),
+                        'share', 'locale', lang),
+                    os.path.join(resources_dir, 'share', 'locale', lang))
 
         # fix pathes within shared libraries
         for library in chain(
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index 1a46c5d..fd6dd20 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.4
+Version: 2.2.5
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/common/domain_inversion.py b/tryton/common/domain_inversion.py
index 335eee8..15c3b58 100644
--- a/tryton/common/domain_inversion.py
+++ b/tryton/common/domain_inversion.py
@@ -49,11 +49,11 @@ def eval_leaf(part, context, boolop=operator.and_):
         # In the case where the leaf concerns a m2o then having a value in the
         # evaluation context is deemed suffisant
         return bool(context.get(field.split('.')[0]))
-    if operand == '=' and not context[field] and boolop == operator.and_:
+    if operand == '=' and not context.get(field) and boolop == operator.and_:
         # We should consider that other domain inversion will set a correct
         # value to this field
         return True
-    context_field = context[field]
+    context_field = context.get(field)
     if isinstance(context_field, datetime.date) and not value:
         if isinstance(context_field, datetime.datetime):
             value = datetime.datetime.min
diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py
index 6aa6530..d63d7e1 100644
--- a/tryton/gui/window/view_form/model/field.py
+++ b/tryton/gui/window/view_form/model/field.py
@@ -631,7 +631,7 @@ class O2MField(CharField):
             record.value[self.name].remove(record2, signal=False,
                 force_remove=True)
 
-        if value and value.get('add') or value.get('update', []):
+        if value and (value.get('add') or value.get('update', [])):
             record.value[self.name].add_fields(fields, signal=False)
             for vals in value.get('add', []):
                 new_record = record.value[self.name].new(default=False,
diff --git a/tryton/gui/window/view_form/model/record.py b/tryton/gui/window/view_form/model/record.py
index 926540a..c64ea84 100644
--- a/tryton/gui/window/view_form/model/record.py
+++ b/tryton/gui/window/view_form/model/record.py
@@ -224,6 +224,8 @@ class Record(SignalEvent):
             self._check_load()
         value = {}
         for name, field in self.group.fields.iteritems():
+            if name not in self._loaded and self.id >= 0:
+                continue
             value[name] = field.get_eval(self, check_load=check_load)
         value['id'] = self.id
         return value
@@ -442,7 +444,7 @@ class Record(SignalEvent):
         else:
             for field in fields:
                 self[field]
-        self.validate([])
+        self.validate(fields or [])
 
     def expr_eval(self, expr, check_load=False):
         if not isinstance(expr, basestring):
diff --git a/tryton/version.py b/tryton/version.py
index 9690753..fa988ef 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.4"
+VERSION = "2.2.5"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit 388329b55432f4cdb5d75acc9232252711f4eb7a
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Sun Feb 24 19:50:19 2013 +0100

    Adding upstream version 2.2.4.

diff --git a/CHANGELOG b/CHANGELOG
index f97db53..9ce8cd0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.4 - 2012-11-05
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.3 - 2012-09-01
 * Bug fixes (see mercurial logs for details)
 
diff --git a/PKG-INFO b/PKG-INFO
index 94147e1..1a46c5d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.3
+Version: 2.2.4
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index 94147e1..1a46c5d 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.3
+Version: 2.2.4
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/common/common.py b/tryton/common/common.py
index 6bffd9f..00ad4bd 100644
--- a/tryton/common/common.py
+++ b/tryton/common/common.py
@@ -244,7 +244,7 @@ def request_server(server_widget):
 def get_toplevel_window():
     windows = [x for x in gtk.window_list_toplevels()
         if x.window and x.props.visible
-        and x.get_window_type() == gtk.WINDOW_TOPLEVEL]
+        and x.props.type == gtk.WINDOW_TOPLEVEL]
     trans2windows = dict((x.get_transient_for(), x) for x in windows)
     for window in set(windows) - set(trans2windows.iterkeys()):
         return window
diff --git a/tryton/gui/main.py b/tryton/gui/main.py
index 17a6ad5..6b41eab 100644
--- a/tryton/gui/main.py
+++ b/tryton/gui/main.py
@@ -1201,6 +1201,8 @@ class Main(object):
     def _sig_remove_book(self, widget, page_widget):
         for page in self.pages:
             if page.widget == page_widget:
+                if not page.widget.props.sensitive:
+                    return
                 page_num = self.notebook.page_num(page.widget)
                 self.notebook.set_current_page(page_num)
                 res = page.sig_close()
@@ -1318,7 +1320,7 @@ class Main(object):
         self.refresh_ssl()
         common.message(_("Database dropped successfully!"))
 
-    def sig_db_restore(self, widget):
+    def sig_db_restore(self, widget=None):
         if not self.sig_logout(widget):
             return False
         filename = common.file_selection(_('Open Backup File to Restore...'),
@@ -1360,7 +1362,7 @@ class Main(object):
             else:
                 common.message(_('Database restore failed!'))
 
-    def sig_db_dump(self, widget):
+    def sig_db_dump(self, widget=None):
         if not self.sig_logout(widget):
             return False
         dialog = DBBackupDrop(function='backup')
diff --git a/tryton/gui/window/view_form/model/record.py b/tryton/gui/window/view_form/model/record.py
index b459869..926540a 100644
--- a/tryton/gui/window/view_form/model/record.py
+++ b/tryton/gui/window/view_form/model/record.py
@@ -115,6 +115,15 @@ class Record(SignalEvent):
     def parent_name(self):
         return self.group.parent_name
 
+    @property
+    def depth(self):
+        parent = self.parent
+        i = 0
+        while parent:
+            i += 1
+            parent = parent.parent
+        return i
+
     def set_modified(self, value):
         if value:
             self.signal('record-modified')
diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py
index 3ff1368..4c5d40e 100644
--- a/tryton/gui/window/view_form/screen/screen.py
+++ b/tryton/gui/window/view_form/screen/screen.py
@@ -548,6 +548,8 @@ class Screen(SignalEvent):
         if not records:
             return
         if delete:
+            # Must delete children records before parent
+            records.sort(key=lambda r: r.depth, reverse=True)
             if not self.group.delete(records):
                 return False
 
@@ -679,7 +681,9 @@ class Screen(SignalEvent):
             store = view.store
             iter_ = store.get_iter(end)
             self.current_record = store.get_value(iter_, 0)
-        elif view.view_type == 'form' and self.current_record.group:
+        elif (view.view_type == 'form'
+                and self.current_record
+                and self.current_record.group):
             group = self.current_record.group
             record = self.current_record
             while group:
@@ -706,7 +710,7 @@ class Screen(SignalEvent):
                 break
             self.current_record = record
         else:
-            self.current_record = len(self.group) and self.group[0]
+            self.current_record = self.group[0] if len(self.group) else None
         view.set_cursor(reset_view=False)
         view.display()
 
@@ -721,7 +725,9 @@ class Screen(SignalEvent):
             store = view.store
             iter_ = store.get_iter(start)
             self.current_record = store.get_value(iter_, 0)
-        elif view.view_type == 'form' and self.current_record.group:
+        elif (view.view_type == 'form'
+                and self.current_record
+                and self.current_record.group):
             group = self.current_record.group
             record = self.current_record
             idx = group.index(record) - 1
@@ -738,7 +744,7 @@ class Screen(SignalEvent):
                     record = parent
             self.current_record = record
         else:
-            self.current_record = len(self.group) and self.group[-1]
+            self.current_record = self.group[-1] if len(self.group) else None
         view.set_cursor(reset_view=False)
         view.display()
 
diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py
index 69d93e7..1efca22 100644
--- a/tryton/gui/window/view_form/view/list.py
+++ b/tryton/gui/window/view_form/view/list.py
@@ -126,6 +126,10 @@ class AdaptModelGroup(gtk.GenericTreeModel):
         group = parent.children_group(self.children_field, check_load=True)
         if group is not record.group:
             record.group.remove(record, remove=True, force_remove=True)
+            # Don't remove record from previous group
+            # as the new parent will change the parent
+            # This prevents concurrency conflict
+            record.group.record_removed.remove(record)
             group.add(record)
             record.modified_fields.setdefault(record.parent_name or 'id')
         group.move(record, 0)
diff --git a/tryton/gui/window/win_form.py b/tryton/gui/window/win_form.py
index da34c31..76d7690 100644
--- a/tryton/gui/window/win_form.py
+++ b/tryton/gui/window/win_form.py
@@ -75,9 +75,8 @@ class WinForm(NoModal):
         self.win.set_title(self.screen.current_view.title)
 
         title = gtk.Label()
-        title.set_use_markup(True)
-        title.modify_font(pango.FontDescription("12"))
-        title.set_label('<b>' + self.screen.current_view.title + '</b>')
+        title.modify_font(pango.FontDescription("bold 12"))
+        title.set_label(self.screen.current_view.title)
         title.set_padding(20, 3)
         title.set_alignment(0.0, 0.5)
         title.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
diff --git a/tryton/version.py b/tryton/version.py
index b409f6a..9690753 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.3"
+VERSION = "2.2.4"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit 506a2c81e5e6c453043d271acf3dec75c7c51a52
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Tue Sep 11 19:36:27 2012 +0200

    Adding upstream version 2.2.3.

diff --git a/CHANGELOG b/CHANGELOG
index a67af88..f97db53 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.3 - 2012-09-01
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.2 - 2012-05-07
 * Bug fixes (see mercurial logs for details)
 
diff --git a/PKG-INFO b/PKG-INFO
index 4d4255a..94147e1 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.0
 Name: tryton
-Version: 2.2.2
+Version: 2.2.3
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index 4d4255a..94147e1 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.0
 Name: tryton
-Version: 2.2.2
+Version: 2.2.3
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/common/common.py b/tryton/common/common.py
index 83cb472..6bffd9f 100644
--- a/tryton/common/common.py
+++ b/tryton/common/common.py
@@ -243,7 +243,8 @@ def request_server(server_widget):
 
 def get_toplevel_window():
     windows = [x for x in gtk.window_list_toplevels()
-        if x.window and x.props.visible]
+        if x.window and x.props.visible
+        and x.get_window_type() == gtk.WINDOW_TOPLEVEL]
     trans2windows = dict((x.get_transient_for(), x) for x in windows)
     for window in set(windows) - set(trans2windows.iterkeys()):
         return window
diff --git a/tryton/common/domain_inversion.py b/tryton/common/domain_inversion.py
index 8da0a6a..335eee8 100644
--- a/tryton/common/domain_inversion.py
+++ b/tryton/common/domain_inversion.py
@@ -3,6 +3,7 @@
 
 import operator
 import types
+import datetime
 
 def in_(a, b):
     if isinstance(a, (list, tuple)):
@@ -52,7 +53,18 @@ def eval_leaf(part, context, boolop=operator.and_):
         # We should consider that other domain inversion will set a correct
         # value to this field
         return True
-    return OPERATORS[operand](context[field], value)
+    context_field = context[field]
+    if isinstance(context_field, datetime.date) and not value:
+        if isinstance(context_field, datetime.datetime):
+            value = datetime.datetime.min
+        else:
+            value = datetime.date.min
+    if isinstance(value, datetime.date) and not context_field:
+        if isinstance(value, datetime.datetime):
+            context_field = datetime.datetime.min
+        else:
+            context_field = datetime.date.min
+    return OPERATORS[operand](context_field, value)
 
 def inverse_leaf(domain):
     if domain in ('AND', 'OR'):
@@ -396,6 +408,15 @@ def test_evaldomain():
     assert eval_domain(domain, {'x': 6})
     assert not eval_domain(domain, {'x': 4})
 
+    domain = [['x', '>', None]]
+    assert eval_domain(domain, {'x': datetime.date.today()})
+    assert eval_domain(domain, {'x': datetime.datetime.now()})
+
+    domain = [['x', '<', datetime.date.today()]]
+    assert eval_domain(domain, {'x': None})
+    domain = [['x', '<', datetime.datetime.now()]]
+    assert eval_domain(domain, {'x': None})
+
     domain = [['x', 'in', [3, 5]]]
     assert eval_domain(domain, {'x': 3})
     assert not eval_domain(domain, {'x': 4})
diff --git a/tryton/gui/window/form.py b/tryton/gui/window/form.py
index 7e46d07..2f351c9 100644
--- a/tryton/gui/window/form.py
+++ b/tryton/gui/window/form.py
@@ -406,19 +406,24 @@ class Form(SignalEvent, TabContent):
         return True
 
     def sig_action(self, widget):
-        self.buttons['action'].props.active = True
+        if self.buttons['action'].props.sensitive:
+            self.buttons['action'].props.active = True
 
     def sig_print(self, widget):
-        self.buttons['print'].props.active = True
+        if self.buttons['print'].props.sensitive:
+            self.buttons['print'].props.active = True
 
     def sig_print_open(self, widget):
-        self.buttons['open'].props.active = True
+        if self.buttons['open'].props.sensitive:
+            self.buttons['open'].props.active = True
 
     def sig_print_email(self, widget):
-        self.buttons['email'].props.active = True
+        if self.buttons['email'].props.sensitive:
+            self.buttons['email'].props.active = True
 
     def sig_relate(self, widget):
-        self.buttons['relate'].props.active = True
+        if self.buttons['relate'].props.sensitive:
+            self.buttons['relate'].props.active = True
 
     def action_popup(self, widget):
         button, = widget.get_children()
diff --git a/tryton/gui/window/view_form/view/form.py b/tryton/gui/window/view_form/view/form.py
index 6e05a63..d25e566 100644
--- a/tryton/gui/window/view_form/view/form.py
+++ b/tryton/gui/window/view_form/view/form.py
@@ -106,7 +106,7 @@ class ViewForm(ParserView):
             # Get first the lazy one to reduce number of requests
             fields = [(name, field.attrs.get('loading', 'eager'))
                     for name, field in record.group.fields.iteritems()]
-            fields.sort(key=operator.itemgetter(1))
+            fields.sort(key=operator.itemgetter(1), reverse=True)
             for field, _ in fields:
                 record[field].get(record, check_load=False)
         for name, widgets in self.widgets.iteritems():
diff --git a/tryton/gui/window/view_form/view/form_gtk/many2one.py b/tryton/gui/window/view_form/view/form_gtk/many2one.py
index 81d8bf5..85d5b87 100644
--- a/tryton/gui/window/view_form/view/form_gtk/many2one.py
+++ b/tryton/gui/window/view_form/view/form_gtk/many2one.py
@@ -83,7 +83,7 @@ class Many2One(WidgetInterface):
     def _color_widget(self):
         return self.wid_text
 
-    def sig_activate(self, widget, event=None, key_press=False):
+    def sig_activate(self, widget=None, event=None, key_press=False):
         if not self.focus_out:
             return
         if not self.field:
@@ -93,7 +93,7 @@ class Many2One(WidgetInterface):
 
         self.focus_out = False
         if not value:
-            if not key_press and not event:
+            if not key_press and not event and widget:
                 widget.emit_stop_by_name('activate')
             if not self._readonly and (self.wid_text.get_text() or \
                     (self.field.get_state_attrs(
@@ -243,7 +243,8 @@ class Many2One(WidgetInterface):
         return False
 
     def set_value(self, record, field):
-        pass # No update of the model, the model is updated in real time !
+        # Simulate a focus-out
+        self.sig_activate()
 
     def display(self, record, field):
         self.changed = False
diff --git a/tryton/gui/window/view_form/view/graph_gtk/graph.py b/tryton/gui/window/view_form/view/graph_gtk/graph.py
index 854c100..695bcec 100644
--- a/tryton/gui/window/view_form/view/graph_gtk/graph.py
+++ b/tryton/gui/window/view_form/view/graph_gtk/graph.py
@@ -276,7 +276,7 @@ class Graph(gtk.DrawingArea):
         cr.restore()
 
     def drawLegend(self, cr, width, height):
-        if not self.attrs.get('legend', True):
+        if not int(self.attrs.get('legend', 1)):
             return
 
         padding = 4
diff --git a/tryton/gui/window/win_export.py b/tryton/gui/window/win_export.py
index 79dae47..43f2c96 100644
--- a/tryton/gui/window/win_export.py
+++ b/tryton/gui/window/win_export.py
@@ -246,7 +246,8 @@ class WinExport(object):
         child = self.model1.iter_children(iter)
         if self.model1.get_value(child, 0) is None:
             prefix_field = self.model1.get_value(iter, 1)
-            name, model = self.fields[prefix_field]
+            _, model = self.fields[prefix_field]
+            name = self.fields_data[prefix_field]['string']
             self.model_populate(self._get_fields(model), iter, prefix_field +
                     '/', name + '/')
             self.model1.remove(child)
diff --git a/tryton/jsonrpc.py b/tryton/jsonrpc.py
index a4c1c17..cda04d6 100644
--- a/tryton/jsonrpc.py
+++ b/tryton/jsonrpc.py
@@ -32,6 +32,11 @@ class Fault(xmlrpclib.Fault):
         super(Fault, self).__init__(faultCode, faultString, **extra)
         self.args = faultString
 
+    def __repr__(self):
+        return (
+            "<Fault %s: %s>" %
+            (repr(self.faultCode), repr(self.faultString))
+            )
 
 class ProtocolError(xmlrpclib.ProtocolError):
     pass
diff --git a/tryton/version.py b/tryton/version.py
index 3b71650..b409f6a 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.2"
+VERSION = "2.2.3"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
commit e8d1701d46f5cedf426cfbe7177359f120000ea5
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Wed May 9 11:47:25 2012 +0200

    Adding upstream version 2.2.2.

diff --git a/CHANGELOG b/CHANGELOG
index 5d86a51..a67af88 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 2.2.2 - 2012-05-07
+* Bug fixes (see mercurial logs for details)
+
 Version 2.2.1 - 2011-12-26
 * Bug fixes (see mercurial logs for details)
 
diff --git a/COPYRIGHT b/COPYRIGHT
index 9636f7a..5db8542 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,7 +1,7 @@
-Copyright (C) 2010-2011 Nicolas Évrard.
-Copyright (C) 2007-2011 Cédric Krier.
+Copyright (C) 2010-2012 Nicolas Évrard.
+Copyright (C) 2007-2012 Cédric Krier.
 Copyright (C) 2007-2011 Bertrand Chenal.
-Copyright (C) 2008-2011 B2CK SPRL.
+Copyright (C) 2008-2012 B2CK SPRL.
 Copyright (C) 2008-2011 Udo Spallek.
 Copyright (C) 2008-2011 virtual things - Preisler & Spallek GbR.
 Copyright (C) 2007-2009 Lorenzo Gil Sanchez.
diff --git a/PKG-INFO b/PKG-INFO
index 59e16ad..4d4255a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.1
+Version: 2.2.2
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton.egg-info/PKG-INFO b/tryton.egg-info/PKG-INFO
index 59e16ad..4d4255a 100644
--- a/tryton.egg-info/PKG-INFO
+++ b/tryton.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: tryton
-Version: 2.2.1
+Version: 2.2.2
 Summary: Tryton client
 Home-page: http://www.tryton.org/
 Author: B2CK
diff --git a/tryton/common/common.py b/tryton/common/common.py
index a68a88d..83cb472 100644
--- a/tryton/common/common.py
+++ b/tryton/common/common.py
@@ -954,7 +954,7 @@ def send_bugtracker(msg):
                     and 'roundup.cgi.exceptions.Unauthorised' in
                     exception.faultString):
                 message(_('Connection error!\nBad username or password!'))
-                return send_bugtracker(msg, parent)
+                return send_bugtracker(msg)
             tb_s = reduce(lambda x, y: x + y,
                     traceback.format_exception(sys.exc_type,
                         sys.exc_value, sys.exc_traceback))
@@ -1461,11 +1461,17 @@ def safe_eval(source, data=None):
         'dict': dict,
         }}, data)
 
-def timezoned_date(date):
-    if pytz and rpc.CONTEXT.get('timezone'):
+def timezoned_date(date, reverse=False):
+    if pytz and rpc.CONTEXT.get('timezone') and rpc.TIMEZONE:
         lzone = pytz.timezone(rpc.CONTEXT['timezone'])
         szone = pytz.timezone(rpc.TIMEZONE)
+        if reverse:
+            lzone, szone = szone, lzone
         sdt = szone.localize(date, is_dst=True)
         ldt = sdt.astimezone(lzone)
         date = ldt
     return date
+
+
+def untimezoned_date(date):
+    return timezoned_date(date, reverse=True).replace(tzinfo=None)
diff --git a/tryton/gui/window/dblogin.py b/tryton/gui/window/dblogin.py
index 4e28d7c..e113121 100644
--- a/tryton/gui/window/dblogin.py
+++ b/tryton/gui/window/dblogin.py
@@ -26,6 +26,7 @@ class DBListEditor(object):
         self.profiles = profiles
         self.current_database = None
         self.old_profile, self.current_profile = None, None
+        self.db_cache = None
         self.updating_db = False
 
         # GTK Stuffs
@@ -41,9 +42,7 @@ class DBListEditor(object):
         vbox_profiles = gtk.VBox(homogeneous=False, spacing=6)
         self.cell = gtk.CellRendererText()
         self.cell.set_property('editable', True)
-        self.cell.connect('edited', self.edit_profilename)
         self.cell.connect('editing-started', self.edit_started)
-        self.cell.connect('editing-canceled', self.edit_canceled)
         self.profile_tree = gtk.TreeView()
         self.profile_tree.set_model(profile_store)
         self.profile_tree.insert_column_with_attributes(-1, _(u'Profile'),
@@ -174,10 +173,12 @@ class DBListEditor(object):
     def clear_entries(self):
         for entryname in ('host', 'port', 'database', 'username'):
             entry = getattr(self, '%s_entry' % entryname)
+            entry.handler_block_by_func(self.update_profiles)
             if entryname == 'port':
                 entry.set_text('8000')
             else:
                 entry.set_text('')
+            entry.handler_unblock_by_func(self.update_profiles)
         self.current_database = None
         self.database_combo.set_active(-1)
         self.database_combo.get_model().clear()
@@ -231,22 +232,13 @@ class DBListEditor(object):
 
         self.display_dbwidget(None, None, self.current_database)
 
-    def edit_canceled(self, renderer):
-        model = self.profile_tree.get_model()
-        for i, row in enumerate(list(model)):
-            if not row[0]:
-                del model[i]
-
-    def check_edit_cancel(self, editable, event, renderer, path):
-        renderer.emit('edited', path, editable.get_text())
-        return False
-
     def edit_started(self, renderer, editable, path):
         if isinstance(editable, gtk.Entry):
-            editable.connect('focus-out-event', self.check_edit_cancel,
+            editable.connect('focus-out-event', self.edit_profilename,
                 renderer, path)
 
-    def edit_profilename(self, renderer, path, newtext):
+    def edit_profilename(self, editable, event, renderer, path):
+        newtext = editable.get_text()
         model = self.profile_tree.get_model()
         oldname = model[path][0]
         if oldname == newtext == '':
@@ -290,6 +282,8 @@ class DBListEditor(object):
         port = self.port_entry.get_text()
         if not (host and port):
             return
+        if (host, port, self.current_profile['name']) == self.db_cache:
+            return
         if self.updating_db:
             return
         if dbname is None:
@@ -307,6 +301,7 @@ class DBListEditor(object):
 
         def callback(dbs, createdb):
             self.updating_db = False
+            self.db_cache = (host, port, self.current_profile['name'])
 
             if dbs is None and createdb is None:
                 pass
@@ -343,6 +338,7 @@ class DBListEditor(object):
         port = int(self.port_entry.get_text())
         dia = DBCreate(host, port)
         dbname = dia.run()
+        self.db_cache = None
         self.username_entry.set_text('admin')
         self.display_dbwidget(None, None, dbname)
 
diff --git a/tryton/gui/window/form.py b/tryton/gui/window/form.py
index 7c9e27d..7e46d07 100644
--- a/tryton/gui/window/form.py
+++ b/tryton/gui/window/form.py
@@ -476,6 +476,7 @@ class Form(SignalEvent, TabContent):
         self.activate_save()
 
     def modified_save(self, reload=True):
+        self.screen.current_view.set_value()
         if self.screen.modified():
             value = sur_3b(
                     _('This record has been modified\n' \
diff --git a/tryton/gui/window/nomodal.py b/tryton/gui/window/nomodal.py
index c100e2b..95e5206 100644
--- a/tryton/gui/window/nomodal.py
+++ b/tryton/gui/window/nomodal.py
@@ -1,5 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
+import gtk
+
 from tryton.gui.main import Main
 import tryton.common as common
 
@@ -22,6 +24,9 @@ class NoModal(object):
 
     def destroy(self):
         self.page.dialogs.remove(self)
+        # Test if the parent is not already destroyed
+        if self.parent not in gtk.window_list_toplevels():
+            return
         self.parent.present()
         self.sensible_widget.props.sensitive = True
         if self.parent_focus:
diff --git a/tryton/gui/window/preference.py b/tryton/gui/window/preference.py
index 3f03596..c86088b 100644
--- a/tryton/gui/window/preference.py
+++ b/tryton/gui/window/preference.py
@@ -96,8 +96,7 @@ class Preference(object):
                     try:
                         rpc.execute(*args)
                     except TrytonServerError, exception:
-                        if not common.process_exception(exception, self.win,
-                                *args):
+                        if not common.process_exception(exception, *args):
                             continue
                     res = True
                     break
diff --git a/tryton/gui/window/view_board/parser.py b/tryton/gui/window/view_board/parser.py
index 7aa0abe..e22021c 100644
--- a/tryton/gui/window/view_board/parser.py
+++ b/tryton/gui/window/view_board/parser.py
@@ -2,10 +2,13 @@
 #this repository contains the full copyright notices and license terms.
 'Parser'
 import gtk
-from tryton.gui.window.view_form.view.form_gtk.parser import _container
+import gettext
+from tryton.gui.window.view_form.view.form_gtk.parser import _container, VBox
 import tryton.common as common
 from action import Action
-from tryton.config import CONFIG, TRYTON_ICON
+from tryton.config import CONFIG
+
+_ = gettext.gettext
 
 
 class ParserBoard(object):
@@ -47,13 +50,6 @@ class ParserBoard(object):
                     xexpand=xexpand, xfill=xfill)
             elif node.localName == 'separator':
                 text = attrs.get('string', '')
-                if 'string' in attrs or 'name' in attrs:
-                    if not text:
-                        if 'name' in attrs and attrs['name'] in fields:
-                            if 'states' in fields[attrs['name']]:
-                                attrs['states'] = \
-                                        fields[attrs['name']]['states']
-                            text = fields[attrs['name']]['string']
                 vbox = VBox(attrs=attrs)
                 if text:
                     label = gtk.Label(text)
@@ -69,22 +65,6 @@ class ParserBoard(object):
             elif node.localName == 'label':
                 text = attrs.get('string', '')
                 if not text:
-                    if 'name' in attrs and attrs['name'] in fields:
-                        if 'states' in fields[attrs['name']]:
-                            attrs['states'] = fields[attrs['name']]['states']
-                        if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
-                            text = _(':') + fields[attrs['name']]['string']
-                        else:
-                            text = fields[attrs['name']]['string'] + _(':')
-                        if 'align' not in attrs:
-                            attrs['align'] = 1.0
-                    else:
-                        for node in node.childNodes:
-                            if node.nodeType == node.TEXT_NODE:
-                                text += node.data
-                            else:
-                                text += node.toxml()
-                if not text:
                     container.empty_add(int(attrs.get('colspan', 1)))
                     continue
                 label = gtk.Label(text)
@@ -115,7 +95,8 @@ class ParserBoard(object):
                 container.wid_add(notebook,
                     colspan=int(attrs.get('colspan', 4)),
                     yexpand=True, yfill=True)
-                widget, new_widgets = self.parse(node, notebook, tooltips=tooltips)
+                widget, new_widgets = self.parse(node, notebook,
+                    tooltips=tooltips)
                 widgets += new_widgets
             elif node.localName == 'page':
                 if CONFIG['client.form_tab'] == 'left':
@@ -124,9 +105,10 @@ class ParserBoard(object):
                     angle = -90
                 else:
                     angle = 0
-                label = gtk.Label(attrs.get('string','No String Attr.'))
+                label = gtk.Label(attrs.get('string', _('No String Attr.')))
                 label.set_angle(angle)
-                widget, new_widgets = self.parse(node, notebook, tooltips=tooltips)
+                widget, new_widgets = self.parse(node, notebook,
+                    tooltips=tooltips)
                 widgets += new_widgets
                 notebook.append_page(widget, label)
             elif node.localName == 'group':
@@ -145,7 +127,8 @@ class ParserBoard(object):
                 hpaned = gtk.HPaned()
                 container.wid_add(hpaned, colspan=int(attrs.get('colspan', 4)),
                         yexpand=True, yfill=True)
-                widget, new_widgets = self.parse(node, paned=hpaned, tooltips=tooltips)
+                widget, new_widgets = self.parse(node, paned=hpaned,
+                    tooltips=tooltips)
                 widgets += new_widgets
                 if 'position' in attrs:
                     hpaned.set_position(int(attrs['position']))
@@ -153,19 +136,20 @@ class ParserBoard(object):
                 vpaned = gtk.VPaned()
                 container.wid_add(vpaned, colspan=int(attrs.get('colspan', 4)),
                         yexpand=True, yfill=True)
-                widget, new_widgets = self.parse(node, paned=vpaned, tooltips=tooltips)
+                widget, new_widgets = self.parse(node, paned=vpaned,
+                    tooltips=tooltips)
                 widgets += new_widgets
                 if 'position' in attrs:
                     vpaned.set_position(int(attrs['position']))
             elif node.localName == 'child':
-                widget, new_widgets = self.parse(node, paned=paned, tooltips=tooltips)
+                widget, new_widgets = self.parse(node, paned=paned,
+                    tooltips=tooltips)
                 widgets += new_widgets
                 if not paned.get_child1():
                     paned.pack1(widget, resize=True, shrink=True)
                 elif not paned.get_child2():
                     paned.pack2(widget, resize=True, shrink=True)
             elif node.localName == 'action':
-                name = str(attrs['name'])
                 widget_act = Action(attrs, self.context)
                 widgets.append(widget_act)
                 yexpand = bool(attrs.get('yexpand', 1))
diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py
index d0f469e..6aa6530 100644
--- a/tryton/gui/window/view_form/model/field.py
+++ b/tryton/gui/window/view_form/model/field.py
@@ -483,6 +483,8 @@ class O2MField(CharField):
                     get_readonly=readonly)
                 values.pop(parent_name, None)
                 result.append(('create', values))
+        if not result[0][1]:
+            del result[0]
         if record_removed:
             result.append(('unlink', [x.id for x in record_removed]))
         if record_deleted:
diff --git a/tryton/gui/window/view_form/model/group.py b/tryton/gui/window/view_form/model/group.py
index 338cf67..7e88343 100644
--- a/tryton/gui/window/view_form/model/group.py
+++ b/tryton/gui/window/view_form/model/group.py
@@ -161,6 +161,7 @@ class Group(SignalEvent, list):
         parent = self.parent
         while parent:
             root = parent.group
+            parent = parent.parent
         return root
 
     def written(self, ids):
@@ -201,15 +202,15 @@ class Group(SignalEvent, list):
 
         new_records = []
         for id in ids:
-            if self.get(id):
-                continue
-            new_record = Record(self.model_name, id, group=self)
-            self.append(new_record)
+            new_record = self.get(id)
+            if not new_record:
+                new_record = Record(self.model_name, id, group=self)
+                self.append(new_record)
+                new_record.signal_connect(self, 'record-changed',
+                    self._record_changed)
+                new_record.signal_connect(self, 'record-modified',
+                    self._record_modified)
             new_records.append(new_record)
-            new_record.signal_connect(self, 'record-changed',
-                self._record_changed)
-            new_record.signal_connect(self, 'record-modified',
-                self._record_modified)
 
         # Remove previously removed or deleted records
         for record in self.record_removed[:]:
diff --git a/tryton/gui/window/view_form/model/record.py b/tryton/gui/window/view_form/model/record.py
index 128f3a4..b459869 100644
--- a/tryton/gui/window/view_form/model/record.py
+++ b/tryton/gui/window/view_form/model/record.py
@@ -277,9 +277,9 @@ class Record(SignalEvent):
         if not records:
             return
         record = records[0]
-        group = record.group
+        root_group = record.group.root_group
         assert all(r.model_name == record.model_name for r in records)
-        assert all(r.group == group for r in records)
+        assert all(r.group.root_group == root_group for r in records)
         records = [r for r in records if r.id >= 0]
         ctx = {}
         ctx.update(rpc.CONTEXT)
@@ -288,7 +288,7 @@ class Record(SignalEvent):
         for rec in records:
             ctx['_timestamp'].update(rec.get_timestamp())
         record_ids = set(r.id for r in records)
-        reload_ids = set(group.on_write_ids(list(record_ids)))
+        reload_ids = set(root_group.on_write_ids(list(record_ids)))
         reload_ids -= record_ids
         reload_ids = list(reload_ids)
         args = ('model', record.model_name, 'delete', list(record_ids), ctx)
@@ -298,7 +298,7 @@ class Record(SignalEvent):
             if not common.process_exception(exception, *args):
                 return False
         if reload_ids:
-            group.root_group.reload(reload_ids)
+            root_group.reload(reload_ids)
         return True
 
     def default_get(self, domain=None, context=None):
@@ -584,5 +584,8 @@ class Record(SignalEvent):
     def destroy(self):
         super(Record, self).destroy()
         self.group = None
+        for v in self.value.itervalues():
+            if hasattr(v, 'destroy'):
+                v.destroy()
         self.value = None
         self.next = None
diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py
index 3853549..3ff1368 100644
--- a/tryton/gui/window/view_form/screen/screen.py
+++ b/tryton/gui/window/view_form/screen/screen.py
@@ -368,6 +368,8 @@ class Screen(SignalEvent):
                 self.fields_view_tree = view
             break
 
+        # Ensure that loading is always eager for fields on tree view
+        # and always lazy for fields only on form view
         if node.localName == 'tree':
             loading = 'eager'
         else:
@@ -375,6 +377,9 @@ class Screen(SignalEvent):
         for field in fields:
             if field not in self.group.fields:
                 fields[field]['loading'] = loading
+            else:
+                fields[field]['loading'] = \
+                    self.group.fields[field].attrs['loading']
 
         children_field = view.get('field_childs')
 
@@ -540,6 +545,8 @@ class Screen(SignalEvent):
             records = [self.current_record]
         elif self.current_view.view_type == 'tree':
             records = self.current_view.selected_records()
+        if not records:
+            return
         if delete:
             if not self.group.delete(records):
                 return False
@@ -747,8 +754,8 @@ class Screen(SignalEvent):
         return [x.id for x in self.group if x.id]
 
     def clear(self):
-        self.group.clear()
         self.current_record = None
+        self.group.clear()
 
     def on_change(self, fieldname, attr):
         self.current_record.on_change(fieldname, attr)
diff --git a/tryton/gui/window/view_form/view/form_gtk/calendar.py b/tryton/gui/window/view_form/view/form_gtk/calendar.py
index 0966ee1..e6e28ae 100644
--- a/tryton/gui/window/view_form/view/form_gtk/calendar.py
+++ b/tryton/gui/window/view_form/view/form_gtk/calendar.py
@@ -8,7 +8,7 @@ import locale
 from interface import WidgetInterface
 import tryton.rpc as rpc
 from tryton.common import DT_FORMAT, DHM_FORMAT, HM_FORMAT, message, \
-        TRYTON_ICON, timezoned_date
+        TRYTON_ICON, timezoned_date, untimezoned_date
 from tryton.common import date_widget, Tooltips, datetime_strftime, \
         get_toplevel_window
 from tryton.translate import date_format
@@ -192,7 +192,8 @@ class DateTime(WidgetInterface):
             date = datetime.datetime(*time.strptime(value, self.format)[:6])
         except ValueError:
             return False
-        date = timezoned_date(date)
+        if timezone:
+            date = untimezoned_date(date)
         return datetime_strftime(date, DHM_FORMAT)
 
     def set_value(self, record, field):
@@ -210,7 +211,8 @@ class DateTime(WidgetInterface):
             self.entry.clear()
         else:
             date = datetime.datetime(*time.strptime(dt_val, DHM_FORMAT)[:6])
-            date = timezoned_date(date)
+            if timezone:
+                date = timezoned_date(date)
             value = datetime_strftime(date, self.format)
             if len(value) > self.entry.get_width_chars():
                 self.entry.set_width_chars(len(value))
diff --git a/tryton/gui/window/view_form/view/form_gtk/float_time.py b/tryton/gui/window/view_form/view/form_gtk/float_time.py
index 1644bab..86c2d49 100644
--- a/tryton/gui/window/view_form/view/form_gtk/float_time.py
+++ b/tryton/gui/window/view_form/view/form_gtk/float_time.py
@@ -35,8 +35,9 @@ class FloatTime(WidgetInterface):
         value = self.entry.get_text()
         if not value:
             return field.set_client(record, 0.0)
+        digits = record.expr_eval(field.attrs.get('digits', (16, 2)))
         return field.set_client(record,
-                common.text_to_float_time(value, self.conv))
+                round(common.text_to_float_time(value, self.conv), digits[1]))
 
     def display(self, record, field):
         super(FloatTime, self).display(record, field)
diff --git a/tryton/gui/window/view_form/view/form_gtk/image.py b/tryton/gui/window/view_form/view/form_gtk/image.py
index 3422406..efc9ac5 100644
--- a/tryton/gui/window/view_form/view/form_gtk/image.py
+++ b/tryton/gui/window/view_form/view/form_gtk/image.py
@@ -207,8 +207,8 @@ class Image(WidgetInterface):
             try:
                 loader = gtk.gdk.PixbufLoader(ftype)
                 loader.write(data, len(data))
-                pixbuf = loader.get_pixbuf()
                 loader.close()
+                pixbuf = loader.get_pixbuf()
             except glib.GError:
                 continue
             if pixbuf:
@@ -216,8 +216,8 @@ class Image(WidgetInterface):
         if not pixbuf:
             loader = gtk.gdk.PixbufLoader('png')
             loader.write(NOIMAGE, len(NOIMAGE))
-            pixbuf = loader.get_pixbuf()
             loader.close()
+            pixbuf = loader.get_pixbuf()
 
         img_height = pixbuf.get_height()
         if img_height > self.height:
diff --git a/tryton/gui/window/view_form/view/form_gtk/many2many.py b/tryton/gui/window/view_form/view/form_gtk/many2many.py
index fc5915f..c429d46 100644
--- a/tryton/gui/window/view_form/view/form_gtk/many2many.py
+++ b/tryton/gui/window/view_form/view/form_gtk/many2many.py
@@ -37,6 +37,7 @@ class Many2Many(WidgetInterface):
         self.wid_text.set_property('width_chars', 13)
         self.wid_text.connect('activate', self._sig_activate)
         self.wid_text.connect('focus-out-event', self._focus_out)
+        self.focus_out = True
         hbox.pack_start(self.wid_text, expand=True, fill=True)
 
         self.but_add = gtk.Button()
@@ -120,10 +121,13 @@ class Many2Many(WidgetInterface):
             self._sig_add()
 
     def _sig_add(self, *args):
+        if not self.focus_out:
+            return
         domain = self.field.domain_get(self.record)
         context = self.field.context_get(self.record)
         value = self.wid_text.get_text()
 
+        self.focus_out = False
         try:
             if value:
                 dom = [('rec_name', 'ilike', '%' + value + '%'), domain]
@@ -133,12 +137,14 @@ class Many2Many(WidgetInterface):
                     dom , 0, CONFIG['client.limit'], None, context)
         except TrytonServerError, exception:
             common.process_exception(exception)
+            self.focus_out = True
             return False
 
         def callback(ids):
             res_id = None
             if ids:
                 res_id = ids[0]
+            self.focus_out = True
             self.screen.load(ids, modified=True)
             self.screen.display(res_id=res_id)
             if self.screen.current_view:
diff --git a/tryton/gui/window/view_form/view/form_gtk/one2many.py b/tryton/gui/window/view_form/view/form_gtk/one2many.py
index 11863cd..21ae30c 100644
--- a/tryton/gui/window/view_form/view/form_gtk/one2many.py
+++ b/tryton/gui/window/view_form/view/form_gtk/one2many.py
@@ -230,7 +230,7 @@ class One2Many(WidgetInterface):
                 self.screen.display()
                 return
         ctx = {}
-        ctx.update(self.record.expr_eval(self.attrs.get('context', {})))
+        ctx.update(self.field.context_get(self.record))
         sequence = None
         if self.screen.current_view.view_type == 'tree':
             sequence = self.screen.current_view.widget_tree.sequence
diff --git a/tryton/gui/window/view_form/view/form_gtk/parser.py b/tryton/gui/window/view_form/view/form_gtk/parser.py
index 19bef62..36fa795 100644
--- a/tryton/gui/window/view_form/view/form_gtk/parser.py
+++ b/tryton/gui/window/view_form/view/form_gtk/parser.py
@@ -732,7 +732,7 @@ class ParserForm(ParserInterface):
                 scrolledwindow.set_size_request(-1, 80)
                 scrolledwindow.add(textview)
                 textview.set_accepts_tab(False)
-                return scrolledwindow, gtk.FILL | gtk.EXPAND
+                return textview, gtk.FILL | gtk.EXPAND
             else:
                 return None, False
 
diff --git a/tryton/gui/window/view_form/view/form_gtk/reference.py b/tryton/gui/window/view_form/view/form_gtk/reference.py
index adb7d11..b84f82c 100644
--- a/tryton/gui/window/view_form/view/form_gtk/reference.py
+++ b/tryton/gui/window/view_form/view/form_gtk/reference.py
@@ -212,7 +212,8 @@ class Reference(WidgetInterface):
                 WinSearch(model, callback, sel_multi=False, ids=ids,
                         context=context, domain=domain)
                 return
-        self.field.set_client(self.record, ('', (name, name)))
+        else:
+            self.field.set_client(self.record, ('', (name, name)))
         self.focus_out = True
         self.changed = True
         self.display(self.record, self.field)
diff --git a/tryton/gui/window/view_form/view/list_gtk/parser.py b/tryton/gui/window/view_form/view/list_gtk/parser.py
index ac66ca0..080ba54 100644
--- a/tryton/gui/window/view_form/view/list_gtk/parser.py
+++ b/tryton/gui/window/view_form/view/list_gtk/parser.py
@@ -90,6 +90,7 @@ class ParserTree(ParserInterface):
         treeview.sequence = attrs.get('sequence', False)
         treeview.colors = attrs.get('colors', '"black"')
         treeview.keyword_open = attrs.get('keyword_open', False)
+        treeview.connect('focus', self.set_selection)
         self.treeview = treeview
         treeview.set_property('rules-hint', True)
         if not self.title:
@@ -258,6 +259,12 @@ class ParserTree(ParserInterface):
         treeview.set_fixed_height_mode(True)
         return treeview, dict_widget, button_list, on_write, [], None
 
+    def set_selection(self, treeview, direction):
+        selection = treeview.get_selection()
+        if len(treeview.get_model()):
+            selection.select_path(0)
+        return False
+
 
 class Char(object):
 
@@ -446,7 +453,7 @@ class Datetime(Date):
         try:
             date = datetime.datetime(*time.strptime(text,
                 self.display_format)[:6])
-            date = common.timezoned_date(date)
+            date = common.untimezoned_date(date)
             date = common.datetime_strftime(date, self.server_format)
         except ValueError:
             date = False
@@ -501,7 +508,9 @@ class FloatTime(Char):
 
     def value_from_text(self, record, text, callback=None):
         field = record[self.field_name]
-        field.set_client(record, common.text_to_float_time(text, self.conv))
+        digits = record.expr_eval(field.attrs.get('digits', (16, 2)))
+        field.set_client(record,
+            round(common.text_to_float_time(text, self.conv), digits[1]))
         if callback:
             callback()
 
diff --git a/tryton/gui/window/win_export.py b/tryton/gui/window/win_export.py
index 94499aa..79dae47 100644
--- a/tryton/gui/window/win_export.py
+++ b/tryton/gui/window/win_export.py
@@ -327,7 +327,7 @@ class WinExport(object):
         try:
             new_id = rpc.execute(*args)
         except TrytonServerError, exception:
-            new_ids = common.process_exception(exception, self.dialog, *args)
+            new_id = common.process_exception(exception, *args)
             if not new_id:
                 return
         self.predef_model.append((
@@ -348,7 +348,7 @@ class WinExport(object):
         try:
             rpc.execute(*args)
         except TrytonServerError, exception:
-            if not common.process_exception(exception, self.dialog, *args):
+            if not common.process_exception(exception, *args):
                 return
         for i in range(len(self.predef_model)):
             if self.predef_model[i][0] == export_id:
@@ -368,9 +368,12 @@ class WinExport(object):
                                 (prefix + parent):
                             self.on_row_expanded(self.view1, iter,
                                     self.model1.get_path(iter))
+                            iter = self.model1.iter_children(iter)
+                            prefix = parent + '/'
                             break
-                        iter = self.model1.iter_next(iter)
-                    prefix = parent + '/'
+                        else:
+                            iter = self.model1.iter_next(iter)
+
             if field not in self.fields_data:
                 continue
             self.model2.append((self.fields_data[field]['string'], field))
@@ -444,6 +447,6 @@ class WinExport(object):
             datas = rpc.execute('model', model,
                     'export_data', ids, fields, ctx)
         except TrytonServerError, exception:
-            common.process_exception(exception, self.dialog)
+            common.process_exception(exception)
             return []
         return datas
diff --git a/tryton/gui/window/win_form.py b/tryton/gui/window/win_form.py
index c81cf0c..da34c31 100644
--- a/tryton/gui/window/win_form.py
+++ b/tryton/gui/window/win_form.py
@@ -1,6 +1,6 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
-from tryton.common import TRYTON_ICON
+from tryton.common import TRYTON_ICON, COLOR_SCHEMES
 import tryton.common as common
 from tryton.config import CONFIG
 import gtk
@@ -8,6 +8,7 @@ import pango
 import gettext
 from tryton.exceptions import TrytonServerError
 from tryton.gui.window.nomodal import NoModal
+import tryton.rpc as rpc
 
 _ = gettext.gettext
 
@@ -56,7 +57,7 @@ class WinForm(NoModal):
         if new and many:
             self.but_ok.add_accelerator('clicked',
                 self.accel_group, gtk.keysyms.Return,
-                gtk.gdk.CONTROL_MASK|gtk.gdk.SHIFT_MASK,
+                gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK,
                 gtk.ACCEL_VISIBLE)
 
             self.but_new = self.win.add_button(gtk.STOCK_NEW,
@@ -130,7 +131,8 @@ class WinForm(NoModal):
             tooltips.set_tip(self.but_del, _('Delete selected record'))
             self.but_del.connect('clicked', self._sig_remove)
             img_del = gtk.Image()
-            img_del.set_from_stock('tryton-delete', gtk.ICON_SIZE_SMALL_TOOLBAR)
+            img_del.set_from_stock('tryton-delete',
+                gtk.ICON_SIZE_SMALL_TOOLBAR)
             img_del.set_alignment(0.5, 0.5)
             self.but_del.add(img_del)
             self.but_del.set_relief(gtk.RELIEF_NONE)
@@ -142,7 +144,8 @@ class WinForm(NoModal):
             tooltips.set_tip(but_pre, _('Previous'))
             but_pre.connect('clicked', self._sig_previous)
             img_pre = gtk.Image()
-            img_pre.set_from_stock('tryton-go-previous', gtk.ICON_SIZE_SMALL_TOOLBAR)
+            img_pre.set_from_stock('tryton-go-previous',
+                gtk.ICON_SIZE_SMALL_TOOLBAR)
             img_pre.set_alignment(0.5, 0.5)
             but_pre.add(img_pre)
             but_pre.set_relief(gtk.RELIEF_NONE)
@@ -155,7 +158,8 @@ class WinForm(NoModal):
             tooltips.set_tip(but_next, _('Next'))
             but_next.connect('clicked', self._sig_next)
             img_next = gtk.Image()
-            img_next.set_from_stock('tryton-go-next', gtk.ICON_SIZE_SMALL_TOOLBAR)
+            img_next.set_from_stock('tryton-go-next',
+                gtk.ICON_SIZE_SMALL_TOOLBAR)
             img_next.set_alignment(0.5, 0.5)
             but_next.add(img_next)
             but_next.set_relief(gtk.RELIEF_NONE)
@@ -167,7 +171,8 @@ class WinForm(NoModal):
             tooltips.set_tip(but_switch, _('Switch'))
             but_switch.connect('clicked', self.switch_view)
             img_switch = gtk.Image()
-            img_switch.set_from_stock('tryton-fullscreen', gtk.ICON_SIZE_SMALL_TOOLBAR)
+            img_switch.set_from_stock('tryton-fullscreen',
+                gtk.ICON_SIZE_SMALL_TOOLBAR)
             img_switch.set_alignment(0.5, 0.5)
             but_switch.add(img_switch)
             but_switch.set_relief(gtk.RELIEF_NONE)
@@ -251,6 +256,7 @@ class WinForm(NoModal):
         from tryton.gui.window.win_search import WinSearch
         domain = []
         context = rpc.CONTEXT.copy()
+        model_name = self.screen.model_name
 
         try:
             if self.wid_text.get_text():
@@ -258,7 +264,7 @@ class WinForm(NoModal):
                         '%' + self.wid_text.get_text() + '%'), domain]
             else:
                 dom = domain
-            ids = rpc.execute('model', self.attrs['relation'], 'search', dom,
+            ids = rpc.execute('model', model_name, 'search', dom,
                     0, CONFIG['client.limit'], None, context)
         except TrytonServerError, exception:
             common.process_exception(exception)
@@ -275,9 +281,8 @@ class WinForm(NoModal):
             self.wid_text.set_text('')
 
         if len(ids) != 1:
-            WinSearch(self.attrs['relation'], callback, sel_multi=True,
-                ids=ids, context=context, domain=domain,
-                views_preload=self.attrs.get('views', {}))
+            WinSearch(model_name, callback, sel_multi=True,
+                ids=ids, context=context, domain=domain)
         else:
             callback(ids)
 
diff --git a/tryton/gui/window/win_import.py b/tryton/gui/window/win_import.py
index 06f6b44..b9d0f7c 100644
--- a/tryton/gui/window/win_import.py
+++ b/tryton/gui/window/win_import.py
@@ -145,9 +145,6 @@ class WinImport(object):
         self.import_csv_enc = gtk.combo_box_new_text()
         self.import_csv_enc.append_text("UTF-8")
         self.import_csv_enc.append_text("Latin1")
-        cboxent_import_csv_enc = gtk.Entry()
-        cboxent_import_csv_enc.unset_flags(gtk.CAN_FOCUS)
-        self.import_csv_enc.add(cboxent_import_csv_enc)
         table.attach(self.import_csv_enc, 1, 2, 1, 2)
 
         label_import_csv_skip = gtk.Label(_("Lines to Skip:"))
@@ -231,7 +228,7 @@ class WinImport(object):
         try:
             return rpc.execute(*args)
         except TrytonServerError, exception:
-            return common.process_exception(exception, self.dialog, *args)
+            return common.process_exception(exception, *args)
 
     def on_row_expanded(self, treeview, iter, path):
         child = self.model1.iter_children(iter)
@@ -245,8 +242,7 @@ class WinImport(object):
     def sig_autodetect(self, widget=None):
         fname = self.import_csv_file.get_filename()
         if not fname:
-            common.message(_('You must select an import file first!'),
-                    self.dialog)
+            common.message(_('You must select an import file first!'))
             return True
         csvsep = self.import_csv_sep.get_text()
         csvdel = self.import_csv_del.get_text()
@@ -258,8 +254,7 @@ class WinImport(object):
             data = csv.reader(open(fname, 'rb'), quotechar=csvdel,
                     delimiter=csvsep)
         except IOError:
-            common.warning(_('Error opening CSV file'), self.dialog,
-                    _('Error'))
+            common.warning(_('Error opening CSV file'), _('Error'))
             return True
         self.sig_unsel_all()
         word = ''
@@ -287,9 +282,8 @@ class WinImport(object):
                     name = self.fields[word][0]
                     field = word
                 else:
-                    common.warning(
-                            _('Error processing the file at field %s.') %
-                            word, self.dialog, _('Error'))
+                    common.warning(_('Error processing the file at field %s.')
+                        % word, _('Error'))
                     return True
                 num = self.model2.append()
                 self.model2.set(num, 0, name, 1, field)
@@ -356,7 +350,7 @@ class WinImport(object):
             res = rpc.execute('model', model, 'import_data', fields, datas,
                     rpc.CONTEXT)
         except TrytonServerError, exception:
-            common.process_exception(exception, self.dialog)
+            common.process_exception(exception)
             return False
         if res[0] >= 0:
             if res[0] == 1:
diff --git a/tryton/gui/window/win_search.py b/tryton/gui/window/win_search.py
index 4712a6c..8495aa7 100644
--- a/tryton/gui/window/win_search.py
+++ b/tryton/gui/window/win_search.py
@@ -85,10 +85,8 @@ class WinSearch(NoModal):
 
     def sig_activate(self, *args):
         self.view.widget_tree.emit_stop_by_name('row_activated')
-        if not self.sel_multi:
-            self.win.response(gtk.RESPONSE_OK)
-            return True
-        return False
+        self.win.response(gtk.RESPONSE_OK)
+        return True
 
     def sig_button(self, view, event):
         if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
diff --git a/tryton/gui/window/wizard.py b/tryton/gui/window/wizard.py
index a15d7b2..863787b 100644
--- a/tryton/gui/window/wizard.py
+++ b/tryton/gui/window/wizard.py
@@ -135,8 +135,8 @@ class Wizard(object):
         try:
             rpc.execute('wizard', self.action, 'delete', self.wiz_id,
                 rpc.CONTEXT)
-            #XXX to remove when company displayed in status bar
-            rpc.context_reload()
+            if self.action == 'ir.module.module.config_wizard':
+                rpc.context_reload()
         except TrytonServerError:
             pass
 
@@ -355,9 +355,13 @@ class WizardDialog(Wizard, NoModal):
             if current_form:
                 for dialog in current_form.dialogs:
                     dialog.show()
-        if hasattr(self.page, 'screen'):
-            self.page.screen.reload(written=True)
         super(WizardDialog, self).destroy()
+        if self.page.dialogs:
+            dialog = self.page.dialogs[-1]
+        else:
+            dialog = self.page
+        if hasattr(dialog, 'screen'):
+            dialog.screen.reload(written=True)
 
     def end(self):
         super(WizardDialog, self).end()
diff --git a/tryton/jsonrpc.py b/tryton/jsonrpc.py
index a38f4a9..a4c1c17 100644
--- a/tryton/jsonrpc.py
+++ b/tryton/jsonrpc.py
@@ -29,7 +29,7 @@ class ResponseError(xmlrpclib.ResponseError):
 class Fault(xmlrpclib.Fault):
 
     def __init__(self, faultCode, faultString='', **extra):
-        super(Fault, self).__init__(faultCode, str(faultString), **extra)
+        super(Fault, self).__init__(faultCode, faultString, **extra)
         self.args = faultString
 
 
diff --git a/tryton/pyson.py b/tryton/pyson.py
index 9c7dde7..1656eaf 100644
--- a/tryton/pyson.py
+++ b/tryton/pyson.py
@@ -6,6 +6,7 @@ try:
 except ImportError:
     import json
 import datetime
+from dateutil.relativedelta import relativedelta
 from functools import reduce
 
 
@@ -453,23 +454,14 @@ class Date(PYSON):
 
     @staticmethod
     def eval(dct, context):
-        date = datetime.date.today()
-        replace = {}
-        for i, j in (('y', 'year'), ('M', 'month'), ('d', 'day')):
-            if dct[i] is not None:
-                replace[j] = dct[i]
-        date = date.replace(**replace)
-        if dct['dy']:
-            year = date.year + dct['dy']
-            date = date.replace(year=year)
-        if dct['dM']:
-            month = date.month + dct['dM']
-            year = date.year + month // 12
-            month = month % 12
-            date = date.replace(year=year, month=month)
-        if dct['dd']:
-            date += datetime.timedelta(days=dct['dd'])
-        return date
+        return datetime.date.today() + relativedelta(
+            year=dct['y'],
+            month=dct['M'],
+            day=dct['d'],
+            years=dct['dy'],
+            months=dct['dM'],
+            days=dct['dd'],
+            )
 
 
 class DateTime(Date):
@@ -517,22 +509,23 @@ class DateTime(Date):
 
     @staticmethod
     def eval(dct, context):
-        date = Date.eval(dct, context)
-        datetime_ = datetime.datetime.combine(date,
-                datetime.datetime.now().time())
-        replace = {}
-        for i, j in (('h', 'hour'), ('m', 'minute'), ('s', 'second'),
-                ('ms', 'microsecond')):
-            if dct[i] is not None:
-                replace[j] = dct[i]
-        datetime_ = datetime_.replace(**replace)
-        delta = {}
-        for i, j in (('dh', 'hours'), ('dm', 'minutes'), ('ds', 'seconds'),
-                ('dms', 'microseconds')):
-            if dct[i]:
-                delta[j] = dct[i]
-        datetime_ += datetime.timedelta(**delta)
-        return datetime_
+        return datetime.datetime.now() + relativedelta(
+            year=dct['y'],
+            month=dct['M'],
+            day=dct['d'],
+            hour=dct['h'],
+            minute=dct['m'],
+            second=dct['s'],
+            microsecond=dct['ms'],
+            years=dct['dy'],
+            months=dct['dM'],
+            days=dct['dd'],
+            hours=dct['dh'],
+            minutes=dct['dm'],
+            seconds=dct['ds'],
+            microseconds=dct['dms'],
+            )
+
 
 CONTEXT = {
     'Eval': Eval,
diff --git a/tryton/version.py b/tryton/version.py
index 77eeb38..3b71650 100644
--- a/tryton/version.py
+++ b/tryton/version.py
@@ -1,7 +1,7 @@
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 PACKAGE = "tryton"
-VERSION = "2.2.1"
+VERSION = "2.2.2"
 LICENSE = "GPL-3"
 WEBSITE = "http://www.tryton.org/"
 
-- 
tryton-client



More information about the tryton-debian-vcs mailing list