[tryton-debian-vcs] tryton-server branch debian updated. debian/3.4.3-1-2-g8334ea6

Mathias Behrle tryton-debian-vcs at alioth.debian.org
Thu Apr 23 16:08:14 UTC 2015


The following commit has been merged in the debian branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/tryton-server.git;a=commitdiff;h=debian/3.4.3-1-2-g8334ea6

commit 8334ea68df8ba9f7efb937cfaeda8c7c95086e7f
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Thu Apr 23 17:00:11 2015 +0200

    Merging upstream version 3.6.0.

diff --git a/CHANGELOG b/CHANGELOG
index 8aa419c..75a39d1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,11 +1,39 @@
-Version 3.4.3 - 2015-03-30
-* Bug fixes (see mercurial logs for details)
-
-Version 3.4.2 - 2015-02-16
-* Bug fixes (see mercurial logs for details)
-
-Version 3.4.1 - 2014-12-03
+Version 3.6.0 - 2015-04-20
 * Bug fixes (see mercurial logs for details)
+* Use bytes and bytearray for Binary
+* Add button_change
+* Add support for PyPy
+* Add support for psycopg2cffi
+* Add noeval on PYSONDecoder
+* Add __repr__ to PYSON
+* Remove safe_eval
+* Add ModelView.view_attributes
+* Add pyson attribute on data field tag
+* Changed into JSON:
+    - record rule domain
+    - trigger condition
+    - 'states', 'domain', 'spell' and 'colors' view attributes
+    - view domain
+    - 'email', 'domain', 'context', 'order' and 'search_value' action fields
+* Add product attribute on form view for One2Many
+* Remove float_time widget
+* Add TimeDelta field
+* search_global yields record instead of id
+* Add ModelTestCase
+* Add test for missing default model access
+* Report API refactorization
+* Add test for access rights of menu and actions
+* Allow to use the dotted notation for order parameters
+* Use action_id to find report to use
+* Allow custom StateView without Model
+* Remove Pool.object_name_list
+* Add translated descriptor for Dict field
+* Clean private context keyword in RPC
+* Add cache section in configuration
+* Use dualmethod on ModelStorage.save
+* New API for on_change: instance changes
+* Add restore_history_before on ModelSQL
+* Remove img_{width,height} form attributes
 
 Version 3.4.0 - 2014-10-20
 * Bug fixes (see mercurial logs for details)
diff --git a/MANIFEST.in b/MANIFEST.in
index b5f249f..30f129b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,7 +2,6 @@ include LICENSE
 include COPYRIGHT
 include README
 include INSTALL
-include TODO
 include CHANGELOG
 include doc/*
 recursive-include doc *.rst
diff --git a/PKG-INFO b/PKG-INFO
index b0a810f..a676571 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 1.1
 Name: trytond
-Version: 3.4.3
+Version: 3.6.0
 Summary: Tryton server
 Home-page: http://www.tryton.org/
 Author: Tryton
 Author-email: issue_tracker at tryton.org
 License: GPL-3
-Download-URL: http://downloads.tryton.org/3.4/
+Download-URL: http://downloads.tryton.org/3.6/
 Description: trytond
         =======
         
@@ -116,4 +116,6 @@ Classifier: Natural Language :: Slovenian
 Classifier: Natural Language :: Spanish
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
diff --git a/TODO b/TODO
deleted file mode 100644
index a6c6fb2..0000000
--- a/TODO
+++ /dev/null
@@ -1,7 +0,0 @@
-- Allow to pickle Model instance
-- Add check on action groups when executing wizard
-- Allow to create fields from the client interface
-- Improve encoding of default properties
-- Add check for index and constraint name lenght (limit 31)
-- Add order attribute on page tag
-- Find the on_change method for inherited fields
diff --git a/bin/trytond b/bin/trytond
index 6f03fe7..89d7b31 100755
--- a/bin/trytond
+++ b/bin/trytond
@@ -10,8 +10,8 @@ DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
 if os.path.isdir(DIR):
     sys.path.insert(0, os.path.dirname(DIR))
 
-import trytond
-from trytond.version import VERSION
+from trytond import __version__
+from trytond import server
 
 
 def parse_commandline():
@@ -20,7 +20,7 @@ def parse_commandline():
     parser = argparse.ArgumentParser(prog='trytond')
 
     parser.add_argument('--version', action='version',
-        version='%(prog)s ' + VERSION)
+        version='%(prog)s ' + __version__)
     parser.add_argument("-c", "--config", dest="configfile", metavar='FILE',
         default=os.environ.get('TRYTOND_CONFIG'), help="specify config file")
     parser.add_argument('--dev', dest='dev', action='store_true',
@@ -65,7 +65,7 @@ if '--profile' in sys.argv:
 
     options = parse_commandline()
     statfile = tempfile.mkstemp(".stat", "trytond-")[1]
-    profile.run('trytond.server.TrytonServer(options).run()', statfile)
+    profile.run('server.TrytonServer(options).run()', statfile)
     s = pstats.Stats(statfile)
     s.sort_stats('cumulative').print_stats()
     s.sort_stats('call').print_stats()
@@ -77,4 +77,4 @@ if '--profile' in sys.argv:
     os.remove(statfile)
 else:
     options = parse_commandline()
-    trytond.server.TrytonServer(options).run()
+    server.TrytonServer(options).run()
diff --git a/doc/conf.py b/doc/conf.py
index 18e62c7..14a3199 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 #
 # Tryton documentation build configuration file, created by
 # sphinx-quickstart on Tue Mar 23 13:25:32 2010.
@@ -18,7 +18,7 @@
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.append(os.path.abspath('.'))
+# sys.path.append(os.path.abspath('.'))
 
 # -- General configuration ----------------------------------------------------
 
@@ -33,7 +33,7 @@ templates_path = ['_templates']
 source_suffix = '.rst'
 
 # The encoding of source files.
-#source_encoding = 'utf-8'
+# source_encoding = 'utf-8'
 
 # The master toctree document.
 master_doc = 'index'
@@ -48,46 +48,46 @@ copyright = (u'2008-2011, Bertrand Chenal, Cédric Krier, Ian Wilson, '
 # built documents.
 #
 # The short X.Y version.
-version = '3.4'
+version = '3.6'
 # The full version, including alpha/beta/rc tags.
-release = '3.4'
+release = '3.6'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-#language = None
+# language = None
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of documents that shouldn't be included in the build.
-#unused_docs = []
+# unused_docs = []
 
 # List of directories, relative to source directory, that shouldn't be searched
 # for source files.
 exclude_trees = ['_build']
 
 # The reST default role (used for this markup: `text`) to use for all documents
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = 'sphinx'
 
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 
 # -- Options for HTML output --------------------------------------------------
@@ -99,26 +99,26 @@ html_theme = 'default'
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
-#html_theme_options = {}
+# html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
@@ -127,38 +127,38 @@ html_static_path = []
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_use_modindex = True
+# html_use_modindex = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = ''
+# html_file_suffix = ''
 
 # Output file base name for HTML help builder.
 htmlhelp_basename = 'trytonddoc'
@@ -167,10 +167,10 @@ htmlhelp_basename = 'trytonddoc'
 # -- Options for LaTeX output -------------------------------------------------
 
 # The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
+# latex_paper_size = 'letter'
 
 # The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
+# latex_font_size = '10pt'
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title, author, documentclass [howto/manual])
@@ -181,17 +181,17 @@ latex_documents = [
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
 
 # Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
+# latex_preamble = ''
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_use_modindex = True
+# latex_use_modindex = True
diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst
index 82b4b1e..18aa231 100644
--- a/doc/ref/models/fields.rst
+++ b/doc/ref/models/fields.rst
@@ -80,7 +80,7 @@ method signature is::
 
     on_change_<field name>()
 
-This method must return a dictionary with the values of fields to be updated.
+This method must change the value of the fields to be updated.
 
 .. note::
 
@@ -269,7 +269,7 @@ A single line string field.
     method ``autocomplete_<field name>`` of the model when the user changes one
     of those field value. The method signature is::
 
-        autocomplete_<field name>(values)
+        autocomplete_<field name>()
 
     This method must return a list of string that will populate the
     ComboboxEntry in the client.
@@ -336,6 +336,9 @@ DateTime
 .. class:: DateTime(string[, format, \**options])
 
 A date and time, represented in Python by a ``datetime.datetime`` instance.
+It is stored in `UTC`_ while displayed in the user timezone.
+
+.. _`UTC`: https://en.wikipedia.org/wiki/Coordinated_Universal_Time
 
 .. attribute:: DateTime.format
 
@@ -361,12 +364,26 @@ A time, represented in Python by a ``datetime.time`` instance.
 
     Same as :attr:`DateTime.format`
 
+TimeDelta
+---------
+
+.. class:: TimeDelta(string[, converter[, \**options]])
+
+An interval, represented in Python by a ``datetime.timedelta`` instance.
+
+.. attribute:: TimeDelta.converter
+
+    The name of the context key containing the time converter.
+    A time converter is a dictionary with the keys: ``s`` (second), ``m``
+    (minute), ``h`` (hour), ``d`` (day), ``w`` (week), ``M`` (month), ``Y``
+    (year) and the value in second.
+
 Binary
 ------
 
 .. class:: Binary(string[, \**options])
 
-A binary field. It will be represented in Python by a ``str`` instance.
+A binary field. It will be represented in Python by a ``bytes`` instance.
 
 :class:`Binary` has one extra optional argument:
 
@@ -770,3 +787,11 @@ A dictionary field with predefined keys.
 
     The name of the :class:`DictSchemaMixin` model that stores the definition
     of keys.
+
+Instance methods:
+
+.. method:: Dict.translated([name[, type_]])
+
+    Returns a descriptor for the translated `values` or `keys` of the field
+    following `type_`. The descriptor must be used on the same class as the
+    field. Default `type_` is `values`.
diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst
index 110636e..26722d4 100644
--- a/doc/ref/models/models.rst
+++ b/doc/ref/models/models.rst
@@ -129,6 +129,15 @@ Static methods:
     Same as :meth:`ModelView.button` but return the action id of the XML `id`
     action.
 
+.. staticmethod:: ModelView.button_change([\*fields])
+
+    Same as :meth:`ModelView.button` but for button that change values of the
+    fields on client side (similar to :ref:`on_change
+    <ref-models-fields-on_change>`).
+
+    .. warning::
+        Only on instance methods.
+
 Class methods:
 
 .. classmethod:: ModelView.fields_view_get([view_id[, view_type[, toolbar]]])
@@ -137,23 +146,15 @@ Class methods:
 
         {
             'model': model name,
+            'type': view type,
+            'view_id': view id,
             'arch': XML description,
             'fields': {
                 field name: {
                     ...
                 },
             },
-            'toolbar': {
-                'print': [
-                    ...
-                ],
-                'action': [
-                    ...
-                ],
-                'relate': [
-                    ...
-                ],
-            },
+            'field_childs': field for tree,
         }
 
 .. classmethod:: ModelView.view_toolbar_get()
@@ -167,6 +168,12 @@ Class methods:
 
     Returns the window title used by the client for the specific view type.
 
+.. classmethod:: ModelView.view_attributes()
+
+    Returns a list of XPath, attribute and value.
+    Each element from the XPath will get the attribute set with the JSON
+    encoded value.
+
 ============
 ModelStorage
 ============
@@ -230,7 +237,7 @@ Static methods:
 
     Return the default value for :attr:`create_date`.
 
-CLass methods:
+Class methods:
 
 .. classmethod:: ModelStorage.create(vlist)
 
@@ -297,7 +304,7 @@ CLass methods:
 
 .. classmethod:: ModelStorage.search_global(cls, text)
 
-    Yield tuples (id, rec_name, icon) for records matching text.
+    Yield tuples (record, name, icon) for records matching text.
     It is used for the global search.
 
 .. classmethod:: ModelStorage.browse(ids)
@@ -336,6 +343,12 @@ CLass methods:
     method must be overridden to add validation and must raise an exception if
     validation fails.
 
+Dual methods:
+
+.. classmethod:: ModelStorage.save(records)
+
+    Save the modification made on the records.
+
 Instance methods:
 
 .. method:: ModelStorage.get_rec_name(name)
@@ -343,10 +356,6 @@ Instance methods:
     Getter for the :class:`trytond.model.fields.Function` field
     :attr:`rec_name`.
 
-.. method:: ModelStorage.save()
-
-    Save the modification made on the record instance.
-
 ========
 ModelSQL
 ========
@@ -373,6 +382,10 @@ Class attributes are:
     second is the sort ordering as `ASC` for ascending or `DESC` for
     descending.
 
+    In case the field used for the first element is a :class:`fields.Many2One`,
+    it is also possible to use the dotted notation to sort on a specific field
+    from the target record.
+
 .. attribute:: ModelSQL._order_name
 
     The name of the field (or an SQL statement) on which the records must be
@@ -425,6 +438,15 @@ Class methods:
         No access rights are verified and the records are not validated.
     ..
 
+.. classmethod:: ModelSQL.restore_history_before(ids, datetime)
+
+    Restore the record ids from history before the specified date time.
+    Restoring a record will still generate an entry in the history table.
+
+    .. warning::
+        No access rights are verified and the records are not validated.
+    ..
+
 .. classmethod:: ModelStorage.search(domain[, offset[, limit[, order[, count[, query]]]]])
 
     Return a list of records that match the :ref:`domain <topics-domain>` or
diff --git a/doc/ref/pool.rst b/doc/ref/pool.rst
index 911a6fe..0ca5638 100644
--- a/doc/ref/pool.rst
+++ b/doc/ref/pool.rst
@@ -35,10 +35,6 @@ Instance methods:
 
     Return the named instance of type from the pool.
 
-.. method:: Pool.object_name_list([type])
-
-    Return the list of instances names.
-
 .. method:: Pool.iterobject([type])
 
     Return an interator over instances names.
diff --git a/doc/ref/pyson.rst b/doc/ref/pyson.rst
index fd1640b..0f3b3a2 100644
--- a/doc/ref/pyson.rst
+++ b/doc/ref/pyson.rst
@@ -50,15 +50,15 @@ Instance method:
     Returns a string representation of a given PYSON statement.
     ``object`` contains a PYSON statement.
 
-.. class:: PYSONDecoder()
+.. class:: PYSONDecoder([context[, noeval]])
 
-Decoder for string into PYSON statement representation.
+Decoder for string into the evaluated or not PYSON statement.
 
 Instance method:
 
     .. method:: PYSONDecoder.decode(object)
 
-    Returns a PYSON statement representation of a given string.
+    Returns a PYSON statement evaluated or not of a given string.
     ``object`` contains a string.
 
 Statements
diff --git a/doc/ref/wizard.rst b/doc/ref/wizard.rst
index 62267e4..a8944ee 100644
--- a/doc/ref/wizard.rst
+++ b/doc/ref/wizard.rst
@@ -99,6 +99,8 @@ StateView
     A :class:`StateView` is a state that will display a form in the client.
     The form is defined by the :class:`~trytond.model.ModelView` with the name
     `model_name`, the `XML` id in `view` and the `buttons`.
+    The default value of the view can be set with a method on wizard having the
+    same name as the state but starting with `default_`.
 
 Instance attributes are:
 
@@ -116,11 +118,14 @@ Instance attributes are:
 
 Instance methods are:
 
-.. method:: StateView.get_view()
+.. method:: StateView.get_view(wizard, state_name)
 
     Returns the view definition like
     :meth:`~trytond.model.ModelView.fields_view_get`.
 
+    * wizard is a :class:`Wizard` instance
+    * state_name is the name of the :class:`StateView` instance
+
 .. method:: StateView.get_defaults(wizard, state_name, fields)
 
     Return default values for the fields.
diff --git a/doc/topics/access_rights.rst b/doc/topics/access_rights.rst
index 96c0d22..41ce7c1 100644
--- a/doc/topics/access_rights.rst
+++ b/doc/topics/access_rights.rst
@@ -7,7 +7,7 @@ Access Rights
 There are 3 levels of access rights: model, field, button and record.
 Every access right is based on the groups of the user.
 The model and field access rights are checked for every RPC call for which
-:attrs:`RPC.check_access` is set. The others are always enforced.
+:attr:`trytond.rpc.RPC.check_access` is set. The others are always enforced.
 
 Model Access
 ============
diff --git a/doc/topics/configuration.rst b/doc/topics/configuration.rst
index 3ca33b1..662450c 100644
--- a/doc/topics/configuration.rst
+++ b/doc/topics/configuration.rst
@@ -4,7 +4,7 @@
 Configuration file for Tryton
 =============================
 
-The configuration file control some aspects of the behavior of Tryton.
+The configuration file controls some aspects of the behavior of Tryton.
 The file uses a simple ini-file format. It consists of sections, led by a
 `[section]` header and followed by `name = value` entries:
 
@@ -37,9 +37,10 @@ Defines the behavior of the JSON-RPC_ network interface.
 listen
 ~~~~~~
 
-Defines a comma separated list of couple of host (or IP address) and port numer
-separeted by a colon to listen on.
-The default value is `localhost:8000`.
+Defines a comma separated list of couples of host (or IP address) and port
+number separated by a colon to listen on.
+
+Default `localhost:8000`
 
 hostname
 ~~~~~~~~
@@ -49,7 +50,9 @@ Defines the hostname for this network interface.
 data
 ~~~~
 
-Defines the root path to retrieve data for `GET` request.
+Defines the root path to retrieve data for `GET` requests.
+
+Default: `/var/www/localhost/tryton`
 
 xmlrpc
 ------
@@ -59,7 +62,7 @@ Defines the behavior of the XML-RPC_ network interface.
 listen
 ~~~~~~
 
-Same as for `jsonrpc` except it have no default value.
+Same as for `jsonrpc` except it has no default value.
 
 webdav
 ------
@@ -69,12 +72,12 @@ Define the behavior of the WebDAV_ network interface.
 listen
 ~~~~~~
 
-Same as for `jsonrpc` except it have no default value.
+Same as for `jsonrpc` except it has no default value.
 
 database
 --------
 
-Defines how database is managed.
+Defines how the database is managed.
 
 uri
 ~~~
@@ -84,7 +87,9 @@ The typical form is:
 
     database://username:password@host:port/
 
-The default available databases are:
+Default: `sqlite://`
+
+The available databases are:
 
 PostgreSQL
 **********
@@ -107,30 +112,68 @@ Same as for PostgreSQL.
 path
 ~~~~
 
-The directory where Tryton should store files and so the user running `trytond`
+The directory where Tryton stores files and so the user running `trytond`
 must have write access on this directory.
-The default value is `/var/lib/trytond/`.
+
+Default: `/var/lib/trytond/`
 
 list
 ~~~~
 
-A boolean value (default: `True`) to list available databases.
+A boolean value to list available databases.
+
+Default: `True`
 
 retry
 ~~~~~
 
-The number of retries when a database operation error occurs during a request.
+The number of retries when a database operational error occurs during a request.
+
+Default: `5`
 
 language
 ~~~~~~~~
 
-The main language (default: `en_US`) of the database that will be stored in the
-main table for translatable fields.
+The main language of the database that will be used for storage in the main
+table for translations.
+
+Default: `en_US`
+
+cache
+-----
+
+Defines size of various cache.
+
+model
+~~~~~
+
+The number of different model kept in the cache per transaction.
+
+Default: `200`
+
+record
+~~~~~~
+
+The number of record loaded kept in the cache of the list.
+It can be changed locally using the `_record_cache_size` key in
+:attr:`Transaction.context`.
+
+Default: `2000`
+
+field
+~~~~~
+
+The number of field to load with an `eager` :attr:`Field.loading`.
+
+Default: `100`
 
 ssl
 ---
 
-Activates the SSL_ on all network protocol.
+Activates SSL_ on all network protocols.
+
+.. note:: SSL_ is activated by defining privatekey.
+        Please refer to SSL-CERT_ on how to use private keys and certficates.
 
 privatekey
 ~~~~~~~~~~
@@ -156,12 +199,12 @@ The available protocols are:
     - `smtp+tls`: SMTP with STARTTLS
     - `smtps`: SMTP with SSL
 
-The default value is: `smtp://localhost:25`
+Default: `smtp://localhost:25`
 
 from
 ~~~~
 
-Defines the default `From` address when Tryton send emails.
+Defines the default `From` address for emails sent by Tryton.
 
 session
 -------
@@ -169,14 +212,16 @@ session
 timeout
 ~~~~~~~
 
-The time in second before a session expires.
+The time in seconds until a session expires.
+
+Default: `600`
 
 super_pwd
 ~~~~~~~~~
 
-Theserver password uses to authenticate database management from the client.
-It is encrypted using using the Unix `crypt(3)` routine.
-Such password can be generated using this command line::
+The server password used to authenticate from the client for database
+management tasks. It is encrypted using using the Unix `crypt(3)` routine.
+A password can be generated using this command line::
 
     python -c 'import getpass,crypt,random,string; print crypt.crypt(getpass.getpass(), "".join(random.sample(string.ascii_letters + string.digits, 8)))'
 
@@ -188,10 +233,14 @@ unoconv
 
 The parameters for `unoconv`.
 
+Default: `pipe,name=trytond;urp;StarOffice.ComponentContext`
+
+
 .. _JSON-RPC: http://en.wikipedia.org/wiki/JSON-RPC
 .. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC
 .. _WebDAV: http://en.wikipedia.org/wiki/WebDAV
 .. _RFC-3986: http://tools.ietf.org/html/rfc3986
 .. _SMTP-URL: http://tools.ietf.org/html/draft-earhart-url-smtp-00
 .. _SSL: http://en.wikipedia.org/wiki/Secure_Sockets_Layer
+.. _SSL-CERT: https://docs.python.org/library/ssl.html#ssl.wrap_socket
 .. _STARTTLS: http://en.wikipedia.org/wiki/STARTTLS
diff --git a/doc/topics/install.rst b/doc/topics/install.rst
index c9f4f1e..889e360 100644
--- a/doc/topics/install.rst
+++ b/doc/topics/install.rst
@@ -15,6 +15,8 @@ Prerequisites
     * polib (https://bitbucket.org/izi/polib/wiki/Home)
     * python-sql 0.2 or later (http://code.google.com/p/python-sql/)
     * Optional: psycopg 2 or later (http://www.initd.org/)
+    * Optional: psycopg2cffi 2.5.0 or later
+      (http://github.com/chtd/psycopg2cffi)
     * Optional: MySQL-python (http://sourceforge.net/projects/mysql-python/)
     * Optional: pywebdav 0.9.8 or later (http://code.google.com/p/pywebdav/)
     * Optional: pydot (http://code.google.com/p/pydot/)
diff --git a/doc/topics/logs.rst b/doc/topics/logs.rst
index ccd4e6d..ea90b17 100644
--- a/doc/topics/logs.rst
+++ b/doc/topics/logs.rst
@@ -4,7 +4,9 @@
 Logging configuration
 =====================
 
-Without any configuration, trytond write INFO messages to standard output.
+Without any configuration, trytond writes ERROR messages to standard output.
+With the verbose flag set, it writes INFO message.
+And with the verbose and development flags set, it write DEBUG message.
 
 Logs can be configured using a `configparser-format`_ file. The filename can
 be specified using trytond ``logconf`` parameter.
diff --git a/doc/topics/models/fields_on_change.rst b/doc/topics/models/fields_on_change.rst
index e47c787..6a77f7c 100644
--- a/doc/topics/models/fields_on_change.rst
+++ b/doc/topics/models/fields_on_change.rst
@@ -10,9 +10,10 @@ method.  The method has the following name::
 
     Model.on_change_<field name>
 
-This is an instance method, an instance of ``Model`` will be created by
-using the values from the form's fields specified by the ``on_change`` list
-defined on the field.
+This is an instance method, an instance of ``Model`` will be created by using
+the values from the form's fields specified by the ``on_change`` list defined
+on the field. Any change made on the instance will be pushed back to the
+client-side record.
 
 There is also a way to define a method that must update a field whenever any
 field from a predefined list is modified. This list is defined by the
@@ -23,43 +24,5 @@ that will be called has the following name::
 
 Just like for the classic ``on_change``, an instance of ``Model`` will be
 created by using the values entered in the form's fields specified by the
-``on_change_with`` attribute.
-
-on_change & on_change_with return values
-----------------------------------------
-
-The return value of the method called will depend of the type of the call that
-occured. In case of an ``on_change`` the returned value will be a dictionary
-whose keys are field names to be modified and whose values will be the
-corresponding new value. In case of an ``on_change_with`` the returned value
-will be the new value of the field.
-
-Pay attention that the new value of a field differs according to its type.
-Simple fields require the value to be of the same type as the field.
-
-Relation fields require some more explanations:
-
-    - a ``field.One2Many`` or a ``field.Many2Many`` will accept either:
-
-        - a list of ``id`` denoting the new value that will replace all
-          previously ids.
-
-        - a dictionary composed of three keys: ``update``, ``add`` and
-          ``remove``.
-
-          The ``update`` key has as value a list of dictionary that denotes the
-          new value of the target's fields. The lines affected by the change
-          are found using the ``id`` key of the dictionary.
-
-          The ``add`` key have as value a list of tuple, the first element is
-          the index where a new line should be added, the second element is a
-          dictionary using the same convention as the dictionaries for the
-          ``update`` key.
-
-          The ``remove`` key will have as value a list of ids that will be
-          remove from the field.
-
-          Records that are not yet saved receive temporary ids (negative
-          integers) that can be used in with ``update`` or ``remove`` to
-          interact with them.
-
+``on_change_with`` attribute. The method must return the new value of the field
+to push back to the client-side record.
diff --git a/doc/topics/modules/index.rst b/doc/topics/modules/index.rst
index fa79017..f1694ed 100644
--- a/doc/topics/modules/index.rst
+++ b/doc/topics/modules/index.rst
@@ -78,8 +78,10 @@ The following snippet gives a first idea of what an xml file looks:
           <record model="res.group" id="group_party_admin">
               <field name="name">Party Administration</field>
           </record>
-          <record model="res.user" id="res.user_admin">
-              <field name="groups" eval="[('add', ref('group_party_admin'))]"/>
+          <record model="res.user-res.group"
+              id="user_admin_group_party_admin">
+              <field name="user" ref="res.user_admin"/>
+              <field name="group" ref="group_party_admin"/>
           </record>
 
           <menuitem name="Party Management" sequence="0" id="menu_party"
@@ -88,7 +90,7 @@ The following snippet gives a first idea of what an xml file looks:
           <record model="ir.ui.view" id="party_view_tree">
               <field name="model">party.party</field>
               <field name="type">tree</field>
-              <field name="arch" type="xml">
+              <field name="arch">
                   <![CDATA[
                   <tree string="Parties">
                       <field name="code"/>
@@ -137,7 +139,8 @@ Here is the list of the tags:
 
         * ``eval``: Python code to evaluate and use result as value.
 
-        * ``type``: If set to xml, it will use the CDATA content as value.
+        * ``pyson``: convert the evaluated value into :ref:`PYSON <ref-pyson>`
+          string.
 
         .. note::
             Field content is considered as a string. So for fields that require
diff --git a/doc/topics/reports/index.rst b/doc/topics/reports/index.rst
index 633820e..a63c3dc 100644
--- a/doc/topics/reports/index.rst
+++ b/doc/topics/reports/index.rst
@@ -221,8 +221,9 @@ Passing custom data to a report
 
 TODO: Examples of overriding Report.execute.
 
-In this example `Report.parse` is overridden and an employee object is set into
-context.  Now the invoice report will be able to access the employee object.
+In this example `Report.get_context` is overridden and an employee
+object is set into context.  Now the invoice report will be able to access the
+employee object.
 
 .. highlight:: python
 
@@ -234,14 +235,18 @@ context.  Now the invoice report will be able to access the employee object.
     class InvoiceReport(Report):
         __name__ = 'account.invoice'
 
-        def parse(self, report, objects, datas, localcontext):
-            employee_obj = Pool().get('company.employee')
-            employee = False
-            if Transaction().context.get('employee'):
-                employee = employee_obj.browse(Transaction().context['employee'])
-            localcontext['employee'] = employee
-            return super(InvoiceReport, self).parse(report, objects, datas,
-                     localcontext)
+        @classmethod
+        def get_context(cls, records, data):
+            pool = Pool()
+            Employee = pool.get('company.employee')
+
+            context = super(InvoiceReport, cls).get_context(records, data)
+            employee_id = Transaction().context.get('employee')
+            employee = Employee(employee_id) if employee_id else None
+            context['employee'] = employee
+
+            return context
+
     Pool.register(InvoiceReport, type_='report')
 
 
diff --git a/doc/topics/views/index.rst b/doc/topics/views/index.rst
index 0f79504..479670c 100644
--- a/doc/topics/views/index.rst
+++ b/doc/topics/views/index.rst
@@ -153,11 +153,19 @@ Each form view must start with this tag.
     * ``string``: The text that will be used as default title for the tab or
       the window.
 
-    * ``on_write``: The name of a function that will be called when the record
-      is saved.  The function must have this syntax:
+    .. _form-attributes-on_write:
+
+    * ``on_write``: The name of a method on the Model of the view that will be
+      called when a record is saved.  The method must return a list of record
+      ids that the client must reload if they are already loaded.  The function
+      must have this syntax:
 
       ``on_write(self, ids)``
 
+      .. note::
+        The method must be registered in :attr:`trytond.model.Model.__rpc__`.
+      ..
+
     * ``col``: The number of columns for the view.
 
     * ``cursor``: The name of the field that must have the cursor by default.
@@ -224,6 +232,9 @@ Display a field of the object with the value of the current record.
     * ``view_ids``: A comma separated list that specifies the view ids used to
       display the relation.
 
+    * ``product``: Only for One2Many fields, a comma separated list of target
+      field name used to create records from the cartesian product.
+
     * ``completion``: Only for Many2One fields, it is a boolean to set the
       completion of the field.
 
@@ -327,6 +338,7 @@ Display a button.
         * ``previous``: to go to the previous record
         * ``close``: to close the current tab
         * ``switch <view type>``: to switch the view to the defined type
+        * ``reload``: to reload the current tab
         * ``reload context``: to reload user context
         * ``reload menu``: to reload menu
 
@@ -475,10 +487,7 @@ Each tree view must start with this tag.
     * ``string``: The text that will be used as default title for the tab or
       the window.
 
-    * ``on_write``: The name of a function that will be called when a record is
-      saved.  The function must have this syntax:
-
-      ``on_write(self, ids)``
+    * ``on_write``: see form-attributes-on_write_.
 
     * ``editable``: If it is set to ``top`` or ``bottom``, the list becomes
       editable and the new record will be added on ``top`` or ``bottom`` of the
diff --git a/setup.py b/setup.py
index 8fee48d..72ea1c3 100644
--- a/setup.py
+++ b/setup.py
@@ -1,35 +1,46 @@
 #!/usr/bin/env python
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 setuptools import setup, find_packages
 import os
-
-PACKAGE, VERSION, LICENSE, WEBSITE = None, None, None, None
-execfile(os.path.join('trytond', 'version.py'))
+import re
+import platform
 
 
 def read(fname):
     return open(os.path.join(os.path.dirname(__file__), fname)).read()
 
-major_version, minor_version, _ = VERSION.split('.', 2)
+
+def get_version():
+    init = read(os.path.join('trytond', '__init__.py'))
+    return re.search('__version__ = "([0-9.]*)"', init).group(1)
+
+version = get_version()
+major_version, minor_version, _ = version.split('.', 2)
 major_version = int(major_version)
 minor_version = int(minor_version)
+name = 'trytond'
 
 download_url = 'http://downloads.tryton.org/%s.%s/' % (
     major_version, minor_version)
 if minor_version % 2:
-    VERSION = '%s.%s.dev0' % (major_version, minor_version)
+    version = '%s.%s.dev0' % (major_version, minor_version)
     download_url = 'hg+http://hg.tryton.org/%s#egg=%s-%s' % (
-        PACKAGE, PACKAGE, VERSION)
+        name, name, version)
+
+if platform.python_implementation() == 'PyPy':
+    pg_require = ['psycopg2cffi >= 2.5']
+else:
+    pg_require = ['psycopg2 >= 2.0']
 
-setup(name=PACKAGE,
-    version=VERSION,
+setup(name=name,
+    version=version,
     description='Tryton server',
     long_description=read('README'),
     author='Tryton',
     author_email='issue_tracker at tryton.org',
-    url=WEBSITE,
+    url='http://www.tryton.org/',
     download_url=download_url,
     keywords='business application platform ERP',
     packages=find_packages(exclude=['*.modules.*', 'modules.*', 'modules',
@@ -65,20 +76,22 @@ setup(name=PACKAGE,
         'Natural Language :: Spanish',
         'Operating System :: OS Independent',
         'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: Implementation :: CPython',
+        'Programming Language :: Python :: Implementation :: PyPy',
         'Topic :: Software Development :: Libraries :: Application Frameworks',
         ],
     platforms='any',
-    license=LICENSE,
+    license='GPL-3',
     install_requires=[
         'lxml >= 2.0',
         'relatorio >= 0.2.0',
         'Genshi',
         'python-dateutil',
         'polib',
-        'python-sql >= 0.2',
+        'python-sql >= 0.4',
         ],
     extras_require={
-        'PostgreSQL': ['psycopg2 >= 2.0'],
+        'PostgreSQL': pg_require,
         'MySQL': ['MySQL-python'],
         'WebDAV': ['PyWebDAV >= 0.9.8'],
         'unoconv': ['unoconv'],
diff --git a/trytond.egg-info/PKG-INFO b/trytond.egg-info/PKG-INFO
index b0a810f..a676571 100644
--- a/trytond.egg-info/PKG-INFO
+++ b/trytond.egg-info/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 1.1
 Name: trytond
-Version: 3.4.3
+Version: 3.6.0
 Summary: Tryton server
 Home-page: http://www.tryton.org/
 Author: Tryton
 Author-email: issue_tracker at tryton.org
 License: GPL-3
-Download-URL: http://downloads.tryton.org/3.4/
+Download-URL: http://downloads.tryton.org/3.6/
 Description: trytond
         =======
         
@@ -116,4 +116,6 @@ Classifier: Natural Language :: Slovenian
 Classifier: Natural Language :: Spanish
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
diff --git a/trytond.egg-info/SOURCES.txt b/trytond.egg-info/SOURCES.txt
index f5a045e..8f46cc3 100644
--- a/trytond.egg-info/SOURCES.txt
+++ b/trytond.egg-info/SOURCES.txt
@@ -4,7 +4,6 @@ INSTALL
 LICENSE
 MANIFEST.in
 README
-TODO
 setup.py
 bin/trytond
 doc/Makefile
@@ -54,7 +53,6 @@ trytond/server.py
 trytond/test_loader.py
 trytond/transaction.py
 trytond/url.py
-trytond/version.py
 trytond.egg-info/PKG-INFO
 trytond.egg-info/SOURCES.txt
 trytond.egg-info/dependency_links.txt
@@ -235,6 +233,7 @@ trytond/ir/view/ui_view_tree_state_list.xml
 trytond/ir/view/ui_view_tree_width_form.xml
 trytond/ir/view/ui_view_tree_width_list.xml
 trytond/model/__init__.py
+trytond/model/descriptors.py
 trytond/model/dictschema.py
 trytond/model/match.py
 trytond/model/model.py
@@ -313,6 +312,7 @@ trytond/tests/history.py
 trytond/tests/import_data.py
 trytond/tests/import_data.xml
 trytond/tests/model.py
+trytond/tests/modelview.py
 trytond/tests/mptt.py
 trytond/tests/run-tests.py
 trytond/tests/sequence.xml
@@ -320,17 +320,22 @@ trytond/tests/test.py
 trytond/tests/test_access.py
 trytond/tests/test_cache.py
 trytond/tests/test_copy.py
+trytond/tests/test_descriptors.py
 trytond/tests/test_exportdata.py
 trytond/tests/test_field_context.py
 trytond/tests/test_fields.py
 trytond/tests/test_history.py
 trytond/tests/test_importdata.py
+trytond/tests/test_ir.py
 trytond/tests/test_mixins.py
 trytond/tests/test_modelsingleton.py
 trytond/tests/test_modelsql.py
+trytond/tests/test_modelstorage.py
+trytond/tests/test_modelview.py
 trytond/tests/test_mptt.py
 trytond/tests/test_protocols.py
 trytond/tests/test_pyson.py
+trytond/tests/test_res.py
 trytond/tests/test_sequence.py
 trytond/tests/test_tools.py
 trytond/tests/test_transaction.py
@@ -338,6 +343,7 @@ trytond/tests/test_trigger.py
 trytond/tests/test_tryton.py
 trytond/tests/test_union.py
 trytond/tests/test_user.py
+trytond/tests/test_webdav.py
 trytond/tests/test_wizard.py
 trytond/tests/test_workflow.py
 trytond/tests/trigger.py
@@ -349,6 +355,7 @@ trytond/tests/workflow.xml
 trytond/tools/StringMatcher.py
 trytond/tools/__init__.py
 trytond/tools/datetime_strftime.py
+trytond/tools/decimal_.py
 trytond/tools/misc.py
 trytond/tools/singleton.py
 trytond/webdav/__init__.py
diff --git a/trytond.egg-info/requires.txt b/trytond.egg-info/requires.txt
index 25bd7b5..b1f81fe 100644
--- a/trytond.egg-info/requires.txt
+++ b/trytond.egg-info/requires.txt
@@ -3,7 +3,7 @@ relatorio >= 0.2.0
 Genshi
 python-dateutil
 polib
-python-sql >= 0.2
+python-sql >= 0.4
 
 [cdecimal]
 cdecimal
diff --git a/trytond/__init__.py b/trytond/__init__.py
index 80f6ae6..99f7805 100644
--- a/trytond/__init__.py
+++ b/trytond/__init__.py
@@ -1,12 +1,14 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 os
 import time
+from email import charset
 
-from . import server
-
-__all__ = ['server']
+__version__ = "3.6.0"
 
 os.environ['TZ'] = 'UTC'
 if hasattr(time, 'tzset'):
     time.tzset()
+
+# set email encoding for utf-8 to 'quoted-printable'
+charset.add_charset('utf-8', charset.QP, charset.QP)
diff --git a/trytond/backend/__init__.py b/trytond/backend/__init__.py
index 6b9abc4..44264ef 100644
--- a/trytond/backend/__init__.py
+++ b/trytond/backend/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 sys
 import urlparse
 
@@ -9,7 +9,7 @@ __all__ = ['name', 'get']
 
 
 def name():
-    return urlparse.urlparse(config.get('database', 'uri', '')).scheme
+    return urlparse.urlparse(config.get('database', 'uri', default='')).scheme
 
 
 def get(prop):
diff --git a/trytond/backend/database.py b/trytond/backend/database.py
index dae7908..6168a03 100644
--- a/trytond/backend/database.py
+++ b/trytond/backend/database.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 trytond.const import MODEL_CACHE_SIZE
+# 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 trytond.config import config
 
 DatabaseIntegrityError = None
 DatabaseOperationalError = None
@@ -117,7 +117,8 @@ class CursorInterface(object):
         context = Transaction().context
         keys = tuple(((key, context[key]) for key in sorted(self.cache_keys)
                 if key in context))
-        return self.cache.setdefault((user, keys), LRUDict(MODEL_CACHE_SIZE))
+        return self.cache.setdefault((user, keys),
+            LRUDict(config.getint('cache', 'model')))
 
     def execute(self, sql, params=None):
         '''
diff --git a/trytond/backend/mysql/__init__.py b/trytond/backend/mysql/__init__.py
index 7f49e23..235b423 100644
--- a/trytond/backend/mysql/__init__.py
+++ b/trytond/backend/mysql/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .database import *
 from .table import *
diff --git a/trytond/backend/mysql/database.py b/trytond/backend/mysql/database.py
index 4960cfc..9f55458 100644
--- a/trytond/backend/mysql/database.py
+++ b/trytond/backend/mysql/database.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.backend.database import DatabaseInterface, CursorInterface
 from trytond.config import config, parse_uri
 import MySQLdb
diff --git a/trytond/backend/mysql/table.py b/trytond/backend/mysql/table.py
index 3fc878f..59d68a1 100644
--- a/trytond/backend/mysql/table.py
+++ b/trytond/backend/mysql/table.py
@@ -1,9 +1,11 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.backend.table import TableHandlerInterface
 import logging
 
+logger = logging.getLogger(__name__)
+
 
 class TableHandler(TableHandlerInterface):
 
@@ -41,7 +43,7 @@ class TableHandler(TableHandlerInterface):
                 self.cursor.execute('ALTER TABLE `%s` '
                     'ADD COLUMN id BIGINT' % self.table_name)
             self._update_definitions()
-        if self.history and not '__id' in self._columns:
+        if self.history and '__id' not in self._columns:
             self.cursor.execute('ALTER TABLE `%s` '
                 'ADD COLUMN __id BIGINT AUTO_INCREMENT '
                 'NOT NULL PRIMARY KEY' % self.table_name)
@@ -56,12 +58,12 @@ class TableHandler(TableHandlerInterface):
 
     @staticmethod
     def table_rename(cursor, old_name, new_name):
-        #Rename table
+        # Rename table
         if (TableHandler.table_exist(cursor, old_name)
                 and not TableHandler.table_exist(cursor, new_name)):
             cursor.execute('ALTER TABLE `%s` RENAME TO `%s`'
                 % (old_name, new_name))
-        #Rename history table
+        # Rename history table
         old_history = old_name + '__history'
         new_history = new_name + '__history'
         if (TableHandler.table_exist(cursor, old_history)
@@ -183,11 +185,11 @@ class TableHandler(TableHandlerInterface):
                         ):
                     self.alter_type(column_name, base_type)
                 else:
-                    logging.getLogger('init').warning(
+                    logger.warning(
                         'Unable to migrate column %s on table %s '
-                        'from %s to %s.'
-                        % (column_name, self.table_name,
-                            self._columns[column_name]['typname'], base_type))
+                        'from %s to %s.',
+                        column_name, self.table_name,
+                        self._columns[column_name]['typname'], base_type)
             if (base_type == 'varchar'
                     and self._columns[column_name]['typname'] == 'varchar'):
                 # Migrate size
@@ -197,13 +199,13 @@ class TableHandler(TableHandlerInterface):
                 elif self._columns[column_name]['size'] == field_size:
                     pass
                 else:
-                    logging.getLogger('init').warning(
+                    logger.warning(
                         'Unable to migrate column %s on table %s '
-                        'from varchar(%s) to varchar(%s).'
-                        % (column_name, self.table_name,
-                            self._columns[column_name]['size'] > 0
-                            and self._columns[column_name]['size'] or 255,
-                            field_size))
+                        'from varchar(%s) to varchar(%s).',
+                        column_name, self.table_name,
+                        self._columns[column_name]['size'] > 0
+                        and self._columns[column_name]['size'] or 255,
+                        field_size)
             return
 
         column_type = column_type[1]
@@ -294,17 +296,17 @@ class TableHandler(TableHandlerInterface):
                         self._column_definition(column_name, nullable=False)))
                 self._update_definitions()
             else:
-                logging.getLogger('init').warning(
+                logger.warning(
                     'Unable to set column %s '
                     'of table %s not null !\n'
                     'Try to re-run: '
                     'trytond.py --update=module\n'
                     'If it doesn\'t work, update records '
                     'and execute manually:\n'
-                    'ALTER TABLE `%s` MODIFY COLUMN `%s` %s'
-                    % (column_name, self.table_name, self.table_name,
-                        column_name, self._column_definition(column_name,
-                            nullable=False)))
+                    'ALTER TABLE `%s` MODIFY COLUMN `%s` %s',
+                    column_name, self.table_name, self.table_name,
+                    column_name, self._column_definition(column_name,
+                        nullable=False))
         elif action == 'remove':
             if self._columns[column_name]['nullable']:
                 return
@@ -331,13 +333,13 @@ class TableHandler(TableHandlerInterface):
         except Exception:
             if exception:
                 raise
-            logging.getLogger('init').warning(
+            logger.warning(
                 'unable to add \'%s\' constraint on table %s !\n'
                 'If you want to have it, you should update the records '
                 'and execute manually:\n'
-                'ALTER table `%s` ADD CONSTRAINT `%s` %s'
-                % (constraint, self.table_name, self.table_name, ident,
-                    constraint,))
+                'ALTER table `%s` ADD CONSTRAINT `%s` %s',
+                constraint, self.table_name, self.table_name, ident,
+                constraint, exc_info=True)
         self._update_definitions()
 
     def drop_constraint(self, ident, exception=False, table=None):
@@ -351,9 +353,9 @@ class TableHandler(TableHandlerInterface):
         except Exception:
             if exception:
                 raise
-            logging.getLogger('init').warning(
-                'unable to drop \'%s\' constraint on table %s!'
-                % (ident, self.table_name))
+            logger.warning(
+                'unable to drop \'%s\' constraint on table %s!',
+                ident, self.table_name)
         self._update_definitions()
 
     def drop_column(self, column_name, exception=False):
@@ -367,9 +369,9 @@ class TableHandler(TableHandlerInterface):
         except Exception:
             if exception:
                 raise
-            logging.getLogger('init').warning(
-                'unable to drop \'%s\' column on table %s!'
-                % (column_name, self.table_name))
+            logger.warning(
+                'unable to drop \'%s\' column on table %s!',
+                column_name, self.table_name)
         self._update_definitions()
 
     @staticmethod
diff --git a/trytond/backend/postgresql/__init__.py b/trytond/backend/postgresql/__init__.py
index 7f49e23..235b423 100644
--- a/trytond/backend/postgresql/__init__.py
+++ b/trytond/backend/postgresql/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .database import *
 from .table import *
diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py
index 8316f63..9529d5c 100644
--- a/trytond/backend/postgresql/database.py
+++ b/trytond/backend/postgresql/database.py
@@ -1,7 +1,18 @@
-#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 trytond.backend.database import DatabaseInterface, CursorInterface
-from trytond.config import config, parse_uri
+# 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 time
+import logging
+import re
+import os
+if os.name == 'posix':
+    import pwd
+from decimal import Decimal
+
+try:
+    from psycopg2cffi import compat
+    compat.register()
+except ImportError:
+    pass
 from psycopg2.pool import ThreadedConnectionPool
 from psycopg2.extensions import cursor as PsycopgCursor
 from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ
@@ -9,23 +20,22 @@ from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
 from psycopg2.extensions import register_type, register_adapter
 from psycopg2.extensions import UNICODE, AsIs
 try:
-    from psycopg2.extensions import PYDATE, PYDATETIME, PYTIME
+    from psycopg2.extensions import PYDATE, PYDATETIME, PYTIME, PYINTERVAL
 except ImportError:
-    PYDATE, PYDATETIME, PYTIME = None, None, None
+    PYDATE, PYDATETIME, PYTIME, PYINTERVAL = None, None, None, None
 from psycopg2 import IntegrityError as DatabaseIntegrityError
 from psycopg2 import OperationalError as DatabaseOperationalError
-import time
-import logging
-import re
-import os
-if os.name == 'posix':
-    import pwd
-from decimal import Decimal
+
 from sql import Flavor
 
+from trytond.backend.database import DatabaseInterface, CursorInterface
+from trytond.config import config, parse_uri
+
 __all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError',
     'Cursor']
 
+logger = logging.getLogger(__name__)
+
 RE_VERSION = re.compile(r'\S+ (\d+)\.(\d+)')
 
 os.environ['PGTZ'] = os.environ.get('TZ', '')
@@ -52,8 +62,7 @@ class Database(DatabaseInterface):
     def connect(self):
         if self._connpool is not None:
             return self
-        logger = logging.getLogger('database')
-        logger.info('connect to "%s"' % self.database_name)
+        logger.info('connect to "%s"', self.database_name)
         uri = parse_uri(config.get('database', 'uri'))
         assert uri.scheme == 'postgresql'
         host = uri.hostname and "host=%s" % uri.hostname or ''
@@ -61,8 +70,8 @@ class Database(DatabaseInterface):
         name = "dbname=%s" % self.database_name
         user = uri.username and "user=%s" % uri.username or ''
         password = uri.password and "password=%s" % uri.password or ''
-        minconn = config.getint('database', 'minconn', 1)
-        maxconn = config.getint('database', 'maxconn', 64)
+        minconn = config.getint('database', 'minconn', default=1)
+        maxconn = config.getint('database', 'maxconn', default=64)
         dsn = '%s %s %s %s %s' % (host, port, name, user, password)
         self._connpool = ThreadedConnectionPool(minconn, maxconn, dsn)
         return self
@@ -379,5 +388,7 @@ if PYDATETIME:
     register_type(PYDATETIME)
 if PYTIME:
     register_type(PYTIME)
+if PYINTERVAL:
+    register_type(PYINTERVAL)
 register_adapter(float, lambda value: AsIs(repr(value)))
 register_adapter(Decimal, lambda value: AsIs(str(value)))
diff --git a/trytond/backend/postgresql/table.py b/trytond/backend/postgresql/table.py
index be4478f..3256fd9 100644
--- a/trytond/backend/postgresql/table.py
+++ b/trytond/backend/postgresql/table.py
@@ -1,11 +1,13 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.backend.table import TableHandlerInterface
 import logging
 
 __all__ = ['TableHandler']
 
+logger = logging.getLogger(__name__)
+
 
 class TableHandler(TableHandlerInterface):
 
@@ -43,7 +45,7 @@ class TableHandler(TableHandlerInterface):
                 self.cursor.execute('ALTER TABLE "%s" '
                     'ADD COLUMN id INTEGER' % self.table_name)
             self._update_definitions()
-        if self.history and not '__id' in self._columns:
+        if self.history and '__id' not in self._columns:
             self.cursor.execute('ALTER TABLE "%s" '
                 'ADD COLUMN __id INTEGER '
                 'DEFAULT nextval(\'"%s"\') NOT NULL' %
@@ -69,7 +71,7 @@ class TableHandler(TableHandlerInterface):
 
     @staticmethod
     def table_rename(cursor, old_name, new_name):
-        #Rename table
+        # Rename table
         if (TableHandler.table_exist(cursor, old_name)
                 and not TableHandler.table_exist(cursor, new_name)):
             cursor.execute('ALTER TABLE "%s" RENAME TO "%s"'
@@ -78,7 +80,7 @@ class TableHandler(TableHandlerInterface):
         old_sequence = old_name + '_id_seq'
         new_sequence = new_name + '_id_seq'
         TableHandler.sequence_rename(cursor, old_sequence, new_sequence)
-        #Rename history table
+        # Rename history table
         old_history = old_name + "__history"
         new_history = new_name + "__history"
         if (TableHandler.table_exist(cursor, old_history)
@@ -209,7 +211,7 @@ class TableHandler(TableHandlerInterface):
         if self.column_exist(column_name):
             if (column_name in ('create_date', 'write_date')
                     and column_type[1].lower() != 'timestamp(6)'):
-                #Migrate dates from timestamp(0) to timestamp
+                # Migrate dates from timestamp(0) to timestamp
                 self.cursor.execute('ALTER TABLE "' + self.table_name + '" '
                     'ALTER COLUMN "' + column_name + '" TYPE timestamp')
             comment()
@@ -225,11 +227,11 @@ class TableHandler(TableHandlerInterface):
                         ]:
                     self.alter_type(column_name, base_type)
                 else:
-                    logging.getLogger('init').warning(
+                    logger.warning(
                         'Unable to migrate column %s on table %s '
-                        'from %s to %s.'
-                        % (column_name, self.table_name,
-                            self._columns[column_name]['typname'], base_type))
+                        'from %s to %s.',
+                        column_name, self.table_name,
+                        self._columns[column_name]['typname'], base_type)
 
             if (base_type == 'varchar'
                     and self._columns[column_name]['typname'] == 'varchar'):
@@ -243,13 +245,13 @@ class TableHandler(TableHandlerInterface):
                         and self._columns[column_name]['size'] < field_size):
                     self.alter_size(column_name, column_type[1])
                 else:
-                    logging.getLogger('init').warning(
+                    logger.warning(
                         'Unable to migrate column %s on table %s '
-                        'from varchar(%s) to varchar(%s).'
-                        % (column_name, self.table_name,
-                            self._columns[column_name]['size'] > 0 and
-                            self._columns[column_name]['size'] or "",
-                            field_size))
+                        'from varchar(%s) to varchar(%s).',
+                        column_name, self.table_name,
+                        self._columns[column_name]['size'] > 0 and
+                        self._columns[column_name]['size'] or "",
+                        field_size)
             return
 
         column_type = column_type[1]
@@ -350,16 +352,15 @@ class TableHandler(TableHandlerInterface):
                     'ALTER COLUMN "' + column_name + '" SET NOT NULL')
                 self._update_definitions()
             else:
-                logging.getLogger('init').warning(
+                logger.warning(
                     'Unable to set column %s '
                     'of table %s not null !\n'
                     'Try to re-run: '
                     'trytond.py --update=module\n'
                     'If it doesn\'t work, update records '
                     'and execute manually:\n'
-                    'ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL'
-                    % (column_name, self.table_name, self.table_name,
-                        column_name))
+                    'ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL',
+                    column_name, self.table_name, self.table_name, column_name)
         elif action == 'remove':
             if not self._columns[column_name]['notnull']:
                 return
@@ -385,13 +386,13 @@ class TableHandler(TableHandlerInterface):
         except Exception:
             if exception:
                 raise
-            logging.getLogger('init').warning(
+            logger.warning(
                 'unable to add \'%s\' constraint on table %s !\n'
                 'If you want to have it, you should update the records '
                 'and execute manually:\n'
-                'ALTER table "%s" ADD CONSTRAINT "%s" %s'
-                % (constraint, self.table_name, self.table_name, ident,
-                    constraint))
+                'ALTER table "%s" ADD CONSTRAINT "%s" %s',
+                constraint, self.table_name, self.table_name, ident,
+                constraint)
         self._update_definitions()
 
     def drop_constraint(self, ident, exception=False, table=None):
@@ -405,9 +406,9 @@ class TableHandler(TableHandlerInterface):
         except Exception:
             if exception:
                 raise
-            logging.getLogger('init').warning(
-                'unable to drop \'%s\' constraint on table %s!'
-                % (ident, self.table_name))
+            logger.warning(
+                'unable to drop \'%s\' constraint on table %s!',
+                ident, self.table_name)
         self._update_definitions()
 
     def drop_column(self, column_name, exception=False):
@@ -421,9 +422,9 @@ class TableHandler(TableHandlerInterface):
         except Exception:
             if exception:
                 raise
-            logging.getLogger('init').warning(
-                'unable to drop \'%s\' column on table %s!'
-                % (column_name, self.table_name))
+            logger.warning(
+                'unable to drop \'%s\' column on table %s!',
+                column_name, self.table_name, exc_info=True)
         self._update_definitions()
 
     @staticmethod
diff --git a/trytond/backend/sqlite/__init__.py b/trytond/backend/sqlite/__init__.py
index 7f49e23..235b423 100644
--- a/trytond/backend/sqlite/__init__.py
+++ b/trytond/backend/sqlite/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .database import *
 from .table import *
diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py
index dd9b2c8..13bcaf2 100644
--- a/trytond/backend/sqlite/database.py
+++ b/trytond/backend/sqlite/database.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.backend.database import DatabaseInterface, CursorInterface
 from trytond.config import config
 import os
@@ -14,7 +14,7 @@ try:
     from pysqlite2 import dbapi2 as sqlite
     from pysqlite2.dbapi2 import IntegrityError as DatabaseIntegrityError
     from pysqlite2.dbapi2 import OperationalError as DatabaseOperationalError
-    #pysqlite2 < 2.5 doesn't return correct rowcount
+    # pysqlite2 < 2.5 doesn't return correct rowcount
     _FIX_ROWCOUNT = sqlite.version_info < (2, 5, 0)
 except ImportError:
     import sqlite3 as sqlite
@@ -183,7 +183,8 @@ class Database(DatabaseInterface):
                 raise IOError('Database "%s" doesn\'t exist!' % db_filename)
         if self._conn is not None:
             return self
-        self._conn = sqlite.connect(path, detect_types=sqlite.PARSE_DECLTYPES)
+        self._conn = sqlite.connect(path,
+            detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES)
         self._conn.create_function('extract', 2, SQLiteExtract.extract)
         self._conn.create_function('date_trunc', 2, date_trunc)
         self._conn.create_function('split_part', 3, split_part)
@@ -194,6 +195,8 @@ class Database(DatabaseInterface):
             self._conn.create_function('replace', 3, replace)
         self._conn.create_function('now', 0, now)
         self._conn.create_function('sign', 1, sign)
+        self._conn.create_function('greatest', -1, max)
+        self._conn.create_function('least', -1, min)
         self._conn.execute('PRAGMA foreign_keys = ON')
         return self
 
@@ -405,11 +408,12 @@ class Cursor(CursorInterface):
     def has_constraint(self):
         return False
 
-sqlite.register_converter('NUMERIC', lambda val: Decimal(val))
+sqlite.register_converter('NUMERIC', lambda val: Decimal(val.decode('utf-8')))
 if sys.version_info[0] == 2:
     sqlite.register_adapter(Decimal, lambda val: buffer(str(val)))
+    sqlite.register_adapter(bytearray, lambda val: buffer(val))
 else:
-    sqlite.register_adapter(Decimal, lambda val: bytes(str(val)))
+    sqlite.register_adapter(Decimal, lambda val: str(val).encode('utf-8'))
 
 
 def adapt_datetime(val):
@@ -417,4 +421,18 @@ def adapt_datetime(val):
 sqlite.register_adapter(datetime.datetime, adapt_datetime)
 sqlite.register_adapter(datetime.time, lambda val: val.isoformat())
 sqlite.register_converter('TIME', lambda val: datetime.time(*map(int,
-            val.split(':'))))
+            val.decode('utf-8').split(':'))))
+sqlite.register_adapter(datetime.timedelta, lambda val: val.total_seconds())
+
+
+def convert_interval(value):
+    value = float(value)
+    # It is not allowed to instatiate timedelta with the min/max total seconds
+    if value >= _interval_max:
+        return datetime.timedelta.max
+    elif value <= _interval_min:
+        return datetime.timedelta.min
+    return datetime.timedelta(seconds=value)
+_interval_max = datetime.timedelta.max.total_seconds()
+_interval_min = datetime.timedelta.min.total_seconds()
+sqlite.register_converter('INTERVAL', convert_interval)
diff --git a/trytond/backend/sqlite/table.py b/trytond/backend/sqlite/table.py
index f2fafcb..0e7317b 100644
--- a/trytond/backend/sqlite/table.py
+++ b/trytond/backend/sqlite/table.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.backend.table import TableHandlerInterface
 import logging
@@ -7,6 +7,8 @@ import re
 
 __all__ = ['TableHandler']
 
+logger = logging.getLogger(__name__)
+
 
 class TableHandler(TableHandlerInterface):
     def __init__(self, cursor, model, module_name=None, history=False):
@@ -44,7 +46,7 @@ class TableHandler(TableHandlerInterface):
 
         # Migration from 1.6 add autoincrement
 
-        if not 'AUTOINCREMENT' in sql.upper():
+        if 'AUTOINCREMENT' not in sql.upper():
             temp_sql = sql.replace(table_name, '_temp_%s' % table_name)
             cursor.execute(temp_sql)
             cursor.execute('PRAGMA table_info("' + table_name + '")')
@@ -71,7 +73,7 @@ class TableHandler(TableHandlerInterface):
                 and not TableHandler.table_exist(cursor, new_name)):
             cursor.execute('ALTER TABLE "%s" RENAME TO "%s"'
                 % (old_name, new_name))
-        #Rename history table
+        # Rename history table
         old_history = old_name + "__history"
         new_history = new_name + "__history"
         if (TableHandler.table_exist(cursor, old_history)
@@ -159,22 +161,22 @@ class TableHandler(TableHandlerInterface):
                 self._field2module[line[0]] = line[1]
 
     def alter_size(self, column_name, column_type):
-        logging.getLogger('init').warning(
+        logger.warning(
             'Unable to alter size of column %s '
-            'of table %s!'
-            % (column_name, self.table_name))
+            'of table %s!',
+            column_name, self.table_name)
 
     def alter_type(self, column_name, column_type):
-        logging.getLogger('init').warning(
+        logger.warning(
             'Unable to alter type of column %s '
-            'of table %s!'
-            % (column_name, self.table_name))
+            'of table %s!',
+            column_name, self.table_name)
 
     def db_default(self, column_name, value):
-        logging.getLogger('init').warning(
+        logger.warning(
             'Unable to set default on column %s '
-            'of table %s!'
-            % (column_name, self.table_name))
+            'of table %s!',
+            column_name, self.table_name)
 
     def add_raw_column(self, column_name, column_type, column_format,
             default_fun=None, field_size=None, migrate=True, string=''):
@@ -191,11 +193,11 @@ class TableHandler(TableHandlerInterface):
                         ]:
                     self.alter_type(column_name, base_type)
                 else:
-                    logging.getLogger('init').warning(
+                    logger.warning(
                         'Unable to migrate column %s on table %s '
-                        'from %s to %s.'
-                        % (column_name, self.table_name,
-                            self._columns[column_name]['typname'], base_type))
+                        'from %s to %s.',
+                        column_name, self.table_name,
+                        self._columns[column_name]['typname'], base_type)
 
             if (base_type == 'VARCHAR'
                     and self._columns[column_name]['typname'] == 'VARCHAR'):
@@ -209,13 +211,13 @@ class TableHandler(TableHandlerInterface):
                         and self._columns[column_name]['size'] < field_size):
                     self.alter_size(column_name, column_type[1])
                 else:
-                    logging.getLogger('init').warning(
+                    logger.warning(
                         'Unable to migrate column %s on table %s '
-                        'from varchar(%s) to varchar(%s).'
-                        % (column_name, self.table_name,
-                            self._columns[column_name]['size'] > 0
-                            and self._columns[column_name]['size'] or "",
-                            field_size))
+                        'from varchar(%s) to varchar(%s).',
+                        column_name, self.table_name,
+                        self._columns[column_name]['size'] > 0
+                        and self._columns[column_name]['size'] or "",
+                        field_size)
             return
 
         column_type = column_type[1]
@@ -238,14 +240,14 @@ class TableHandler(TableHandlerInterface):
         self._update_definitions()
 
     def add_fk(self, column_name, reference, on_delete=None):
-        logging.getLogger('init').warning(
-            'Unable to add foreign key on table %s!'
-            % (self.table_name,))
+        logger.warning(
+            'Unable to add foreign key on table %s!',
+            self.table_name)
 
     def drop_fk(self, column_name, table=None):
-        logging.getLogger('init').warning(
-            'Unable to drop foreign key on table %s!'
-            % (self.table_name,))
+        logger.warning(
+            'Unable to drop foreign key on table %s!',
+            self.table_name)
 
     def index_action(self, column_name, action='add', table=None):
         if isinstance(column_name, basestring):
@@ -277,32 +279,32 @@ class TableHandler(TableHandlerInterface):
             return
 
         if action == 'add':
-            logging.getLogger('init').warning(
+            logger.warning(
                 'Unable to set not null on column %s '
-                'of table %s!'
-                % (column_name, self.table_name))
+                'of table %s!',
+                column_name, self.table_name)
         elif action == 'remove':
-            logging.getLogger('init').warning(
+            logger.warning(
                 'Unable to remove not null on column %s '
-                'of table %s!'
-                % (column_name, self.table_name))
+                'of table %s!',
+                column_name, self.table_name)
         else:
             raise Exception('Not null action not supported!')
 
     def add_constraint(self, ident, constraint, exception=False):
-        logging.getLogger('init').warning(
-            'Unable to add constraint on table %s!'
-            % (self.table_name,))
+        logger.warning(
+            'Unable to add constraint on table %s!',
+            self.table_name)
 
     def drop_constraint(self, ident, exception=False, table=None):
-        logging.getLogger('init').warning(
-            'Unable to drop constraint on table %s!'
-            % (self.table_name,))
+        logger.warning(
+            'Unable to drop constraint on table %s!',
+            self.table_name)
 
     def drop_column(self, column_name, exception=False):
-        logging.getLogger('init').warning(
-            'Unable to drop \'%s\' column on table %s!'
-            % (column_name, self.table_name))
+        logger.warning(
+            'Unable to drop "%s" column on table %s!',
+            column_name, self.table_name)
 
     @staticmethod
     def drop_table(cursor, model, table, cascade=False):
diff --git a/trytond/backend/table.py b/trytond/backend/table.py
index 6f2a9cd..afdeb6b 100644
--- a/trytond/backend/table.py
+++ b/trytond/backend/table.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 
 
 class TableHandlerInterface(object):
diff --git a/trytond/cache.py b/trytond/cache.py
index 6ccd039..2caf83f 100644
--- a/trytond/cache.py
+++ b/trytond/cache.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 threading import Lock
 from collections import OrderedDict
 
@@ -116,6 +116,11 @@ class Cache(object):
                 Cache._resets[dbname].clear()
             cursor.commit()
 
+    @classmethod
+    def drop(cls, dbname):
+        for inst in cls._cache_instance:
+            inst._cache.pop(dbname, None)
+
 
 class LRUDict(OrderedDict):
     """
diff --git a/trytond/config.py b/trytond/config.py
index 99e3057..46f5d1f 100644
--- a/trytond/config.py
+++ b/trytond/config.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 os
 import ConfigParser
 import urlparse
@@ -51,6 +51,10 @@ class TrytonConfigParser(ConfigParser.RawConfigParser):
         self.set('database', 'list', 'True')
         self.set('database', 'retry', 5)
         self.set('database', 'language', 'en_US')
+        self.add_section('cache')
+        self.set('cache', 'model', 200)
+        self.set('cache', 'record', 2000)
+        self.set('cache', 'field', 100)
         self.add_section('ssl')
         self.add_section('email')
         self.set('email', 'uri', 'smtp://localhost:25')
@@ -65,30 +69,37 @@ class TrytonConfigParser(ConfigParser.RawConfigParser):
             return
         self.read(configfile)
 
-    def get(self, section, option, default=None):
+    def get(self, section, option, *args, **kwargs):
+        default = kwargs.pop('default', None)
         try:
-            return ConfigParser.RawConfigParser.get(self, section, option)
+            return ConfigParser.RawConfigParser.get(self, section, option,
+                *args, **kwargs)
         except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
             return default
 
-    def getint(self, section, option, default=None):
+    def getint(self, section, option, *args, **kwargs):
+        default = kwargs.pop('default', None)
         try:
-            return ConfigParser.RawConfigParser.getint(self, section, option)
+            return ConfigParser.RawConfigParser.getint(self, section, option,
+                *args, **kwargs)
         except (ConfigParser.NoOptionError, ConfigParser.NoSectionError,
                 TypeError):
             return default
 
-    def getfloat(self, section, option, default=None):
+    def getfloat(self, section, option, *args, **kwargs):
+        default = kwargs.pop('default', None)
         try:
-            return ConfigParser.RawConfigParser.getfloat(self, section, option)
+            return ConfigParser.RawConfigParser.getfloat(self, section, option,
+                *args, **kwargs)
         except (ConfigParser.NoOptionError, ConfigParser.NoSectionError,
                 TypeError):
             return default
 
-    def getboolean(self, section, option, default=None):
+    def getboolean(self, section, option, *args, **kwargs):
+        default = kwargs.pop('default', None)
         try:
             return ConfigParser.RawConfigParser.getboolean(
-                self, section, option)
+                self, section, option, *args, **kwargs)
         except (ConfigParser.NoOptionError, ConfigParser.NoSectionError,
                 AttributeError):
             return default
diff --git a/trytond/const.py b/trytond/const.py
index 13995b3..23503c0 100644
--- a/trytond/const.py
+++ b/trytond/const.py
@@ -1,6 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
-
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 OPERATORS = (
     'child_of',
     'not child_of',
@@ -16,8 +15,4 @@ OPERATORS = (
     '>=',
     '<',
     '>',
-)
-
-MODEL_CACHE_SIZE = 200
-RECORD_CACHE_SIZE = 2000
-BROWSE_FIELD_TRESHOLD = 100
+    )
diff --git a/trytond/convert.py b/trytond/convert.py
index 8d5515c..fea9638 100644
--- a/trytond/convert.py
+++ b/trytond/convert.py
@@ -1,18 +1,20 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 time
 from xml import sax
 import logging
-import traceback
-import sys
 import re
 from sql import Table
 from itertools import izip
 from collections import defaultdict
+from decimal import Decimal
 
-from .version import VERSION
-from .tools import safe_eval, grouped_slice
+from . import __version__
+from .tools import grouped_slice
 from .transaction import Transaction
+from .pyson import PYSONEncoder, CONTEXT
+
+logger = logging.getLogger(__name__)
 
 CDATA_START = re.compile('^\s*\<\!\[cdata\[', re.IGNORECASE)
 CDATA_END = re.compile('\]\]\>\s*$', re.IGNORECASE)
@@ -53,7 +55,7 @@ class MenuitemTagHandler:
                 values[attr] = attributes.get(attr)
 
         if attributes.get('active'):
-            values['active'] = bool(safe_eval(attributes['active']))
+            values['active'] = bool(eval(attributes['active']))
 
         if values.get('parent'):
             values['parent'] = self.mh.get_id(values['parent'])
@@ -208,12 +210,13 @@ class RecordTagHandler:
             search_attr = attributes.get('search', '')
             ref_attr = attributes.get('ref', '')
             eval_attr = attributes.get('eval', '')
+            pyson_attr = bool(int(attributes.get('pyson', '0')))
 
             if search_attr:
                 search_model = self.model._fields[field_name].model_name
                 SearchModel = self.mh.pool.get(search_model)
                 with Transaction().set_context(active_test=False):
-                    found, = SearchModel.search(safe_eval(search_attr))
+                    found, = SearchModel.search(eval(search_attr))
                     self.values[field_name] = found.id
 
             elif ref_attr:
@@ -222,10 +225,16 @@ class RecordTagHandler:
             elif eval_attr:
                 context = {}
                 context['time'] = time
-                context['version'] = VERSION.rsplit('.', 1)[0]
+                context['version'] = __version__.rsplit('.', 1)[0]
                 context['ref'] = self.mh.get_id
                 context['obj'] = lambda *a: 1
-                self.values[field_name] = safe_eval(eval_attr, context)
+                context['Decimal'] = Decimal
+                if pyson_attr:
+                    context.update(CONTEXT)
+                value = eval(eval_attr, context)
+                if pyson_attr:
+                    value = PYSONEncoder().encode(value)
+                self.values[field_name] = value
 
         else:
             raise Exception("Tags '%s' not supported inside tag record." %
@@ -325,7 +334,7 @@ class Fs2bdAccessor:
         Whe call the prefetch function here to. Like that whe are sure
         not to erase data when get is called.
         """
-        if not module in self.fetched_modules:
+        if module not in self.fetched_modules:
             self.fetch_new_module(module)
         if fs_id not in self.fs2db[module]:
             self.fs2db[module][fs_id] = {}
@@ -366,13 +375,12 @@ class Fs2bdAccessor:
             record_ids.setdefault(rec.model, [])
             record_ids[rec.model].append(rec.db_id)
 
-        object_name_list = set(self.pool.object_name_list())
-
         self.browserecord[module] = {}
         for model_name in record_ids.keys():
-            if model_name not in object_name_list:
+            try:
+                Model = self.pool.get(model_name)
+            except KeyError:
                 continue
-            Model = self.pool.get(model_name)
             self.browserecord[module][model_name] = {}
             for sub_record_ids in grouped_slice(record_ids[model_name]):
                 with Transaction().set_context(active_test=False):
@@ -436,8 +444,9 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
         try:
             self.sax_parser.parse(source)
         except Exception:
-            logging.getLogger("convert").error(
-                "Error while parsing xml file:\n" + self.current_state())
+            logger.error(
+                "Error while parsing xml file:\n" + self.current_state(),
+                exc_info=True)
             raise
         return self.to_delete
 
@@ -469,8 +478,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
                 pass
 
             else:
-                logging.getLogger("convert").info("Tag %s not supported" %
-                    name)
+                logger.info("Tag %s not supported", (name,))
                 return
         elif not self.skip_data:
             self.taghandler.startElement(name, attributes)
@@ -535,16 +543,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
         elif field_type == 'reference':
             if not getattr(record, key):
                 return None
-            elif not isinstance(getattr(record, key), basestring):
-                return str(getattr(record, key))
-            ref_mode, ref_id = getattr(record, key).split(',', 1)
-            try:
-                ref_id = safe_eval(ref_id)
-            except Exception:
-                pass
-            if isinstance(ref_id, (list, tuple)):
-                ref_id = ref_id[0]
-            return ref_mode + ',' + str(ref_id)
+            return str(getattr(record, key))
         elif field_type in ['one2many', 'many2many']:
             raise Unhandled_field("Unhandled field %s" % key)
         else:
@@ -613,7 +612,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
                     % (fs_id, module))
 
             record = self.fs2db.get_browserecord(module, Model.__name__, db_id)
-            #Re-create record if it was deleted
+            # Re-create record if it was deleted
             if not record:
                 with Transaction().set_context(
                         module=module, language='en_US'):
@@ -668,10 +667,10 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
                 # if they are not false in a boolean context (ie None,
                 # False, {} or [])
                 if db_field != expected_value and (db_field or expected_value):
-                    logging.getLogger("convert").warning(
+                    logger.warning(
                         "Field %s of %s@%s not updated (id: %s), because "
-                        "it has changed since the last update"
-                        % (key, record.id, model, fs_id))
+                        "it has changed since the last update",
+                        key, record.id, model, fs_id)
                     continue
 
                 # so, the field in the fs and in the db are different,
@@ -792,34 +791,33 @@ def post_import(pool, module, to_delete):
             ('module', '=', module),
             ], order=[('id', 'DESC')])
 
-    object_name_list = set(pool.object_name_list())
     for mrec in mdata:
         model, db_id = mrec.model, mrec.db_id
 
-        logging.getLogger("convert").info(
-                'Deleting %s@%s' % (db_id, model))
+        logger.info('Deleting %s@%s', db_id, model)
         try:
             # Deletion of the record
-            if model in object_name_list:
+            try:
                 Model = pool.get(model)
+            except KeyError:
+                Model = None
+            if Model:
                 Model.delete([Model(db_id)])
                 mdata_delete.append(mrec)
             else:
-                logging.getLogger("convert").warning(
-                        'Could not delete id %d of model %s because model no '
-                        'longer exists.' % (db_id, model))
+                logger.warning(
+                    'Could not delete id %d of model %s because model no '
+                    'longer exists.', db_id, model)
             cursor.commit()
         except Exception:
             cursor.rollback()
-            tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
-            logging.getLogger("convert").error(
+            logger.error(
                 'Could not delete id: %d of model %s\n'
-                    'There should be some relation '
-                    'that points to this resource\n'
-                    'You should manually fix this '
-                    'and restart --update=module\n'
-                    'Exception: %s' %
-                    (db_id, model, tb_s))
+                'There should be some relation '
+                'that points to this resource\n'
+                'You should manually fix this '
+                'and restart --update=module\n',
+                db_id, model, exc_info=True)
             if 'active' in Model._fields:
                 Model.write([Model(db_id)], {
                         'active': False,
diff --git a/trytond/error.py b/trytond/error.py
index cb61f70..97a86bd 100644
--- a/trytond/error.py
+++ b/trytond/error.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.transaction import Transaction
 from trytond.pool import Pool
 from trytond.exceptions import UserError, UserWarning
diff --git a/trytond/exceptions.py b/trytond/exceptions.py
index 5f3db66..dd086b1 100644
--- a/trytond/exceptions.py
+++ b/trytond/exceptions.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 
 
 class UserError(Exception):
diff --git a/trytond/ir/__init__.py b/trytond/ir/__init__.py
index 062897b..ab1e31e 100644
--- a/trytond/ir/__init__.py
+++ b/trytond/ir/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 ..pool import Pool
 from .configuration import *
 from .translation import *
diff --git a/trytond/ir/action.py b/trytond/ir/action.py
index 606796a..2482c6b 100644
--- a/trytond/ir/action.py
+++ b/trytond/ir/action.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 base64
 import os
 from operator import itemgetter
@@ -10,9 +10,9 @@ from sql import Table
 from sql.aggregate import Count
 
 from ..model import ModelView, ModelStorage, ModelSQL, fields
-from ..tools import file_open, safe_eval
+from ..tools import file_open
 from .. import backend
-from ..pyson import PYSONEncoder, CONTEXT, PYSON
+from ..pyson import PYSONDecoder, PYSON
 from ..transaction import Transaction
 from ..pool import Pool
 from ..cache import Cache
@@ -477,10 +477,6 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
     @classmethod
     def __setup__(cls):
         super(ActionReport, cls).__setup__()
-        cls._sql_constraints += [
-            ('report_name_module_uniq', 'UNIQUE(report_name, module)',
-                'The internal name must be unique by module!'),
-        ]
         cls._error_messages.update({
                 'invalid_email': 'Invalid email definition on report "%s".',
                 })
@@ -537,13 +533,17 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
                         limit=limit, offset=offset))
                 for report_id, report in cursor.fetchall():
                     if report:
-                        report = buffer(base64.decodestring(str(report)))
+                        report = fields.Binary.cast(
+                            base64.decodestring(bytes(report)))
                         cursor.execute(*action_report.update(
                                 [action_report.report_content_custom],
                                 [report],
                                 where=action_report.id == report_id))
             table.drop_column('report_content_data')
 
+        # Migration from 3.4 remove report_name_module_uniq constraint
+        table.drop_constraint('report_name_module_uniq')
+
     @staticmethod
     def default_type():
         return 'ir.action.report'
@@ -579,7 +579,7 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
         for report in reports:
             if report.email:
                 try:
-                    value = safe_eval(report.email, CONTEXT)
+                    value = PYSONDecoder().decode(report.email)
                 except Exception:
                     value = None
                 if isinstance(value, dict):
@@ -593,7 +593,7 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
     @classmethod
     def get_report_content(cls, reports, name):
         contents = {}
-        converter = buffer
+        converter = fields.Binary.cast
         default = None
         format_ = Transaction().context.pop('%s.%s'
             % (cls.__name__, name), '')
@@ -625,7 +625,7 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
     @classmethod
     def get_style_content(cls, reports, name):
         contents = {}
-        converter = buffer
+        converter = fields.Binary.cast
         default = None
         format_ = Transaction().context.pop('%s.%s'
             % (cls.__name__, name), '')
@@ -645,14 +645,13 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
     @classmethod
     def get_pyson(cls, reports, name):
         pysons = {}
-        encoder = PYSONEncoder()
         field = name[6:]
         defaults = {
             'email': '{}',
             }
         for report in reports:
-            pysons[report.id] = encoder.encode(safe_eval(getattr(report, field)
-                    or defaults.get(field, 'None'), CONTEXT))
+            pysons[report.id] = (getattr(report, field)
+                or defaults.get(field, 'null'))
         return pysons
 
     @classmethod
@@ -812,7 +811,7 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView):
                 if not domain:
                     continue
                 try:
-                    value = safe_eval(domain, CONTEXT)
+                    value = PYSONDecoder().decode(domain)
                 except Exception:
                     cls.raise_user_error('invalid_domain', {
                             'domain': domain,
@@ -844,7 +843,7 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView):
         for action in actions:
             if action.context:
                 try:
-                    value = safe_eval(action.context, CONTEXT)
+                    value = PYSONDecoder().decode(action.context)
                 except Exception:
                     cls.raise_user_error('invalid_context', {
                             'context': action.context,
@@ -875,15 +874,12 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView):
             for view in self.act_window_views]
 
     def get_domains(self, name):
-        encoder = PYSONEncoder()
-        return [(domain.name,
-                encoder.encode(safe_eval(domain.domain or '[]', CONTEXT)))
+        return [(domain.name, domain.domain or '[]')
             for domain in self.act_window_domains]
 
     @classmethod
     def get_pyson(cls, windows, name):
         pysons = {}
-        encoder = PYSONEncoder()
         field = name[6:]
         defaults = {
             'domain': '[]',
@@ -891,8 +887,8 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView):
             'search_value': '{}',
             }
         for window in windows:
-            pysons[window.id] = encoder.encode(safe_eval(getattr(window, field)
-                    or defaults.get(field, 'None'), CONTEXT))
+            pysons[window.id] = (getattr(window, field)
+                or defaults.get(field, 'null'))
         return pysons
 
     @classmethod
@@ -971,11 +967,45 @@ class ActionActWindowDomain(ModelSQL, ModelView):
     def __setup__(cls):
         super(ActionActWindowDomain, cls).__setup__()
         cls._order.insert(0, ('sequence', 'ASC'))
+        cls._error_messages.update({
+                'invalid_domain': ('Invalid domain or search criteria '
+                    '"%(domain)s" on action "%(action)s".'),
+                })
 
     @staticmethod
     def default_active():
         return True
 
+    @classmethod
+    def validate(cls, actions):
+        super(ActionActWindowDomain, cls).validate(actions)
+        cls.check_domain(actions)
+
+    @classmethod
+    def check_domain(cls, actions):
+        for action in actions:
+            if not action.domain:
+                continue
+            try:
+                value = PYSONDecoder().decode(action.domain)
+            except Exception:
+                value = None
+            if isinstance(value, PYSON):
+                if not value.types() == set([list]):
+                    value = None
+            elif not isinstance(value, list):
+                value = None
+            else:
+                try:
+                    fields.domain_validate(value)
+                except Exception:
+                    value = None
+            if value is None:
+                cls.raise_user_error('invalid_domain', {
+                        'domain': action.domain,
+                        'action': action.rec_name,
+                        })
+
 
 class ActionWizard(ActionMixin, ModelSQL, ModelView):
     "Action wizard"
diff --git a/trytond/ir/attachment.py b/trytond/ir/attachment.py
index 0195c70..51f6097 100644
--- a/trytond/ir/attachment.py
+++ b/trytond/ir/attachment.py
@@ -1,8 +1,9 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 os
 import hashlib
 from sql.operators import Concat
+from sql.conditionals import Coalesce
 
 from ..model import ModelView, ModelSQL, fields
 from ..config import config
@@ -54,6 +55,7 @@ class Attachment(ModelSQL, ModelView):
     @classmethod
     def __setup__(cls):
         super(Attachment, cls).__setup__()
+        cls._order.insert(0, ('last_modification', 'DESC'))
         cls._sql_constraints += [
             ('resource_name', 'UNIQUE(resource, name)',
                 'The  names of attachments must be unique by resource!'),
@@ -128,7 +130,7 @@ class Attachment(ModelSQL, ModelView):
             else:
                 try:
                     with open(filename, 'rb') as file_p:
-                        value = buffer(file_p.read())
+                        value = fields.Binary.cast(file_p.read())
                 except IOError:
                     pass
         return value
@@ -191,6 +193,11 @@ class Attachment(ModelSQL, ModelView):
         return (self.write_date if self.write_date else self.create_date
             ).replace(microsecond=0)
 
+    @staticmethod
+    def order_last_modification(tables):
+        table, _ = tables[None]
+        return [Coalesce(table.write_date, table.create_date)]
+
     def get_last_user(self, name):
         return (self.write_uid.rec_name if self.write_uid
             else self.create_uid.rec_name)
diff --git a/trytond/ir/cache.py b/trytond/ir/cache.py
index 5c83ceb..b38a6b7 100644
--- a/trytond/ir/cache.py
+++ b/trytond/ir/cache.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 ..model import ModelSQL, fields
 
 __all__ = [
diff --git a/trytond/ir/configuration.py b/trytond/ir/configuration.py
index 4ee7307..ed1b09e 100644
--- a/trytond/ir/configuration.py
+++ b/trytond/ir/configuration.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 ..model import ModelSQL, ModelSingleton, fields
 from ..cache import Cache
 from ..config import config
diff --git a/trytond/ir/cron.py b/trytond/ir/cron.py
index 438a0d2..336def8 100644
--- a/trytond/ir/cron.py
+++ b/trytond/ir/cron.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 datetime
 from dateutil.relativedelta import relativedelta
 import traceback
@@ -20,6 +20,8 @@ __all__ = [
     'Cron',
     ]
 
+logger = logging.getLogger(__name__)
+
 _INTERVALTYPES = {
     'days': lambda interval: relativedelta(days=interval),
     'hours': lambda interval: relativedelta(hours=interval),
@@ -140,7 +142,6 @@ class Cron(ModelSQL, ModelView):
         msg['To'] = to_addr
         msg['From'] = from_addr
         msg['Subject'] = Header(subject, 'utf-8')
-        logger = logging.getLogger(__name__)
         if not to_addr:
             logger.error(msg.as_string())
         else:
@@ -148,9 +149,9 @@ class Cron(ModelSQL, ModelView):
                 server = get_smtp_server()
                 server.sendmail(from_addr, to_addr, msg.as_string())
                 server.quit()
-            except Exception, exception:
-                logger.error('Unable to deliver email (%s):\n %s'
-                    % (exception, msg.as_string()))
+            except Exception:
+                logger.error('Unable to deliver email:\n %s',
+                    msg.as_string(), exc_info=True)
 
     @classmethod
     def _callback(cls, cron):
@@ -202,8 +203,4 @@ class Cron(ModelSQL, ModelView):
                     transaction.cursor.commit()
                 except Exception:
                     transaction.cursor.rollback()
-                    tb_s = reduce(lambda x, y: x + y,
-                            traceback.format_exception(*sys.exc_info()))
-                    tb_s = tb_s.decode('utf-8', 'ignore')
-                    logger = logging.getLogger('cron')
-                    logger.error('Exception:\n%s' % tb_s)
+                    logger.error('Running cron %s', cron.id, exc_info=True)
diff --git a/trytond/ir/date.py b/trytond/ir/date.py
index fabf04a..41624ce 100644
--- a/trytond/ir/date.py
+++ b/trytond/ir/date.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level
-#of this repository contains the full copyright notices and license terms.
+# 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 datetime
 
 from ..model import Model
diff --git a/trytond/ir/export.py b/trytond/ir/export.py
index c37b8bc..9210f39 100644
--- a/trytond/ir/export.py
+++ b/trytond/ir/export.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 "Exports"
 from ..model import ModelView, ModelSQL, fields
 
diff --git a/trytond/ir/gen_time_locale.py b/trytond/ir/gen_time_locale.py
index 9f78290..4d153a2 100644
--- a/trytond/ir/gen_time_locale.py
+++ b/trytond/ir/gen_time_locale.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 time
 import locale
 import os
@@ -183,8 +183,8 @@ if __name__ == '__main__':
     with open(os.path.join(os.path.dirname(__file__), 'time_locale.py'),
             'w') as fp:
         fp.write('''# -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of')
-#this repository contains the full copyright notices and license terms.')
+# This file is part of Tryton.  The COPYRIGHT file at the top level of')
+# this repository contains the full copyright notices and license terms.')
 ''')
         time_locale = {}
         fp.write('TIME_LOCALE = \\\n')
@@ -201,9 +201,8 @@ if __name__ == '__main__':
                 'fr_FR',
                 'nl_NL',
                 'ru_RU',
+                'sl_SI',
                 ]:
             time_locale[lang] = locale_strftime(lang)
-            #fp.write('"' + lang + '": ' + repr(time_locale) + ',\n')
-        #fp.write('}\n')
         pp = pprint.PrettyPrinter(stream=fp)
         pp.pprint(time_locale)
diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py
index c0e175b..3210fd6 100644
--- a/trytond/ir/lang.py
+++ b/trytond/ir/lang.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 datetime
 import warnings
 from ast import literal_eval
@@ -36,10 +36,10 @@ class Lang(ModelSQL, ModelView):
             ('rtl', 'Right-to-left'),
             ], 'Direction', required=True)
 
-    #date
+    # date
     date = fields.Char('Date', required=True)
 
-    #number
+    # number
     grouping = fields.Char('Grouping', required=True)
     decimal_point = fields.Char('Decimal Separator', required=True)
     thousands_sep = fields.Char('Thousands Separator')
diff --git a/trytond/ir/lang.xml b/trytond/ir/lang.xml
index 00b333d..f6a953f 100644
--- a/trytond/ir/lang.xml
+++ b/trytond/ir/lang.xml
@@ -126,7 +126,7 @@ this repository contains the full copyright notices and license terms. -->
         <record model="ir.action.act_window" id="act_lang_form">
             <field name="name">Languages</field>
             <field name="res_model">ir.lang</field>
-            <field name="context">{'active_test': False}</field>
+            <field name="context" eval="{'active_test': False}" pyson="1"/>
         </record>
         <record model="ir.action.act_window.view"
             id="act_lang_form_view1">
diff --git a/trytond/ir/locale/bg_BG.po b/trytond/ir/locale/bg_BG.po
index 4209638..0a8f43f 100644
--- a/trytond/ir/locale/bg_BG.po
+++ b/trytond/ir/locale/bg_BG.po
@@ -42,6 +42,10 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr ""
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr ""
@@ -62,10 +66,6 @@ msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr ""
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "Вътрепното име трябва да е уникално в модула!"
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr "Имената на прикачените файлове трябва да са уникални по ресурс!"
@@ -2241,9 +2241,9 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Макс брой редове"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
-msgstr "Минимално забавяне"
+msgstr ""
 
 msgctxt "field:ir.trigger,model:"
 msgid "Model"
@@ -2768,7 +2768,9 @@ msgstr ""
 "диаграмата."
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
 msgstr ""
 
 msgctxt "help:ir.rule.group,default_p:"
@@ -2803,13 +2805,11 @@ msgstr ""
 "Ограничаване на броя извиквания на \"Действие на фунция\" по записи.\n"
 "0 - няма ограничение"
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Минимално забавяне в минути между извикването на \"Действие на функция\" за този запис.\n"
-"0 за да няма забавяне"
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -4094,11 +4094,11 @@ msgstr "Печат"
 
 #, fuzzy
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Добре"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "Добре"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4114,7 +4114,7 @@ msgid "Cancel"
 msgstr "Отказ"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "Добре"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4134,7 +4134,7 @@ msgid "Cancel"
 msgstr "Отказ"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Добре"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4160,7 +4160,7 @@ msgstr ""
 
 #, fuzzy
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Добре"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/locale/ca_ES.po b/trytond/ir/locale/ca_ES.po
index 9e9483f..bb4e662 100644
--- a/trytond/ir/locale/ca_ES.po
+++ b/trytond/ir/locale/ca_ES.po
@@ -27,8 +27,8 @@ msgid ""
 "The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
 "exceeds its limit."
 msgstr ""
-"El nombre de dígits \"%(digits)s\" del camp \"%(field)s\" de \"%(value)s\" "
-"és superior al seu límit."
+"El nombre de decimals \"%(digits)s\" del camp \"%(field)s\" de \"%(value)s\""
+" és superior al seu límit."
 
 msgctxt "error:domain_validation_record:"
 msgid ""
@@ -50,6 +50,12 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr "El valor \"%(value)s\" del camp \"%(field)s\" de \"%(model)s\" no existeix."
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"El domini o el criteri de cerca \"%(domain)s\" de l'acció \"%(action)s\" no "
+"és correcte."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "El context \"%(context)s\" de l'acció \"%(action)s\" no és correcte."
@@ -66,17 +72,13 @@ msgstr "La vista \"%(view)s\" per l'acció \"%(action)s\" no és correcte."
 
 msgctxt "error:ir.action.keyword:"
 msgid "Wrong wizard model in keyword action \"%s\"."
-msgstr "Assistent incorrecte en l'acció de teclat \"%s\"."
+msgstr "El model d'assistent és incorrecte en l'acció de teclat \"%s\"."
 
 msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr ""
 "La definició del correu electrònic sobre l'informe \"%s\", no és correcta."
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "El nom intern ha de ser únic per mòdul."
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr "El nom dels adjunts ha de ser únic per recurs."
@@ -921,7 +923,7 @@ msgstr "Data"
 
 msgctxt "field:ir.attachment,data_size:"
 msgid "Data size"
-msgstr "Camp data"
+msgstr "Mida de les dades"
 
 msgctxt "field:ir.attachment,description:"
 msgid "Description"
@@ -1209,7 +1211,7 @@ msgstr "Direcció"
 
 msgctxt "field:ir.lang,grouping:"
 msgid "Grouping"
-msgstr "Agrupant"
+msgstr "Agrupació"
 
 msgctxt "field:ir.lang,id:"
 msgid "ID"
@@ -1265,7 +1267,7 @@ msgstr "Informació"
 
 msgctxt "field:ir.model,model:"
 msgid "Model Name"
-msgstr "Nom del model"
+msgstr "Model"
 
 msgctxt "field:ir.model,module:"
 msgid "Module"
@@ -2189,7 +2191,7 @@ msgstr "Idioma"
 
 msgctxt "field:ir.trigger,action_function:"
 msgid "Action Function"
-msgstr "Acció funció"
+msgstr "Funció de l'acció"
 
 msgctxt "field:ir.trigger,action_model:"
 msgid "Action Model"
@@ -2219,7 +2221,7 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Número límit"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
 msgstr "Retard mínim"
 
@@ -2237,7 +2239,7 @@ msgstr "Al crear"
 
 msgctxt "field:ir.trigger,on_delete:"
 msgid "On Delete"
-msgstr "Al eliminar"
+msgstr "A l'eliminar"
 
 msgctxt "field:ir.trigger,on_time:"
 msgid "On Time"
@@ -2660,7 +2662,7 @@ msgid ""
 "Leave empty for the same as template, see unoconv documentation for "
 "compatible format"
 msgstr ""
-"Deixeu-ho en blanc per mantenir el mateix que a la plantilla, veieu la "
+"Deixeu-lo buit per mantenir el mateix que a la plantilla, consulteu la "
 "documentació de l'unoconv per conèixer formats compatibles."
 
 msgctxt "help:ir.action.report,style:"
@@ -2714,8 +2716,12 @@ msgid ""
 msgstr "Una expressió regular de Python per excloure models de les gràfiques."
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
-msgstr "El domini s'avalua amb \"user\" com l'usuari actual."
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
+msgstr ""
+"El domini s'avalua amb PYSON on el context conté:\n"
+"- \"user\" és l'usuari actual"
 
 msgctxt "help:ir.rule.group,default_p:"
 msgid "Add this rule to all users by default"
@@ -2746,16 +2752,16 @@ msgid ""
 "Limit the number of call to \"Action Function\" by records.\n"
 "0 for no limit."
 msgstr ""
-"Número límit de crides \"Acció de funcions\".\n"
-"0 per no límit"
+"Número límit de crides a la \"Funció de l'acció\".\n"
+"Poseu un 0 per indicar no límit."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Marge mínim en minuts entre les crides \"Acció de funció\" pel mateix registre.\n"
-"0 per no deixar marge."
+"Indica un temps mínim de retard entre crides a la \"Funció de l'acció\" per al mateix registre.\n"
+"Deixeu-ho buit si no hi ha retard."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -3093,7 +3099,7 @@ msgstr "Mòdul"
 
 msgctxt "model:ir.module.module.config_wizard.done,name:"
 msgid "Module Config Wizard Done"
-msgstr "Assistent de configuració del modul finalitzat"
+msgstr "Finalització assistent de configuració de mòduls"
 
 msgctxt "model:ir.module.module.config_wizard.first,name:"
 msgid "Module Config Wizard First"
@@ -3113,7 +3119,7 @@ msgstr "Dependències del mòdul"
 
 msgctxt "model:ir.module.module.install_upgrade.done,name:"
 msgid "Module Install Upgrade Done"
-msgstr "Finalització instal·lació/actualització mòdul"
+msgstr "Finalització instal·lació/actualització de mòduls"
 
 msgctxt "model:ir.module.module.install_upgrade.start,name:"
 msgid "Module Install Upgrade Start"
@@ -3687,6 +3693,10 @@ msgctxt "view:ir.attachment:"
 msgid "Attachments"
 msgstr "Adjunts"
 
+msgctxt "view:ir.attachment:"
+msgid "Last Modification Time"
+msgstr "Hora última modificació"
+
 msgctxt "view:ir.cron:"
 msgid "Action to trigger"
 msgstr "Acció de disparador"
@@ -3765,7 +3775,7 @@ msgstr "Configuració del mòdul"
 
 msgctxt "view:ir.module.module.config_wizard.done:"
 msgid "The configuration is done."
-msgstr "S'ha realitzat la configuració correctament."
+msgstr "La configuració s'ha realitzat correctament."
 
 msgctxt "view:ir.module.module.config_wizard.first:"
 msgid "Welcome to the module configuration wizard!"
@@ -3931,7 +3941,7 @@ msgstr "Neteja traduccions"
 
 msgctxt "view:ir.translation.clean.succeed:"
 msgid "Clean Translations Succeed!"
-msgstr "La neteja de les traduccions s'ha realitzat."
+msgstr "La neteja de les traduccions s'ha realitzat correctament."
 
 msgctxt "view:ir.translation.export.result:"
 msgid "Export Translation"
@@ -4034,11 +4044,11 @@ msgid "Print"
 msgstr "Imprimir"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "D'acord"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "D'acord"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4054,7 +4064,7 @@ msgid "Cancel"
 msgstr "Cancel·la"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "D'acord"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4074,7 +4084,7 @@ msgid "Cancel"
 msgstr "Cancel·la"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "D'acord"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4098,7 +4108,7 @@ msgid "Set"
 msgstr "Defineix"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "D'acord"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/locale/cs_CZ.po b/trytond/ir/locale/cs_CZ.po
index a149887..dbedf88 100644
--- a/trytond/ir/locale/cs_CZ.po
+++ b/trytond/ir/locale/cs_CZ.po
@@ -40,6 +40,10 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr ""
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr ""
@@ -60,10 +64,6 @@ msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr ""
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr ""
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr ""
@@ -2181,7 +2181,7 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr ""
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
 msgstr ""
 
@@ -2670,7 +2670,9 @@ msgid ""
 msgstr ""
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
 msgstr ""
 
 msgctxt "help:ir.rule.group,default_p:"
@@ -2699,10 +2701,10 @@ msgid ""
 "0 for no limit."
 msgstr ""
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
 
 msgctxt "help:ir.ui.view_search,domain:"
@@ -3979,11 +3981,11 @@ msgid "Print"
 msgstr ""
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr ""
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr ""
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -3999,7 +4001,7 @@ msgid "Cancel"
 msgstr ""
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr ""
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4019,7 +4021,7 @@ msgid "Cancel"
 msgstr ""
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr ""
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4043,7 +4045,7 @@ msgid "Set"
 msgstr ""
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr ""
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/locale/de_DE.po b/trytond/ir/locale/de_DE.po
index 7009184..cbcdb44 100644
--- a/trytond/ir/locale/de_DE.po
+++ b/trytond/ir/locale/de_DE.po
@@ -50,6 +50,12 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr "Der Wert \"%(value)s\" für Feld \"%(field)s\" in \"%(model)s\" existiert nicht."
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"Ungültiger Wertebereich (Domain) oder Suchkriterien \"%(domain)s\" in Aktion"
+" \"%(action)s\"."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "Ungültiger Kontext \"%(context)s\" in Aktion \"%(action)s\"."
@@ -72,10 +78,6 @@ msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr "Ungültige E-Mailadresse in Bericht \"%s\"."
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "Der interne Name kann pro Modul nur einmal vergeben werden!"
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr ""
@@ -2197,7 +2199,7 @@ msgstr "Sprache"
 
 msgctxt "field:ir.trigger,action_function:"
 msgid "Action Function"
-msgstr "Aktion Funktion"
+msgstr "Aktionsfunktion"
 
 msgctxt "field:ir.trigger,action_model:"
 msgid "Action Model"
@@ -2227,9 +2229,9 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Limit Durchläufe"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
-msgstr "Minimale Verzögerung"
+msgstr "Minimalverzögerung"
 
 msgctxt "field:ir.trigger,model:"
 msgid "Model"
@@ -2381,7 +2383,7 @@ msgstr "Gruppen"
 
 msgctxt "field:ir.ui.menu,icon:"
 msgid "Icon"
-msgstr "Icon"
+msgstr "Symbol"
 
 msgctxt "field:ir.ui.menu,id:"
 msgid "ID"
@@ -2724,10 +2726,12 @@ msgstr ""
 "Graphen ausschließen."
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
 msgstr ""
-"Wertebereich (Domain) wird ausgewertet mit \"user\" als der aktuelle "
-"Benutzer"
+"Wertebereich (Domain) wird ausgewertet mit einem PYSON Kontext, der enthält:\n"
+"- \"user\" als der aktuelle Benutzer"
 
 msgctxt "help:ir.rule.group,default_p:"
 msgid "Add this rule to all users by default"
@@ -2762,13 +2766,13 @@ msgstr ""
 "Begrenzung der Anzahl von Aufrufen von \"Aktion Funktion\" durch Datensätze.\n"
 "0 bedeutet kein Limit."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Minimale Verzögerung in Minuten zwischen Aufrufen von \"Aktion Funktion\" für denselben Datensatz.\n"
-"0 bedeutet keine Verzögerung."
+"Einen Minimalzeitraum setzen für den Aufruf von 'Aktionsfunktion' für denselben Datensatz.\n"
+"Leer bedeutet keine Verzögerung."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -3700,6 +3704,10 @@ msgctxt "view:ir.attachment:"
 msgid "Attachments"
 msgstr "Anhänge"
 
+msgctxt "view:ir.attachment:"
+msgid "Last Modification Time"
+msgstr "Letzte Änderung"
+
 msgctxt "view:ir.cron:"
 msgid "Action to trigger"
 msgstr "Auszuführende Aktion"
@@ -4080,11 +4088,11 @@ msgid "Print"
 msgstr "Drucken"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4100,7 +4108,7 @@ msgid "Cancel"
 msgstr "Abbrechen"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4120,7 +4128,7 @@ msgid "Cancel"
 msgstr "Abbrechen"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "OK"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4144,7 +4152,7 @@ msgid "Set"
 msgstr "Aktualisieren"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "OK"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/locale/es_AR.po b/trytond/ir/locale/es_AR.po
index d59aedf..1f8b326 100644
--- a/trytond/ir/locale/es_AR.po
+++ b/trytond/ir/locale/es_AR.po
@@ -4,14 +4,6 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:access_error:"
 msgid ""
-"You try to bypass an access rule!\n"
-"(Document type: %s)"
-msgstr ""
-"¡Está intentando evitar una regla de acceso!\n"
-"(Tipo de documento: %s)"
-
-msgctxt "error:access_error:"
-msgid ""
 "You try to bypass an access rule.\n"
 "(Document type: %s)"
 msgstr ""
@@ -49,6 +41,12 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr "El valor «%(value)s» del campo «%(field)s» en «%(model)s» no existe."
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"El dominio o el criterio de búsqueda «%(domain)s» en la acción «%(action)s» "
+"no es correcto."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "El contexto «%(context)s» en la acción «%(action)s» no es correcto."
@@ -72,10 +70,6 @@ msgid "Invalid email definition on report \"%s\"."
 msgstr ""
 "La definición de correo electrónico en el informe «%s» no es correcta."
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "¡El nombre interno de los módulos debe ser único!"
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr "¡Los nombres de los archivos adjuntos deben ser únicos por recurso!"
@@ -274,14 +268,6 @@ msgstr "XML de la vista «%s» no es correcto."
 
 msgctxt "error:read_error:"
 msgid ""
-"You try to read records that don't exist anymore!\n"
-"(Document type: %s)"
-msgstr ""
-"¡Está intentando leer registros que ya no existen!\n"
-"(Tipo de documento: %s)"
-
-msgctxt "error:read_error:"
-msgid ""
 "You try to read records that don't exist anymore.\n"
 "(Document type: %s)"
 msgstr ""
@@ -346,14 +332,6 @@ msgstr "Demasiadas relaciones encontradas: %r en %s"
 
 msgctxt "error:write_error:"
 msgid ""
-"You try to write on records that don't exist anymore!\n"
-"(Document type: %s)"
-msgstr ""
-"¡Está tratando de escribir en registros que ya no existen!\n"
-"(Tipo de documento: %s)"
-
-msgctxt "error:write_error:"
-msgid ""
 "You try to write on records that don't exist anymore.\n"
 "(Document type: %s)"
 msgstr ""
@@ -2224,7 +2202,7 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Número Límite"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
 msgstr "Retardo Mínimo"
 
@@ -2721,8 +2699,12 @@ msgstr ""
 "la gráfica."
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
-msgstr "El dominio es evaluado con «user» que es el usuario actual."
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
+msgstr ""
+"El dominio es evaluado con un contexto PYSON que contiene:\n"
+"- «user» como el usuario actual"
 
 msgctxt "help:ir.rule.group,default_p:"
 msgid "Add this rule to all users by default"
@@ -2756,13 +2738,13 @@ msgstr ""
 "Limitar el número de llamada a la \"Función de Acción\" por registros.\n"
 "0 para ningún límite."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Establecer un retraso mínimo en minutos entre llamadas a la «Función de la acción» para el mismo registro.\n"
-"0 para ningún retraso."
+"Definir un retraso mínimo entre llamadas a la «Función de la acción» para el mismo registro.\n"
+"Vacío para ningún retraso."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -3108,8 +3090,7 @@ msgstr "Asistente de configuración inicial del módulo"
 
 msgctxt "model:ir.module.module.config_wizard.item,name:"
 msgid "Config wizard to run after installing module"
-msgstr ""
-"El asistente de configuración a ejecutar después de instalar un módulo"
+msgstr "Asistente de configuración después de instalar un módulo"
 
 msgctxt "model:ir.module.module.config_wizard.other,name:"
 msgid "Module Config Wizard Other"
@@ -3493,7 +3474,7 @@ msgstr "Realizado"
 
 msgctxt "selection:ir.module.module.config_wizard.item,state:"
 msgid "Open"
-msgstr "Para abrir"
+msgstr "Pendiente"
 
 msgctxt "selection:ir.module.module.dependency,state:"
 msgid "Installed"
@@ -3605,7 +3586,7 @@ msgstr "Tablón"
 
 msgctxt "selection:ir.ui.view,type:"
 msgid "Calendar"
-msgstr "Calendarios"
+msgstr "Calendario"
 
 msgctxt "selection:ir.ui.view,type:"
 msgid "Form"
@@ -3695,6 +3676,10 @@ msgctxt "view:ir.attachment:"
 msgid "Attachments"
 msgstr "Adjuntos"
 
+msgctxt "view:ir.attachment:"
+msgid "Last Modification Time"
+msgstr "Última Modificación"
+
 msgctxt "view:ir.cron:"
 msgid "Action to trigger"
 msgstr "Acción a disparar"
@@ -3881,38 +3866,6 @@ msgctxt "view:ir.rule:"
 msgid "Test"
 msgstr "Prueba"
 
-msgctxt "view:ir.sequence.strict:"
-msgid "${day}"
-msgstr "${day}"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "${month}"
-msgstr "${month}"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "${year}"
-msgstr "${year}"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Day:"
-msgstr "Día:"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Legend (for prefix, suffix)"
-msgstr "Leyenda (variables para usar en prefijo y/o sufijo)"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Month:"
-msgstr "Mes:"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Sequences Strict"
-msgstr "Secuencias estrictas"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Year:"
-msgstr "Año:"
-
 msgctxt "view:ir.sequence.type:"
 msgid "Sequence Type"
 msgstr "Tipo de secuencia"
@@ -4074,11 +4027,11 @@ msgid "Print"
 msgstr "Imprimir"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4094,7 +4047,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4114,7 +4067,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4138,7 +4091,7 @@ msgid "Set"
 msgstr "Definir"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/locale/es_CO.po b/trytond/ir/locale/es_CO.po
index 7b69425..f8cf8d3 100644
--- a/trytond/ir/locale/es_CO.po
+++ b/trytond/ir/locale/es_CO.po
@@ -50,6 +50,12 @@ msgstr ""
 "El valor \"%(value)s\" en el campo \"%(field)s\" en el modelo \"%(model)s\" "
 "no existe."
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"Dominio inválido o criterio de busqueda \"%(domain)s\" en acción "
+"\"%(action)s\"."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "Contexto inválido \"%(context)s\" en action \"%(action)s\"."
@@ -72,10 +78,6 @@ msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr "El correo electronico definido en el informe \"%s\" es inválido."
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "¡El nombre interno de los módulos debe ser único!"
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr "¡Los nombres de los archivos adjuntos deben ser únicos por recurso!"
@@ -2222,9 +2224,9 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Número Límite"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
-msgstr "Retardo Mínimo"
+msgstr ""
 
 msgctxt "field:ir.trigger,model:"
 msgid "Model"
@@ -2719,8 +2721,12 @@ msgstr ""
 "concuerden con el gráfico."
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
-msgstr "El dominio es evaluado con el \"usuario\" como el usuario actual"
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
+msgstr ""
+"El dominio es evaluado en un contexto PYSON conteniendo: - \"usuario\" como "
+"el usuario actual"
 
 msgctxt "help:ir.rule.group,default_p:"
 msgid "Add this rule to all users by default"
@@ -2754,13 +2760,13 @@ msgstr ""
 "Limitar el número de llamada a la \"Función de Acción\" por registros.\n"
 "0 para ningún límite."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Establecer un retraso mínimo en minutos entre llamadas a la \"Función de Acción\" para el mismo registro.\n"
-"0 para ningún retraso."
+"Fijar un tiempo mínimo de retraso entre la llamada a la \"Función Acción\" "
+"para el mismo registro. Dejar vacío para no retraso."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -3693,6 +3699,10 @@ msgctxt "view:ir.attachment:"
 msgid "Attachments"
 msgstr "Adjuntos"
 
+msgctxt "view:ir.attachment:"
+msgid "Last Modification Time"
+msgstr ""
+
 msgctxt "view:ir.cron:"
 msgid "Action to trigger"
 msgstr "Acción a disparar"
@@ -4073,11 +4083,11 @@ msgid "Print"
 msgstr "Imprimir"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4093,7 +4103,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4113,7 +4123,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4137,7 +4147,7 @@ msgid "Set"
 msgstr "Establecer"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/locale/es_EC.po b/trytond/ir/locale/es_EC.po
index de8595c..e79ca9b 100644
--- a/trytond/ir/locale/es_EC.po
+++ b/trytond/ir/locale/es_EC.po
@@ -4,14 +4,6 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:access_error:"
 msgid ""
-"You try to bypass an access rule!\n"
-"(Document type: %s)"
-msgstr ""
-"¡Está intentando evitar una regla de acceso!\n"
-"(Tipo de documento: %s)"
-
-msgctxt "error:access_error:"
-msgid ""
 "You try to bypass an access rule.\n"
 "(Document type: %s)"
 msgstr ""
@@ -27,8 +19,8 @@ msgid ""
 "The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
 "exceeds its limit."
 msgstr ""
-"El número de decimales \"%(digits)s\" del campo \"%(field)s\" en el "
-"valor\"%(value)s\" excede su limite."
+"El número de decimales \"%(digits)s\" del campo \"%(field)s\" en "
+"\"%(value)s\" excede su limite."
 
 msgctxt "error:domain_validation_record:"
 msgid ""
@@ -50,6 +42,12 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr "El valor \"%(value)s\" del campo \"%(field)s\" en \"%(model)s\" no existe."
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"El dominio o el criterio de búsqueda \"%(domain)s\" en la acción "
+"«%(action)s» no es válido."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "El contexto \"%(context)s\" en la acción \"%(action)s\" no es válido."
@@ -70,11 +68,7 @@ msgstr "Modelo de asistente incorrecto en la acción de teclado \"%s\"."
 
 msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
-msgstr "La definición de correo electrónico sobre el informe \"%s\" no es válido."
-
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "¡El nombre interno debe ser único por módulo!"
+msgstr "La definición de correo electrónico sobre el informe \"%s\" no es válida."
 
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
@@ -92,7 +86,7 @@ msgid ""
 "\n"
 "%s\n"
 msgstr ""
-"La siguiente acción falló cuando se ejecutaba: \"%s\"\n"
+"Ha fallado la acción cuando se ejecutaba: \"%s\"\n"
 "%s\n"
 " Traza del programa:\n"
 "\n"
@@ -105,12 +99,12 @@ msgstr "El idioma por defecto no puede ser eliminado."
 msgctxt "error:ir.lang:"
 msgid "Invalid date format \"%(format)s\" on \"%(language)s\" language."
 msgstr ""
-"El formato de la fecha \"%(format)s\" para el idioma \"%(languages)s\" no es"
-" válido."
+"El formato de la fecha \"%(format)s\" del idioma \"%(languages)s\" no es "
+"válido."
 
 msgctxt "error:ir.lang:"
 msgid "Invalid grouping \"%(grouping)s\" on \"%(language)s\" language."
-msgstr "Agrupamiento no válido \"%(grouping)s\" en idioma \"%(languages)s\"."
+msgstr "La agrupación \"%(grouping)s\" del idioma \"%(languages)s\" no es válida."
 
 msgctxt "error:ir.lang:"
 msgid "The default language must be translatable."
@@ -142,7 +136,7 @@ msgstr "¡El nombre del botón en el modelo debe ser único!"
 
 msgctxt "error:ir.model.data:"
 msgid "The triple (fs_id, module, model) must be unique!"
-msgstr "La terna (fs_id, modulo, modelo) debe ser única!"
+msgstr "¡La terna (fs_id, modulo, modelo) debe ser única!"
 
 msgctxt "error:ir.model.field.access:"
 msgid "You can not read the field! (%s.%s)"
@@ -154,7 +148,8 @@ msgstr "¡No puede escribir en el campo! (%s.%s)"
 
 msgctxt "error:ir.model.field:"
 msgid "Model Field name \"%s\" is not a valid python identifier."
-msgstr "El nombre del Campo Modelo \"%s\" no es un identificador python válido."
+msgstr ""
+"El nombre del Campo Modelo \"%s\" no es un identificador de Python válido."
 
 msgctxt "error:ir.model.field:"
 msgid "The field name in model must be unique!"
@@ -162,7 +157,7 @@ msgstr "¡El nombre del campo en el modelo debe ser único!"
 
 msgctxt "error:ir.model:"
 msgid "Module name \"%s\" is not a valid python identifier."
-msgstr "El nombre del modulo \"%s\" no es un identificador python válido."
+msgstr "El nombre del modulo \"%s\" no es un identificador de Python válido."
 
 msgctxt "error:ir.model:"
 msgid "The model must be unique!"
@@ -170,7 +165,7 @@ msgstr "¡El modelo debe ser único!"
 
 msgctxt "error:ir.module.module.dependency:"
 msgid "Dependency must be unique by module!"
-msgstr "¡Las dependencias por módulo deben ser únicas!"
+msgstr "¡Las dependencias deben ser únicas por módulo!"
 
 msgctxt "error:ir.module.module:"
 msgid "Missing dependencies %s for module \"%s\""
@@ -187,28 +182,27 @@ msgstr "¡El nombre del módulo debe ser único!"
 
 msgctxt "error:ir.module.module:"
 msgid "You can not remove a module that is installed or will be installed"
-msgstr ""
-"No puede eliminar un módulo que está instalado o que va a ser instalado"
+msgstr "No puede eliminar un módulo que está instalado o que será instalado"
 
 msgctxt "error:ir.rule.group:"
 msgid "Global and Default are mutually exclusive!"
-msgstr "Global y Predeterminado son mutuamente excluyentes"
+msgstr "¡\"Global\" y \"Por Defecto\" son mutuamente excluyentes!"
 
 msgctxt "error:ir.rule:"
 msgid "Invalid domain in rule \"%s\"."
-msgstr "Dominio no válido en la regla \"%s\"."
+msgstr "El dominio en la regla \"%s\" no es válido."
 
 msgctxt "error:ir.sequence.strict:"
 msgid "Invalid prefix \"%(prefix)s\" on sequence \"%(sequence)s\"."
-msgstr "El prefijo \"%(prefix)s\" en la secuencia \"%(sequence)s\" no es válido."
+msgstr "El prefijo \"%(prefix)s\" de la secuencia \"%(sequence)s\" no es válido."
 
 msgctxt "error:ir.sequence.strict:"
 msgid "Invalid suffix \"%(suffix)s\" on sequence \"%(sequence)s\"."
-msgstr "El sufijo \"%(sufix)s\" de la secuencia \"%(sequence)s\" no es válido."
+msgstr "El sufijo \"%(suffix)s\" de la secuencia \"%(sequence)s\" no es válido."
 
 msgctxt "error:ir.sequence.strict:"
 msgid "Last Timestamp cannot be in the future on sequence \"%s\"."
-msgstr "La ultima fecha-hora no puede ser del futuro para la secuencia \"%s\"."
+msgstr "La ultima fecha-hora no puede ser del futuro en la secuencia \"%s\"."
 
 msgctxt "error:ir.sequence.strict:"
 msgid "Missing sequence."
@@ -220,16 +214,15 @@ msgstr "El redondeo de la fecha-hora debe ser mayor que 0"
 
 msgctxt "error:ir.sequence:"
 msgid "Invalid prefix \"%(prefix)s\" on sequence \"%(sequence)s\"."
-msgstr "El prefijo \"%(prefix)s\" en la secuencia \"%(sequence)s\" no es válido."
+msgstr "El prefijo \"%(prefix)s\" de la secuencia \"%(sequence)s\" no es válido."
 
 msgctxt "error:ir.sequence:"
 msgid "Invalid suffix \"%(suffix)s\" on sequence \"%(sequence)s\"."
-msgstr "El sufijo \"%(sufix)s\" de la secuencia \"%(sequence)s\" no es válido."
+msgstr "El sufijo \"%(suffix)s\" de la secuencia \"%(sequence)s\" no es válido."
 
 msgctxt "error:ir.sequence:"
 msgid "Last Timestamp cannot be in the future on sequence \"%s\"."
-msgstr ""
-"La ultima fecha-hora no puede estar en el futuro para la secuencia \"%s\"."
+msgstr "La ultima fecha-hora no puede ser del futuro en la secuencia \"%s\"."
 
 msgctxt "error:ir.sequence:"
 msgid "Missing sequence."
@@ -248,7 +241,7 @@ msgid ""
 "You can not export translation %(name)s because it is an overridden "
 "translation by module %(overriding_module)s"
 msgstr ""
-"No puede exportar la traduccion %(name)s porque es una traducción "
+"No puede exportar la traducción %(name)s porque es una traducción "
 "sobreescrita por el módulo %(overrinding_module)s"
 
 msgctxt "error:ir.trigger:"
@@ -265,19 +258,11 @@ msgstr ""
 
 msgctxt "error:ir.ui.menu:"
 msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
-msgstr "\"%s\" no es un nombre válido de menú porque no esta permitido contener \"/\""
+msgstr "\"%s\" no es un nombre de menú válido porque no puede contener \"/\""
 
 msgctxt "error:ir.ui.view:"
 msgid "Invalid XML for view \"%s\"."
-msgstr "XML no válido para la vista \"%s\"."
-
-msgctxt "error:read_error:"
-msgid ""
-"You try to read records that don't exist anymore!\n"
-"(Document type: %s)"
-msgstr ""
-"¡Está intentando leer registros que ya no existen!\n"
-"(Tipo de documento: %s)"
+msgstr "XML de la vista \"%s\" no es válido."
 
 msgctxt "error:read_error:"
 msgid ""
@@ -297,7 +282,7 @@ msgstr ""
 
 msgctxt "error:reference_syntax_error:"
 msgid "Syntax error for reference %r in %s"
-msgstr "Error de sintaxis para referencia %r en %s"
+msgstr "Error de sintaxis para la referencia %r de %s"
 
 msgctxt "error:relation_not_found:"
 msgid "Relation not found: %r in %s"
@@ -305,15 +290,15 @@ msgstr "Relación no encontrada: %r en %s"
 
 msgctxt "error:required_field:"
 msgid "The field \"%(field)s\" on \"%(model)s\" is required."
-msgstr "El campo \"%(field)s\" en \"%(model)s\" es requerido."
+msgstr "El campo \"%(field)s\" de \"%(model)s\" es requerido."
 
 msgctxt "error:required_validation_record:"
 msgid "The field \"%(field)s\" on \"%(model)s\" is required."
-msgstr "El campo \"%(field)s\" en \"%(model)s\" es requerido."
+msgstr "El campo \"%(field)s\" de \"%(model)s\" es requerido."
 
 msgctxt "error:search_function_missing:"
 msgid "Missing search function on field \"%s\"."
-msgstr "Falta la función de búsqueda en el campo \"%s\"."
+msgstr "Falta la función de búsqueda del campo \"%s\"."
 
 msgctxt "error:selection_validation_record:"
 msgid ""
@@ -321,7 +306,7 @@ msgid ""
 "the selection."
 msgstr ""
 "El valor \"%(value)s\" del campo \"%(field)s\" en el modelo \"%(model)s\" no"
-" esta en la selección."
+" está en la selección."
 
 msgctxt "error:selection_value_notfound:"
 msgid "Value not in the selection for field \"%s\"."
@@ -330,7 +315,7 @@ msgstr "El valor no se encuentra en la selección para el campo \"%s\"."
 msgctxt "error:size_validation_record:"
 msgid "The size \"%(size)s\" of the field \"%(field)s\" on \"%(model)s\" is too long."
 msgstr ""
-"El tamaño \"%(size)s\" del campo \"%(field)s\" en \"%(model)s\" is demasiado"
+"El tamaño \"%(size)s\" del campo \"%(field)s\" en \"%(model)s\" es demasiado"
 " largo."
 
 msgctxt "error:time_format_validation_record:"
@@ -341,15 +326,7 @@ msgstr ""
 
 msgctxt "error:too_many_relations_found:"
 msgid "Too many relations found: %r in %s"
-msgstr "Demasiadas relaciones encontradas: %r en %s"
-
-msgctxt "error:write_error:"
-msgid ""
-"You try to write on records that don't exist anymore!\n"
-"(Document type: %s)"
-msgstr ""
-"¡Está tratando de escribir en registros que ya no existen!\n"
-"(Tipo de documento: %s)"
+msgstr "Se han encontrado demasiadas relaciones: %r en %s"
 
 msgctxt "error:write_error:"
 msgid ""
@@ -365,7 +342,7 @@ msgstr "No está autorizado para modificar este registro."
 
 msgctxt "error:xml_id_syntax_error:"
 msgid "Syntax error for XML id %r in %s"
-msgstr "Error de sintaxis para XML id %r en %s"
+msgstr "Error de sintaxis para el XML id %r de %s"
 
 msgctxt "error:xml_record_desc:"
 msgid "This record is part of the base configuration."
@@ -377,11 +354,11 @@ msgstr "Activo"
 
 msgctxt "field:ir.action,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action,groups:"
 msgid "Groups"
@@ -417,11 +394,11 @@ msgstr "Uso"
 
 msgctxt "field:ir.action,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.action.act_window,act_window_domains:"
 msgid "Domains"
@@ -441,19 +418,19 @@ msgstr "Activo"
 
 msgctxt "field:ir.action.act_window,context:"
 msgid "Context Value"
-msgstr "Valor del Contexto"
+msgstr "Valor del contexto"
 
 msgctxt "field:ir.action.act_window,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action.act_window,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action.act_window,domain:"
 msgid "Domain Value"
-msgstr "Valor del Dominio"
+msgstr "Valor del dominio"
 
 msgctxt "field:ir.action.act_window,domains:"
 msgid "Domains"
@@ -485,7 +462,7 @@ msgstr "Nombre"
 
 msgctxt "field:ir.action.act_window,order:"
 msgid "Order Value"
-msgstr "Valor Ordenado"
+msgstr "Valor del orden"
 
 msgctxt "field:ir.action.act_window,pyson_context:"
 msgid "PySON Context"
@@ -493,7 +470,7 @@ msgstr "Contexto PySON"
 
 msgctxt "field:ir.action.act_window,pyson_domain:"
 msgid "PySON Domain"
-msgstr "Dominio de PySON"
+msgstr "Dominio PySON"
 
 msgctxt "field:ir.action.act_window,pyson_order:"
 msgid "PySON Order"
@@ -501,7 +478,7 @@ msgstr "Orden PySON"
 
 msgctxt "field:ir.action.act_window,pyson_search_value:"
 msgid "PySON Search Criteria"
-msgstr "Criterio de Búsqueda de PySON"
+msgstr "Criterio de búsqueda PySON"
 
 msgctxt "field:ir.action.act_window,rec_name:"
 msgid "Name"
@@ -513,7 +490,7 @@ msgstr "Modelo"
 
 msgctxt "field:ir.action.act_window,search_value:"
 msgid "Search Criteria"
-msgstr "Criterio de Búsqueda"
+msgstr "Criterio de búsqueda"
 
 msgctxt "field:ir.action.act_window,type:"
 msgid "Type"
@@ -529,15 +506,15 @@ msgstr "Vistas"
 
 msgctxt "field:ir.action.act_window,window_name:"
 msgid "Window Name"
-msgstr "Nombre de Ventana"
+msgstr "Nombre de la ventana"
 
 msgctxt "field:ir.action.act_window,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action.act_window,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.action.act_window.domain,act_window:"
 msgid "Action"
@@ -549,11 +526,11 @@ msgstr "Activo"
 
 msgctxt "field:ir.action.act_window.domain,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action.act_window.domain,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action.act_window.domain,domain:"
 msgid "Domain"
@@ -577,11 +554,11 @@ msgstr "Secuencia"
 
 msgctxt "field:ir.action.act_window.domain,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action.act_window.domain,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.action.act_window.view,act_window:"
 msgid "Action"
@@ -593,11 +570,11 @@ msgstr "Activo"
 
 msgctxt "field:ir.action.act_window.view,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action.act_window.view,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action.act_window.view,id:"
 msgid "ID"
@@ -617,11 +594,11 @@ msgstr "Vista"
 
 msgctxt "field:ir.action.act_window.view,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action.act_window.view,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.action.keyword,action:"
 msgid "Action"
@@ -629,11 +606,11 @@ msgstr "Acción"
 
 msgctxt "field:ir.action.keyword,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action.keyword,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action.keyword,groups:"
 msgid "Groups"
@@ -645,7 +622,7 @@ msgstr "ID"
 
 msgctxt "field:ir.action.keyword,keyword:"
 msgid "Keyword"
-msgstr "Tecla"
+msgstr "Acción de teclado"
 
 msgctxt "field:ir.action.keyword,model:"
 msgid "Model"
@@ -657,11 +634,11 @@ msgstr "Nombre"
 
 msgctxt "field:ir.action.keyword,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action.keyword,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.action.report,action:"
 msgid "Action"
@@ -673,11 +650,11 @@ msgstr "Activo"
 
 msgctxt "field:ir.action.report,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action.report,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action.report,direct_print:"
 msgid "Direct Print"
@@ -769,11 +746,11 @@ msgstr "Uso"
 
 msgctxt "field:ir.action.report,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action.report,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.action.url,action:"
 msgid "Action"
@@ -785,11 +762,11 @@ msgstr "Activo"
 
 msgctxt "field:ir.action.url,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action.url,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action.url,groups:"
 msgid "Groups"
@@ -805,7 +782,7 @@ msgstr "ID"
 
 msgctxt "field:ir.action.url,keywords:"
 msgid "Keywords"
-msgstr "Teclas"
+msgstr "Acción de teclado"
 
 msgctxt "field:ir.action.url,name:"
 msgid "Name"
@@ -821,7 +798,7 @@ msgstr "Tipo"
 
 msgctxt "field:ir.action.url,url:"
 msgid "Action Url"
-msgstr "URL de la acción"
+msgstr "Acción de URL"
 
 msgctxt "field:ir.action.url,usage:"
 msgid "Usage"
@@ -829,11 +806,11 @@ msgstr "Uso"
 
 msgctxt "field:ir.action.url,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action.url,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.action.wizard,action:"
 msgid "Action"
@@ -845,11 +822,11 @@ msgstr "Activo"
 
 msgctxt "field:ir.action.wizard,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action.wizard,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action.wizard,email:"
 msgid "Email"
@@ -869,7 +846,7 @@ msgstr "ID"
 
 msgctxt "field:ir.action.wizard,keywords:"
 msgid "Keywords"
-msgstr "Teclas"
+msgstr "Acción de teclado"
 
 msgctxt "field:ir.action.wizard,model:"
 msgid "Model"
@@ -901,11 +878,11 @@ msgstr "Nombre del asistente"
 
 msgctxt "field:ir.action.wizard,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action.wizard,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.attachment,collision:"
 msgid "Collision"
@@ -913,11 +890,11 @@ msgstr "Colisión"
 
 msgctxt "field:ir.attachment,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.attachment,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.attachment,data:"
 msgid "Data"
@@ -925,7 +902,7 @@ msgstr "Datos"
 
 msgctxt "field:ir.attachment,data_size:"
 msgid "Data size"
-msgstr "Tamaño de datos"
+msgstr "Tamaño de los datos"
 
 msgctxt "field:ir.attachment,description:"
 msgid "Description"
@@ -973,19 +950,19 @@ msgstr "Tipo"
 
 msgctxt "field:ir.attachment,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.attachment,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.cache,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.cache,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.cache,id:"
 msgid "ID"
@@ -1001,23 +978,23 @@ msgstr "Nombre"
 
 msgctxt "field:ir.cache,timestamp:"
 msgid "Timestamp"
-msgstr "Marca de tiempo"
+msgstr "Fecha-hora"
 
 msgctxt "field:ir.cache,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.cache,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.configuration,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.configuration,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.configuration,id:"
 msgid "ID"
@@ -1025,7 +1002,7 @@ msgstr "ID"
 
 msgctxt "field:ir.configuration,language:"
 msgid "language"
-msgstr "Lenguaje"
+msgstr "Idioma"
 
 msgctxt "field:ir.configuration,rec_name:"
 msgid "Name"
@@ -1033,11 +1010,11 @@ msgstr "Nombre"
 
 msgctxt "field:ir.configuration,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.configuration,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.cron,active:"
 msgid "Active"
@@ -1049,11 +1026,11 @@ msgstr "Argumentos"
 
 msgctxt "field:ir.cron,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.cron,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.cron,function:"
 msgid "Function"
@@ -1065,11 +1042,11 @@ msgstr "ID"
 
 msgctxt "field:ir.cron,interval_number:"
 msgid "Interval Number"
-msgstr "Número de Intervalos"
+msgstr "Número de intervalos"
 
 msgctxt "field:ir.cron,interval_type:"
 msgid "Interval Unit"
-msgstr "Unidad de Intervalo"
+msgstr "Unidad de intervalo"
 
 msgctxt "field:ir.cron,model:"
 msgid "Model"
@@ -1081,11 +1058,11 @@ msgstr "Nombre"
 
 msgctxt "field:ir.cron,next_call:"
 msgid "Next Call"
-msgstr "Siguiente Llamada"
+msgstr "Siguiente llamada"
 
 msgctxt "field:ir.cron,number_calls:"
 msgid "Number of Calls"
-msgstr "Número de Llamadas"
+msgstr "Número de llamadas"
 
 msgctxt "field:ir.cron,rec_name:"
 msgid "Name"
@@ -1093,23 +1070,23 @@ msgstr "Nombre"
 
 msgctxt "field:ir.cron,repeat_missed:"
 msgid "Repeat Missed"
-msgstr "Repetir Fallidos"
+msgstr "Repetir fallidos"
 
 msgctxt "field:ir.cron,request_user:"
 msgid "Request User"
-msgstr "Solicitud de Usuario"
+msgstr "Usuario solicitante"
 
 msgctxt "field:ir.cron,user:"
 msgid "Execution User"
-msgstr "Ejecutado por"
+msgstr "Usuario para la ejecución"
 
 msgctxt "field:ir.cron,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.cron,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.date,id:"
 msgid "ID"
@@ -1117,11 +1094,11 @@ msgstr "ID"
 
 msgctxt "field:ir.export,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.export,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.export,export_fields:"
 msgid "Fields"
@@ -1145,19 +1122,19 @@ msgstr "Recurso"
 
 msgctxt "field:ir.export,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.export,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.export.line,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.export.line,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.export.line,export:"
 msgid "Export"
@@ -1177,11 +1154,11 @@ msgstr "Nombre"
 
 msgctxt "field:ir.export.line,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.export.line,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.lang,active:"
 msgid "Active"
@@ -1193,11 +1170,11 @@ msgstr "Código"
 
 msgctxt "field:ir.lang,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.lang,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.lang,date:"
 msgid "Date"
@@ -1229,7 +1206,7 @@ msgstr "Nombre"
 
 msgctxt "field:ir.lang,thousands_sep:"
 msgid "Thousands Separator"
-msgstr "Separador de Miles"
+msgstr "Separador de miles"
 
 msgctxt "field:ir.lang,translatable:"
 msgid "Translatable"
@@ -1237,19 +1214,19 @@ msgstr "Traducible"
 
 msgctxt "field:ir.lang,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.lang,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.model,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.model,fields:"
 msgid "Fields"
@@ -1257,7 +1234,7 @@ msgstr "Campos"
 
 msgctxt "field:ir.model,global_search_p:"
 msgid "Global Search"
-msgstr "Busqueda Global"
+msgstr "Busqueda global"
 
 msgctxt "field:ir.model,id:"
 msgid "ID"
@@ -1269,7 +1246,7 @@ msgstr "Información"
 
 msgctxt "field:ir.model,model:"
 msgid "Model Name"
-msgstr "Nombre del Modelo"
+msgstr "Nombre del modelo"
 
 msgctxt "field:ir.model,module:"
 msgid "Module"
@@ -1285,19 +1262,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.model,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.model,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model.access,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.model.access,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.model.access,description:"
 msgid "Description"
@@ -1317,19 +1294,19 @@ msgstr "Modelo"
 
 msgctxt "field:ir.model.access,perm_create:"
 msgid "Create Access"
-msgstr "Permiso para Crear"
+msgstr "Permiso para crear"
 
 msgctxt "field:ir.model.access,perm_delete:"
 msgid "Delete Access"
-msgstr "Permiso para Borrar"
+msgstr "Permiso para eliminar"
 
 msgctxt "field:ir.model.access,perm_read:"
 msgid "Read Access"
-msgstr "Permiso para Leer"
+msgstr "Permiso para leer"
 
 msgctxt "field:ir.model.access,perm_write:"
 msgid "Write Access"
-msgstr "Permiso para Modificar"
+msgstr "Permiso para modificar"
 
 msgctxt "field:ir.model.access,rec_name:"
 msgid "Name"
@@ -1337,19 +1314,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.model.access,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.model.access,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model.button,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.model.button,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.model.button,groups:"
 msgid "Groups"
@@ -1373,23 +1350,23 @@ msgstr "Nombre"
 
 msgctxt "field:ir.model.button,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.model.button,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model.data,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.model.data,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.model.data,db_id:"
 msgid "Resource ID"
-msgstr "ID del recurso"
+msgstr "ID del Recurso"
 
 msgctxt "field:ir.model.data,fs_id:"
 msgid "Identifier on File System"
@@ -1413,11 +1390,11 @@ msgstr "Módulo"
 
 msgctxt "field:ir.model.data,noupdate:"
 msgid "No Update"
-msgstr "No Actualizar"
+msgstr "Sin Actualizar"
 
 msgctxt "field:ir.model.data,out_of_sync:"
 msgid "Out of Sync"
-msgstr "Fuera de Sincronización"
+msgstr "Fuera de sincronización"
 
 msgctxt "field:ir.model.data,rec_name:"
 msgid "Name"
@@ -1429,23 +1406,23 @@ msgstr "Valores"
 
 msgctxt "field:ir.model.data,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.model.data,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model.field,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.model.field,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.model.field,field_description:"
 msgid "Field Description"
-msgstr "Descripción del Campo"
+msgstr "Descripción del campo"
 
 msgctxt "field:ir.model.field,groups:"
 msgid "Groups"
@@ -1477,27 +1454,27 @@ msgstr "Nombre"
 
 msgctxt "field:ir.model.field,relation:"
 msgid "Model Relation"
-msgstr "Relación del Modelo"
+msgstr "Relación del modelo"
 
 msgctxt "field:ir.model.field,ttype:"
 msgid "Field Type"
-msgstr "Tipo de Campo"
+msgstr "Tipo de campo"
 
 msgctxt "field:ir.model.field,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.model.field,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model.field.access,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.model.field.access,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.model.field.access,description:"
 msgid "Description"
@@ -1517,19 +1494,19 @@ msgstr "ID"
 
 msgctxt "field:ir.model.field.access,perm_create:"
 msgid "Create Access"
-msgstr "Permiso para Crear"
+msgstr "Permiso para crear"
 
 msgctxt "field:ir.model.field.access,perm_delete:"
 msgid "Delete Access"
-msgstr "Permiso para Eliminar"
+msgstr "Permiso para eliminar"
 
 msgctxt "field:ir.model.field.access,perm_read:"
 msgid "Read Access"
-msgstr "Permiso para Leer"
+msgstr "Permiso para leer"
 
 msgctxt "field:ir.model.field.access,perm_write:"
 msgid "Write Access"
-msgstr "Permiso para Modificar"
+msgstr "Permiso para modificar"
 
 msgctxt "field:ir.model.field.access,rec_name:"
 msgid "Name"
@@ -1537,11 +1514,11 @@ msgstr "Nombre"
 
 msgctxt "field:ir.model.field.access,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.model.field.access,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model.print_model_graph.start,filter:"
 msgid "Filter"
@@ -1561,11 +1538,11 @@ msgstr "Hijos"
 
 msgctxt "field:ir.module.module,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.module.module,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.module.module,dependencies:"
 msgid "Dependencies"
@@ -1597,11 +1574,11 @@ msgstr "Versión"
 
 msgctxt "field:ir.module.module,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.module.module,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.module.module.config_wizard.done,id:"
 msgid "ID"
@@ -1617,11 +1594,11 @@ msgstr "Acción"
 
 msgctxt "field:ir.module.module.config_wizard.item,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.module.module.config_wizard.item,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.module.module.config_wizard.item,id:"
 msgid "ID"
@@ -1641,11 +1618,11 @@ msgstr "Estado"
 
 msgctxt "field:ir.module.module.config_wizard.item,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.module.module.config_wizard.item,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.module.module.config_wizard.other,id:"
 msgid "ID"
@@ -1657,11 +1634,11 @@ msgstr "Porcentaje"
 
 msgctxt "field:ir.module.module.dependency,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.module.module.dependency,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.module.module.dependency,id:"
 msgid "ID"
@@ -1685,11 +1662,11 @@ msgstr "Estado"
 
 msgctxt "field:ir.module.module.dependency,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.module.module.dependency,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.module.module.install_upgrade.done,id:"
 msgid "ID"
@@ -1705,11 +1682,11 @@ msgstr "Módulos a actualizar"
 
 msgctxt "field:ir.property,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.property,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.property,field:"
 msgid "Field"
@@ -1733,19 +1710,19 @@ msgstr "Valor"
 
 msgctxt "field:ir.property,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.property,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.rule,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.rule,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.rule,domain:"
 msgid "Domain"
@@ -1765,23 +1742,23 @@ msgstr "Grupo"
 
 msgctxt "field:ir.rule,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.rule,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.rule.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.rule.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.rule.group,default_p:"
 msgid "Default"
-msgstr "Predeterminado"
+msgstr "Por defecto"
 
 msgctxt "field:ir.rule.group,global_p:"
 msgid "Global"
@@ -1805,19 +1782,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.rule.group,perm_create:"
 msgid "Create Access"
-msgstr "Permiso para Crear"
+msgstr "Permiso para crear"
 
 msgctxt "field:ir.rule.group,perm_delete:"
 msgid "Delete Access"
-msgstr "Permiso para Eliminar"
+msgstr "Permiso para eliminar"
 
 msgctxt "field:ir.rule.group,perm_read:"
 msgid "Read Access"
-msgstr "Permiso para Leer"
+msgstr "Permiso para leer"
 
 msgctxt "field:ir.rule.group,perm_write:"
 msgid "Write Access"
-msgstr "Permiso para Modificar"
+msgstr "Permiso para modificar"
 
 msgctxt "field:ir.rule.group,rec_name:"
 msgid "Name"
@@ -1833,11 +1810,11 @@ msgstr "Usuarios"
 
 msgctxt "field:ir.rule.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.rule.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.sequence,active:"
 msgid "Active"
@@ -1845,15 +1822,15 @@ msgstr "Activo"
 
 msgctxt "field:ir.sequence,code:"
 msgid "Sequence Code"
-msgstr "Código de Secuencia"
+msgstr "Código de secuencia"
 
 msgctxt "field:ir.sequence,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.sequence,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.sequence,id:"
 msgid "ID"
@@ -1861,23 +1838,23 @@ msgstr "ID"
 
 msgctxt "field:ir.sequence,last_timestamp:"
 msgid "Last Timestamp"
-msgstr "Última Fecha-hora"
+msgstr "Última fecha-hora"
 
 msgctxt "field:ir.sequence,name:"
 msgid "Sequence Name"
-msgstr "Nombre de Secuencia"
+msgstr "Nombre de secuencia"
 
 msgctxt "field:ir.sequence,number_increment:"
 msgid "Increment Number"
-msgstr "Número de Incremento"
+msgstr "Número de incremento"
 
 msgctxt "field:ir.sequence,number_next:"
 msgid "Next Number"
-msgstr "Siguiente Número"
+msgstr "Siguiente número"
 
 msgctxt "field:ir.sequence,number_next_internal:"
 msgid "Next Number"
-msgstr "Siguiente Número"
+msgstr "Siguiente número"
 
 msgctxt "field:ir.sequence,padding:"
 msgid "Number padding"
@@ -1897,11 +1874,11 @@ msgstr "Sufijo"
 
 msgctxt "field:ir.sequence,timestamp_offset:"
 msgid "Timestamp Offset"
-msgstr "Desfase de Fecha-hora"
+msgstr "Desfase de fecha-hora"
 
 msgctxt "field:ir.sequence,timestamp_rounding:"
 msgid "Timestamp Rounding"
-msgstr "Redondeo de Fecha-hora"
+msgstr "Redondeo de fecha-hora"
 
 msgctxt "field:ir.sequence,type:"
 msgid "Type"
@@ -1909,11 +1886,11 @@ msgstr "Tipo"
 
 msgctxt "field:ir.sequence,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.sequence,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.sequence.strict,active:"
 msgid "Active"
@@ -1921,15 +1898,15 @@ msgstr "Activo"
 
 msgctxt "field:ir.sequence.strict,code:"
 msgid "Sequence Code"
-msgstr "Código de Secuencia"
+msgstr "Código de secuencia"
 
 msgctxt "field:ir.sequence.strict,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.sequence.strict,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.sequence.strict,id:"
 msgid "ID"
@@ -1937,23 +1914,23 @@ msgstr "ID"
 
 msgctxt "field:ir.sequence.strict,last_timestamp:"
 msgid "Last Timestamp"
-msgstr "Última Fecha-hora"
+msgstr "Última fecha-hora"
 
 msgctxt "field:ir.sequence.strict,name:"
 msgid "Sequence Name"
-msgstr "Nombre de Secuencia"
+msgstr "Nombre de secuencia"
 
 msgctxt "field:ir.sequence.strict,number_increment:"
 msgid "Increment Number"
-msgstr "Número de Incremento"
+msgstr "Número de incremento"
 
 msgctxt "field:ir.sequence.strict,number_next:"
 msgid "Next Number"
-msgstr "Siguiente Número"
+msgstr "Siguiente número"
 
 msgctxt "field:ir.sequence.strict,number_next_internal:"
 msgid "Next Number"
-msgstr "Siguiente Número"
+msgstr "Siguiente número"
 
 msgctxt "field:ir.sequence.strict,padding:"
 msgid "Number padding"
@@ -1973,11 +1950,11 @@ msgstr "Sufijo"
 
 msgctxt "field:ir.sequence.strict,timestamp_offset:"
 msgid "Timestamp Offset"
-msgstr "Desfase de Fecha-hora"
+msgstr "Desfase de fecha-hora"
 
 msgctxt "field:ir.sequence.strict,timestamp_rounding:"
 msgid "Timestamp Rounding"
-msgstr "Redondeo de Fecha-hora"
+msgstr "Redondeo de fecha-hora"
 
 msgctxt "field:ir.sequence.strict,type:"
 msgid "Type"
@@ -1985,23 +1962,23 @@ msgstr "Tipo"
 
 msgctxt "field:ir.sequence.strict,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.sequence.strict,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.sequence.type,code:"
 msgid "Sequence Code"
-msgstr "Código de Secuencia"
+msgstr "Código de la secuencia"
 
 msgctxt "field:ir.sequence.type,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.sequence.type,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.sequence.type,id:"
 msgid "ID"
@@ -2009,7 +1986,7 @@ msgstr "ID"
 
 msgctxt "field:ir.sequence.type,name:"
 msgid "Sequence Name"
-msgstr "Nombre de Secuencia"
+msgstr "Nombre de la secuencia"
 
 msgctxt "field:ir.sequence.type,rec_name:"
 msgid "Name"
@@ -2017,19 +1994,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.sequence.type,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.sequence.type,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.session,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.session,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.session,id:"
 msgid "ID"
@@ -2045,19 +2022,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.session,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.session,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.session.wizard,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.session.wizard,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.session.wizard,data:"
 msgid "Data"
@@ -2073,19 +2050,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.session.wizard,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.session.wizard,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.translation,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.translation,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.translation,fuzzy:"
 msgid "Fuzzy"
@@ -2109,11 +2086,11 @@ msgstr "Módulo"
 
 msgctxt "field:ir.translation,name:"
 msgid "Field Name"
-msgstr "Nombre de Campo"
+msgstr "Nombre del campo"
 
 msgctxt "field:ir.translation,overriding_module:"
 msgid "Overriding Module"
-msgstr "Sobreescritura del Módulo"
+msgstr "Sobreescritura del módulo"
 
 msgctxt "field:ir.translation,rec_name:"
 msgid "Name"
@@ -2137,15 +2114,15 @@ msgstr "Tipo"
 
 msgctxt "field:ir.translation,value:"
 msgid "Translation Value"
-msgstr "Valor de la Traducción"
+msgstr "Valor de la traducción"
 
 msgctxt "field:ir.translation,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.translation,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.translation.clean.start,id:"
 msgid "ID"
@@ -2197,7 +2174,7 @@ msgstr "Función de la Acción"
 
 msgctxt "field:ir.trigger,action_model:"
 msgid "Action Model"
-msgstr "Acción de Modelo"
+msgstr "Modelo de la Acción"
 
 msgctxt "field:ir.trigger,active:"
 msgid "Active"
@@ -2209,11 +2186,11 @@ msgstr "Condición"
 
 msgctxt "field:ir.trigger,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.trigger,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.trigger,id:"
 msgid "ID"
@@ -2221,11 +2198,11 @@ msgstr "ID"
 
 msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
-msgstr "Número Límite"
+msgstr "Número límite"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
-msgstr "Retardo Mínimo"
+msgstr "Retardo mínimo"
 
 msgctxt "field:ir.trigger,model:"
 msgid "Model"
@@ -2237,19 +2214,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.trigger,on_create:"
 msgid "On Create"
-msgstr "Al Crearse"
+msgstr "Al crear"
 
 msgctxt "field:ir.trigger,on_delete:"
 msgid "On Delete"
-msgstr "Al Borrar"
+msgstr "Al Eliminar"
 
 msgctxt "field:ir.trigger,on_time:"
 msgid "On Time"
-msgstr "Al Tiempo"
+msgstr "A Tiempo"
 
 msgctxt "field:ir.trigger,on_write:"
 msgid "On Write"
-msgstr "Al Modificar"
+msgstr "Al modificar"
 
 msgctxt "field:ir.trigger,rec_name:"
 msgid "Name"
@@ -2257,19 +2234,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.trigger,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.trigger,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.trigger.log,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.trigger.log,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.trigger.log,id:"
 msgid "ID"
@@ -2289,19 +2266,19 @@ msgstr "Disparador"
 
 msgctxt "field:ir.trigger.log,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.trigger.log,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.ui.icon,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.ui.icon,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.ui.icon,icon:"
 msgid "Icon"
@@ -2333,11 +2310,11 @@ msgstr "Secuencia"
 
 msgctxt "field:ir.ui.icon,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.ui.icon,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.ui.menu,action:"
 msgid "Action"
@@ -2345,7 +2322,7 @@ msgstr "Acción"
 
 msgctxt "field:ir.ui.menu,action_keywords:"
 msgid "Action Keywords"
-msgstr "Teclas de Acción"
+msgstr "Acciones de teclado"
 
 msgctxt "field:ir.ui.menu,active:"
 msgid "Active"
@@ -2357,15 +2334,15 @@ msgstr "Hijos"
 
 msgctxt "field:ir.ui.menu,complete_name:"
 msgid "Complete Name"
-msgstr "Nombre Completo"
+msgstr "Nombre completo"
 
 msgctxt "field:ir.ui.menu,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.ui.menu,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.ui.menu,favorite:"
 msgid "Favorite"
@@ -2401,19 +2378,19 @@ msgstr "Secuencia"
 
 msgctxt "field:ir.ui.menu,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.ui.menu,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.ui.menu.favorite,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.ui.menu.favorite,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.ui.menu.favorite,id:"
 msgid "ID"
@@ -2437,23 +2414,23 @@ msgstr "Usuario"
 
 msgctxt "field:ir.ui.menu.favorite,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.ui.menu.favorite,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.ui.view,arch:"
 msgid "View Architecture"
-msgstr "Ver Arquitectura"
+msgstr "Arquitectura de la vista"
 
 msgctxt "field:ir.ui.view,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.ui.view,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.ui.view,data:"
 msgid "Data"
@@ -2473,7 +2450,7 @@ msgstr "ID"
 
 msgctxt "field:ir.ui.view,inherit:"
 msgid "Inherited View"
-msgstr "Vista Heredada"
+msgstr "Vista heredada"
 
 msgctxt "field:ir.ui.view,model:"
 msgid "Model"
@@ -2497,15 +2474,15 @@ msgstr "Nombre"
 
 msgctxt "field:ir.ui.view,type:"
 msgid "View Type"
-msgstr "Tipo de Vista"
+msgstr "Tipo de vista"
 
 msgctxt "field:ir.ui.view,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.ui.view,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.ui.view.show.start,id:"
 msgid "ID"
@@ -2513,11 +2490,11 @@ msgstr "ID"
 
 msgctxt "field:ir.ui.view_search,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.ui.view_search,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.ui.view_search,domain:"
 msgid "Domain"
@@ -2545,23 +2522,23 @@ msgstr "Usuario"
 
 msgctxt "field:ir.ui.view_search,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.ui.view_search,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.ui.view_tree_state,child_name:"
 msgid "Child Name"
-msgstr "Nombre del Hijo"
+msgstr "Nombre del hijo"
 
 msgctxt "field:ir.ui.view_tree_state,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.ui.view_tree_state,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.ui.view_tree_state,domain:"
 msgid "Domain"
@@ -2577,7 +2554,7 @@ msgstr "Modelo"
 
 msgctxt "field:ir.ui.view_tree_state,nodes:"
 msgid "Expanded Nodes"
-msgstr "Nodos Expandidos"
+msgstr "Nodos expandidos"
 
 msgctxt "field:ir.ui.view_tree_state,rec_name:"
 msgid "Name"
@@ -2585,7 +2562,7 @@ msgstr "Nombre"
 
 msgctxt "field:ir.ui.view_tree_state,selected_nodes:"
 msgid "Selected Nodes"
-msgstr "Nodos Seleccionados"
+msgstr "Nodos seleccionados"
 
 msgctxt "field:ir.ui.view_tree_state,user:"
 msgid "User"
@@ -2593,19 +2570,19 @@ msgstr "Usuario"
 
 msgctxt "field:ir.ui.view_tree_state,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.ui.view_tree_state,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.ui.view_tree_width,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.ui.view_tree_width,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.ui.view_tree_width,field:"
 msgid "Field"
@@ -2633,15 +2610,15 @@ msgstr "Ancho"
 
 msgctxt "field:ir.ui.view_tree_width,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.ui.view_tree_width,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "help:ir.action.act_window,limit:"
 msgid "Default limit for the list view"
-msgstr "Límite predeterminado para la vista de lista"
+msgstr "Límite por defecto para la vista de lista"
 
 msgctxt "help:ir.action.act_window,search_value:"
 msgid "Default search criteria for the list view"
@@ -2649,7 +2626,7 @@ msgstr "Criterio de búsqueda por defecto para la vista de lista"
 
 msgctxt "help:ir.action.act_window,window_name:"
 msgid "Use the action name as window name"
-msgstr "Usar el nombre de la acción como el nombre de la ventana"
+msgstr "Utilizar el nombre de la acción como el nombre de la ventana"
 
 msgctxt "help:ir.action.report,email:"
 msgid ""
@@ -2664,7 +2641,7 @@ msgid ""
 "Leave empty for the same as template, see unoconv documentation for "
 "compatible format"
 msgstr ""
-"Dejar vacío para el mismo como plantilla, consulte la documentación de "
+"Dejar vacío para el mismo tipo como plantilla, consulte la documentación de "
 "unoconv sobre formatos compatibles."
 
 msgctxt "help:ir.action.report,style:"
@@ -2680,12 +2657,12 @@ msgid ""
 "Number of times the function is called, a negative number indicates that the"
 " function will always be called"
 msgstr ""
-"Número de veces que se llama la función, un número negativo indica que la "
+"Número de veces que se llama a la función, un número negativo indica que la "
 "función siempre se llamará"
 
 msgctxt "help:ir.cron,request_user:"
 msgid "The user who will receive requests in case of failure"
-msgstr "El usuario que recibirá peticiones en caso de error"
+msgstr "El usuario que recibirá solicitudes en caso de error"
 
 msgctxt "help:ir.cron,user:"
 msgid "The user used to execute this action"
@@ -2697,7 +2674,7 @@ msgstr "Etiqueta RFC 4646: http://tools.ietf.org/html/rfc4646"
 
 msgctxt "help:ir.model,module:"
 msgid "Module in which this model is defined"
-msgstr "El módulo en el que este modelo está definido"
+msgstr "Módulo en el que se define este modelo"
 
 msgctxt "help:ir.model.data,db_id:"
 msgid "The id of the record in the database."
@@ -2709,7 +2686,7 @@ msgstr "El id del registro tal y como se conoce en el sistema de archivos."
 
 msgctxt "help:ir.model.field,module:"
 msgid "Module in which this field is defined"
-msgstr "El módulo en el que este campo está definido"
+msgstr "Módulo en el que se define este campo"
 
 msgctxt "help:ir.model.print_model_graph.start,filter:"
 msgid ""
@@ -2720,8 +2697,12 @@ msgstr ""
 "que concuerden."
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
-msgstr "El dominio es evaluado con el \"usuario\" como el usuario actual"
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
+msgstr ""
+"El dominio es evaluado con un contexto PYSON que contiene:\n"
+"- \"user\" como el usuario actual"
 
 msgctxt "help:ir.rule.group,default_p:"
 msgid "Add this rule to all users by default"
@@ -2745,23 +2726,23 @@ msgid ""
 "It triggers the action if true."
 msgstr ""
 "Una instrucción Python evaluada con el registro representado por \"self\"\n"
-"Se dispara la acción si es verdadera."
+"Dispara la acción si es verdadera."
 
 msgctxt "help:ir.trigger,limit_number:"
 msgid ""
 "Limit the number of call to \"Action Function\" by records.\n"
 "0 for no limit."
 msgstr ""
-"Limitar el número de llamada a la \"Función de Acción\" por registros.\n"
+"Limitar el número de llamadas a la \"Función de la Acción\" por registros.\n"
 "0 para ningún límite."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Establecer un retraso mínimo en minutos entre llamadas a la \"Función de Acción\" para el mismo registro.\n"
-"0 para ningún retraso."
+"Definir un retraso mínimo entre llamadas a la \"Función de la Acción\" para el mismo registro.\n"
+"Vacío para ningún retraso."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -2773,7 +2754,7 @@ msgstr "Acción"
 
 msgctxt "model:ir.action,name:act_action_act_window_form"
 msgid "Window Actions"
-msgstr "Ventana de Acciones"
+msgstr "Acciones de ventana"
 
 msgctxt "model:ir.action,name:act_action_form"
 msgid "Actions"
@@ -2797,11 +2778,11 @@ msgstr "Adjuntos"
 
 msgctxt "model:ir.action,name:act_config_wizard_item_form"
 msgid "Config Wizard Items"
-msgstr "Elementos del Asistente de Configuración"
+msgstr "Elementos del asistente de configuración"
 
 msgctxt "model:ir.action,name:act_cron_form"
 msgid "Scheduled Actions"
-msgstr "Acciones Programadas"
+msgstr "Acciones programadas"
 
 msgctxt "model:ir.action,name:act_export_form"
 msgid "Exports"
@@ -2825,20 +2806,19 @@ msgstr "Menú"
 
 msgctxt "model:ir.action,name:act_model_access_form"
 msgid "Models Access"
-msgstr "Permisos de Modelos"
+msgstr "Permisos de accesso a modelos"
 
 msgctxt "model:ir.action,name:act_model_button_form"
 msgid "Buttons"
 msgstr "Botones"
 
-#, fuzzy
 msgctxt "model:ir.action,name:act_model_data_form"
 msgid "Data"
 msgstr "Datos"
 
 msgctxt "model:ir.action,name:act_model_field_access_form"
 msgid "Fields Access"
-msgstr "Permisos de los Campos"
+msgstr "Permisos de acceso a campos"
 
 msgctxt "model:ir.action,name:act_model_fields_form"
 msgid "Fields"
@@ -2850,11 +2830,11 @@ msgstr "Modelos"
 
 msgctxt "model:ir.action,name:act_module_config"
 msgid "Configure Modules"
-msgstr "Configuración de Módulos"
+msgstr "Configurar los módulos"
 
 msgctxt "model:ir.action,name:act_module_config_wizard"
 msgid "Module Configuration"
-msgstr "Configuración de Módulo"
+msgstr "Configuración del módulo"
 
 msgctxt "model:ir.action,name:act_module_form"
 msgid "Modules"
@@ -2862,7 +2842,7 @@ msgstr "Módulos"
 
 msgctxt "model:ir.action,name:act_module_install_upgrade"
 msgid "Perform Pending Installation/Upgrade"
-msgstr "Realizar Instalaciones/Actualizaciones Pendientes"
+msgstr "Realizar instalaciones/actualizaciones pendientes"
 
 msgctxt "model:ir.action,name:act_property_form"
 msgid "Properties"
@@ -2870,11 +2850,11 @@ msgstr "Propiedades"
 
 msgctxt "model:ir.action,name:act_property_form_default"
 msgid "Default Properties"
-msgstr "Propiedades Predeterminadas"
+msgstr "Propiedades por defecto"
 
 msgctxt "model:ir.action,name:act_rule_group_form"
 msgid "Record Rules"
-msgstr "Reglas de Registros"
+msgstr "Reglas de registros"
 
 msgctxt "model:ir.action,name:act_sequence_form"
 msgid "Sequences"
@@ -2882,19 +2862,19 @@ msgstr "Secuencias"
 
 msgctxt "model:ir.action,name:act_sequence_strict_form"
 msgid "Sequences Strict"
-msgstr "Secuencias Estrictas"
+msgstr "Secuencias estrictas"
 
 msgctxt "model:ir.action,name:act_sequence_type_form"
 msgid "Sequence Types"
-msgstr "Tipos de Secuencia"
+msgstr "Tipos de secuencia"
 
 msgctxt "model:ir.action,name:act_translation_clean"
 msgid "Clean Translations"
-msgstr "Limpiar Traducciones"
+msgstr "Limpiar traducciones"
 
 msgctxt "model:ir.action,name:act_translation_export"
 msgid "Export Translations"
-msgstr "Exportar Traducciones"
+msgstr "Exportar traducciones"
 
 msgctxt "model:ir.action,name:act_translation_form"
 msgid "Translations"
@@ -2902,11 +2882,11 @@ msgstr "Traducciones"
 
 msgctxt "model:ir.action,name:act_translation_set"
 msgid "Set Report Translations"
-msgstr "Establecer Traducciones de los Informes"
+msgstr "Definir traducciones de los informes"
 
 msgctxt "model:ir.action,name:act_translation_update"
 msgid "Synchronize Translations"
-msgstr "Sincronizar Traducciones"
+msgstr "Sincronizar traducciones"
 
 msgctxt "model:ir.action,name:act_trigger_form"
 msgid "Triggers"
@@ -2918,19 +2898,19 @@ msgstr "Vistas"
 
 msgctxt "model:ir.action,name:act_view_search"
 msgid "View Search"
-msgstr "Buscar Vista"
+msgstr "Búsqueda favorita"
 
 msgctxt "model:ir.action,name:act_view_show"
 msgid "Show View"
-msgstr "Mostar Vista"
+msgstr "Mostrar vista"
 
 msgctxt "model:ir.action,name:act_view_tree_state"
 msgid "Tree State"
-msgstr "Estado de Arbol"
+msgstr "Estado de árbol"
 
 msgctxt "model:ir.action,name:act_view_tree_width_form"
 msgid "View Tree Width"
-msgstr "Ancho de la Vista de Arbol"
+msgstr "Ancho de la vista de árbol"
 
 msgctxt "model:ir.action,name:print_model_graph"
 msgid "Graph"
@@ -2942,13 +2922,12 @@ msgstr "Gráfico"
 
 msgctxt "model:ir.action.act_window,name:"
 msgid "Action act window"
-msgstr "Acción ventana acción"
+msgstr "Acción de ventana"
 
 msgctxt "model:ir.action.act_window.domain,name:"
 msgid "Action act window domain"
-msgstr "Accion en dominio de ventana de acción"
+msgstr "Dominio de acción de ventana"
 
-#, fuzzy
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
 msgid "All"
@@ -2957,23 +2936,23 @@ msgstr "Todo"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
 msgid "Out of Sync"
-msgstr "Fuera de Sincronización"
+msgstr "Fuera de sincronización"
 
 msgctxt "model:ir.action.act_window.view,name:"
 msgid "Action act window view"
-msgstr "Acción en vista de ventana de acción"
+msgstr "Acción de vista de ventana"
 
 msgctxt "model:ir.action.keyword,name:"
 msgid "Action keyword"
-msgstr "Tecla de acción "
+msgstr "Acción de teclado"
 
 msgctxt "model:ir.action.report,name:"
 msgid "Action report"
-msgstr "Acción en Reporte"
+msgstr "Informe de acción"
 
 msgctxt "model:ir.action.url,name:"
 msgid "Action URL"
-msgstr "URL de la acción"
+msgstr "Acción de URL"
 
 msgctxt "model:ir.action.wizard,name:"
 msgid "Action wizard"
@@ -3073,7 +3052,7 @@ msgstr "Modelo"
 
 msgctxt "model:ir.model.access,name:"
 msgid "Model access"
-msgstr "Permisos de modelo"
+msgstr "Permisos de acceso a modelo"
 
 msgctxt "model:ir.model.button,name:"
 msgid "Model Button"
@@ -3089,11 +3068,11 @@ msgstr "Campo de modelo"
 
 msgctxt "model:ir.model.field.access,name:"
 msgid "Model Field Access"
-msgstr "Permiso Campo de Modelo"
+msgstr "Permisos de acceso a campo de modelo"
 
 msgctxt "model:ir.model.print_model_graph.start,name:"
 msgid "Print Model Graph"
-msgstr "Imprimir Gráfico del Modelo"
+msgstr "Imprimir gráfico del modelo"
 
 msgctxt "model:ir.module.module,name:"
 msgid "Module"
@@ -3101,11 +3080,11 @@ msgstr "Módulo"
 
 msgctxt "model:ir.module.module.config_wizard.done,name:"
 msgid "Module Config Wizard Done"
-msgstr "Asitente de Configuración del Módulo - Realizado"
+msgstr "Asitente de configuración del módulo - Realizado"
 
 msgctxt "model:ir.module.module.config_wizard.first,name:"
 msgid "Module Config Wizard First"
-msgstr "Asistente de Configuración del Módulo - Primero"
+msgstr "Asistente de configuración inicial del módulo"
 
 msgctxt "model:ir.module.module.config_wizard.item,name:"
 msgid "Config wizard to run after installing module"
@@ -3113,7 +3092,7 @@ msgstr "Asistente de configuración a ejecutar después de instalar un módulo"
 
 msgctxt "model:ir.module.module.config_wizard.other,name:"
 msgid "Module Config Wizard Other"
-msgstr "Asistente de Configuración del Módulo - Otro"
+msgstr "Asistente de configuración del módulo - Otro"
 
 msgctxt "model:ir.module.module.dependency,name:"
 msgid "Module dependency"
@@ -3121,11 +3100,11 @@ msgstr "Dependencias del módulo"
 
 msgctxt "model:ir.module.module.install_upgrade.done,name:"
 msgid "Module Install Upgrade Done"
-msgstr "Instalación / Actualización del Módulo - Realizada"
+msgstr "Instalación o actualización de módulos realizada"
 
 msgctxt "model:ir.module.module.install_upgrade.start,name:"
 msgid "Module Install Upgrade Start"
-msgstr "Instalación / Actualización del Módulo - Iniciar"
+msgstr "Iniciar la instalación o actualización de módulos"
 
 msgctxt "model:ir.property,name:"
 msgid "Property"
@@ -3145,7 +3124,7 @@ msgstr "Secuencia"
 
 msgctxt "model:ir.sequence.strict,name:"
 msgid "Sequence Strict"
-msgstr "Secuencia Estricta"
+msgstr "Secuencia estricta"
 
 msgctxt "model:ir.sequence.type,name:"
 msgid "Sequence type"
@@ -3157,7 +3136,7 @@ msgstr "Sesión"
 
 msgctxt "model:ir.session.wizard,name:"
 msgid "Session Wizard"
-msgstr "Asistente de Sesión"
+msgstr "Asistente de sesión"
 
 msgctxt "model:ir.translation,name:"
 msgid "Translation"
@@ -3181,11 +3160,11 @@ msgstr "Exportar traducción - archivo"
 
 msgctxt "model:ir.translation.set.start,name:"
 msgid "Set Translation"
-msgstr "Establecer Traducción"
+msgstr "Definir traducción"
 
 msgctxt "model:ir.translation.set.succeed,name:"
 msgid "Set Translation"
-msgstr "Establecer Traducción"
+msgstr "Definir traducción"
 
 msgctxt "model:ir.translation.update.start,name:"
 msgid "Update translation"
@@ -3197,7 +3176,7 @@ msgstr "Disparador"
 
 msgctxt "model:ir.trigger.log,name:"
 msgid "Trigger Log"
-msgstr "Registro del Disparador"
+msgstr "Registro del disparador"
 
 msgctxt "model:ir.ui.icon,name:"
 msgid "Icon"
@@ -3217,7 +3196,7 @@ msgstr "Acciones"
 
 msgctxt "model:ir.ui.menu,name:menu_action_act_window"
 msgid "Window Actions"
-msgstr "Ventana de Acciones"
+msgstr "Acciones de ventana"
 
 msgctxt "model:ir.ui.menu,name:menu_action_report_form"
 msgid "Reports"
@@ -3241,11 +3220,11 @@ msgstr "Adjuntos"
 
 msgctxt "model:ir.ui.menu,name:menu_config_wizard_item_form"
 msgid "Config Wizard Items"
-msgstr "Elementos del Asistente de Configuración"
+msgstr "Elementos del asistente de configuración"
 
 msgctxt "model:ir.ui.menu,name:menu_cron_form"
 msgid "Scheduled Actions"
-msgstr "Acciones Programadas"
+msgstr "Acciones programadas"
 
 msgctxt "model:ir.ui.menu,name:menu_export_form"
 msgid "Exports"
@@ -3257,7 +3236,7 @@ msgstr "Iconos"
 
 msgctxt "model:ir.ui.menu,name:menu_ir_sequence_type"
 msgid "Sequence Types"
-msgstr "Tipos de Secuencia"
+msgstr "Tipos de secuencia"
 
 msgctxt "model:ir.ui.menu,name:menu_lang_form"
 msgid "Languages"
@@ -3273,20 +3252,19 @@ msgstr "Menú"
 
 msgctxt "model:ir.ui.menu,name:menu_model_access_form"
 msgid "Models Access"
-msgstr "Permisos de Modelos"
+msgstr "Permisos de acceso a modelos"
 
 msgctxt "model:ir.ui.menu,name:menu_model_button_form"
 msgid "Buttons"
 msgstr "Botones"
 
-#, fuzzy
 msgctxt "model:ir.ui.menu,name:menu_model_data_form"
 msgid "Data"
 msgstr "Datos"
 
 msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
 msgid "Fields Access"
-msgstr "Permisos de Campos"
+msgstr "Permisos de acceso a campos"
 
 msgctxt "model:ir.ui.menu,name:menu_model_form"
 msgid "Models"
@@ -3302,7 +3280,7 @@ msgstr "Módulos"
 
 msgctxt "model:ir.ui.menu,name:menu_module_install_upgrade"
 msgid "Perform Pending Installation/Upgrade"
-msgstr "Realizar Instalaciones/Actualizaciones Pendientes"
+msgstr "Realizar instalaciones/actualizaciones pendientes"
 
 msgctxt "model:ir.ui.menu,name:menu_modules"
 msgid "Modules"
@@ -3314,15 +3292,15 @@ msgstr "Propiedades"
 
 msgctxt "model:ir.ui.menu,name:menu_property_form_default"
 msgid "Default Properties"
-msgstr "Propiedades por Defecto"
+msgstr "Propiedades por defecto"
 
 msgctxt "model:ir.ui.menu,name:menu_rule_group_form"
 msgid "Record Rules"
-msgstr "Reglas de Registros"
+msgstr "Reglas de registros"
 
 msgctxt "model:ir.ui.menu,name:menu_scheduler"
 msgid "Scheduler"
-msgstr "Programador de Tareas"
+msgstr "Programador de tareas"
 
 msgctxt "model:ir.ui.menu,name:menu_sequence_form"
 msgid "Sequences"
@@ -3330,7 +3308,7 @@ msgstr "Secuencias"
 
 msgctxt "model:ir.ui.menu,name:menu_sequence_strict_form"
 msgid "Sequences Strict"
-msgstr "Secuencias Estrictas"
+msgstr "Secuencias estrictas"
 
 msgctxt "model:ir.ui.menu,name:menu_sequences"
 msgid "Sequences"
@@ -3342,7 +3320,7 @@ msgstr "Limpiar Traducciones"
 
 msgctxt "model:ir.ui.menu,name:menu_translation_export"
 msgid "Export Translations"
-msgstr "Exportar Traducciones"
+msgstr "Exportar traducciones"
 
 msgctxt "model:ir.ui.menu,name:menu_translation_form"
 msgid "Translations"
@@ -3350,11 +3328,11 @@ msgstr "Traducciones"
 
 msgctxt "model:ir.ui.menu,name:menu_translation_set"
 msgid "Set Translations"
-msgstr "Establecer Traducciones"
+msgstr "Definir traducciones"
 
 msgctxt "model:ir.ui.menu,name:menu_translation_update"
 msgid "Synchronize Translations"
-msgstr "Sincronizar Traducciones"
+msgstr "Sincronizar traducciones"
 
 msgctxt "model:ir.ui.menu,name:menu_trigger_form"
 msgid "Triggers"
@@ -3362,7 +3340,7 @@ msgstr "Disparadores"
 
 msgctxt "model:ir.ui.menu,name:menu_ui"
 msgid "User Interface"
-msgstr "Interfaz de Usuario"
+msgstr "Interfaz de usuario"
 
 msgctxt "model:ir.ui.menu,name:menu_view"
 msgid "Views"
@@ -3370,15 +3348,15 @@ msgstr "Vistas"
 
 msgctxt "model:ir.ui.menu,name:menu_view_search"
 msgid "View Search"
-msgstr "Buscar Vista"
+msgstr "Búsquedas favoritas"
 
 msgctxt "model:ir.ui.menu,name:menu_view_tree_state"
 msgid "Tree State"
-msgstr "Estado de Arbol"
+msgstr "Estado de árbol"
 
 msgctxt "model:ir.ui.menu,name:menu_view_tree_width"
 msgid "View Tree Width"
-msgstr "Ancho de la Vista de Arbol"
+msgstr "Ancho de la vista de árbol"
 
 msgctxt "model:ir.ui.menu,name:model_model_fields_form"
 msgid "Fields"
@@ -3386,7 +3364,7 @@ msgstr "Campos"
 
 msgctxt "model:ir.ui.menu.favorite,name:"
 msgid "Menu Favorite"
-msgstr "Menú Favorito"
+msgstr "Menú favorito"
 
 msgctxt "model:ir.ui.view,name:"
 msgid "View"
@@ -3398,23 +3376,23 @@ msgstr "Mostrar vista"
 
 msgctxt "model:ir.ui.view_search,name:"
 msgid "View Search"
-msgstr "Buscar Vista"
+msgstr "Búsqueda favorita"
 
 msgctxt "model:ir.ui.view_tree_state,name:"
 msgid "View Tree State"
-msgstr "Estado de Vista de Arbol"
+msgstr "Estado de vista de árbol"
 
 msgctxt "model:ir.ui.view_tree_width,name:"
 msgid "View Tree Width"
-msgstr "Ancho de la Vista de Arbol"
+msgstr "Ancho de la vista de árbol"
 
 msgctxt "selection:ir.action.keyword,keyword:"
 msgid "Action form"
-msgstr "Acción formulario"
+msgstr "Acción de formulario"
 
 msgctxt "selection:ir.action.keyword,keyword:"
 msgid "Action tree"
-msgstr "Árbol de acciones"
+msgstr "Acción de árbol"
 
 msgctxt "selection:ir.action.keyword,keyword:"
 msgid "Form relate"
@@ -3426,7 +3404,7 @@ msgstr "Abrir Gráfico"
 
 msgctxt "selection:ir.action.keyword,keyword:"
 msgid "Open tree"
-msgstr "Desplegar árbol"
+msgstr "Abrir árbol"
 
 msgctxt "selection:ir.action.keyword,keyword:"
 msgid "Print form"
@@ -3478,19 +3456,19 @@ msgstr "No instalado"
 
 msgctxt "selection:ir.module.module,state:"
 msgid "To be installed"
-msgstr "Para ser instalado"
+msgstr "Para instalar"
 
 msgctxt "selection:ir.module.module,state:"
 msgid "To be removed"
-msgstr "Para ser eliminado"
+msgstr "Para eliminar"
 
 msgctxt "selection:ir.module.module,state:"
 msgid "To be upgraded"
-msgstr "Para ser actualizado"
+msgstr "Para actualizar"
 
 msgctxt "selection:ir.module.module.config_wizard.item,state:"
 msgid "Done"
-msgstr "Terminado"
+msgstr "Realizado"
 
 msgctxt "selection:ir.module.module.config_wizard.item,state:"
 msgid "Open"
@@ -3506,15 +3484,15 @@ msgstr "No instalado"
 
 msgctxt "selection:ir.module.module.dependency,state:"
 msgid "To be installed"
-msgstr "Para ser instalado"
+msgstr "Para instalar"
 
 msgctxt "selection:ir.module.module.dependency,state:"
 msgid "To be removed"
-msgstr "Para ser eliminado"
+msgstr "Para eliminar"
 
 msgctxt "selection:ir.module.module.dependency,state:"
 msgid "To be upgraded"
-msgstr "Para ser actualizado"
+msgstr "Para actualizar"
 
 msgctxt "selection:ir.module.module.dependency,state:"
 msgid "Unknown"
@@ -3522,11 +3500,11 @@ msgstr "Desconocido"
 
 msgctxt "selection:ir.sequence,type:"
 msgid "Decimal Timestamp"
-msgstr "Fecha-hora Decimal"
+msgstr "Fecha-hora decimal"
 
 msgctxt "selection:ir.sequence,type:"
 msgid "Hexadecimal Timestamp"
-msgstr "Fecha-hora Hexadecimal"
+msgstr "Fecha-hora hexadecimal"
 
 msgctxt "selection:ir.sequence,type:"
 msgid "Incremental"
@@ -3534,11 +3512,11 @@ msgstr "Incremental"
 
 msgctxt "selection:ir.sequence.strict,type:"
 msgid "Decimal Timestamp"
-msgstr "Fecha-hora Decimal"
+msgstr "Fecha-hora decimal"
 
 msgctxt "selection:ir.sequence.strict,type:"
 msgid "Hexadecimal Timestamp"
-msgstr "Fecha-hora Hexadecimal"
+msgstr "Fecha-hora hexadecimal"
 
 msgctxt "selection:ir.sequence.strict,type:"
 msgid "Incremental"
@@ -3574,7 +3552,7 @@ msgstr "Vista"
 
 msgctxt "selection:ir.translation,type:"
 msgid "Wizard Button"
-msgstr "Botón de Asistente"
+msgstr "Botón del asistente"
 
 msgctxt "selection:ir.ui.menu,action:"
 msgid ""
@@ -3642,11 +3620,11 @@ msgstr "General"
 
 msgctxt "view:ir.action.act_window:"
 msgid "Open Window"
-msgstr "Abrir Ventana"
+msgstr "Abrir ventana"
 
 msgctxt "view:ir.action.act_window:"
 msgid "Open a Window"
-msgstr "Abrir una Ventana"
+msgstr "Abrir una ventana"
 
 msgctxt "view:ir.action.keyword:"
 msgid "Keyword"
@@ -3696,17 +3674,21 @@ msgctxt "view:ir.attachment:"
 msgid "Attachments"
 msgstr "Adjuntos"
 
+msgctxt "view:ir.attachment:"
+msgid "Last Modification Time"
+msgstr "Hora de última modificación"
+
 msgctxt "view:ir.cron:"
 msgid "Action to trigger"
 msgstr "Acción a disparar"
 
 msgctxt "view:ir.cron:"
 msgid "Scheduled Action"
-msgstr "Acción Programada"
+msgstr "Acción programada"
 
 msgctxt "view:ir.cron:"
 msgid "Scheduled Actions"
-msgstr "Acciones Programadas"
+msgstr "Acciones programadas"
 
 msgctxt "view:ir.export:"
 msgid "Exports"
@@ -3714,7 +3696,7 @@ msgstr "Exportaciones"
 
 msgctxt "view:ir.lang:"
 msgid "Date Formatting"
-msgstr "Formato de Fecha"
+msgstr "Formato de fecha"
 
 msgctxt "view:ir.lang:"
 msgid "Language"
@@ -3726,7 +3708,7 @@ msgstr "Idiomas"
 
 msgctxt "view:ir.lang:"
 msgid "Numbers Formatting"
-msgstr "Formato de Números"
+msgstr "Formato de números"
 
 msgctxt "view:ir.model.access:"
 msgid "Access controls"
@@ -3742,7 +3724,7 @@ msgstr "Botones"
 
 msgctxt "view:ir.model.data:"
 msgid "Model Data"
-msgstr "Datos de Modelo"
+msgstr "Datos del modelo"
 
 msgctxt "view:ir.model.data:"
 msgid "Sync"
@@ -3750,7 +3732,7 @@ msgstr "Sincronizar"
 
 msgctxt "view:ir.model.field.access:"
 msgid "Field Access"
-msgstr "Permiso de Campo"
+msgstr "Permisos de acceso a campo"
 
 msgctxt "view:ir.model.field:"
 msgid "Field"
@@ -3762,7 +3744,7 @@ msgstr "Campos"
 
 msgctxt "view:ir.model.print_model_graph.start:"
 msgid "Print Model Graph"
-msgstr "Imprimir Gráfico del Modelo"
+msgstr "Imprimir gráfico del modelo"
 
 msgctxt "view:ir.model:"
 msgid "Model Description"
@@ -3778,23 +3760,23 @@ msgstr "La configuracion se ha realizado."
 
 msgctxt "view:ir.module.module.config_wizard.first:"
 msgid "Welcome to the module configuration wizard!"
-msgstr "Bienvenido al asistente de configuración del módulo!"
+msgstr "¡Bienvenido al asistente de configuración del módulo!"
 
 msgctxt "view:ir.module.module.config_wizard.first:"
 msgid ""
 "You will be able to configure your installation depending on the modules you"
 " have installed."
 msgstr ""
-"Usted será capaz de configurar su instalación dependiendo de los módulos que"
-" ha instalado."
+"Usted podrá configurar su instalación dependiendo de los módulos que ha "
+"instalado."
 
 msgctxt "view:ir.module.module.config_wizard.item:"
 msgid "Config Wizard Items"
-msgstr "Elementos del Asistente de Configuración"
+msgstr "Elementos del asistente de configuración"
 
 msgctxt "view:ir.module.module.config_wizard.other:"
 msgid "Configuration Wizard Next Step!"
-msgstr "¡Siguiente Paso del Asistente de Configuración!"
+msgstr "¡Siguiente paso del asistente de configuración!"
 
 msgctxt "view:ir.module.module.dependency:"
 msgid "Dependencies"
@@ -3806,15 +3788,15 @@ msgstr "Dependencia"
 
 msgctxt "view:ir.module.module.install_upgrade.done:"
 msgid "System Upgrade Done"
-msgstr "Actualización del Sistema Realizada"
+msgstr "Actualización del sistema realizada"
 
 msgctxt "view:ir.module.module.install_upgrade.done:"
 msgid "The modules have been upgraded / installed !"
-msgstr "¡Los módulos se han actualizado / instalado!"
+msgstr "¡Los módulos han sido actualizados/instalados!"
 
 msgctxt "view:ir.module.module.install_upgrade.start:"
 msgid "Note that this operation may take a few minutes."
-msgstr "Tenga en cuenta que esta operación puede demorar varios minutos."
+msgstr "Tenga en cuenta que esta operación puede tardar unos minutos."
 
 msgctxt "view:ir.module.module.install_upgrade.start:"
 msgid "System Upgrade"
@@ -3822,31 +3804,31 @@ msgstr "Actualización del Sistema"
 
 msgctxt "view:ir.module.module.install_upgrade.start:"
 msgid "Your system will be upgraded."
-msgstr "Su sistema sera actualizado."
+msgstr "Su sistema será actualizado."
 
 msgctxt "view:ir.module.module:"
 msgid "Cancel Installation"
-msgstr "Cancelar Instalación"
+msgstr "Cancelar instalación"
 
 msgctxt "view:ir.module.module:"
 msgid "Cancel Uninstallation"
-msgstr "Cancelar Desinstalación"
+msgstr "Cancelar desinstalación"
 
 msgctxt "view:ir.module.module:"
 msgid "Cancel Upgrade"
-msgstr "Cancelar Actualización"
+msgstr "Cancelar actualización"
 
 msgctxt "view:ir.module.module:"
 msgid "Mark for Installation"
-msgstr "Marcar para Instalar"
+msgstr "Marcar para instalar"
 
 msgctxt "view:ir.module.module:"
 msgid "Mark for Uninstallation (beta)"
-msgstr "Marcar para Desinstalar (beta)"
+msgstr "Marcar para desinstalar (beta)"
 
 msgctxt "view:ir.module.module:"
 msgid "Mark for Upgrade"
-msgstr "Marcar para Actualizar"
+msgstr "Marcar para actualizar"
 
 msgctxt "view:ir.module.module:"
 msgid "Module"
@@ -3868,7 +3850,7 @@ msgctxt "view:ir.rule.group:"
 msgid ""
 "If there is no test defined, the rule is always satisfied if not global"
 msgstr ""
-"Si no hay ninguna prueba definida, la regla se aplica siempre que no sea "
+"Si no hay ninguna prueba definida, la regla se aplica siempre si no es "
 "global"
 
 msgctxt "view:ir.rule.group:"
@@ -3877,47 +3859,15 @@ msgstr "Reglas de Registros"
 
 msgctxt "view:ir.rule.group:"
 msgid "The rule is satisfied if at least one test is True"
-msgstr "La regla se satisface si al menos una condición es verdadera"
+msgstr "La regla se satisface si al menos una condición es cierta"
 
 msgctxt "view:ir.rule:"
 msgid "Test"
 msgstr "Prueba"
 
-msgctxt "view:ir.sequence.strict:"
-msgid "${day}"
-msgstr "${day}"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "${month}"
-msgstr "${month}"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "${year}"
-msgstr "${year}"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Day:"
-msgstr "Día:"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Legend (for prefix, suffix)"
-msgstr "Leyenda (para prefijo y/o sufijo)"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Month:"
-msgstr "Mes:"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Sequences Strict"
-msgstr "Secuencias Estrictas"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Year:"
-msgstr "Año:"
-
 msgctxt "view:ir.sequence.type:"
 msgid "Sequence Type"
-msgstr "Tipo de Secuencia"
+msgstr "Tipo de secuencia"
 
 msgctxt "view:ir.sequence:"
 msgid "${day}"
@@ -3961,15 +3911,15 @@ msgstr "Año:"
 
 msgctxt "view:ir.translation.clean.start:"
 msgid "Clean Translations"
-msgstr "Limpiar Traducciones"
+msgstr "Limpiar las traducciones"
 
 msgctxt "view:ir.translation.clean.start:"
 msgid "Clean Translations?"
-msgstr "¿Limpiar las Traducciones?"
+msgstr "¿Limpiar las traducciones?"
 
 msgctxt "view:ir.translation.clean.succeed:"
 msgid "Clean Translations"
-msgstr "Limpiar Traducciones"
+msgstr "Limpiar traducciones"
 
 msgctxt "view:ir.translation.clean.succeed:"
 msgid "Clean Translations Succeed!"
@@ -3977,31 +3927,31 @@ msgstr "¡Las traducciones se limpiaron correctamente!"
 
 msgctxt "view:ir.translation.export.result:"
 msgid "Export Translation"
-msgstr "Exportar Traducción"
+msgstr "Exportar traducción"
 
 msgctxt "view:ir.translation.export.start:"
 msgid "Export Translation"
-msgstr "Exportar Traducción"
+msgstr "Exportar traducción"
 
 msgctxt "view:ir.translation.set.start:"
 msgid "Set Translations"
-msgstr "Establecer Traducciones"
+msgstr "Definir traducciones"
 
 msgctxt "view:ir.translation.set.start:"
 msgid "Synchronize Translations?"
-msgstr "¿Sincronizar las Traducciones?"
+msgstr "¿Sincronizar las traducciones?"
 
 msgctxt "view:ir.translation.set.succeed:"
 msgid "Set Succeed!"
-msgstr "¡Realizado Exitosamente!"
+msgstr "¡Las traducciones se han realizado correctamente!"
 
 msgctxt "view:ir.translation.set.succeed:"
 msgid "Set Translations"
-msgstr "Establecer Traducciones"
+msgstr "Definir traducciones"
 
 msgctxt "view:ir.translation.update.start:"
 msgid "Synchronize Translations"
-msgstr "Sincronizar Traducciones"
+msgstr "Sincronizar traducciones"
 
 msgctxt "view:ir.translation:"
 msgid "Translations"
@@ -4025,11 +3975,11 @@ msgstr "Iconos"
 
 msgctxt "view:ir.ui.menu.favorite:"
 msgid "Menu Favorite"
-msgstr "Menú Favorito"
+msgstr "Menú favorito"
 
 msgctxt "view:ir.ui.menu.favorite:"
 msgid "Menu Favorites"
-msgstr "Menu Favoritos"
+msgstr "Menu favoritos"
 
 msgctxt "view:ir.ui.menu:"
 msgid "Menu"
@@ -4045,27 +3995,27 @@ msgstr "_Mostrar"
 
 msgctxt "view:ir.ui.view_search:"
 msgid "View Search"
-msgstr "Buscar Vista"
+msgstr "Búsqueda favorita"
 
 msgctxt "view:ir.ui.view_search:"
 msgid "View Searches"
-msgstr "Vista Busquedas"
+msgstr "Búsquedas favoritas"
 
 msgctxt "view:ir.ui.view_tree_state:"
 msgid "View Tree State"
-msgstr "Estado de Vista de Arbol"
+msgstr "Estado de vista de árbol"
 
 msgctxt "view:ir.ui.view_tree_state:"
 msgid "Views Tree State"
-msgstr "Estado de la Vista de Arbol"
+msgstr "Estado de la vista de árbol"
 
 msgctxt "view:ir.ui.view_tree_width:"
 msgid "View Tree Width"
-msgstr "Ancho de la Vista de Arbol"
+msgstr "Ancho de la vista de árbol"
 
 msgctxt "view:ir.ui.view_tree_width:"
 msgid "Views Tree Width"
-msgstr "Ancho de la Vista de Arbol"
+msgstr "Ancho de la vista de árbol"
 
 msgctxt "wizard_button:ir.model.print_model_graph,start,end:"
 msgid "Cancel"
@@ -4076,12 +4026,12 @@ msgid "Print"
 msgstr "Imprimir"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
-msgstr "Aceptar"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
-msgstr "Aceptar"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
 msgid "Cancel"
@@ -4096,8 +4046,8 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
-msgstr "Aceptar"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
 msgid "Cancel"
@@ -4105,7 +4055,7 @@ msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,upgrade:"
 msgid "Start Upgrade"
-msgstr "Iniciar Actualización"
+msgstr "Iniciar actualización"
 
 msgctxt "wizard_button:ir.translation.clean,start,clean:"
 msgid "Clean"
@@ -4116,8 +4066,8 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
-msgstr "Aceptar"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
 msgid "Close"
@@ -4137,11 +4087,11 @@ msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.translation.set,start,set_:"
 msgid "Set"
-msgstr "Establecer"
+msgstr "Definir"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
-msgstr "Aceptar"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
 msgid "Cancel"
diff --git a/trytond/ir/locale/es_ES.po b/trytond/ir/locale/es_ES.po
index 987d042..2d297c8 100644
--- a/trytond/ir/locale/es_ES.po
+++ b/trytond/ir/locale/es_ES.po
@@ -27,8 +27,8 @@ msgid ""
 "The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
 "exceeds its limit."
 msgstr ""
-"El número de dígitos \"%(digits)s\" del campo \"%(field)s\" de \"%(value)s\""
-" es superior a su límite."
+"El número de decimales \"%(digits)s\" del campo \"%(field)s\" de "
+"\"%(value)s\" es superior a su límite."
 
 msgctxt "error:domain_validation_record:"
 msgid ""
@@ -50,6 +50,12 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr "El valor \"%(value)s\" del campo \"%(field)s\" de \"%(model)s\" no existe."
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"El dominio o el criterio de búsqueda \"%(domain)s\" en la acción "
+"\"%(action)s\" no es correcto."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "El contexto \"%(context)s\" en la acción \"%(action)s\" no es correcto."
@@ -66,17 +72,13 @@ msgstr "La vista \"%(view)s\" para la acción \"%(action)s\" no es correcto."
 
 msgctxt "error:ir.action.keyword:"
 msgid "Wrong wizard model in keyword action \"%s\"."
-msgstr "Asistente incorrecto en la acción de teclado \"%s\"."
+msgstr "El modelo de asistente es incorrecto en la acción de teclado \"%s\"."
 
 msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr ""
 "La definición de correo electrónico sobre el informe \"%s\" no es correcta."
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "El nombre interno debe ser único por módulo."
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr "El nombre de los adjuntos debe ser único por registro."
@@ -1267,7 +1269,7 @@ msgstr "Información"
 
 msgctxt "field:ir.model,model:"
 msgid "Model Name"
-msgstr "Nombre del modelo"
+msgstr "Modelo"
 
 msgctxt "field:ir.model,module:"
 msgid "Module"
@@ -2221,9 +2223,9 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Número límite"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
-msgstr "Margen mínimo"
+msgstr "Retraso mínimo"
 
 msgctxt "field:ir.trigger,model:"
 msgid "Model"
@@ -2662,8 +2664,8 @@ msgid ""
 "Leave empty for the same as template, see unoconv documentation for "
 "compatible format"
 msgstr ""
-"Dejarlo vacío para el mismo tipo que la plantilla. Ver la documentación "
-"unoconv para formatos compatibles."
+"Dejarlo vacío para el mismo tipo que la plantilla. Consultar la "
+"documentación unoconv para conocer formatos compatibles."
 
 msgctxt "help:ir.action.report,style:"
 msgid "Define the style to apply on the report."
@@ -2718,8 +2720,12 @@ msgstr ""
 "concuerden."
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
-msgstr "El dominio es evaluado con el usuario \"user\" que es el usuario actual."
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
+msgstr ""
+"El dominio se evalua con PYSON dónde el contexto contiene:\n"
+"- \"user\" es el usuario actual"
 
 msgctxt "help:ir.rule.group,default_p:"
 msgid "Add this rule to all users by default"
@@ -2753,13 +2759,13 @@ msgstr ""
 "Número límite de llamadas a la \"Función de la acción\".\n"
 "0 para no límite."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Margen mínimo en minutos entre las llamadas a la \"Función de la acción\" para el mismo registro.\n"
-"0 para no dejar margen."
+"Indica un tiempo mínimo de retraso entre llamadas a la \"Función de la acción\" para el mismo registro.\n"
+"Dejarlo vacío si no hay retraso."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -3097,11 +3103,11 @@ msgstr "Módulo"
 
 msgctxt "model:ir.module.module.config_wizard.done,name:"
 msgid "Module Config Wizard Done"
-msgstr "Asitente de configuración de los módulos realizado"
+msgstr "Finalización asistente de configuración de módulos"
 
 msgctxt "model:ir.module.module.config_wizard.first,name:"
 msgid "Module Config Wizard First"
-msgstr "Inicio asistente de configuración del módulo"
+msgstr "Asistente de configuración del módulo - Primero"
 
 msgctxt "model:ir.module.module.config_wizard.item,name:"
 msgid "Config wizard to run after installing module"
@@ -3117,7 +3123,7 @@ msgstr "Dependencias del módulo"
 
 msgctxt "model:ir.module.module.install_upgrade.done,name:"
 msgid "Module Install Upgrade Done"
-msgstr "Finalizar la instalación o actualización de módulos"
+msgstr "Finalización instalación o actualización de módulos"
 
 msgctxt "model:ir.module.module.install_upgrade.start,name:"
 msgid "Module Install Upgrade Start"
@@ -3691,6 +3697,10 @@ msgctxt "view:ir.attachment:"
 msgid "Attachments"
 msgstr "Adjuntos"
 
+msgctxt "view:ir.attachment:"
+msgid "Last Modification Time"
+msgstr "Hora última modificación"
+
 msgctxt "view:ir.cron:"
 msgid "Action to trigger"
 msgstr "Acción a disparar"
@@ -3935,7 +3945,7 @@ msgstr "Limpiar traducciones"
 
 msgctxt "view:ir.translation.clean.succeed:"
 msgid "Clean Translations Succeed!"
-msgstr "Las traducciones se han limpiado correctamente."
+msgstr "La limpieza de las traducciones se ha realizado correctamente."
 
 msgctxt "view:ir.translation.export.result:"
 msgid "Export Translation"
@@ -4038,11 +4048,11 @@ msgid "Print"
 msgstr "Imprimir"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4058,7 +4068,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4078,7 +4088,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4102,7 +4112,7 @@ msgid "Set"
 msgstr "Definir"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/locale/fr_FR.po b/trytond/ir/locale/fr_FR.po
index 44e6984..844d47d 100644
--- a/trytond/ir/locale/fr_FR.po
+++ b/trytond/ir/locale/fr_FR.po
@@ -52,6 +52,12 @@ msgstr ""
 "La valeur « %(value)s » du champ « %(field)s » sur « %(model)s » n'existe "
 "pas."
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"Le domaine ou critère de recherche « %(domain)s » de l'action « %(action)s »"
+" est invalide."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "Le contexte « %(context)s » de l'action « %(action)s » est invalide."
@@ -74,10 +80,6 @@ msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr "Définition de mail incorrecte sur le rapport « %s »."
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "Le nom interne doit être unique par module !"
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr ""
@@ -2236,9 +2238,9 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Nombre limite"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
-msgstr "Délais minimal"
+msgstr "Délai minimal"
 
 msgctxt "field:ir.trigger,model:"
 msgid "Model"
@@ -2682,7 +2684,7 @@ msgstr ""
 
 msgctxt "help:ir.action.report,style:"
 msgid "Define the style to apply on the report."
-msgstr "Définit le style à appliquer sur le rapport."
+msgstr "Défini le style à appliquer sur le rapport."
 
 msgctxt "help:ir.action.wizard,window:"
 msgid "Run wizard in a new window"
@@ -2733,8 +2735,12 @@ msgstr ""
 "graphe"
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
-msgstr "Le domaine est évalué avec « user » comme utilisateur courant"
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
+msgstr ""
+"Le domaine est évalué avec un context PYSON contenant:\n"
+"- « user » comme utilisateur courant"
 
 msgctxt "help:ir.rule.group,default_p:"
 msgid "Add this rule to all users by default"
@@ -2767,13 +2773,13 @@ msgstr ""
 "Limite le nombre d'appel aux « Fonction action » par enregistrement.\n"
 "0 signifie pas de limite."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Défini un délais minimum en minutes entre les appels aux « Fonctions actions » sur un même modèle.\n"
-"0 signifie pas de délais."
+"Défini un delai minimal entre l'appel à la « fonction de l'action » pour le même enregistrement.\n"
+"Vide pour aucun délai."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -3705,6 +3711,10 @@ msgctxt "view:ir.attachment:"
 msgid "Attachments"
 msgstr "Pièces jointes"
 
+msgctxt "view:ir.attachment:"
+msgid "Last Modification Time"
+msgstr "Heure de dernière modification"
+
 msgctxt "view:ir.cron:"
 msgid "Action to trigger"
 msgstr "Action à déclencher"
@@ -4085,12 +4095,12 @@ msgid "Print"
 msgstr "Imprimer"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
-msgstr "Ok"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
-msgstr "Ok"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
 msgid "Cancel"
@@ -4105,8 +4115,8 @@ msgid "Cancel"
 msgstr "Annuler"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
-msgstr "Ok"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
 msgid "Cancel"
@@ -4125,8 +4135,8 @@ msgid "Cancel"
 msgstr "Annuler"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
-msgstr "Ok"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
 msgid "Close"
@@ -4149,8 +4159,8 @@ msgid "Set"
 msgstr "Mise à jour"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
-msgstr "Ok"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
 msgid "Cancel"
diff --git a/trytond/ir/locale/nl_NL.po b/trytond/ir/locale/nl_NL.po
index a34a383..f3a0e8c 100644
--- a/trytond/ir/locale/nl_NL.po
+++ b/trytond/ir/locale/nl_NL.po
@@ -42,6 +42,10 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr ""
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr ""
@@ -62,10 +66,6 @@ msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr ""
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "De interne naam moet uniek zijn per module!"
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr "De namen van de bijlagen moeten uniek zijn per bron!"
@@ -2251,9 +2251,9 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Begrenzing nummer"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
-msgstr "Minimum vertraging"
+msgstr ""
 
 msgctxt "field:ir.trigger,model:"
 msgid "Model"
@@ -2759,7 +2759,9 @@ msgid ""
 msgstr ""
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
 msgstr ""
 
 msgctxt "help:ir.rule.group,default_p:"
@@ -2792,14 +2794,11 @@ msgstr ""
 "Beperk het aantal aanroepen van \"Uitvoerende functie\" door items.\n"
 "Gebruik 0 voor geen beperking."
 
-#, fuzzy
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Stelt de minimum vertraging in tussen afzonderlijke aanroepen van \"Uitvoerende functie\" door hetzelfde item.\n"
-"Gebruik 0 voor geen vertraging."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -4100,12 +4099,12 @@ msgstr ""
 
 #, fuzzy
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Oké"
 
 #, fuzzy
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "Oké"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4123,7 +4122,7 @@ msgstr "Annuleren"
 
 #, fuzzy
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "Oké"
 
 #, fuzzy
@@ -4146,7 +4145,7 @@ msgstr "Annuleren"
 
 #, fuzzy
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Oké"
 
 #, fuzzy
@@ -4175,7 +4174,7 @@ msgstr ""
 
 #, fuzzy
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Oké"
 
 #, fuzzy
diff --git a/trytond/ir/locale/ru_RU.po b/trytond/ir/locale/ru_RU.po
index 687f877..372924d 100644
--- a/trytond/ir/locale/ru_RU.po
+++ b/trytond/ir/locale/ru_RU.po
@@ -44,6 +44,13 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr ""
 
+#, fuzzy
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"Некорректный домен или условия поиска \"%(domain)s\" у действия "
+"\"%(action)s\"."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "Некорректный контекст \"%(context)s\" у действия \"%(action)s\"."
@@ -66,10 +73,6 @@ msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr "Некорректная эл.почта у отчета \"%s\"."
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "Наименование модуля должно быть уникальным!"
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr "Имена вложений должны быть уникальны для ресурса!"
@@ -2200,9 +2203,9 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Номер лимита"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
-msgstr "Минимальная задержка"
+msgstr ""
 
 msgctxt "field:ir.trigger,model:"
 msgid "Model"
@@ -2705,8 +2708,11 @@ msgstr ""
 "Ввод регулярного выражения Python исключит соответствующие модели из "
 "диаграммы."
 
+#, fuzzy
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
 msgstr ""
 "Домен расчитывается для пользователя \"user\" как для текущего пользователя"
 
@@ -2741,13 +2747,11 @@ msgstr ""
 "Ограничение количества вызовов \"Действие функции\" от записей. \"0\" - нет "
 "ограничений."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Задайте минимальную задержку в минутах между вызовом \"Действие функции\" для одинаковых записей.\n"
-"\"0\" для отсутствия задержки."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -4033,11 +4037,11 @@ msgstr "Печать"
 
 #, fuzzy
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Ок"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "Ок"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4053,7 +4057,7 @@ msgid "Cancel"
 msgstr "Отменить"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "Ок"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4073,7 +4077,7 @@ msgid "Cancel"
 msgstr "Отменить"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Ок"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4097,7 +4101,7 @@ msgid "Set"
 msgstr "Применить"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "Ок"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/locale/sl_SI.po b/trytond/ir/locale/sl_SI.po
index 8b73500..ff1f95b 100644
--- a/trytond/ir/locale/sl_SI.po
+++ b/trytond/ir/locale/sl_SI.po
@@ -50,6 +50,12 @@ msgctxt "error:foreign_model_missing:"
 msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
 msgstr "Vrednost \"%(value)s\" polja \"%(field)s\" pri \"%(model)s\" ne obstaja."
 
+msgctxt "error:ir.action.act_window.domain:"
+msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
+msgstr ""
+"Neveljavna domena ali iskalni kriterij \"%(domain)s\" pri ukrepu "
+"\"%(action)s\"."
+
 msgctxt "error:ir.action.act_window:"
 msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
 msgstr "Neveljaven kontekst \"%(context)s\" pri ukrepu \"%(action)s\"."
@@ -72,10 +78,6 @@ msgctxt "error:ir.action.report:"
 msgid "Invalid email definition on report \"%s\"."
 msgstr "Neveljavna definicija epošte na izpisu \"%s\"."
 
-msgctxt "error:ir.action.report:"
-msgid "The internal name must be unique by module!"
-msgstr "Interni naziv mora biti enoličen med moduli."
-
 msgctxt "error:ir.attachment:"
 msgid "The  names of attachments must be unique by resource!"
 msgstr "Imena priponk morajo biti unikatna med resursi."
@@ -401,7 +403,7 @@ msgstr "Ime"
 
 msgctxt "field:ir.action,type:"
 msgid "Type"
-msgstr "Vrsta"
+msgstr "Tip"
 
 msgctxt "field:ir.action,usage:"
 msgid "Usage"
@@ -961,7 +963,7 @@ msgstr "Povzetek"
 
 msgctxt "field:ir.attachment,type:"
 msgid "Type"
-msgstr "Vrsta"
+msgstr "Tip"
 
 msgctxt "field:ir.attachment,write_date:"
 msgid "Write Date"
@@ -1897,7 +1899,7 @@ msgstr "Zaokroževanje časovnega žiga"
 
 msgctxt "field:ir.sequence,type:"
 msgid "Type"
-msgstr "Vrsta"
+msgstr "Tip"
 
 msgctxt "field:ir.sequence,write_date:"
 msgid "Write Date"
@@ -1973,7 +1975,7 @@ msgstr "Zaokroževanje časovnega žiga"
 
 msgctxt "field:ir.sequence.strict,type:"
 msgid "Type"
-msgstr "Vrsta"
+msgstr "Tip"
 
 msgctxt "field:ir.sequence.strict,write_date:"
 msgid "Write Date"
@@ -2125,7 +2127,7 @@ msgstr "MD5 izvora"
 
 msgctxt "field:ir.translation,type:"
 msgid "Type"
-msgstr "Vrsta"
+msgstr "Tip"
 
 msgctxt "field:ir.translation,value:"
 msgid "Translation Value"
@@ -2215,7 +2217,7 @@ msgctxt "field:ir.trigger,limit_number:"
 msgid "Limit Number"
 msgstr "Število klicev"
 
-msgctxt "field:ir.trigger,minimum_delay:"
+msgctxt "field:ir.trigger,minimum_time_delay:"
 msgid "Minimum Delay"
 msgstr "Minimalna zakasnitev"
 
@@ -2710,8 +2712,12 @@ msgid ""
 msgstr "Vpis Python regularnega izraza izloči najdene modele iz grafa."
 
 msgctxt "help:ir.rule,domain:"
-msgid "Domain is evaluated with \"user\" as the current user"
-msgstr "Domena ima vrednost \"user\" za trenutnega uporabnika "
+msgid ""
+"Domain is evaluated with a PYSON context containing:\n"
+"- \"user\" as the current user"
+msgstr ""
+"Domena je ovrednotena s PYSON kontekstom, ki vsebuje:\n"
+"- \"user\" kot trenutni uporabnik"
 
 msgctxt "help:ir.rule.group,default_p:"
 msgid "Add this rule to all users by default"
@@ -2745,13 +2751,13 @@ msgstr ""
 "Omeji število klicev \"funkcije ukrepa\" po posameznem zapisu.\n"
 "0 za neomejeno število."
 
-msgctxt "help:ir.trigger,minimum_delay:"
+msgctxt "help:ir.trigger,minimum_time_delay:"
 msgid ""
-"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
-"0 for no delay."
+"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
+"empty for no delay."
 msgstr ""
-"Nastavi minimalno zakasnitev v minutah med klicema \"funkcije ukrepa\" istega zapisa.\n"
-"0 za brez zakasnitve."
+"Nastavi minimalno časovno zakasnitev za klic \"Funkcija ukrepa\" pri istem zapisu.\n"
+"Pusti prazno za brez zakasnitve."
 
 msgctxt "help:ir.ui.view_search,domain:"
 msgid "The PYSON domain"
@@ -3683,6 +3689,10 @@ msgctxt "view:ir.attachment:"
 msgid "Attachments"
 msgstr "Priloge"
 
+msgctxt "view:ir.attachment:"
+msgid "Last Modification Time"
+msgstr "Zadnja sprememba"
+
 msgctxt "view:ir.cron:"
 msgid "Action to trigger"
 msgstr "Sprožitev ukrepa"
@@ -4027,11 +4037,11 @@ msgid "Print"
 msgstr "Natisni"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,done,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "V redu"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,action:"
-msgid "Ok"
+msgid "OK"
 msgstr "V redu"
 
 msgctxt "wizard_button:ir.module.module.config_wizard,first,end:"
@@ -4047,7 +4057,7 @@ msgid "Cancel"
 msgstr "Prekliči"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,done,config:"
-msgid "Ok"
+msgid "OK"
 msgstr "V redu"
 
 msgctxt "wizard_button:ir.module.module.install_upgrade,start,end:"
@@ -4067,7 +4077,7 @@ msgid "Cancel"
 msgstr "Prekliči"
 
 msgctxt "wizard_button:ir.translation.clean,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "V redu"
 
 msgctxt "wizard_button:ir.translation.export,result,end:"
@@ -4091,7 +4101,7 @@ msgid "Set"
 msgstr "Nastavi"
 
 msgctxt "wizard_button:ir.translation.set,succeed,end:"
-msgid "Ok"
+msgid "OK"
 msgstr "V redu"
 
 msgctxt "wizard_button:ir.translation.update,start,end:"
diff --git a/trytond/ir/model.py b/trytond/ir/model.py
index 2217dc8..c4152b2 100644
--- a/trytond/ir/model.py
+++ b/trytond/ir/model.py
@@ -1,7 +1,9 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 re
 import heapq
+
+from sql import Null
 from sql.aggregate import Max
 from sql.conditionals import Case
 from collections import defaultdict
@@ -20,6 +22,7 @@ from ..pyson import Bool, Eval
 from ..rpc import RPC
 from .. import backend
 from ..protocols.jsonrpc import JSONDecoder, JSONEncoder
+from ..tools import is_instance_method
 try:
     from ..tools.StringMatcher import StringMatcher
 except ImportError:
@@ -168,7 +171,7 @@ class Model(ModelSQL, ModelView):
     def global_search(cls, text, limit, menu='ir.ui.menu'):
         """
         Search on models for text including menu
-        Returns a list of tuple (ratio, model, model_name, id, rec_name, icon)
+        Returns a list of tuple (ratio, model, model_name, id, name, icon)
         The size of the list is limited to limit
         """
         pool = Pool()
@@ -194,12 +197,12 @@ class Model(ModelSQL, ModelView):
                 Model = pool.get(model.model)
                 if not hasattr(Model, 'search_global'):
                     continue
-                for id_, rec_name, icon in Model.search_global(text):
-                    if isinstance(rec_name, str):
-                        rec_name = rec_name.decode('utf-8')
-                    s.set_seq1(rec_name)
+                for record, name, icon in Model.search_global(text):
+                    if isinstance(name, str):
+                        name = name.decode('utf-8')
+                    s.set_seq1(name)
                     yield (s.ratio(), model.model, model.rec_name,
-                        id_, rec_name, icon)
+                        record.id, name, icon)
         return heapq.nlargest(int(limit), generate())
 
 
@@ -309,7 +312,7 @@ class ModelField(ModelSQL, ModelView):
         for field_name in model_fields:
             if model_fields[field_name]['module'] == module_name \
                     and field_name not in model._fields:
-                #XXX This delete field even when it is defined later
+                # XXX This delete field even when it is defined later
                 # in the module
                 cursor.execute(*ir_model_field.delete(
                         where=ir_model_field.id ==
@@ -511,7 +514,7 @@ class ModelAccess(ModelSQL, ModelView):
                 Max(Case((model_access.perm_create, 1), else_=0)),
                 Max(Case((model_access.perm_delete, 1), else_=0)),
                 where=ir_model.model.in_(models)
-                & ((user_group.user == user) | (model_access.group == None)),
+                & ((user_group.user == user) | (model_access.group == Null)),
                 group_by=ir_model.model))
         access.update(dict(
                 (m, {'read': r, 'write': w, 'create': c, 'delete': d})
@@ -562,8 +565,7 @@ class ModelAccess(ModelSQL, ModelView):
             selection = field.selection
             if isinstance(selection, basestring):
                 sel_func = getattr(Model, field.selection)
-                if (not hasattr(sel_func, 'im_self')
-                        or sel_func.im_self):
+                if not is_instance_method(Model, field.selection):
                     selection = sel_func()
                 else:
                     # XXX Can not check access right on instance method
@@ -697,7 +699,7 @@ class ModelFieldAccess(ModelSQL, ModelView):
                 Max(Case((field_access.perm_create, 1), else_=0)),
                 Max(Case((field_access.perm_delete, 1), else_=0)),
                 where=ir_model.model.in_(models)
-                & ((user_group.user == user) | (field_access.group == None)),
+                & ((user_group.user == user) | (field_access.group == Null)),
                 group_by=[ir_model.model, model_field.name]))
         for m, f, r, w, c, d in cursor.fetchall():
             accesses[m][f] = {'read': r, 'write': w, 'create': c, 'delete': d}
@@ -876,7 +878,7 @@ class ModelData(ModelSQL, ModelView):
         Operator = fields.SQL_OPERATORS[operator]
         query = table.select(table.id,
             where=Operator(
-                (table.fs_values != table.values) & (table.fs_values != None),
+                (table.fs_values != table.values) & (table.fs_values != Null),
                 value))
         return [('id', 'in', query)]
 
@@ -916,10 +918,9 @@ class ModelData(ModelSQL, ModelView):
             return dict(json.loads(values, object_hook=JSONDecoder()))
         except ValueError:
             # Migration from 3.2
-            from ..tools import safe_eval
             from decimal import Decimal
             import datetime
-            return safe_eval(values, {
+            return eval(values, {
                     'Decimal': Decimal,
                     'datetime': datetime,
                     })
@@ -1008,7 +1009,7 @@ class ModelGraph(Report):
         graph.set('ratio', 'auto')
         cls.fill_graph(models, graph, level=data['level'], filter=filter)
         data = graph.create(prog='dot', format='png')
-        return ('png', buffer(data), False, action_report.name)
+        return ('png', fields.Binary.cast(data), False, action_report.name)
 
     @classmethod
     def fill_graph(cls, models, graph, level=1, filter=None):
diff --git a/trytond/ir/model.xml b/trytond/ir/model.xml
index 9c5641a..ae4d69d 100644
--- a/trytond/ir/model.xml
+++ b/trytond/ir/model.xml
@@ -204,7 +204,7 @@ this repository contains the full copyright notices and license terms. -->
             id="act_model_data_form_domain_out_of_sync">
             <field name="name">Out of Sync</field>
             <field name="sequence" eval="10"/>
-            <field name="domain">[('out_of_sync', '=', True)]</field>
+            <field name="domain" eval="[('out_of_sync', '=', True)]" pyson="1"/>
             <field name="act_window" ref="act_model_data_form"/>
         </record>
         <record model="ir.action.act_window.domain"
diff --git a/trytond/ir/module/__init__.py b/trytond/ir/module/__init__.py
index 9a8fa1a..98704bf 100644
--- a/trytond/ir/module/__init__.py
+++ b/trytond/ir/module/__init__.py
@@ -1,3 +1,3 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .module import *
diff --git a/trytond/ir/module/module.py b/trytond/ir/module/module.py
index 8153bde..1dca76d 100644
--- a/trytond/ir/module/module.py
+++ b/trytond/ir/module/module.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 functools import wraps
 
 from trytond.model import ModelView, ModelSQL, fields
@@ -9,7 +9,7 @@ from trytond.wizard import Wizard, StateView, Button, StateTransition, \
 from trytond import backend
 from trytond.pool import Pool
 from trytond.transaction import Transaction
-from trytond.pyson import Eval
+from trytond.pyson import Eval, If
 from trytond.rpc import RPC
 
 __all__ = [
@@ -121,6 +121,15 @@ class Module(ModelSQL, ModelView):
         return child_ids
 
     @classmethod
+    def view_attributes(cls):
+        return [('/tree', 'colors',
+                If(Eval('state').in_(['to upgrade', 'to install']),
+                    'blue',
+                    If(Eval('state') == 'uninstalled',
+                        'grey',
+                        'black')))]
+
+    @classmethod
     def delete(cls, records):
         for module in records:
             if module.state in (
@@ -423,7 +432,7 @@ class ModuleConfigWizard(Wizard):
     first = StateView('ir.module.module.config_wizard.first',
         'ir.module_config_wizard_first_view_form', [
             Button('Cancel', 'end', 'tryton-cancel'),
-            Button('Ok', 'action', 'tryton-ok', default=True),
+            Button('OK', 'action', 'tryton-ok', default=True),
             ])
     other = StateView('ir.module.module.config_wizard.other',
         'ir.module_config_wizard_other_view_form', [
@@ -433,7 +442,7 @@ class ModuleConfigWizard(Wizard):
     action = ConfigStateAction()
     done = StateView('ir.module.module.config_wizard.done',
         'ir.module_config_wizard_done_view_form', [
-            Button('Ok', 'end', 'tryton-close', default=True),
+            Button('OK', 'end', 'tryton-close', default=True),
             ])
 
     def transition_start(self):
@@ -479,7 +488,7 @@ class ModuleInstallUpgrade(Wizard):
     upgrade = StateTransition()
     done = StateView('ir.module.module.install_upgrade.done',
         'ir.module_install_upgrade_done_view_form', [
-            Button('Ok', 'config', 'tryton-ok', default=True),
+            Button('OK', 'config', 'tryton-ok', default=True),
             ])
     config = StateAction('ir.act_module_config_wizard')
 
diff --git a/trytond/ir/module/module.xml b/trytond/ir/module/module.xml
index 8dc897f..530fc8d 100644
--- a/trytond/ir/module/module.xml
+++ b/trytond/ir/module/module.xml
@@ -18,7 +18,7 @@ this repository contains the full copyright notices and license terms. -->
             <field name="name">Modules</field>
             <field name="type">ir.action.act_window</field>
             <field name="res_model">ir.module.module</field>
-            <field name="domain">[('name', '!=', 'tests')]</field>
+            <field name="domain" eval="[('name', '!=', 'tests')]" pyson="1"/>
         </record>
         <record model="ir.action.act_window.view"
             id="act_module_form_view1">
diff --git a/trytond/ir/property.py b/trytond/ir/property.py
index f5c79ff..2e5db5d 100644
--- a/trytond/ir/property.py
+++ b/trytond/ir/property.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 decimal import Decimal
 from ..model import ModelView, ModelSQL, fields
 from ..transaction import Transaction
diff --git a/trytond/ir/property.xml b/trytond/ir/property.xml
index a7e1718..2ea792e 100644
--- a/trytond/ir/property.xml
+++ b/trytond/ir/property.xml
@@ -17,7 +17,7 @@ this repository contains the full copyright notices and license terms. -->
             <field name="name">Default Properties</field>
             <field name="type">ir.action.act_window</field>
             <field name="res_model">ir.property</field>
-            <field name="domain">[('res', '=', None)]</field>
+            <field name="domain" eval="[('res', '=', None)]" pyson="1"/>
         </record>
         <record model="ir.action.act_window.view"
             id="act_property_form_view1_default">
diff --git a/trytond/ir/rule.py b/trytond/ir/rule.py
index 0c60fe7..351b577 100644
--- a/trytond/ir/rule.py
+++ b/trytond/ir/rule.py
@@ -1,11 +1,11 @@
-#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 ..model import ModelView, ModelSQL, fields
-from ..tools import safe_eval
+# 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 ..model import ModelView, ModelSQL, fields, EvalEnvironment
 from ..transaction import Transaction
 from ..cache import Cache
 from ..pool import Pool
 from .. import backend
+from ..pyson import PYSONDecoder
 
 __all__ = [
     'RuleGroup', 'Rule',
@@ -92,11 +92,11 @@ class RuleGroup(ModelSQL, ModelView):
 class Rule(ModelSQL, ModelView):
     "Rule"
     __name__ = 'ir.rule'
-    _rec_name = 'field'
     rule_group = fields.Many2One('ir.rule.group', 'Group', select=True,
        required=True, ondelete="CASCADE")
     domain = fields.Char('Domain', required=True,
-        help='Domain is evaluated with "user" as the current user')
+        help='Domain is evaluated with a PYSON context containing:\n'
+        '- "user" as the current user')
     _domain_get_cache = Cache('ir_rule.domain_get', context=False)
 
     @classmethod
@@ -127,7 +127,7 @@ class Rule(ModelSQL, ModelView):
         ctx = cls._get_context()
         for rule in rules:
             try:
-                value = safe_eval(rule.domain, ctx)
+                value = PYSONDecoder(ctx).decode(rule.domain)
             except Exception:
                 cls.raise_user_error('invalid_domain', (rule.rec_name,))
             if not isinstance(value, list):
@@ -143,7 +143,7 @@ class Rule(ModelSQL, ModelView):
         User = Pool().get('res.user')
         user_id = Transaction().user
         with Transaction().set_context(_check_access=False):
-            user = User(user_id)
+            user = EvalEnvironment(User(user_id), User)
         return {
             'user': user,
             }
@@ -219,7 +219,7 @@ class Rule(ModelSQL, ModelView):
             for rule in cls.browse(ids):
                 assert rule.domain, ('Rule domain empty,'
                     'check if migration was done')
-                dom = safe_eval(rule.domain, ctx)
+                dom = PYSONDecoder(ctx).decode(rule.domain)
                 if rule.rule_group.global_p:
                     clause_global.setdefault(rule.rule_group.id, ['OR'])
                     clause_global[rule.rule_group.id].append(dom)
diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py
index 93bae63..33d0c18 100644
--- a/trytond/ir/sequence.py
+++ b/trytond/ir/sequence.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 string import Template
 import time
 from itertools import izip
@@ -174,6 +174,18 @@ class Sequence(ModelSQL, ModelView):
                 })
 
     @classmethod
+    def view_attributes(cls):
+        return [
+            ('//group[@id="incremental"]', 'states', {
+                    'invisible': ~Eval('type').in_(['incremental']),
+                    }),
+            ('//group[@id="timestamp"]', 'states', {
+                    'invisible': ~Eval('type').in_(
+                        ['decimal timestamp', 'hexadecimal timestamp']),
+                    }),
+            ]
+
+    @classmethod
     def create(cls, vlist):
         sequences = super(Sequence, cls).create(vlist)
         for sequence, values in izip(sequences, vlist):
@@ -310,7 +322,7 @@ class Sequence(ModelSQL, ModelView):
                     % sequence._sql_sequence_name)
                 number_next, = cursor.fetchone()
             else:
-                #Pre-fetch number_next
+                # Pre-fetch number_next
                 number_next = sequence.number_next_internal
 
                 cls.write([sequence], {
diff --git a/trytond/ir/sequence.xml b/trytond/ir/sequence.xml
index be5d996..c9c3506 100644
--- a/trytond/ir/sequence.xml
+++ b/trytond/ir/sequence.xml
@@ -20,7 +20,7 @@ this repository contains the full copyright notices and license terms. -->
             <field name="name">Sequences</field>
             <field name="type">ir.action.act_window</field>
             <field name="res_model">ir.sequence</field>
-            <field name="context">{'active_test': False}</field>
+            <field name="context" eval="{'active_test': False}" pyson="1"/>
         </record>
         <record model="ir.action.act_window.view"
             id="act_sequence_form_view1">
@@ -51,7 +51,7 @@ this repository contains the full copyright notices and license terms. -->
             <field name="name">Sequences Strict</field>
             <field name="type">ir.action.act_window</field>
             <field name="res_model">ir.sequence.strict</field>
-            <field name="context">{'active_test': False}</field>
+            <field name="context" eval="{'active_test': False}" pyson="1"/>
         </record>
         <record model="ir.action.act_window.view"
             id="act_sequence_strict_form_view1">
diff --git a/trytond/ir/session.py b/trytond/ir/session.py
index d9beae9..99e8e8a 100644
--- a/trytond/ir/session.py
+++ b/trytond/ir/session.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 try:
     import simplejson as json
 except ImportError:
diff --git a/trytond/ir/time_locale.py b/trytond/ir/time_locale.py
index 1371e77..6077daf 100644
--- a/trytond/ir/time_locale.py
+++ b/trytond/ir/time_locale.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of')
-#this repository contains the full copyright notices and license terms.')
+# This file is part of Tryton.  The COPYRIGHT file at the top level of')
+# this repository contains the full copyright notices and license terms.')
 TIME_LOCALE = \
 {'bg_BG': {'%A': [u'\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u043d\u0438\u043a',
                   u'\u0432\u0442\u043e\u0440\u043d\u0438\u043a',
@@ -305,7 +305,7 @@ TIME_LOCALE = \
                   u'oct',
                   u'nov',
                   u'dic'],
-           '%p': [u'AM', u'PM']},
+           '%p': [u'', u'']},
  'es_ES': {'%A': [u'lunes',
                   u'martes',
                   u'mi\xe9rcoles',
@@ -443,59 +443,65 @@ TIME_LOCALE = \
                   u'\u041e\u043a\u0442\u044f\u0431\u0440\u044c',
                   u'\u041d\u043e\u044f\u0431\u0440\u044c',
                   u'\u0414\u0435\u043a\u0430\u0431\u0440\u044c'],
-           '%a': [u'\u041f\u043d.',
-                  u'\u0412\u0442.',
-                  u'\u0421\u0440.',
-                  u'\u0427\u0442.',
-                  u'\u041f\u0442.',
-                  u'\u0421\u0431.',
-                  u'\u0412\u0441.'],
+           '%a': [u'\u041f\u043d',
+                  u'\u0412\u0442',
+                  u'\u0421\u0440',
+                  u'\u0427\u0442',
+                  u'\u041f\u0442',
+                  u'\u0421\u0431',
+                  u'\u0412\u0441'],
            '%b': [None,
-                  u'\u044f\u043d\u0432.',
-                  u'\u0444\u0435\u0432\u0440.',
-                  u'\u043c\u0430\u0440\u0442\u0430',
-                  u'\u0430\u043f\u0440.',
-                  u'\u043c\u0430\u044f',
-                  u'\u0438\u044e\u043d\u044f',
-                  u'\u0438\u044e\u043b\u044f',
-                  u'\u0430\u0432\u0433.',
-                  u'\u0441\u0435\u043d\u0442.',
-                  u'\u043e\u043a\u0442.',
-                  u'\u043d\u043e\u044f\u0431.',
-                  u'\u0434\u0435\u043a.'],
+                  u'\u044f\u043d\u0432',
+                  u'\u0444\u0435\u0432',
+                  u'\u043c\u0430\u0440',
+                  u'\u0430\u043f\u0440',
+                  u'\u043c\u0430\u0439',
+                  u'\u0438\u044e\u043d',
+                  u'\u0438\u044e\u043b',
+                  u'\u0430\u0432\u0433',
+                  u'\u0441\u0435\u043d',
+                  u'\u043e\u043a\u0442',
+                  u'\u043d\u043e\u044f',
+                  u'\u0434\u0435\u043a'],
            '%p': [u'', u'']},
-  'sl_SI': {'%A':  [u'ponedeljek',
-                    u'torek',
-                    u'sreda',
-                    u'\u010detrtek',
-                    u'petek',
-                    u'sobota',
-                    u'nedelja'],
-             '%B': [None,
-                    u'januar',
-                    u'februar',
-                    u'marec',
-                    u'april',
-                    u'maj',
-                    u'junij',
-                    u'julij',
-                    u'avgust',
-                    u'september',
-                    u'oktober',
-                    u'november',
-                    u'december'],
-             '%a': [u'pon', u'tor', u'sre', u'\u010det', u'pet', u'sob', u'ned'],
-             '%b': [None,
-                    u'jan',
-                    u'feb',
-                    u'mar',
-                    u'apr',
-                    u'maj',
-                    u'jun',
-                    u'jul',
-                    u'avg',
-                    u'sep',
-                    u'okt',
-                    u'nov',
-                    u'dec'],
-             '%p': [u'', u'']}}
+ 'sl_SI': {'%A': [u'ponedeljek',
+                  u'torek',
+                  u'sreda',
+                  u'\u010detrtek',
+                  u'petek',
+                  u'sobota',
+                  u'nedelja'],
+           '%B': [None,
+                  u'januar',
+                  u'februar',
+                  u'marec',
+                  u'april',
+                  u'maj',
+                  u'junij',
+                  u'julij',
+                  u'avgust',
+                  u'september',
+                  u'oktober',
+                  u'november',
+                  u'december'],
+           '%a': [u'pon',
+                  u'tor',
+                  u'sre',
+                  u'\u010det',
+                  u'pet',
+                  u'sob',
+                  u'ned'],
+           '%b': [None,
+                  u'jan',
+                  u'feb',
+                  u'mar',
+                  u'apr',
+                  u'maj',
+                  u'jun',
+                  u'jul',
+                  u'avg',
+                  u'sep',
+                  u'okt',
+                  u'nov',
+                  u'dec'],
+           '%p': [u'', u'']}}
diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py
index b0c8a54..17af7ff 100644
--- a/trytond/ir/translation.py
+++ b/trytond/ir/translation.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 try:
     import cStringIO as StringIO
 except ImportError:
@@ -12,7 +12,8 @@ import os
 from hashlib import md5
 from lxml import etree
 from itertools import izip
-from sql import Column
+
+from sql import Column, Null
 from sql.functions import Substring, Position
 from sql.conditionals import Case
 from sql.operators import Or, And
@@ -23,11 +24,11 @@ from ..wizard import Wizard, StateView, StateTransition, StateAction, \
     Button
 from ..tools import file_open, reduce_ids, grouped_slice
 from .. import backend
-from ..pyson import PYSONEncoder
+from ..pyson import PYSONEncoder, Eval
 from ..transaction import Transaction
 from ..pool import Pool
 from ..cache import Cache
-from ..const import RECORD_CACHE_SIZE
+from ..config import config
 
 __all__ = ['Translation',
     'TranslationSetStart', 'TranslationSetSucceed', 'TranslationSet',
@@ -126,7 +127,7 @@ class Translation(ModelSQL, ModelView):
 
         # Migration from 2.2 and 2.8
         cursor.execute(*ir_translation.update([ir_translation.res_id],
-                [-1], where=(ir_translation.res_id == None)
+                [-1], where=(ir_translation.res_id == Null)
                 | (ir_translation.res_id == 0)))
 
         table = TableHandler(Transaction().cursor, cls, module_name)
@@ -148,7 +149,7 @@ class Translation(ModelSQL, ModelView):
                 & (ir_translation.name == name)
                 # Keep searching on all values for migration
                 & ((ir_translation.res_id == -1)
-                    | (ir_translation.res_id == None)
+                    | (ir_translation.res_id == Null)
                     | (ir_translation.res_id == 0))))
         trans_id = None
         if cursor.rowcount == -1 or cursor.rowcount is None:
@@ -336,13 +337,15 @@ class Translation(ModelSQL, ModelView):
     @classmethod
     def search_rec_name(cls, name, clause):
         clause = tuple(clause)
-        translations = cls.search(['OR',
-                ('src',) + clause[1:],
-                ('value',) + clause[1:],
-                ])
-        if translations:
-            return [('id', 'in', [t.id for t in translations])]
-        return [(cls._rec_name,) + clause[1:]]
+        if clause[1].startswith('!') or clause[1].startswith('not '):
+            bool_op = 'AND'
+        else:
+            bool_op = 'OR'
+        return [bool_op,
+            ('src',) + clause[1:],
+            ('value',) + clause[1:],
+            (cls._rec_name,) + clause[1:],
+            ]
 
     @classmethod
     def search_model(cls, name, clause):
@@ -372,6 +375,10 @@ class Translation(ModelSQL, ModelView):
         return md5((src or '').encode('utf-8')).hexdigest()
 
     @classmethod
+    def view_attributes(cls):
+        return [('/form//field[@name="value"]', 'spell', Eval('lang'))]
+
+    @classmethod
     def get_ids(cls, name, ttype, lang, ids):
         "Return translation for each id"
         pool = Pool()
@@ -436,7 +443,7 @@ class Translation(ModelSQL, ModelView):
                         (table.type == ttype),
                         (table.name == name),
                         (table.value != ''),
-                        (table.value != None),
+                        (table.value != Null),
                         red_sql,
                         ))
                 if fuzzy_sql:
@@ -611,7 +618,7 @@ class Translation(ModelSQL, ModelView):
             & (table.type == ttype)
             & (table.name == name)
             & (table.value != '')
-            & (table.value != None)
+            & (table.value != Null)
             & (table.fuzzy == False)
             & (table.res_id == -1))
         if source is not None:
@@ -656,7 +663,7 @@ class Translation(ModelSQL, ModelView):
                         (table.type == ttype),
                         (table.name == name),
                         (table.value != ''),
-                        (table.value != None),
+                        (table.value != Null),
                         (table.fuzzy == False),
                         (table.res_id == -1),
                         ))
@@ -780,7 +787,7 @@ class Translation(ModelSQL, ModelView):
                     (model_data.db_id, model_data.noupdate)
 
         translations = set()
-        to_create = []
+        to_save = []
         pofile = polib.pofile(po_path)
 
         id2translation = {}
@@ -795,7 +802,7 @@ class Translation(ModelSQL, ModelView):
                 raise ValueError('Unknow translation type: %s' %
                     translation.type)
             key2ids.setdefault(key, []).append(translation.id)
-            if len(module_translations) <= RECORD_CACHE_SIZE:
+            if len(module_translations) <= config.getint('cache', 'record'):
                 id2translation[translation.id] = translation
 
         def override_translation(ressource_id, new_translation):
@@ -829,12 +836,14 @@ class Translation(ModelSQL, ModelView):
         # Make a first loop to retreive translation ids in the right order to
         # get better read locality and a full usage of the cache.
         translation_ids = []
-        if len(module_translations) <= RECORD_CACHE_SIZE:
+        if len(module_translations) <= config.getint('cache', 'record'):
             processes = (True,)
         else:
             processes = (False, True)
         for processing in processes:
-            if processing and len(module_translations) > RECORD_CACHE_SIZE:
+            if (processing
+                    and len(module_translations) > config.getint('cache',
+                        'record')):
                 id2translation = dict((t.id, t)
                     for t in cls.browse(translation_ids))
             for entry in pofile:
@@ -872,26 +881,15 @@ class Translation(ModelSQL, ModelView):
                     continue
 
                 if not ids:
-                    to_create.append(translation._save_values)
-                else:
-                    to_write = []
+                    to_save.append(translation)
+                elif not noupdate:
                     for translation_id in ids:
                         old_translation = id2translation[translation_id]
-                        if (old_translation.value != translation.value
-                                or old_translation.fuzzy !=
-                                translation.fuzzy):
-                            to_write.append(old_translation)
-                    with Transaction().set_context(module=module):
-                        if to_write and not noupdate:
-                            cls.write(to_write, {
-                                    'value': translation.value,
-                                    'fuzzy': translation.fuzzy,
-                                    })
-                        translations |= set(cls.browse(ids))
-
-        if to_create:
-            with Transaction().set_context(module=module):
-                translations |= set(cls.create(to_create))
+                        old_translation.value = translation.value
+                        old_translation.fuzzy = translation.fuzzy
+                        to_save.append(old_translation)
+        cls.save(to_save)
+        translations |= set(to_save)
 
         if translations:
             all_translations = set(cls.search([
@@ -983,7 +981,7 @@ class TranslationSet(Wizard):
     set_ = StateTransition()
     succeed = StateView('ir.translation.set.succeed',
         'ir.translation_set_succeed_view_form', [
-            Button('Ok', 'end', 'tryton-ok', default=True),
+            Button('OK', 'end', 'tryton-ok', default=True),
             ])
 
     def _translate_report(self, node):
@@ -1229,7 +1227,7 @@ class TranslationClean(Wizard):
     clean = StateTransition()
     succeed = StateView('ir.translation.clean.succeed',
         'ir.translation_clean_succeed_view_form', [
-            Button('Ok', 'end', 'tryton-ok', default=True),
+            Button('OK', 'end', 'tryton-ok', default=True),
             ])
 
     @staticmethod
@@ -1239,9 +1237,10 @@ class TranslationClean(Wizard):
             model_name, field_name = translation.name.split(',', 1)
         except ValueError:
             return True
-        if model_name not in pool.object_name_list():
+        try:
+            Model = pool.get(model_name)
+        except KeyError:
             return True
-        Model = pool.get(model_name)
         if field_name not in Model._fields:
             return True
 
@@ -1252,10 +1251,11 @@ class TranslationClean(Wizard):
             model_name, field_name = translation.name.split(',', 1)
         except ValueError:
             return True
-        if model_name not in pool.object_name_list():
+        try:
+            Model = pool.get(model_name)
+        except KeyError:
             return True
         if translation.res_id >= 0:
-            Model = pool.get(model_name)
             if field_name not in Model._fields:
                 return True
             field = Model._fields[field_name]
@@ -1282,9 +1282,10 @@ class TranslationClean(Wizard):
             model_name, field_name = translation.name.split(',', 1)
         except ValueError:
             return True
-        if model_name not in pool.object_name_list():
+        try:
+            Model = pool.get(model_name)
+        except KeyError:
             return True
-        Model = pool.get(model_name)
         if field_name not in Model._fields:
             return True
         field = Model._fields[field_name]
@@ -1300,7 +1301,9 @@ class TranslationClean(Wizard):
     def _clean_view(translation):
         pool = Pool()
         model_name = translation.name
-        if model_name not in pool.object_name_list():
+        try:
+            pool.get(model_name)
+        except KeyError:
             return True
 
     @staticmethod
@@ -1311,10 +1314,10 @@ class TranslationClean(Wizard):
                 translation.name.split(',', 2)
         except ValueError:
             return True
-        if (wizard_name not in
-                pool.object_name_list(type='wizard')):
+        try:
+            Wizard = pool.get(wizard_name, type='wizard')
+        except KeyError:
             return True
-        Wizard = pool.get(wizard_name, type='wizard')
         if not Wizard:
             return True
         state = Wizard.states.get(state_name)
@@ -1331,9 +1334,10 @@ class TranslationClean(Wizard):
             model_name, field_name = translation.name.split(',', 1)
         except ValueError:
             return True
-        if model_name not in pool.object_name_list():
+        try:
+            Model = pool.get(model_name)
+        except KeyError:
             return True
-        Model = pool.get(model_name)
         if field_name not in Model._fields:
             return True
         field = Model._fields[field_name]
@@ -1368,8 +1372,15 @@ class TranslationClean(Wizard):
                 'recursion_error',
                 ):
             return False
-        if model_name in pool.object_name_list():
+        Model, Wizard = None, None
+        try:
             Model = pool.get(model_name)
+        except KeyError:
+            try:
+                Wizard = pool.get(model_name, type='wizard')
+            except KeyError:
+                pass
+        if Model:
             errors = Model._error_messages.values()
             if issubclass(Model, ModelSQL):
                 errors += Model._sql_error_messages.values()
@@ -1377,8 +1388,7 @@ class TranslationClean(Wizard):
                     errors.append(error)
             if translation.src not in errors:
                 return True
-        elif model_name in pool.object_name_list(type='wizard'):
-            Wizard = pool.get(model_name, type='wizard')
+        elif Wizard:
             errors = Wizard._error_messages.values()
             if translation.src not in errors:
                 return True
@@ -1530,10 +1540,10 @@ class TranslationUpdate(Wizard):
                 & translation.src.in_(
                     translation.select(translation.src,
                         where=((translation.value == '')
-                            | (translation.value == None))
+                            | (translation.value == Null))
                         & (translation.lang == lang)))
                 & (translation.value != '')
-                & (translation.value != None),
+                & (translation.value != Null),
                 group_by=translation.src))
 
         for row in cursor.dictfetchall():
@@ -1541,13 +1551,13 @@ class TranslationUpdate(Wizard):
                     [translation.fuzzy, translation.value],
                     [True, row['value']],
                     where=(translation.src == row['src'])
-                    & ((translation.value == '') | (translation.value == None))
+                    & ((translation.value == '') | (translation.value == Null))
                     & (translation.lang == lang)))
 
         cursor.execute(*translation.update(
                 [translation.fuzzy],
                 [False],
-                where=((translation.value == '') | (translation.value == None))
+                where=((translation.value == '') | (translation.value == Null))
                 & (translation.lang == lang)))
 
         action['pyson_domain'] = PYSONEncoder().encode([
@@ -1608,9 +1618,8 @@ class TranslationExport(Wizard):
     def transition_export(self):
         pool = Pool()
         Translation = pool.get('ir.translation')
-        file_data = Translation.translation_export(
+        self.result.file = Translation.translation_export(
             self.start.language.code, self.start.module.name)
-        self.result.file = buffer(file_data) if file_data else None
         return 'result'
 
     def default_result(self, fields):
diff --git a/trytond/ir/translation.xml b/trytond/ir/translation.xml
index e93b30b..fbb8424 100644
--- a/trytond/ir/translation.xml
+++ b/trytond/ir/translation.xml
@@ -16,7 +16,7 @@ this repository contains the full copyright notices and license terms. -->
         <record model="ir.action.act_window" id="act_translation_form">
             <field name="name">Translations</field>
             <field name="res_model">ir.translation</field>
-            <field name="domain">[('module', '!=', None)]</field>
+            <field name="domain" eval="[('module', '!=', None)]" pyson="1"/>
         </record>
         <record model="ir.action.act_window.view"
             id="act_translation_form_view1">
diff --git a/trytond/ir/trigger.py b/trytond/ir/trigger.py
index 9b88d2f..704400d 100644
--- a/trytond/ir/trigger.py
+++ b/trytond/ir/trigger.py
@@ -1,13 +1,13 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 datetime
 import time
-from sql import Literal
+from sql import Literal, Null
 from sql.aggregate import Count, Max
 
-from ..model import ModelView, ModelSQL, fields
-from ..pyson import Eval
-from ..tools import safe_eval, grouped_slice
+from ..model import ModelView, ModelSQL, fields, EvalEnvironment
+from ..pyson import Eval, PYSONDecoder
+from ..tools import grouped_slice
 from .. import backend
 from ..tools import reduce_ids
 from ..transaction import Transaction
@@ -29,7 +29,7 @@ class Trigger(ModelSQL, ModelView):
             'invisible': (Eval('on_create', False)
                 | Eval('on_write', False)
                 | Eval('on_delete', False)),
-        }, depends=['on_create', 'on_write', 'on_delete'])
+            }, depends=['on_create', 'on_write', 'on_delete'])
     on_create = fields.Boolean('On Create', select=True, states={
         'invisible': Eval('on_time', False),
         }, depends=['on_time'])
@@ -45,9 +45,10 @@ class Trigger(ModelSQL, ModelView):
     limit_number = fields.Integer('Limit Number', required=True,
         help='Limit the number of call to "Action Function" by records.\n'
         '0 for no limit.')
-    minimum_delay = fields.Float('Minimum Delay', help='Set a minimum delay '
-        'in minutes between call to "Action Function" for the same record.\n'
-        '0 for no delay.')
+    minimum_time_delay = fields.TimeDelta('Minimum Delay',
+        help='Set a minimum time delay between call to "Action Function" '
+        'for the same record.\n'
+        'empty for no delay.')
     action_model = fields.Many2One('ir.model', 'Action Model', required=True)
     action_function = fields.Char('Action Function', required=True)
     _get_triggers_cache = Cache('ir_trigger.get_triggers')
@@ -67,6 +68,29 @@ class Trigger(ModelSQL, ModelView):
         cls._order.insert(0, ('name', 'ASC'))
 
     @classmethod
+    def __register__(cls, module_name):
+        TableHandler = backend.get('TableHandler')
+        cursor = Transaction().cursor
+        table = TableHandler(cursor, cls, module_name)
+        sql_table = cls.__table__()
+
+        super(Trigger, cls).__register__(module_name)
+
+        # Migration from 3.4:
+        # change minimum_delay into timedelta minimu_time_delay
+        if table.column_exist('minimum_delay'):
+            cursor.execute(*sql_table.select(
+                    sql_table.id, sql_table.minimum_delay,
+                    where=sql_table.minimum_delay != Null))
+            for id_, delay in cursor.fetchall():
+                delay = datetime.timedelta(hours=delay)
+                cursor.execute(*sql_table.update(
+                        [sql_table.minimu_time_delay],
+                        [delay],
+                        where=sql_table.id == id_))
+            table.drop_column('minimum_delay')
+
+    @classmethod
     def validate(cls, triggers):
         super(Trigger, cls).validate(triggers)
         cls.check_condition(triggers)
@@ -96,36 +120,24 @@ class Trigger(ModelSQL, ModelView):
     @fields.depends('on_time')
     def on_change_on_time(self):
         if self.on_time:
-            return {
-                    'on_create': False,
-                    'on_write': False,
-                    'on_delete': False,
-                    }
-        return {}
+            self.on_create = False
+            self.on_write = False
+            self.on_delete = False
 
     @fields.depends('on_create')
     def on_change_on_create(self):
         if self.on_create:
-            return {
-                    'on_time': False,
-                    }
-        return {}
+            self.on_time = False
 
     @fields.depends('on_write')
     def on_change_on_write(self):
         if self.on_write:
-            return {
-                    'on_time': False,
-                    }
-        return {}
+            self.on_time = False
 
     @fields.depends('on_delete')
     def on_change_on_delete(self):
         if self.on_delete:
-            return {
-                    'on_time': False,
-                    }
-        return {}
+            self.on_time = False
 
     @classmethod
     def get_triggers(cls, model_name, mode):
@@ -159,8 +171,8 @@ class Trigger(ModelSQL, ModelView):
         env['current_date'] = datetime.datetime.today()
         env['time'] = time
         env['context'] = Transaction().context
-        env['self'] = record
-        return bool(safe_eval(trigger.condition, env))
+        env['self'] = EvalEnvironment(record, record.__class__)
+        return bool(PYSONDecoder(env).decode(trigger.condition))
 
     @classmethod
     def trigger_action(cls, records, trigger):
@@ -194,8 +206,8 @@ class Trigger(ModelSQL, ModelView):
                         new_ids.append(record_id)
             ids = new_ids
 
-        # Filter on minimum_delay
-        if trigger.minimum_delay:
+        # Filter on minimum_time_delay
+        if trigger.minimum_time_delay:
             new_ids = []
             for sub_ids in grouped_slice(ids):
                 sub_ids = list(sub_ids)
@@ -223,15 +235,14 @@ class Trigger(ModelSQL, ModelView):
                         delay[record_id] = datetime.datetime(year, month, day,
                                 hours, minutes, seconds, microseconds)
                     if (datetime.datetime.now() - delay[record_id]
-                            >= datetime.timedelta(
-                                minutes=trigger.minimum_delay)):
+                            >= trigger.minimum_time_delay):
                         new_ids.append(record_id)
             ids = new_ids
 
         records = Model.browse(ids)
         if records:
             getattr(ActionModel, trigger.action_function)(records, trigger)
-        if trigger.limit_number or trigger.minimum_delay:
+        if trigger.limit_number or trigger.minimum_time_delay:
             to_create = []
             for record in records:
                 to_create.append({
diff --git a/trytond/ir/ui/__init__.py b/trytond/ir/ui/__init__.py
index c86640b..4effdfa 100644
--- a/trytond/ir/ui/__init__.py
+++ b/trytond/ir/ui/__init__.py
@@ -1,2 +1,2 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
diff --git a/trytond/ir/ui/calendar.rnc b/trytond/ir/ui/calendar.rnc
index 0d3fcfe..75fb807 100644
--- a/trytond/ir/ui/calendar.rnc
+++ b/trytond/ir/ui/calendar.rnc
@@ -9,4 +9,19 @@ attlist.calendar &=
   [ a:defaultValue = "Unknown" ] attribute string { text }?
 field = element field { attlist.field, empty }
 attlist.field &= attribute name { text }
-start = calendar
+data = element data { attlist.data, xpath* }
+attlist.data &= empty
+
+xpath = element xpath {
+    attlist.xpath, ( field )*
+  }
+attlist.xpath &= attribute expr { text }
+attlist.xpath &=
+  [ a:defaultValue = "inside" ]
+  attribute position { "inside"
+      | "replace"
+      | "replace_attributes"
+      | "after"
+      | "before" }?
+
+start = data | calendar
diff --git a/trytond/ir/ui/calendar.rng b/trytond/ir/ui/calendar.rng
index eee6466..9b756e4 100644
--- a/trytond/ir/ui/calendar.rng
+++ b/trytond/ir/ui/calendar.rng
@@ -30,7 +30,47 @@
   <define name="attlist.field" combine="interleave">
     <attribute name="name"/>
   </define>
+  <define name="data">
+    <element name="data">
+      <ref name="attlist.data"/>
+      <zeroOrMore>
+        <ref name="xpath"/>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="attlist.data" combine="interleave">
+    <empty/>
+  </define>
+  <define name="xpath">
+    <element name="xpath">
+      <ref name="attlist.xpath"/>
+      <zeroOrMore>
+        <choice>
+          <ref name="field"/>
+        </choice>
+      </zeroOrMore>
+    </element>
+  </define>
+  <define name="attlist.xpath" combine="interleave">
+    <attribute name="expr"/>
+  </define>
+  <define name="attlist.xpath" combine="interleave">
+    <optional>
+      <attribute name="position" a:defaultValue="inside">
+        <choice>
+          <value>inside</value>
+          <value>replace</value>
+          <value>replace_attributes</value>
+          <value>after</value>
+          <value>before</value>
+        </choice>
+      </attribute>
+    </optional>
+  </define>
   <start>
-    <ref name="calendar"/>
+    <choice>
+      <ref name="data"/>
+      <ref name="calendar"/>
+    </choice>
   </start>
 </grammar>
diff --git a/trytond/ir/ui/form.rnc b/trytond/ir/ui/form.rnc
index 5943d85..01ac6c4 100644
--- a/trytond/ir/ui/form.rnc
+++ b/trytond/ir/ui/form.rnc
@@ -49,7 +49,7 @@ attlist.field &=
     | "selection"
     | "char"
     | "password"
-    | "float_time"
+    | "timedelta"
     | "boolean"
     | "reference"
     | "binary"
@@ -85,6 +85,7 @@ attlist.field &=
   [ a:defaultValue = "0" ] attribute tree_invisible { "0" | "1" }?
 attlist.field &= attribute mode { text }?
 attlist.field &= attribute view_ids { text }?
+attlist.field &= attribute product { text }?
 attlist.field &=
   [ a:defaultValue = "0" ] attribute invisible { "0" | "1" }?
 attlist.field &= attribute sum { text }?
@@ -98,9 +99,6 @@ attlist.field &=
     | "top_to_bottom"
   }?
 attlist.field &= attribute spell { text }?
-attlist.field &= attribute float_time { text }?
-attlist.field &= attribute img_width { text }?
-attlist.field &= attribute img_height { text }?
 attlist.field &=
   [a:defaultValue = "0"] attribute filename_visible { "0" | "1" }?
 attlist.field &= [a:defaultValue = "0"] attribute pre_validate { "0" | "1" }?
diff --git a/trytond/ir/ui/form.rng b/trytond/ir/ui/form.rng
index 87b446c..457ffa1 100644
--- a/trytond/ir/ui/form.rng
+++ b/trytond/ir/ui/form.rng
@@ -148,7 +148,7 @@
           <value>selection</value>
           <value>char</value>
           <value>password</value>
-          <value>float_time</value>
+          <value>timedelta</value>
           <value>boolean</value>
           <value>reference</value>
           <value>binary</value>
@@ -287,6 +287,11 @@
   </define>
   <define name="attlist.field" combine="interleave">
     <optional>
+      <attribute name="product"/>
+    </optional>
+  </define>
+  <define name="attlist.field" combine="interleave">
+    <optional>
       <attribute name="invisible" a:defaultValue="0">
         <choice>
           <value>0</value>
@@ -329,21 +334,6 @@
   </define>
   <define name="attlist.field" combine="interleave">
     <optional>
-      <attribute name="float_time"/>
-    </optional>
-  </define>
-  <define name="attlist.field" combine="interleave">
-    <optional>
-      <attribute name="img_width"/>
-    </optional>
-  </define>
-  <define name="attlist.field" combine="interleave">
-    <optional>
-      <attribute name="img_height"/>
-    </optional>
-  </define>
-  <define name="attlist.field" combine="interleave">
-    <optional>
       <attribute name="filename_visible" a:defaultValue="0">
         <choice>
           <value>0</value>
diff --git a/trytond/ir/ui/graph.rnc b/trytond/ir/ui/graph.rnc
index 3a74cfc..9373be3 100644
--- a/trytond/ir/ui/graph.rnc
+++ b/trytond/ir/ui/graph.rnc
@@ -23,8 +23,7 @@ attlist.field &= attribute domain { text }?
 attlist.field &= attribute fill { "0" | "1" }?
 attlist.field &= attribute empty { "0" | "1" }?
 attlist.field &= attribute color { text }?
-attlist.field &= attribute widget { "float_time" }?
-attlist.field &= attribute float_time { text }?
+attlist.field &= attribute timedelta { text }?
 attlist.field &=
     [ a:defaultValue = "linear" ]
     attribute interpolation { "constant-left" | "constant-right" | "constant-center" | "linear" }?
diff --git a/trytond/ir/ui/graph.rng b/trytond/ir/ui/graph.rng
index 125c49c..eb55457 100644
--- a/trytond/ir/ui/graph.rng
+++ b/trytond/ir/ui/graph.rng
@@ -115,14 +115,7 @@
   </define>
   <define name="attlist.field" combine="interleave">
     <optional>
-      <attribute name="widget">
-        <value>float_time</value>
-      </attribute>
-    </optional>
-  </define>
-  <define name="attlist.field" combine="interleave">
-    <optional>
-      <attribute name="float_time"/>
+      <attribute name="timedelta"/>
     </optional>
   </define>
   <define name="attlist.field" combine="interleave">
diff --git a/trytond/ir/ui/menu.py b/trytond/ir/ui/menu.py
index 94af4d8..d3d83c9 100644
--- a/trytond/ir/ui/menu.py
+++ b/trytond/ir/ui/menu.py
@@ -1,7 +1,9 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 itertools import groupby
 
+from sql import Null
+
 from trytond.model import ModelView, ModelSQL, fields
 from trytond.transaction import Transaction
 from trytond.pool import Pool
@@ -169,7 +171,7 @@ class UIMenu(ModelSQL, ModelView):
                     ('rec_name', 'ilike', '%%%s%%' % text),
                     ]):
             if record.action:
-                yield record.id, record.rec_name, record.icon
+                yield record, record.rec_name, record.icon
 
     @classmethod
     def search(cls, domain, offset=0, limit=None, order=None, count=False,
@@ -294,7 +296,7 @@ class UIMenuFavorite(ModelSQL, ModelView):
     @staticmethod
     def order_sequence(tables):
         table, _ = tables[None]
-        return [table.sequence == None, table.sequence]
+        return [table.sequence == Null, table.sequence]
 
     @staticmethod
     def default_user():
diff --git a/trytond/ir/ui/menu.xml b/trytond/ir/ui/menu.xml
index 4a0a610..7b11a1d 100644
--- a/trytond/ir/ui/menu.xml
+++ b/trytond/ir/ui/menu.xml
@@ -28,7 +28,7 @@ this repository contains the full copyright notices and license terms. -->
             <field name="name">Menu</field>
             <field name="res_model">ir.ui.menu</field>
             <field name="usage">menu</field>
-            <field name="domain">[('parent', '=', None)]</field>
+            <field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
         </record>
         <record model="ir.action.act_window.view"
             id="act_menu_tree_view1">
diff --git a/trytond/ir/ui/tree.rnc b/trytond/ir/ui/tree.rnc
index 925331e..e42ec0d 100644
--- a/trytond/ir/ui/tree.rnc
+++ b/trytond/ir/ui/tree.rnc
@@ -27,7 +27,7 @@ attlist.field &=
     | "selection"
     | "float"
     | "numeric"
-    | "float_time"
+    | "timedelta"
     | "integer"
     | "datetime"
     | "time"
@@ -58,9 +58,9 @@ attlist.field &=
     | "bottom_to_top"
     | "top_to_bottom"
   }?
-attlist.field &= attribute float_time { text }?
 attlist.field &= [a:defaultValue = "0"] attribute pre_validate { "0" | "1" }?
 attlist.field &= [a:defaultValue = "1"] attribute completion { "0" | "1" }?
+attlist.field &= attribute string { text }?
 attlist.field &= [a:defaultValue = "1"] attribute factor { text }?
 attlist.field &= attribute filename { text }?
 prefix = element prefix { attlist.affix, empty }
diff --git a/trytond/ir/ui/tree.rng b/trytond/ir/ui/tree.rng
index 0508c44..44a5d3c 100644
--- a/trytond/ir/ui/tree.rng
+++ b/trytond/ir/ui/tree.rng
@@ -97,7 +97,7 @@
           <value>selection</value>
           <value>float</value>
           <value>numeric</value>
-          <value>float_time</value>
+          <value>timedelta</value>
           <value>integer</value>
           <value>datetime</value>
           <value>time</value>
@@ -170,11 +170,6 @@
   </define>
   <define name="attlist.field" combine="interleave">
     <optional>
-      <attribute name="float_time"/>
-    </optional>
-  </define>
-  <define name="attlist.field" combine="interleave">
-    <optional>
       <attribute name="pre_validate" a:defaultValue="0">
         <choice>
           <value>0</value>
@@ -195,6 +190,11 @@
   </define>
   <define name="attlist.field" combine="interleave">
     <optional>
+      <attribute name="string"/>
+    </optional>
+  </define>
+  <define name="attlist.field" combine="interleave">
+    <optional>
       <attribute name="factor" a:defaultValue="1"/>
     </optional>
   </define>
diff --git a/trytond/ir/ui/view.py b/trytond/ir/ui/view.py
index 2c3fb90..ccc469d 100644
--- a/trytond/ir/ui/view.py
+++ b/trytond/ir/ui/view.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 os
 import sys
 import logging
@@ -11,8 +11,8 @@ except ImportError:
 from lxml import etree
 from trytond.model import ModelView, ModelSQL, fields
 from trytond import backend
-from trytond.pyson import CONTEXT, Eval, Bool, PYSONDecoder
-from trytond.tools import safe_eval, file_open
+from trytond.pyson import Eval, Bool, PYSONDecoder
+from trytond.tools import file_open
 from trytond.transaction import Transaction
 from trytond.wizard import Wizard, StateView, Button
 from trytond.pool import Pool
@@ -24,6 +24,8 @@ __all__ = [
     'ViewTreeWidth', 'ViewTreeState', 'ViewSearch',
     ]
 
+logger = logging.getLogger(__name__)
+
 
 class View(ModelSQL, ModelView):
     "View"
@@ -137,11 +139,10 @@ class View(ModelSQL, ModelView):
                 rng_type = view.inherit.type if view.inherit else view.type
                 validator = etree.RelaxNG(etree=cls.get_rng(rng_type))
                 if not validator.validate(tree):
-                    logger = logging.getLogger('ir')
                     error_log = reduce(lambda x, y: str(x) + '\n' + str(y),
                             validator.error_log.filter_from_errors())
-                    logger.error('Invalid xml view:\n%s'
-                        % (str(error_log) + '\n' + xml))
+                    logger.error('Invalid xml view:\n%s',
+                        str(error_log) + '\n' + xml)
                     cls.raise_user_error('invalid_xml', (view.rec_name,))
             root_element = tree.getroottree().getroot()
 
@@ -154,14 +155,13 @@ class View(ModelSQL, ModelView):
                 for attr in ('states', 'domain', 'spell', 'colors'):
                     if element.get(attr):
                         try:
-                            value = safe_eval(element.get(attr), CONTEXT)
+                            value = PYSONDecoder().decode(element.get(attr))
                             validates.get(attr, lambda a: True)(value)
                         except Exception, e:
-                            logger = logging.getLogger('ir')
                             logger.error('Invalid pyson view element "%s:%s":'
-                                '\n%s\n%s'
-                                % (element.get('id') or element.get('name'),
-                                    attr, str(e), xml))
+                                '\n%s\n%s',
+                                element.get('id') or element.get('name'), attr,
+                                str(e), xml)
                             return False
                 for child in element:
                     if not encode(child):
@@ -221,7 +221,7 @@ class ShowView(Wizard):
         def __init__(self, model_name, buttons):
             StateView.__init__(self, model_name, None, buttons)
 
-        def get_view(self):
+        def get_view(self, wizard, state_name):
             pool = Pool()
             View = pool.get('ir.ui.view')
             view = View(Transaction().context.get('active_id'))
@@ -339,6 +339,8 @@ class ViewTreeState(ModelSQL, ModelView):
 
     @classmethod
     def set(cls, model, domain, child_name, nodes, selected_nodes):
+        # Normalize the json domain
+        domain = json.dumps(json.loads(domain))
         current_user = Transaction().user
         records = cls.search([
                 ('user', '=', current_user),
diff --git a/trytond/ir/view/action_act_window_list.xml b/trytond/ir/view/action_act_window_list.xml
index 586003a..e9dd672 100644
--- a/trytond/ir/view/action_act_window_list.xml
+++ b/trytond/ir/view/action_act_window_list.xml
@@ -5,4 +5,5 @@ this repository contains the full copyright notices and license terms. -->
     <field name="name"/>
     <field name="res_model"/>
     <field name="domain"/>
+    <field name="active" tree_invisible="1"/>
 </tree>
diff --git a/trytond/ir/view/action_list.xml b/trytond/ir/view/action_list.xml
index 28a7fcc..cdc8e09 100644
--- a/trytond/ir/view/action_list.xml
+++ b/trytond/ir/view/action_list.xml
@@ -4,4 +4,5 @@ this repository contains the full copyright notices and license terms. -->
 <tree string="Action">
     <field name="name"/>
     <field name="type"/>
+    <field name="active" tree_invisible="1"/>
 </tree>
diff --git a/trytond/ir/view/action_report_list.xml b/trytond/ir/view/action_report_list.xml
index 5acdb1c..48b29e3 100644
--- a/trytond/ir/view/action_report_list.xml
+++ b/trytond/ir/view/action_report_list.xml
@@ -6,5 +6,6 @@ this repository contains the full copyright notices and license terms. -->
     <field name="type"/>
     <field name="model"/>
     <field name="report_name"/>
+    <field name="active" tree_invisible="1"/>
 </tree>
 
diff --git a/trytond/ir/view/action_url_list.xml b/trytond/ir/view/action_url_list.xml
index ad519f1..b10b9e8 100644
--- a/trytond/ir/view/action_url_list.xml
+++ b/trytond/ir/view/action_url_list.xml
@@ -5,4 +5,5 @@ this repository contains the full copyright notices and license terms. -->
     <field name="name"/>
     <field name="type"/>
     <field name="url"/>
+    <field name="active" tree_invisible="1"/>
 </tree>
diff --git a/trytond/ir/view/action_wizard_list.xml b/trytond/ir/view/action_wizard_list.xml
index ee6745e..89dc207 100644
--- a/trytond/ir/view/action_wizard_list.xml
+++ b/trytond/ir/view/action_wizard_list.xml
@@ -5,4 +5,5 @@ this repository contains the full copyright notices and license terms. -->
     <field name="name"/>
     <field name="type"/>
     <field name="wiz_name"/>
+    <field name="active" tree_invisible="1"/>
 </tree>
diff --git a/trytond/ir/view/attachment_list.xml b/trytond/ir/view/attachment_list.xml
index 07ca122..22a8385 100644
--- a/trytond/ir/view/attachment_list.xml
+++ b/trytond/ir/view/attachment_list.xml
@@ -6,6 +6,8 @@ this repository contains the full copyright notices and license terms. -->
     <field name="name"/>
     <field name="summary"/>
     <field name="last_user"/>
-    <field name="last_modification"/>
+    <field name="last_modification" widget="date"/>
+    <field name="last_modification" widget="time"
+        string="Last Modification Time"/>
     <field name="data"/>
 </tree>
diff --git a/trytond/ir/view/cron_list.xml b/trytond/ir/view/cron_list.xml
index 635f25a..b8a2e86 100644
--- a/trytond/ir/view/cron_list.xml
+++ b/trytond/ir/view/cron_list.xml
@@ -2,7 +2,8 @@
 <!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
 this repository contains the full copyright notices and license terms. -->
 <tree string="Scheduled Actions">
-    <field name="next_call"/>
+    <field name="next_call" widget="date"/>
+    <field name="next_call" widget="time"/>
     <field name="name"/>
     <field name="user"/>
     <field name="request_user"/>
diff --git a/trytond/ir/view/module_list.xml b/trytond/ir/view/module_list.xml
index cf60347..921f224 100644
--- a/trytond/ir/view/module_list.xml
+++ b/trytond/ir/view/module_list.xml
@@ -1,8 +1,7 @@
 <?xml version="1.0"?>
 <!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
 this repository contains the full copyright notices and license terms. -->
-<tree string="Modules" on_write="on_write"
-    colors="If(In(Eval('state', ''), ['to upgrade', 'to install']), 'blue', If(Equal(Eval('state', ''), 'uninstalled'), 'grey', 'black'))">
+<tree string="Modules" on_write="on_write">
     <field name="name" expand="1"/>
     <field name="version"/>
     <field name="state"/>
diff --git a/trytond/ir/view/sequence_form.xml b/trytond/ir/view/sequence_form.xml
index 9b96b59..13b1834 100644
--- a/trytond/ir/view/sequence_form.xml
+++ b/trytond/ir/view/sequence_form.xml
@@ -14,8 +14,7 @@ this repository contains the full copyright notices and license terms. -->
     <field name="suffix"/>
     <label name="type"/>
     <field name="type"/>
-    <group string="Incremental" id="incremental" colspan="4"
-        states="{'invisible': Not(In(Eval('type'), ['incremental']))}">
+    <group string="Incremental" id="incremental" colspan="4">
         <label name="padding"/>
         <field name="padding" />
         <label name="number_increment"/>
@@ -23,8 +22,7 @@ this repository contains the full copyright notices and license terms. -->
         <label name="number_next"/>
         <field name="number_next"/>
     </group>
-    <group string="Timestamp" id="timestamp" colspan="4"
-        states="{'invisible': Not(In(Eval('type'), ['decimal timestamp', 'hexadecimal timestamp']))}">
+    <group string="Timestamp" id="timestamp" colspan="4">
         <label name="timestamp_rounding"/>
         <field name="timestamp_rounding"/>
         <label name="timestamp_offset"/>
diff --git a/trytond/ir/view/translation_form.xml b/trytond/ir/view/translation_form.xml
index 38c0f48..8835c4d 100644
--- a/trytond/ir/view/translation_form.xml
+++ b/trytond/ir/view/translation_form.xml
@@ -20,6 +20,6 @@ this repository contains the full copyright notices and license terms. -->
         <separator name="src"/>
         <separator name="value"/>
         <field name="src"/>
-        <field name="value" spell="Eval('lang')"/>
+        <field name="value"/>
     </group>
 </form>
diff --git a/trytond/ir/view/trigger_form.xml b/trytond/ir/view/trigger_form.xml
index 5301e59..5077179 100644
--- a/trytond/ir/view/trigger_form.xml
+++ b/trytond/ir/view/trigger_form.xml
@@ -22,8 +22,8 @@ this repository contains the full copyright notices and license terms. -->
     <field name="condition" colspan="3"/>
     <label name="limit_number"/>
     <field name="limit_number"/>
-    <label name="minimum_delay"/>
-    <field name="minimum_delay" widget="float_time"/>
+    <label name="minimum_time_delay"/>
+    <field name="minimum_time_delay"/>
     <label name="action_model"/>
     <field name="action_model"/>
     <label name="action_function"/>
diff --git a/trytond/model/__init__.py b/trytond/model/__init__.py
index dae657d..1a71ef8 100644
--- a/trytond/model/__init__.py
+++ b/trytond/model/__init__.py
@@ -1,14 +1,16 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .model import Model
 from .modelview import ModelView
-from .modelstorage import ModelStorage
+from .modelstorage import ModelStorage, EvalEnvironment
 from .modelsingleton import ModelSingleton
 from .modelsql import ModelSQL
 from .workflow import Workflow
 from .dictschema import DictSchemaMixin
 from .match import MatchMixin
 from .union import UnionMixin
+from .descriptors import dualmethod
 
 __all__ = ['Model', 'ModelView', 'ModelStorage', 'ModelSingleton', 'ModelSQL',
-    'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin']
+    'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin', 'dualmethod',
+    'EvalEnvironment']
diff --git a/trytond/model/descriptors.py b/trytond/model/descriptors.py
new file mode 100644
index 0000000..eace007
--- /dev/null
+++ b/trytond/model/descriptors.py
@@ -0,0 +1,34 @@
+# 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 functools
+
+
+class dualmethod(object):
+    """Descriptor implementing combination of class and instance method
+
+    When called on an instance, the class is passed as the first argument and a
+    list with the instance as the second.
+    When called on a class, the class itsefl is passed as the first argument.
+
+    >>> class Example(object):
+    ...     @dualmethod
+    ...     def method(cls, instances):
+    ...         print len(instances)
+    ...
+    >>> Example.method([Example()])
+    1
+    >>> Example().method()
+    1
+    """
+    def __init__(self, func):
+        self.func = func
+
+    def __get__(self, instance, owner):
+
+        @functools.wraps(self.func)
+        def newfunc(*args, **kwargs):
+            if instance:
+                return self.func(owner, [instance], *args, **kwargs)
+            else:
+                return self.func(owner, *args, **kwargs)
+        return newfunc
diff --git a/trytond/model/fields/__init__.py b/trytond/model/fields/__init__.py
index 636f22a..529b3e9 100644
--- a/trytond/model/fields/__init__.py
+++ b/trytond/model/fields/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .field import *
 from .boolean import *
diff --git a/trytond/model/fields/binary.py b/trytond/model/fields/binary.py
index 42faa79..ab65b91 100644
--- a/trytond/model/fields/binary.py
+++ b/trytond/model/fields/binary.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 sql import Query, Expression
 
 from .field import Field, SQLType
@@ -9,9 +9,10 @@ from ... import backend
 
 class Binary(Field):
     '''
-    Define a binary field (``str``).
+    Define a binary field (``bytes``).
     '''
     _type = 'binary'
+    cast = bytearray if bytes == str else bytes
 
     def __init__(self, string='', help='', required=False, readonly=False,
             domain=None, states=None, select=False, on_change=None,
@@ -28,10 +29,10 @@ class Binary(Field):
             select=select, on_change=on_change, on_change_with=on_change_with,
             depends=depends, context=context, loading=loading)
 
-    @staticmethod
-    def get(ids, model, name, values=None):
+    @classmethod
+    def get(cls, ids, model, name, values=None):
         '''
-        Convert the binary value into ``str``
+        Convert the binary value into ``bytes``
 
         :param ids: a list of ids
         :param model: a string with the name of the model
@@ -42,7 +43,7 @@ class Binary(Field):
         if values is None:
             values = {}
         res = {}
-        converter = buffer
+        converter = cls.cast
         default = None
         format_ = Transaction().context.pop('%s.%s' % (model.__name__, name),
             '')
@@ -50,7 +51,14 @@ class Binary(Field):
             converter = len
             default = 0
         for i in values:
-            res[i['id']] = converter(i[name]) if i[name] else default
+            value = i[name]
+            if value:
+                if isinstance(value, unicode):
+                    value = value.encode('utf-8')
+                value = converter(value)
+            else:
+                value = default
+            res[i['id']] = value
         for i in ids:
             res.setdefault(i, default)
         return res
diff --git a/trytond/model/fields/boolean.py b/trytond/model/fields/boolean.py
index 0dc00f4..dad5b63 100644
--- a/trytond/model/fields/boolean.py
+++ b/trytond/model/fields/boolean.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .field import Field, SQLType
 
diff --git a/trytond/model/fields/char.py b/trytond/model/fields/char.py
index 6254077..d2d6b61 100644
--- a/trytond/model/fields/char.py
+++ b/trytond/model/fields/char.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 warnings
 
 from sql import Query, Expression
diff --git a/trytond/model/fields/date.py b/trytond/model/fields/date.py
index 428f6c4..e2b8445 100644
--- a/trytond/model/fields/date.py
+++ b/trytond/model/fields/date.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 datetime
 from sql import Query, Expression
 
@@ -134,3 +134,60 @@ class Time(DateTime):
 
     def sql_type(self):
         return SQLType('TIME', 'TIME')
+
+
+class TimeDelta(Field):
+    '''
+    Define a timedelta field (``timedelta``).
+    '''
+    _type = 'timedelta'
+
+    def __init__(self, string='', converter=None, help='', required=False,
+            readonly=False, domain=None, states=None, select=False,
+            on_change=None, on_change_with=None, depends=None,
+            context=None, loading='eager'):
+        '''
+        :param converter: The name of the context key containing
+            the time converter.
+        '''
+        super(TimeDelta, self).__init__(string=string, help=help,
+            required=required, readonly=readonly, domain=domain, states=states,
+            select=select, on_change=on_change, on_change_with=on_change_with,
+            depends=depends, context=context, loading=loading)
+        self.converter = converter
+
+    @staticmethod
+    def sql_format(value):
+        if isinstance(value, (Query, Expression)):
+            return value
+        if value is None:
+            return None
+        assert(isinstance(value, datetime.timedelta))
+        db_type = backend.name()
+        if db_type == 'mysql':
+            return value.total_seconds()
+        return value
+
+    def sql_type(self):
+        db_type = backend.name()
+        if db_type == 'mysql':
+            return SQLType('DOUBLE', 'DOUBLE(255, 6)')
+        return SQLType('INTERVAL', 'INTERVAL')
+
+    @classmethod
+    def get(cls, ids, model, name, values=None):
+        result = {}
+        for row in values:
+            value = row[name]
+            if (value is not None
+                    and not isinstance(value, datetime.timedelta)):
+                if value >= datetime.timedelta.max.total_seconds():
+                    value = datetime.timedelta.max
+                elif value <= datetime.timedelta.min.total_seconds():
+                    value = datetime.timedelta.min
+                else:
+                    value = datetime.timedelta(seconds=value)
+                result[row['id']] = value
+            else:
+                result[row['id']] = value
+        return result
diff --git a/trytond/model/fields/dict.py b/trytond/model/fields/dict.py
index ef63d52..be65ba2 100644
--- a/trytond/model/fields/dict.py
+++ b/trytond/model/fields/dict.py
@@ -8,6 +8,8 @@ from sql import Query, Expression
 
 from .field import Field, SQLType
 from ...protocols.jsonrpc import JSONDecoder, JSONEncoder
+from ...pool import Pool
+from ...tools import grouped_slice
 
 
 class Dict(Field):
@@ -42,3 +44,50 @@ class Dict(Field):
 
     def sql_type(self):
         return SQLType('TEXT', 'TEXT')
+
+    def translated(self, name=None, type_='values'):
+        "Return a descriptor for the translated value of the field"
+        if name is None:
+            name = self.name
+        if name is None:
+            raise ValueError('Missing name argument')
+        return TranslatedDict(name, type_)
+
+
+class TranslatedDict(object):
+    'A descriptor for translated values of Dict field'
+
+    def __init__(self, name, type_):
+        assert type_ in ['keys', 'values']
+        self.name = name
+        self.type_ = type_
+
+    def __get__(self, inst, cls):
+        if inst is None:
+            return self
+        pool = Pool()
+        schema_model = getattr(cls, self.name).schema_model
+        SchemaModel = pool.get(schema_model)
+
+        value = getattr(inst, self.name)
+        if not value:
+            return value
+
+        domain = []
+        if self.type_ == 'values':
+            domain = [('type_', '=', 'selection')]
+
+        records = []
+        for key_names in grouped_slice(value.keys()):
+            records += SchemaModel.search([
+                    ('name', 'in', key_names),
+                    ] + domain)
+        keys = SchemaModel.get_keys(records)
+
+        if self.type_ == 'keys':
+            return {k['name']: k['string'] for k in keys}
+
+        elif self.type_ == 'values':
+            trans = {k['name']: dict(k['selection']) for k in keys}
+            return {k: v if k not in trans else trans[k].get(v, v)
+                for k, v in value.iteritems()}
diff --git a/trytond/model/fields/field.py b/trytond/model/fields/field.py
index 3eb95f2..d09bdb0 100644
--- a/trytond/model/fields/field.py
+++ b/trytond/model/fields/field.py
@@ -1,14 +1,14 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 collections import namedtuple
 import warnings
 from functools import wraps
 
-from sql import operators, Column, Literal, Select, CombiningQuery
+from sql import operators, Column, Literal, Select, CombiningQuery, Null
 from sql.conditionals import Coalesce, NullIf
 from sql.operators import Concat
 
-from trytond.pyson import PYSON
+from trytond.pyson import PYSON, PYSONEncoder, Eval
 from trytond.const import OPERATORS
 from trytond.transaction import Transaction
 from trytond.pool import Pool
@@ -91,6 +91,25 @@ def depends(*fields, **kwargs):
     return decorator
 
 
+def get_eval_fields(value):
+    "Return fields evaluated"
+    class Encoder(PYSONEncoder):
+        def __init__(self, *args, **kwargs):
+            super(Encoder, self).__init__(*args, **kwargs)
+            self.fields = set()
+
+        def default(self, obj):
+            if isinstance(obj, Eval):
+                fname = obj._value
+                if not fname.startswith('_parent_'):
+                    self.fields.add(fname)
+            return super(Encoder, self).default(obj)
+
+    encoder = Encoder()
+    encoder.encode(value)
+    return encoder.fields
+
+
 SQL_OPERATORS = {
     '=': operators.Equal,
     '!=': operators.NotEqual,
@@ -241,9 +260,9 @@ class Field(object):
             if (not isinstance(value, (Select, CombiningQuery))
                     and any(v is None for v in value)):
                 if operator == 'in':
-                    expression |= (column == None)
+                    expression |= (column == Null)
                 else:
-                    expression &= (column != None)
+                    expression &= (column != Null)
         return expression
 
     def convert_domain(self, domain, tables, Model):
diff --git a/trytond/model/fields/float.py b/trytond/model/fields/float.py
index 42a8db5..be716af 100644
--- a/trytond/model/fields/float.py
+++ b/trytond/model/fields/float.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 sql import Query, Expression
 
 from ... import backend
diff --git a/trytond/model/fields/function.py b/trytond/model/fields/function.py
index 8a72955..e5a48af 100644
--- a/trytond/model/fields/function.py
+++ b/trytond/model/fields/function.py
@@ -1,9 +1,10 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 inspect
 import copy
 from trytond.model.fields.field import Field
+from trytond.tools import is_instance_method
 from trytond.transaction import Transaction
 
 
@@ -57,6 +58,10 @@ class Function(Field):
                 return
         setattr(self._field, name, value)
 
+    @property
+    def sql_type(self):
+        raise AttributeError
+
     def convert_domain(self, domain, tables, Model):
         name, operator, value = domain[:3]
         if not self.searcher:
@@ -71,10 +76,11 @@ class Function(Field):
         '''
         with Transaction().set_context(_check_access=False):
             method = getattr(Model, self.getter)
+            instance_method = is_instance_method(Model, self.getter)
 
             def call(name):
                 records = Model.browse(ids)
-                if not hasattr(method, 'im_self') or method.im_self:
+                if not instance_method:
                     return method(records, name)
                 else:
                     return dict((r.id, method(r, name)) for r in records)
diff --git a/trytond/model/fields/integer.py b/trytond/model/fields/integer.py
index 64f51bf..720fa4a 100644
--- a/trytond/model/fields/integer.py
+++ b/trytond/model/fields/integer.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 sql import Query, Expression
 
 from ... import backend
diff --git a/trytond/model/fields/many2many.py b/trytond/model/fields/many2many.py
index 46f4da5..7816f26 100644
--- a/trytond/model/fields/many2many.py
+++ b/trytond/model/fields/many2many.py
@@ -1,7 +1,8 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 itertools import chain
-from sql import Cast, Literal
+
+from sql import Cast, Literal, Null
 from sql.functions import Substring, Position
 
 from .field import Field, size_validate
@@ -235,7 +236,7 @@ class Many2Many(Field):
                 return Target(**data)
             else:
                 return Target(data)
-        value = [instance(x) for x in (value or [])]
+        value = tuple(instance(x) for x in (value or []))
         super(Many2Many, self).__set__(inst, value)
 
     def convert_domain_child(self, domain, tables):
@@ -281,7 +282,7 @@ class Many2Many(Field):
                 if Target != Model:
                     query = Target.search([(domain[3], 'child_of', value)],
                         order=[], query=True)
-                    where = (target.in_(query) & (origin != None))
+                    where = (target.in_(query) & (origin != Null))
                     if origin_where:
                         where &= origin_where
                     query = relation.select(origin, where=where)
diff --git a/trytond/model/fields/many2one.py b/trytond/model/fields/many2one.py
index e6aa9ba..a7051a5 100644
--- a/trytond/model/fields/many2one.py
+++ b/trytond/model/fields/many2one.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 types import NoneType
 from sql import Query, Expression
 from sql.operators import Or
@@ -180,25 +180,33 @@ class Many2One(Field):
         return column.in_(query)
 
     def convert_order(self, name, tables, Model):
-        if getattr(Model, 'order_%s' % name, None):
-            return super(Many2One, self).convert_order(name, tables, Model)
-        assert name == self.name
+        fname, _, oexpr = name.partition('.')
+        if not oexpr and getattr(Model, 'order_%s' % fname, None):
+            return super(Many2One, self).convert_order(fname, tables, Model)
+        assert fname == self.name
 
         Target = self.get_target()
 
-        oname = 'id'
-        if Target._rec_name in Target._fields:
-            oname = Target._rec_name
-        if Target._order_name in Target._fields:
-            oname = Target._order_name
+        if oexpr:
+            oname, _, _ = oexpr.partition('.')
+        else:
+            oname = 'id'
+            if Target._rec_name in Target._fields:
+                oname = Target._rec_name
+            if Target._order_name in Target._fields:
+                oname = Target._order_name
+            oexpr = oname
 
-        ofield = Target._fields[oname]
         table, _ = tables[None]
-        target_tables = tables.get(name)
+        if oname == 'id':
+            return [self.sql_column(table)]
+
+        ofield = Target._fields[oname]
+        target_tables = tables.get(fname)
         if target_tables is None:
             target = Target.__table__()
             target_tables = {
                 None: (target, target.id == self.sql_column(table)),
                 }
-            tables[name] = target_tables
-        return ofield.convert_order(oname, target_tables, Target)
+            tables[fname] = target_tables
+        return ofield.convert_order(oexpr, target_tables, Target)
diff --git a/trytond/model/fields/numeric.py b/trytond/model/fields/numeric.py
index 3580bb7..ce9abdc 100644
--- a/trytond/model/fields/numeric.py
+++ b/trytond/model/fields/numeric.py
@@ -1,13 +1,20 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 decimal import Decimal
-from sql import Query, Expression, Cast, Literal, Select, CombiningQuery
+from sql import Query, Expression, Cast, Literal, Select, CombiningQuery, As
 
 from ... import backend
 from .field import SQLType
 from .float import Float
 
 
+class SQLite_Cast(Cast):
+
+    def as_(self, output_name):
+        # Use PARSE_COLNAMES instead of CAST for final column
+        return As(self.expression, '%s [NUMERIC]' % output_name)
+
+
 class Numeric(Float):
     '''
     Define a numeric field (``decimal``).
@@ -36,7 +43,7 @@ class Numeric(Float):
         db_type = backend.name()
         if db_type == 'sqlite':
             # Must be casted as Decimal is stored as bytes
-            column = Cast(column, self.sql_type().base)
+            column = SQLite_Cast(column, self.sql_type().base)
         return column
 
     def _domain_value(self, operator, value):
diff --git a/trytond/model/fields/one2many.py b/trytond/model/fields/one2many.py
index 5387ab6..2ea150f 100644
--- a/trytond/model/fields/one2many.py
+++ b/trytond/model/fields/one2many.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 itertools import chain
 from sql import Cast, Literal
 from sql.functions import Substring, Position
@@ -218,7 +218,7 @@ class One2Many(Field):
                 return Target(**data)
             else:
                 return Target(data)
-        value = [instance(x) for x in (value or [])]
+        value = tuple(instance(x) for x in (value or []))
         super(One2Many, self).__set__(inst, value)
 
     def convert_domain(self, domain, tables, Model):
diff --git a/trytond/model/fields/one2one.py b/trytond/model/fields/one2one.py
index 4e6fe87..60139dc 100644
--- a/trytond/model/fields/one2one.py
+++ b/trytond/model/fields/one2one.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 types import NoneType
 
 from trytond.model.fields.field import Field
diff --git a/trytond/model/fields/property.py b/trytond/model/fields/property.py
index 94a77a4..84621f1 100644
--- a/trytond/model/fields/property.py
+++ b/trytond/model/fields/property.py
@@ -1,8 +1,8 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
-
+# 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 copy
-from sql import Cast, Literal
+
+from sql import Cast, Literal, Null
 from sql.functions import Substring, Position
 from sql.conditionals import Case
 
@@ -82,7 +82,7 @@ class Property(Function):
         model_field = Field.__table__()
         model = IrModel.__table__()
 
-        #Fetch res ids that comply with the domain
+        # Fetch res ids that comply with the domain
         join = property_.join(model_field,
             condition=model_field.id == property_.field)
         join = join.join(model,
@@ -119,12 +119,12 @@ class Property(Function):
                 dom_operator = 'not in'
             return [('id', dom_operator, [x[0] for x in props])]
 
-        #Fetch the res ids that doesn't use the default value
+        # Fetch the res ids that doesn't use the default value
         cursor.execute(*property_.select(
                 Cast(Substring(property_.res,
                         Position(',', property_.res) + Literal(1)),
                     Model.id.sql_type().base),
-                where=property_cond & (property_.res != None)))
+                where=property_cond & (property_.res != Null)))
 
         fetchall = cursor.fetchall()
         if not fetchall:
@@ -165,7 +165,7 @@ class Property(Function):
             return column.in_(value)
         elif ((value is False or value is None)
                 and operator in ('=', '!=')):
-            return (column == None) == value
+            return (column == Null) == value
         elif operator == 'not like':
             return column.like(value)
         elif operator == 'not ilike':
diff --git a/trytond/model/fields/reference.py b/trytond/model/fields/reference.py
index 19db451..970351c 100644
--- a/trytond/model/fields/reference.py
+++ b/trytond/model/fields/reference.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 types import NoneType
 import warnings
 
diff --git a/trytond/model/fields/selection.py b/trytond/model/fields/selection.py
index 1f75d27..bd29efe 100644
--- a/trytond/model/fields/selection.py
+++ b/trytond/model/fields/selection.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 warnings
 
 from sql.conditionals import Case
diff --git a/trytond/model/fields/sha.py b/trytond/model/fields/sha.py
index 222aca8..2df89d3 100644
--- a/trytond/model/fields/sha.py
+++ b/trytond/model/fields/sha.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 hashlib
 from sql import Query, Expression
 
diff --git a/trytond/model/fields/text.py b/trytond/model/fields/text.py
index 6d2b37e..f97a805 100644
--- a/trytond/model/fields/text.py
+++ b/trytond/model/fields/text.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .field import SQLType
 from .char import Char
diff --git a/trytond/model/model.py b/trytond/model/model.py
index 13e8554..eca190f 100644
--- a/trytond/model/model.py
+++ b/trytond/model/model.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 copy
 import collections
@@ -32,17 +32,10 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
         cls.__rpc__ = {
             'default_get': RPC(),
             'fields_get': RPC(),
-            'on_change': RPC(instantiate=0),
-            'on_change_with': RPC(instantiate=0),
             'pre_validate': RPC(instantiate=0),
             }
         cls._error_messages = {}
 
-        if hasattr(cls, '__depend_methods'):
-            cls.__depend_methods = cls.__depend_methods.copy()
-        else:
-            cls.__depend_methods = collections.defaultdict(set)
-
         # Copy fields and update depends
         for attr in dir(cls):
             if attr.startswith('_'):
@@ -60,31 +53,6 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
             field = copy.deepcopy(field)
             setattr(cls, field_name, field)
 
-            for attribute in ('on_change', 'on_change_with', 'autocomplete',
-                    'selection_change_with'):
-                if attribute == 'selection_change_with':
-                    if isinstance(
-                            getattr(field, 'selection', None), basestring):
-                        function_name = field.selection
-                    else:
-                        continue
-                else:
-                    function_name = '%s_%s' % (attribute, field_name)
-                if not getattr(cls, function_name, None):
-                    continue
-                # Search depends on all parent class because field has been
-                # copied with the original definition
-                for parent_cls in cls.__mro__:
-                    function = getattr(parent_cls, function_name, None)
-                    if not function:
-                        continue
-                    if getattr(function, 'depends', None):
-                        setattr(field, attribute,
-                            getattr(field, attribute) | function.depends)
-                    if getattr(function, 'depend_methods', None):
-                        cls.__depend_methods[(field_name, attribute)] |= \
-                            function.depend_methods
-
     @classmethod
     def __post_setup__(cls):
         super(Model, cls).__post_setup__()
@@ -110,30 +78,6 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
                 'Default function defined in %s but field %s does not exist!' \
                 % (cls.__name__, k,)
 
-        # Update __rpc__
-        for field_name, field in cls._fields.iteritems():
-            if isinstance(field, (fields.Selection, fields.Reference)) \
-                    and not isinstance(field.selection, (list, tuple)) \
-                    and field.selection not in cls.__rpc__:
-                instantiate = 0 if field.selection_change_with else None
-                cls.__rpc__.setdefault(field.selection,
-                    RPC(instantiate=instantiate))
-
-            for attribute in ('on_change', 'on_change_with', 'autocomplete'):
-                function_name = '%s_%s' % (attribute, field_name)
-                if getattr(cls, function_name, None):
-                    cls.__rpc__.setdefault(function_name, RPC(instantiate=0))
-
-        # Update depend on methods
-        for (field_name, attribute), others in (
-                cls.__depend_methods.iteritems()):
-            field = getattr(cls, field_name)
-            for other in others:
-                other_field = getattr(cls, other)
-                setattr(field, attribute,
-                    getattr(field, attribute)
-                    | getattr(other_field, attribute))
-
         # Set name to fields
         for name, field in cls._fields.iteritems():
             if field.name is None:
@@ -148,6 +92,7 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
         '''
         Returns the first non-empty line of the model docstring.
         '''
+        assert cls.__doc__, '%s has no docstring' % cls
         lines = cls.__doc__.splitlines()
         for line in lines:
             line = line.strip()
@@ -188,7 +133,7 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
                 value[field_name] = cls._defaults[field_name]()
             field = cls._fields[field_name]
             if (field._type == 'boolean'
-                    and not field_name in value):
+                    and field_name not in value):
                 value[field_name] = False
             if isinstance(field, fields.Property):
                 value[field_name] = Property.get(field_name, cls.__name__)
@@ -217,7 +162,7 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
         FieldAccess = pool.get('ir.model.field.access')
         ModelAccess = pool.get('ir.model.access')
 
-        #Add translation to cache
+        # Add translation to cache
         language = Transaction().language
         trans_args = []
         for field in (x for x in cls._fields.keys()
@@ -267,6 +212,7 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
                     'filename',
                     'selection_change_with',
                     'domain',
+                    'converter',
                     ):
                 if getattr(cls._fields[field], arg, None) is not None:
                     value = getattr(cls._fields[field], arg)
@@ -291,7 +237,10 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
                 res[field]['sortable'] = False
             if ((isinstance(cls._fields[field], fields.Function)
                     and not cls._fields[field].searcher)
-                    or cls._fields[field]._type in ('binary', 'sha')):
+                    or (cls._fields[field]._type in ('binary', 'sha'))
+                    or (isinstance(cls._fields[field], fields.Property)
+                        and isinstance(cls._fields[field]._field,
+                            fields.Many2One))):
                 res[field]['searchable'] = False
             else:
                 res[field]['searchable'] = True
@@ -391,21 +340,6 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
                 del res[i]
         return res
 
-    def on_change(self, fieldnames):
-        changes = []
-        for fieldname in sorted(fieldnames):
-            method = getattr(self, 'on_change_%s' % fieldname, None)
-            if method:
-                changes.append(method())
-        return changes
-
-    def on_change_with(self, fieldnames):
-        changes = {}
-        for fieldname in fieldnames:
-            method_name = 'on_change_with_%s' % fieldname
-            changes[fieldname] = getattr(self, method_name)()
-        return changes
-
     def pre_validate(self):
         pass
 
@@ -429,6 +363,7 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
                 setattr(parent, field, value)
             else:
                 setattr(self, parent_name, {field: value})
+        self._init_values = self._values.copy() if self._values else None
 
     def __getattr__(self, name):
         if name == 'id':
@@ -471,8 +406,8 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
     def __eq__(self, other):
         if not isinstance(other, Model):
             return NotImplemented
-        if self.id is None or other.id is None:
-            return False
+        elif self.id is None or other.id is None:
+            return id(self) == id(other)
         return (self.__name__, self.id) == (other.__name__, other.id)
 
     def __lt__(self, other):
@@ -481,11 +416,7 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
         return self.id < other.id
 
     def __ne__(self, other):
-        if not isinstance(other, Model):
-            return NotImplemented
-        if self.id is None or other.id is None:
-            return True
-        return (self.__name__, self.id) != (other.__name__, other.id)
+        return not self == other
 
     def __hash__(self):
         return hash((self.__name__, self.id))
@@ -495,18 +426,27 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
 
     @property
     def _default_values(self):
-        if self.id >= 0:
-            return self.id
+        """Return the values not stored.
+        By default, the value of a field is its internal representation except:
+            - for Many2One and One2One field: the id
+            - for Reference field: the string model,id
+            - for Many2Many: the list of ids
+            - for One2Many: the list of `_default_values`
+        """
         values = {}
         if self._values:
             for fname, value in self._values.iteritems():
                 field = self._fields[fname]
-                if isinstance(field, fields.Reference):
-                    if value is not None:
-                        value = str(value)
-                elif isinstance(value, Model):
-                    value = value._default_values
-                elif isinstance(value, list):
-                    value = [r._default_values for r in value]
+                if field._type in ('many2one', 'one2one', 'reference'):
+                    if value:
+                        if field._type == 'reference':
+                            value = str(value)
+                        else:
+                            value = value.id
+                elif field._type in ('one2many', 'many2many'):
+                    if field._type == 'one2many':
+                        value = [r._default_values for r in value]
+                    else:
+                        value = [r.id for r in value]
                 values[fname] = value
         return values
diff --git a/trytond/model/modelsingleton.py b/trytond/model/modelsingleton.py
index e1347f6..948a4ff 100644
--- a/trytond/model/modelsingleton.py
+++ b/trytond/model/modelsingleton.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.model import ModelStorage
 
 
diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py
index 2fc90bb..71acbfe 100644
--- a/trytond/model/modelsql.py
+++ b/trytond/model/modelsql.py
@@ -1,11 +1,12 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 re
 import datetime
 from functools import reduce
 from itertools import islice, izip, chain, ifilter
+from collections import OrderedDict
 
-from sql import Table, Column, Literal, Desc, Asc, Expression, Flavor
+from sql import Table, Column, Literal, Desc, Asc, Expression, Flavor, Null
 from sql.functions import Now, Extract
 from sql.conditionals import Coalesce
 from sql.operators import Or, And, Operator
@@ -15,12 +16,15 @@ from trytond.model import ModelStorage, ModelView
 from trytond.model import fields
 from trytond import backend
 from trytond.tools import reduce_ids, grouped_slice
-from trytond.const import OPERATORS, RECORD_CACHE_SIZE
+from trytond.const import OPERATORS
 from trytond.transaction import Transaction
 from trytond.pool import Pool
 from trytond.cache import LRUDict
 from trytond.exceptions import ConcurrencyException
 from trytond.rpc import RPC
+
+from .modelstorage import cache_size
+
 _RE_UNIQUE = re.compile('UNIQUE\s*\((.*)\)', re.I)
 _RE_CHECK = re.compile('CHECK\s*\((.*)\)', re.I)
 
@@ -82,10 +86,9 @@ class ModelSQL(ModelStorage):
             if field_name == 'id':
                 continue
             default_fun = None
-            try:
-                sql_type = field.sql_type()
-            except NotImplementedError:
+            if hasattr(field, 'set'):
                 continue
+            sql_type = field.sql_type()
             if field_name in cls._defaults:
                 default_fun = cls._defaults[field_name]
 
@@ -295,8 +298,7 @@ class ModelSQL(ModelStorage):
                         [[id_, Now(), user] for id_ in sub_ids]))
 
     @classmethod
-    def restore_history(cls, ids, datetime):
-        'Restore record ids from history at the date time'
+    def _restore_history(cls, ids, datetime, _before=False):
         if not cls._history:
             return
         transaction = Transaction()
@@ -324,7 +326,11 @@ class ModelSQL(ModelStorage):
         to_update = []
         for id_ in ids:
             column_datetime = Coalesce(history.write_date, history.create_date)
-            hwhere = (column_datetime <= datetime) & (history.id == id_)
+            if not _before:
+                hwhere = (column_datetime <= datetime)
+            else:
+                hwhere = (column_datetime < datetime)
+            hwhere &= (history.id == id_)
             horder = (column_datetime.desc, Column(history, '__id').desc)
             cursor.execute(*history.select(*hcolumns,
                     where=hwhere, order_by=horder, limit=1))
@@ -353,6 +359,16 @@ class ModelSQL(ModelStorage):
             cls.__insert_history(to_update)
 
     @classmethod
+    def restore_history(cls, ids, datetime):
+        'Restore record ids from history at the date time'
+        cls._restore_history(ids, datetime)
+
+    @classmethod
+    def restore_history_before(cls, ids, datetime):
+        'Restore record ids from history before the date time'
+        cls._restore_history(ids, datetime, _before=True)
+
+    @classmethod
     def __check_timestamp(cls, ids):
         transaction = Transaction()
         cursor = transaction.cursor
@@ -488,7 +504,7 @@ class ModelSQL(ModelStorage):
         cls.__insert_history(new_ids)
 
         records = cls.browse(new_ids)
-        for sub_records in grouped_slice(records, RECORD_CACHE_SIZE):
+        for sub_records in grouped_slice(records, cache_size()):
             cls._validate(sub_records)
 
         field_names = cls._fields.keys()
@@ -552,8 +568,9 @@ class ModelSQL(ModelStorage):
 
         columns = []
         for f in fields_names + fields_related.keys() + datetime_fields:
-            if (f in cls._fields and not hasattr(cls._fields[f], 'set')):
-                columns.append(Column(table, f).as_(f))
+            field = cls._fields.get(f)
+            if field and not hasattr(field, 'set'):
+                columns.append(field.sql_column(table).as_(f))
             elif f == '_timestamp' and not table_query:
                 sql_type = fields.Char('timestamp').sql_type().base
                 columns.append(Extract('EPOCH',
@@ -594,7 +611,8 @@ class ModelSQL(ModelStorage):
             result = [{'id': x} for x in ids]
 
         for column in columns:
-            field = column.output_name
+            # Split the output name to remove SQLite type detection
+            field = column.output_name.split()[0]
             if field == '_timestamp':
                 continue
             if (getattr(cls._fields[field], 'translate', False)
@@ -736,7 +754,9 @@ class ModelSQL(ModelStorage):
         Config = pool.get('ir.configuration')
 
         assert not len(args) % 2
-        all_records = sum(((records, values) + args)[0:None:2], [])
+        # Remove possible duplicates from all records
+        all_records = list(OrderedDict.fromkeys(
+                sum(((records, values) + args)[0:None:2], [])))
         all_ids = [r.id for r in all_records]
         all_field_names = set()
 
@@ -823,7 +843,7 @@ class ModelSQL(ModelStorage):
             field.set(cls, fname, *fargs)
 
         cls.__insert_history(all_ids)
-        for sub_records in grouped_slice(all_records, RECORD_CACHE_SIZE):
+        for sub_records in grouped_slice(all_records, cache_size()):
             cls._validate(sub_records, field_names=all_field_names)
         cls.trigger_write(trigger_eligibles)
 
@@ -858,7 +878,7 @@ class ModelSQL(ModelStorage):
                     and field.left and field.right):
                 tree_ids[fname] = []
                 for sub_ids in grouped_slice(ids):
-                    where = reduce_ids(Column(table, fname), sub_ids)
+                    where = reduce_ids(field.sql_column(table), sub_ids)
                     cursor.execute(*table.select(table.id, where=where))
                     tree_ids[fname] += [x[0] for x in cursor.fetchall()]
 
@@ -980,10 +1000,11 @@ class ModelSQL(ModelStorage):
             }
         if order is None or order is False:
             order = cls._order
-        for fname, otype in order:
+        for oexpr, otype in order:
+            fname, _, extra_expr = oexpr.partition('.')
             field = cls._fields[fname]
             Order = order_types[otype.upper()]
-            forder = field.convert_order(fname, tables, cls)
+            forder = field.convert_order(oexpr, tables, cls)
             order_by.extend((Order(o) for o in forder))
 
         main_table, _ = tables[None]
@@ -1020,12 +1041,12 @@ class ModelSQL(ModelStorage):
                     main_table.create_date).as_('_datetime'))
             columns.append(Column(main_table, '__id'))
         if not query:
-            columns += [Column(main_table, name).as_(name)
-                for name, field in cls._fields.iteritems()
-                if not hasattr(field, 'get')
-                and name != 'id'
-                and not getattr(field, 'translate', False)
-                and field.loading == 'eager']
+            columns += [f.sql_column(main_table).as_(n)
+                for n, f in cls._fields.iteritems()
+                if not hasattr(f, 'get')
+                and n != 'id'
+                and not getattr(f, 'translate', False)
+                and f.loading == 'eager']
             if not cls.table_query():
                 sql_type = fields.Char('timestamp').sql_type().base
                 columns += [Extract('EPOCH',
@@ -1040,7 +1061,7 @@ class ModelSQL(ModelStorage):
         rows = cursor.dictfetchmany(cursor.IN_MAX)
         cache = cursor.get_cache()
         if cls.__name__ not in cache:
-            cache[cls.__name__] = LRUDict(RECORD_CACHE_SIZE)
+            cache[cls.__name__] = LRUDict(cache_size())
         delete_records = transaction.delete_records.setdefault(cls.__name__,
             set())
 
@@ -1065,8 +1086,8 @@ class ModelSQL(ModelStorage):
                 where = reduce_ids(history.id, sub_ids)
                 cursor.execute(*history.select(history.id, history.write_date,
                         where=where
-                        & (history.write_date != None)
-                        & (history.create_date == None)
+                        & (history.write_date != Null)
+                        & (history.create_date == Null)
                         & (history.write_date
                             <= transaction.context['_datetime'])))
                 for deleted_id, delete_date in cursor.fetchall():
@@ -1233,7 +1254,7 @@ class ModelSQL(ModelStorage):
         old_left, old_right, parent_id = fetchone
         if old_left == old_right == 0:
             cursor.execute(*table.select(Max(right),
-                    where=field == None))
+                    where=field == Null))
             old_left, = cursor.fetchone()
             old_left += 1
             old_right = old_left + 1
@@ -1248,7 +1269,7 @@ class ModelSQL(ModelStorage):
             cursor.execute(*table.select(right, where=table.id == parent_id))
             parent_right = cursor.fetchone()[0]
         else:
-            cursor.execute(*table.select(Max(right), where=field == None))
+            cursor.execute(*table.select(Max(right), where=field == Null))
             fetchone = cursor.fetchone()
             if fetchone:
                 parent_right = fetchone[0] + 1
diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py
index 5831c9d..45eb7b3 100644
--- a/trytond/model/modelstorage.py
+++ b/trytond/model/modelstorage.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 datetime
 import time
@@ -18,17 +18,25 @@ from collections import defaultdict
 
 from trytond.model import Model
 from trytond.model import fields
-from trytond.tools import reduce_domain, memoize
+from trytond.tools import reduce_domain, memoize, is_instance_method, \
+    grouped_slice
 from trytond.pyson import PYSONEncoder, PYSONDecoder, PYSON
-from trytond.const import OPERATORS, RECORD_CACHE_SIZE, BROWSE_FIELD_TRESHOLD
+from trytond.const import OPERATORS
+from trytond.config import config
 from trytond.transaction import Transaction
 from trytond.pool import Pool
 from trytond.cache import LRUDict, freeze
 from trytond import backend
 from trytond.rpc import RPC
 from .modelview import ModelView
+from .descriptors import dualmethod
 
-__all__ = ['ModelStorage']
+__all__ = ['ModelStorage', 'EvalEnvironment']
+
+
+def cache_size():
+    return Transaction().context.get('_record_cache_size',
+        config.getint('cache', 'record'))
 
 
 class ModelStorage(Model):
@@ -358,7 +366,10 @@ class ModelStorage(Model):
             fields_names = cls._fields.keys()
         if 'id' not in fields_names:
             fields_names.append('id')
-        return cls.read(map(int, records), fields_names)
+        rows = cls.read(map(int, records), fields_names)
+        index = {r.id: i for i, r in enumerate(records)}
+        rows.sort(key=lambda r: index[r['id']])
+        return rows
 
     @classmethod
     def _search_domain_active(cls, domain, active_test=True):
@@ -376,7 +387,7 @@ class ModelStorage(Model):
             active_found = False
             while i < len(domain):
                 arg = domain[i]
-                #add test for xmlrpc that doesn't handle tuple
+                # add test for xmlrpc that doesn't handle tuple
                 if (isinstance(arg, tuple)
                         or (isinstance(arg, list)
                             and len(arg) > 2
@@ -419,13 +430,13 @@ class ModelStorage(Model):
     @classmethod
     def search_global(cls, text):
         '''
-        Yield tuples (id, rec_name, icon) for text
+        Yield tuples (record, name, icon) for text
         '''
         # TODO improve search clause
         for record in cls.search([
                     ('rec_name', 'ilike', '%%%s%%' % text),
                     ]):
-            yield record.id, record.rec_name, None
+            yield record, record.rec_name, None
 
     @classmethod
     def browse(cls, ids):
@@ -433,7 +444,7 @@ class ModelStorage(Model):
         Return a list of instance for the ids
         '''
         ids = map(int, ids)
-        local_cache = LRUDict(RECORD_CACHE_SIZE)
+        local_cache = LRUDict(cache_size())
         return [cls(int(x), _ids=ids, _local_cache=local_cache) for x in ids]
 
     @staticmethod
@@ -848,7 +859,7 @@ class ModelStorage(Model):
 
         def call(name):
             method = getattr(cls, name)
-            if not hasattr(method, 'im_self') or method.im_self:
+            if not is_instance_method(cls, name):
                 return method(records)
             else:
                 return all(method(r) for r in records)
@@ -859,13 +870,14 @@ class ModelStorage(Model):
             if not call(field[0]):
                 cls.raise_user_error(field[1])
 
-        if not 'res.user' in pool.object_name_list() \
-                or Transaction().user == 0:
-            ctx_pref = {
-            }
-        else:
-            User = pool.get('res.user')
-            ctx_pref = User.get_preferences(context_only=True)
+        ctx_pref = {}
+        if Transaction().user:
+            try:
+                User = pool.get('res.user')
+            except KeyError:
+                pass
+            else:
+                ctx_pref = User.get_preferences(context_only=True)
 
         def is_pyson(test):
             if isinstance(test, PYSON):
@@ -917,23 +929,26 @@ class ModelStorage(Model):
 
         def validate_relation_domain(field, records, Relation, domain):
             if field._type in ('many2one', 'one2many', 'many2many', 'one2one'):
-                relations = []
+                relations = set()
                 for record in records:
                     if getattr(record, field.name):
                         if field._type in ('many2one', 'one2one'):
-                            relations.append(getattr(record, field.name))
+                            relations.add(getattr(record, field.name))
                         else:
-                            relations.extend(getattr(record, field.name))
+                            relations.update(getattr(record, field.name))
             else:
-                relations = records
+                # Cache alignment is not a problem
+                relations = set(records)
             if relations:
-                finds = Relation.search(['AND',
-                        [('id', 'in', [r.id for r in relations])],
-                        domain,
-                        ])
-                if set(relations) != set(finds):
-                    cls.raise_user_error('domain_validation_record',
-                        error_args=cls._get_error_args(field.name))
+                for sub_relations in grouped_slice(relations):
+                    sub_relations = set(sub_relations)
+                    finds = Relation.search(['AND',
+                            [('id', 'in', [r.id for r in sub_relations])],
+                            domain,
+                            ])
+                    if sub_relations != set(finds):
+                        cls.raise_user_error('domain_validation_record',
+                            error_args=cls._get_error_args(field.name))
 
         field_names = set(field_names or [])
         function_fields = {name for name, field in cls._fields.iteritems()
@@ -1053,10 +1068,10 @@ class ModelStorage(Model):
                                 value, _ = value.split(',')
                         if not isinstance(field.selection, (tuple, list)):
                             sel_func = getattr(cls, field.selection)
-                            if field.selection_change_with:
-                                test = sel_func(record)
-                            else:
+                            if not is_instance_method(cls, field.selection):
                                 test = sel_func()
+                            else:
+                                test = sel_func(record)
                             test = set(dict(test))
                         # None and '' are equivalent
                         if '' in test or None in test:
@@ -1149,7 +1164,7 @@ class ModelStorage(Model):
         if _local_cache is not None:
             self._local_cache = _local_cache
         else:
-            self._local_cache = LRUDict(RECORD_CACHE_SIZE)
+            self._local_cache = LRUDict(cache_size())
         self._local_cache.counter = Transaction().counter
 
         super(ModelStorage, self).__init__(id, **kwargs)
@@ -1158,7 +1173,7 @@ class ModelStorage(Model):
     def _cache(self):
         cache = self._cursor_cache
         if self.__name__ not in cache:
-            cache[self.__name__] = LRUDict(RECORD_CACHE_SIZE)
+            cache[self.__name__] = LRUDict(cache_size())
         return cache[self.__name__]
 
     def __getattr__(self, name):
@@ -1201,7 +1216,7 @@ class ModelStorage(Model):
             to_remove = set(x for x, y in fread_accesses.iteritems()
                     if not y and x != name)
 
-            threshold = BROWSE_FIELD_TRESHOLD
+            threshold = config.getint('cache', 'field')
 
             def not_cached(item):
                 fname, field = item
@@ -1232,7 +1247,10 @@ class ModelStorage(Model):
         # add depends of field with context
         for field in ffields.values():
             if field.context:
-                for context_field_name in field.depends:
+                eval_fields = fields.get_eval_fields(field.context)
+                for context_field_name in eval_fields:
+                    if context_field_name in field.depends:
+                        continue
                     context_field = self._fields.get(context_field_name)
                     if context_field not in ffields:
                         ffields[context_field_name] = context_field
@@ -1282,7 +1300,7 @@ class ModelStorage(Model):
             with Transaction().set_context(**ctx):
                 key = (Model, freeze(ctx))
                 local_cache = model2cache.setdefault(key,
-                    LRUDict(RECORD_CACHE_SIZE))
+                    LRUDict(cache_size()))
                 ids = model2ids.setdefault(key, [])
                 if field._type in ('many2one', 'one2one', 'reference'):
                     ids.append(value)
@@ -1390,26 +1408,48 @@ class ModelStorage(Model):
             values[fname] = value
         return values
 
-    def save(self):
-        save_values = self._save_values
-        values = self._values
-        self._values = None
-        if save_values or self.id < 0:
-            try:
-                with Transaction().set_cursor(self._cursor), \
-                        Transaction().set_user(self._user), \
-                        Transaction().set_context(self._context):
-                    if self.id < 0:
-                        self._ids.remove(self.id)
-                        try:
-                            self.id = self.create([save_values])[0].id
-                        finally:
-                            self._ids.append(self.id)
-                    else:
-                        self.write([self], save_values)
-            except:
-                self._values = values
-                raise
+    @dualmethod
+    def save(cls, records):
+        if not records:
+            return
+        values = {}
+        save_values = {}
+        to_create = []
+        to_write = []
+        cursor = records[0]._cursor
+        user = records[0]._user
+        context = records[0]._context
+        for record in records:
+            assert cursor == record._cursor
+            assert user == record._user
+            assert context == record._context
+            save_values[record] = record._save_values
+            values[record] = record._values
+            record._values = None
+            if record.id is None or record.id < 0:
+                to_create.append(record)
+            elif save_values[record]:
+                to_write.append(record)
+        transaction = Transaction()
+        try:
+            with transaction.set_cursor(cursor), \
+                    transaction.set_user(user), \
+                    transaction.set_context(context):
+                if to_create:
+                    news = cls.create([save_values[r] for r in to_create])
+                    for record, new in izip(to_create, news):
+                        record._ids.remove(record.id)
+                        record.id = new.id
+                        record._ids.append(record.id)
+                if to_write:
+                    cls.write(*sum(
+                            (([r], save_values[r]) for r in to_write), ()))
+        except:
+            for record in records:
+                record._values = values[record]
+            raise
+        for record in records:
+            record._init_values = None
 
 
 class EvalEnvironment(dict):
diff --git a/trytond/model/modelview.py b/trytond/model/modelview.py
index 5d1950f..5f74fbc 100644
--- a/trytond/model/modelview.py
+++ b/trytond/model/modelview.py
@@ -1,12 +1,13 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 lxml import etree
 from functools import wraps
 import copy
+import collections
 
-from trytond.model import Model
-from trytond.tools import safe_eval, ClassProperty
-from trytond.pyson import PYSONEncoder, CONTEXT
+from trytond.model import Model, fields
+from trytond.tools import ClassProperty, is_instance_method
+from trytond.pyson import PYSONDecoder, PYSONEncoder
 from trytond.transaction import Transaction
 from trytond.cache import Cache
 from trytond.pool import Pool
@@ -42,26 +43,26 @@ def _inherit_apply(src, inherit):
                         index = parent.index(enext)
                         parent.insert(index, child)
                 else:
-                    parent.extend(element2.getchildren())
+                    parent.extend(list(element2))
                 parent.remove(element)
             elif pos == 'replace_attributes':
-                child = element2.getchildren()[0]
+                child = element2[0]
                 for attr in child.attrib:
                     element.set(attr, child.get(attr))
             elif pos == 'inside':
-                element.extend(element2.getchildren())
+                element.extend(list(element2))
             elif pos == 'after':
                 parent = element.getparent()
                 enext = element.getnext()
                 if enext is not None:
-                    for child in element2:
+                    for child in list(element2):
                         index = parent.index(enext)
                         parent.insert(index, child)
                 else:
-                    parent.extend(element2.getchildren())
+                    parent.extend(list(element2))
             elif pos == 'before':
                 parent = element.getparent()
-                for child in element2:
+                for child in list(element2):
                     index = parent.index(element)
                     parent.insert(index, child)
             else:
@@ -74,6 +75,20 @@ def _inherit_apply(src, inherit):
     return etree.tostring(tree_src, encoding='utf-8')
 
 
+def on_change(func):
+    @wraps(func)
+    def wrapper(self, *args, **kwargs):
+        result = func(self, *args, **kwargs)
+        assert result is None, func
+        return self
+    wrapper.on_change = True
+    return wrapper
+
+
+def on_change_result(record):
+    return record._changed_values
+
+
 class ModelView(Model):
     """
     Define a model with views in Tryton.
@@ -101,8 +116,102 @@ class ModelView(Model):
         super(ModelView, cls).__setup__()
         cls.__rpc__['fields_view_get'] = RPC()
         cls.__rpc__['view_toolbar_get'] = RPC()
+        cls.__rpc__['on_change'] = RPC(instantiate=0)
+        cls.__rpc__['on_change_with'] = RPC(instantiate=0)
         cls._buttons = {}
 
+        if hasattr(cls, '__depend_methods'):
+            cls.__depend_methods = cls.__depend_methods.copy()
+        else:
+            cls.__depend_methods = collections.defaultdict(set)
+
+        if hasattr(cls, '__change_buttons'):
+            cls.__change_buttons = cls.__change_buttons.copy()
+        else:
+            cls.__change_buttons = collections.defaultdict(set)
+
+        def setup_field(field, field_name):
+            for attribute in ('on_change', 'on_change_with', 'autocomplete',
+                    'selection_change_with'):
+                if attribute == 'selection_change_with':
+                    if isinstance(
+                            getattr(field, 'selection', None), basestring):
+                        function_name = field.selection
+                    else:
+                        continue
+                else:
+                    function_name = '%s_%s' % (attribute, field_name)
+                if not getattr(cls, function_name, None):
+                    continue
+                # Search depends on all parent class because field has been
+                # copied with the original definition
+                for parent_cls in cls.__mro__:
+                    function = getattr(parent_cls, function_name, None)
+                    if not function:
+                        continue
+                    if getattr(function, 'depends', None):
+                        setattr(field, attribute,
+                            getattr(field, attribute) | function.depends)
+                    if getattr(function, 'depend_methods', None):
+                        cls.__depend_methods[(field_name, attribute)] |= \
+                            function.depend_methods
+                function = getattr(cls, function_name, None)
+                if (attribute == 'on_change'
+                        and not getattr(function, 'on_change', None)):
+                    # Decorate on_change to always return self
+                    setattr(cls, function_name, on_change(function))
+
+        def setup_callable(function, name):
+            if hasattr(function, 'change'):
+                cls.__change_buttons[name] |= function.change
+
+        for name in dir(cls):
+            attr = getattr(cls, name)
+            if isinstance(attr, fields.Field):
+                setup_field(attr, name)
+            elif isinstance(attr, collections.Callable):
+                setup_callable(attr, name)
+
+    @classmethod
+    def __post_setup__(cls):
+        super(ModelView, cls).__post_setup__()
+
+        # Update __rpc__
+        for field_name, field in cls._fields.iteritems():
+            if isinstance(field, (fields.Selection, fields.Reference)) \
+                    and not isinstance(field.selection, (list, tuple)) \
+                    and field.selection not in cls.__rpc__:
+                instantiate = 0 if field.selection_change_with else None
+                cls.__rpc__.setdefault(field.selection,
+                    RPC(instantiate=instantiate))
+
+            for attribute in ('on_change', 'on_change_with', 'autocomplete'):
+                function_name = '%s_%s' % (attribute, field_name)
+                if getattr(cls, function_name, None):
+                    result = None
+                    if attribute == 'on_change':
+                        result = on_change_result
+                    cls.__rpc__.setdefault(function_name,
+                        RPC(instantiate=0, result=result))
+
+        for button in cls._buttons:
+            if not is_instance_method(cls, button):
+                cls.__rpc__.setdefault(button,
+                    RPC(readonly=False, instantiate=0))
+            else:
+                cls.__rpc__.setdefault(button,
+                    RPC(instantiate=0, result=on_change_result))
+
+        # Update depend on methods
+        for (field_name, attribute), others in (
+                cls.__depend_methods.iteritems()):
+            field = getattr(cls, field_name)
+            for other in others:
+                other_field = getattr(cls, other)
+                setattr(field, attribute,
+                    getattr(field, attribute)
+                    | getattr(other_field, attribute))
+
     @classmethod
     def fields_view_get(cls, view_id=None, view_type='form'):
         '''
@@ -110,8 +219,11 @@ class ModelView(Model):
         If view_id is None the first one will be used of view_type.
         The definition is a dictionary with keys:
            - model: the model name
+           - type: the type of the view
+           - view_id: the id of the view
            - arch: the xml description of the view
            - fields: a dictionary with the definition of each field in the view
+           - field_childs: the name of the childs field for tree
         '''
         key = (cls.__name__, view_id, view_type)
         result = cls._fields_view_get_cache.get(key)
@@ -193,8 +305,8 @@ class ModelView(Model):
                     raise_p = True
             for view in views:
                 if view.domain:
-                    if not safe_eval(view.domain,
-                            {'context': Transaction().context}):
+                    if not PYSONDecoder({'context': Transaction().context}
+                            ).decode(view.domain):
                         continue
                 if not view.arch or not view.arch.strip():
                     continue
@@ -281,11 +393,21 @@ class ModelView(Model):
         return value
 
     @classmethod
+    def view_attributes(cls):
+        'Return a list of xpath, attribute name and value'
+        return []
+
+    @classmethod
     def _view_look_dom_arch(cls, tree, type, field_children=None):
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
         FieldAccess = pool.get('ir.model.field.access')
 
+        encoder = PYSONEncoder()
+        for xpath, attribute, value in cls.view_attributes():
+            for element in tree.xpath(xpath):
+                element.set(attribute, encoder.encode(value))
+
         fields_width = {}
         tree_root = tree.getroottree().getroot()
 
@@ -307,15 +429,16 @@ class ModelView(Model):
 
         # Remove field without read access
         for field in fields_to_remove:
-            for element in tree.xpath(
-                    '//field[@name="%s"] | //label[@name="%s"]'
-                    % (field, field)):
-                if type == 'form':
-                    element.tag = 'label'
-                    element.attrib.clear()
-                elif type == 'tree':
+            xpath = ('//field[@name="%(field)s"] | //label[@name="%(field)s"]'
+                ' | //page[@name="%(field)s"] | //group[@name="%(field)s"]'
+                ' | //separator[@name="%(field)s"]') % {'field': field}
+            for element in tree.xpath(xpath):
+                if type == 'tree' or element.tag == 'page':
                     parent = element.getparent()
                     parent.remove(element)
+                elif type == 'form':
+                    element.tag = 'label'
+                    element.attrib.clear()
 
         if type == 'tree':
             ViewTreeWidth = pool.get('ir.ui.view_tree_width')
@@ -424,15 +547,7 @@ class ModelView(Model):
             if element.get('name') in fields_width:
                 element.set('width', str(fields_width[element.get('name')]))
 
-        # convert attributes into pyson
         encoder = PYSONEncoder()
-        for attr in ('states', 'domain', 'spell', 'colors'):
-            if (element.get(attr)
-                    # Avoid double evaluation from inherit with different model
-                    and '__' not in element.get(attr)):
-                element.set(attr, encoder.encode(safe_eval(element.get(attr),
-                    CONTEXT)))
-
         if element.tag == 'button':
             button_name = element.attrib['name']
             if button_name in cls._buttons:
@@ -446,6 +561,14 @@ class ModelView(Model):
                 states['readonly'] = True
             element.set('states', encoder.encode(states))
 
+            change = cls.__change_buttons[button_name]
+            if change:
+                element.set('change', encoder.encode(list(change)))
+            if not is_instance_method(cls, button_name):
+                element.set('type', 'class')
+            else:
+                element.set('type', 'instance')
+
         # translate view
         if Transaction().language != 'en_US':
             for attr in ('string', 'sum', 'confirm', 'help'):
@@ -516,3 +639,83 @@ class ModelView(Model):
                 return action_id
             return wrapper
         return decorator
+
+    @staticmethod
+    def button_change(*fields):
+        def decorator(func):
+            func = ModelView.button(func)
+            func = on_change(func)
+            func.change = set(fields)
+            return func
+        return decorator
+
+    def on_change(self, fieldnames):
+        for fieldname in sorted(fieldnames):
+            method = getattr(self, 'on_change_%s' % fieldname, None)
+            if method:
+                method()
+        # XXX remove backward compatibility
+        return [self._changed_values]
+
+    def on_change_with(self, fieldnames):
+        changes = {}
+        for fieldname in fieldnames:
+            method_name = 'on_change_with_%s' % fieldname
+            changes[fieldname] = getattr(self, method_name)()
+        return changes
+
+    @property
+    def _changed_values(self):
+        """Return the values changed since the instantiation.
+        By default, the value of a field is its internal representation except:
+            - for Many2One and One2One field: the id.
+            - for Reference field: the string model,id
+            - for Many2Many: the list of ids
+            - for One2Many: a dictionary composed of three keys:
+                - add: a list of tuple, the first element is the index where
+                  the new line is added, the second element is
+                  `_default_values`
+                - update: a list of dictionary of `_changed_values` including
+                  the `id`
+                - remove: a list of ids
+        """
+        from .modelstorage import ModelStorage
+        changed = {}
+        init_values = self._init_values or {}
+        if not self._values:
+            return changed
+        for fname, value in self._values.iteritems():
+            field = self._fields[fname]
+            if (value == init_values.get(fname)
+                    and field._type != 'one2many'):
+                continue
+            if field._type in ('many2one', 'one2one', 'reference'):
+                if value:
+                    if isinstance(value, ModelStorage):
+                        changed['%s.rec_name' % fname] = value.rec_name
+                    if field._type == 'reference':
+                        value = str(value)
+                    else:
+                        value = value.id
+            elif field._type == 'one2many':
+                targets = value
+                init_targets = list(init_values.get(fname, []))
+                value = collections.defaultdict(list)
+                value['remove'] = [t.id for t in init_targets if t.id]
+                for i, target in enumerate(targets):
+                    if target.id in value['remove']:
+                        value['remove'].remove(target.id)
+                        target_changed = target._changed_values
+                        if target_changed:
+                            target_changed['id'] = target.id
+                            value['update'].append(target_changed)
+                    else:
+                        value['add'].append((i, target._default_values))
+                if not value['remove']:
+                    del value['remove']
+                if not value:
+                    continue
+            elif field._type == 'many2many':
+                value = [r.id for r in value]
+            changed[fname] = value
+        return changed
diff --git a/trytond/model/workflow.py b/trytond/model/workflow.py
index ed346cd..5d79934 100644
--- a/trytond/model/workflow.py
+++ b/trytond/model/workflow.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 functools import wraps
 
 
diff --git a/trytond/modules/__init__.py b/trytond/modules/__init__.py
index 7c233b5..d196c9d 100644
--- a/trytond/modules/__init__.py
+++ b/trytond/modules/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 os
 import sys
 import itertools
@@ -19,6 +19,8 @@ from trytond.transaction import Transaction
 from trytond.cache import Cache
 import trytond.convert as convert
 
+logger = logging.getLogger(__name__)
+
 ir_module = Table('ir_module_module')
 ir_model_data = Table('ir_model_data')
 
@@ -165,7 +167,7 @@ def create_graph(module_list):
         # add 'package' in the graph
         all_deps = deps + [x for x in xdep if x in all_packages]
         if reduce(lambda x, y: x and y in graph, all_deps, True):
-            if not package in current:
+            if package not in current:
                 packages.pop(0)
                 continue
             later.clear()
@@ -202,7 +204,6 @@ def load_module_graph(graph, pool, update=None, lang=None):
         update = []
     modules_todo = []
     models_to_update_history = set()
-    logger = logging.getLogger('modules')
     cursor = Transaction().cursor
 
     modules = [x.name for x in graph]
@@ -222,25 +223,25 @@ def load_module_graph(graph, pool, update=None, lang=None):
             if package_state not in ('to install', 'to upgrade'):
                 if package_state == 'installed':
                     package_state = 'to upgrade'
-                else:
+                elif package_state != 'to remove':
                     package_state = 'to install'
             for child in package.childs:
                 module2state[child.name] = package_state
             for type in classes.keys():
                 for cls in classes[type]:
-                    logger.info('%s:register %s' % (module, cls.__name__))
+                    logger.info('%s:register %s', module, cls.__name__)
                     cls.__register__(module)
             for model in classes['model']:
                 if hasattr(model, '_history'):
                     models_to_update_history.add(model.__name__)
 
-            #Instanciate a new parser for the package:
+            # Instanciate a new parser for the package:
             tryton_parser = convert.TrytondXmlHandler(pool=pool, module=module,
                 module_state=package_state)
 
             for filename in package.info.get('xml', []):
                 filename = filename.replace('/', os.sep)
-                logger.info('%s:loading %s' % (module, filename))
+                logger.info('%s:loading %s', module, filename)
                 # Feed the parser with xml content:
                 with tools.file_open(OPJ(module, filename)) as fp:
                     tryton_parser.parse_xmlstream(fp)
@@ -253,11 +254,13 @@ def load_module_graph(graph, pool, update=None, lang=None):
                 lang2 = os.path.splitext(os.path.basename(filename))[0]
                 if lang2 not in lang:
                     continue
-                logger.info('%s:loading %s' % (module,
-                        filename[len(package.info['directory']) + 1:]))
+                logger.info('%s:loading %s', module,
+                    filename[len(package.info['directory']) + 1:])
                 Translation = pool.get('ir.translation')
                 Translation.translation_import(lang2, module, filename)
 
+            if package_state == 'to remove':
+                continue
             cursor.execute(*ir_module.select(ir_module.id,
                     where=(ir_module.name == package.name)))
             try:
@@ -276,7 +279,7 @@ def load_module_graph(graph, pool, update=None, lang=None):
     for model_name in models_to_update_history:
         model = pool.get(model_name)
         if model._history:
-            logger.info('history:update %s' % model.__name__)
+            logger.info('history:update %s', model.__name__)
             model._update_history_table()
 
     # Vacuum :
@@ -293,6 +296,8 @@ def get_module_list():
         for file in os.listdir(MODULES_PATH):
             if file.startswith('.'):
                 continue
+            if file == '__pycache__':
+                continue
             if os.path.isdir(OPJ(MODULES_PATH, file)):
                 module_list.add(file)
     update_egg_modules()
@@ -316,11 +321,10 @@ def register_classes():
     trytond.webdav.register()
     import trytond.tests
     trytond.tests.register()
-    logger = logging.getLogger('modules')
 
     for package in create_graph(get_module_list())[0]:
         module = package.name
-        logger.info('%s:registering classes' % module)
+        logger.info('%s:registering classes', module)
 
         if module in ('ir', 'res', 'webdav', 'tests'):
             MODULES.append(module)
@@ -364,9 +368,6 @@ def load_modules(database_name, pool, update=None, lang=None):
         global res
         cursor = Transaction().cursor
         if update:
-            # Migration from 2.2: workflow module removed
-            cursor.execute(*ir_module.delete(
-                    where=(ir_module.name == 'workflow')))
             cursor.execute(*ir_module.select(ir_module.name,
                     where=ir_module.state.in_(('installed', 'to install',
                             'to upgrade', 'to remove'))))
@@ -391,7 +392,7 @@ def load_modules(database_name, pool, update=None, lang=None):
             fetchall = cursor.fetchall()
             if fetchall:
                 for (mod_name,) in fetchall:
-                    #TODO check if ressource not updated by the user
+                    # TODO check if ressource not updated by the user
                     cursor.execute(*ir_model_data.select(ir_model_data.model,
                             ir_model_data.db_id,
                             where=(ir_model_data.module == mod_name),
diff --git a/trytond/monitor.py b/trytond/monitor.py
index 128a0bf..866d594 100644
--- a/trytond/monitor.py
+++ b/trytond/monitor.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 sys
 import os
 import subprocess
diff --git a/trytond/pool.py b/trytond/pool.py
index 65c6f49..7bdd4c4 100644
--- a/trytond/pool.py
+++ b/trytond/pool.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 threading import RLock
 import logging
 from trytond.modules import load_modules, register_classes
@@ -8,13 +8,18 @@ import __builtin__
 
 __all__ = ['Pool', 'PoolMeta', 'PoolBase']
 
+logger = logging.getLogger(__name__)
+
 
 class PoolMeta(type):
 
     def __new__(cls, name, bases, dct):
         new = type.__new__(cls, name, bases, dct)
         if '__name__' in dct:
-            new.__name__ = dct['__name__']
+            try:
+                new.__name__ = dct['__name__']
+            except TypeError:
+                new.__name__ = dct['__name__'].encode('utf-8')
         return new
 
 
@@ -134,7 +139,6 @@ class Pool(object):
         Set update to proceed to update
         lang is a list of language code to be updated
         '''
-        logger = logging.getLogger('pool')
         with self._lock:
             if not self._started:
                 self.start()
@@ -142,9 +146,9 @@ class Pool(object):
             # Don't reset pool if already init and not to update
             if not update and self._pool.get(self.database_name):
                 return
-            logger.info('init pool for "%s"' % self.database_name)
+            logger.info('init pool for "%s"', self.database_name)
             self._pool.setdefault(self.database_name, {})
-            #Clean the _pool before loading modules
+            # Clean the _pool before loading modules
             for type in self.classes.keys():
                 self._pool[self.database_name][type] = {}
             restart = not load_modules(self.database_name, self, update=update,
@@ -183,20 +187,6 @@ class Pool(object):
         with self._locks[self.database_name]:
             self._pool[self.database_name][type][cls.__name__] = cls
 
-    def object_name_list(self, type='model'):
-        '''
-        Return the object name list of a type
-
-        :param type: the type
-        :return: a list of name
-        '''
-        if type == '*':
-            res = []
-            for type in self.classes.keys():
-                res += self._pool[self.database_name][type].keys()
-            return res
-        return self._pool[self.database_name][type].keys()
-
     def iterobject(self, type='model'):
         '''
         Return an iterator over object name, object
diff --git a/trytond/protocols/__init__.py b/trytond/protocols/__init__.py
index c86640b..4effdfa 100644
--- a/trytond/protocols/__init__.py
+++ b/trytond/protocols/__init__.py
@@ -1,2 +1,2 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
diff --git a/trytond/protocols/common.py b/trytond/protocols/common.py
index 959b83a..c38ac17 100644
--- a/trytond/protocols/common.py
+++ b/trytond/protocols/common.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 errno
 import os
 import socket
diff --git a/trytond/protocols/dispatcher.py b/trytond/protocols/dispatcher.py
index 05e3345..e22994c 100644
--- a/trytond/protocols/dispatcher.py
+++ b/trytond/protocols/dispatcher.py
@@ -1,9 +1,8 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 logging
 import time
-import sys
 import pydoc
 
 from sql import Table
@@ -12,12 +11,12 @@ from trytond.pool import Pool
 from trytond import security
 from trytond import backend
 from trytond.config import config
-from trytond.version import VERSION
+from trytond import __version__
 from trytond.transaction import Transaction
 from trytond.cache import Cache
 from trytond.exceptions import UserError, UserWarning, NotLogged, \
     ConcurrencyException
-from trytond.rpc import RPC
+from trytond.tools import is_instance_method
 
 logger = logging.getLogger(__name__)
 
@@ -44,17 +43,17 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
                 Cache.clean(database_name)
                 Cache.resets(database_name)
             msg = res and 'successful login' or 'bad login or password'
-            logger.info('%s \'%s\' from %s:%d using %s on database \'%s\''
-                % (msg, user, host, port, protocol, database_name))
+            logger.info('%s \'%s\' from %s:%d using %s on database \'%s\'',
+                msg, user, host, port, protocol, database_name)
             return res or False
         elif method == 'logout':
             name = security.logout(database_name, user, session)
-            logger.info(('logout \'%s\' from %s:%d '
-                    'using %s on database \'%s\'')
-                % (name, host, port, protocol, database_name))
+            logger.info('logout \'%s\' from %s:%d '
+                'using %s on database \'%s\'',
+                name, host, port, protocol, database_name)
             return True
         elif method == 'version':
-            return VERSION
+            return __version__
         elif method == 'list_lang':
             return [
                 ('bg_BG', 'Български'),
@@ -98,7 +97,7 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
         database = Database(database_name).connect()
         database_list = Pool.database_list()
         pool = Pool(database_name)
-        if not database_name in database_list:
+        if database_name not in database_list:
             pool.init()
         if method == 'listMethods':
             res = []
@@ -106,9 +105,6 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
                 for object_name, obj in pool.iterobject(type=type):
                     for method in obj.__rpc__:
                         res.append(type + '.' + object_name + '.' + method)
-                    if hasattr(obj, '_buttons'):
-                        for button in obj._buttons:
-                            res.append(type + '.' + object_name + '.' + button)
             return res
         elif method == 'methodSignature':
             return 'signatures not supported'
@@ -132,7 +128,7 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
 
     database_list = Pool.database_list()
     pool = Pool(database_name)
-    if not database_name in database_list:
+    if database_name not in database_list:
         with Transaction().start(database_name, user,
                 readonly=True) as transaction:
             pool.init()
@@ -140,15 +136,15 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
 
     if method in obj.__rpc__:
         rpc = obj.__rpc__[method]
-    elif method in getattr(obj, '_buttons', {}):
-        rpc = RPC(readonly=False, instantiate=0)
     else:
         raise UserError('Calling method %s on %s %s is not allowed!'
             % (method, object_type, object_name))
 
-    exception_message = ('Exception calling %s.%s.%s from %s@%s:%d/%s' %
-        (object_type, object_name, method, user, host, port, database_name))
+    log_message = '%s.%s.%s(*%s, **%s) from %s@%s:%d/%s'
+    log_args = (object_type, object_name, method, args, kwargs,
+        user, host, port, database_name)
 
+    logger.info(log_message, *log_args)
     for count in range(config.getint('database', 'retry'), -1, -1):
         with Transaction().start(database_name, user,
                 readonly=rpc.readonly) as transaction:
@@ -157,7 +153,8 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
                 c_args, c_kwargs, transaction.context, transaction.timestamp \
                     = rpc.convert(obj, *args, **kwargs)
                 meth = getattr(obj, method)
-                if not hasattr(meth, 'im_self') or meth.im_self:
+                if (rpc.instantiate is None
+                        or not is_instance_method(obj, method)):
                     result = rpc.result(meth(*c_args, **c_kwargs))
                 else:
                     assert rpc.instantiate == 0
@@ -175,11 +172,11 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
                     continue
                 raise
             except (NotLogged, ConcurrencyException, UserError, UserWarning):
-                logger.debug(exception_message, exc_info=sys.exc_info())
+                logger.debug(log_message, *log_args, exc_info=True)
                 transaction.cursor.rollback()
                 raise
             except Exception:
-                logger.error(exception_message, exc_info=sys.exc_info())
+                logger.error(log_message, *log_args, exc_info=True)
                 transaction.cursor.rollback()
                 raise
             Cache.resets(database_name)
@@ -189,10 +186,12 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
             try:
                 Session.reset(session)
             except DatabaseOperationalError:
+                logger.debug('Reset session failed', exc_info=True)
                 # Silently fail when reseting session
                 transaction.cursor.rollback()
             else:
                 transaction.cursor.commit()
+        logger.debug('Result: %s', result)
         return result
 
 
@@ -212,7 +211,7 @@ def create(database_name, password, lang, admin_password):
 
     try:
         with Transaction().start(None, 0, close=True, autocommit=True) \
-            as transaction:
+                as transaction:
             transaction.database.create(transaction.cursor, database_name)
             transaction.cursor.commit()
 
@@ -244,11 +243,10 @@ def create(database_name, password, lang, admin_password):
             transaction.cursor.commit()
             res = True
     except Exception:
-        logger.error('CREATE DB: %s failed' % database_name,
-            exc_info=sys.exc_info())
+        logger.error('CREATE DB: %s failed', database_name, exc_info=True)
         raise
     else:
-        logger.info('CREATE DB: %s' % (database_name,))
+        logger.info('CREATE DB: %s', database_name)
     return res
 
 
@@ -260,18 +258,18 @@ def drop(database_name, password):
     time.sleep(1)
 
     with Transaction().start(None, 0, close=True, autocommit=True) \
-        as transaction:
+            as transaction:
         cursor = transaction.cursor
         try:
             Database.drop(cursor, database_name)
             cursor.commit()
         except Exception:
-            logger.error('DROP DB: %s failed' % database_name,
-                exc_info=sys.exc_info())
+            logger.error('DROP DB: %s failed', database_name, exc_info=True)
             raise
         else:
-            logger.info('DROP DB: %s' % (database_name))
+            logger.info('DROP DB: %s', database_name)
             Pool.stop(database_name)
+            Cache.drop(database_name)
     return True
 
 
@@ -283,8 +281,11 @@ def dump(database_name, password):
     time.sleep(1)
 
     data = Database.dump(database_name)
-    logger.info('DUMP DB: %s' % (database_name))
-    return buffer(data)
+    logger.info('DUMP DB: %s', database_name)
+    if bytes == str:
+        return bytearray(data)
+    else:
+        return bytes(data)
 
 
 def restore(database_name, password, data, update=False):
@@ -298,7 +299,7 @@ def restore(database_name, password, data, update=False):
     except Exception:
         pass
     Database.restore(database_name, data)
-    logger.info('RESTORE DB: %s' % (database_name))
+    logger.info('RESTORE DB: %s', database_name)
     if update:
         with Transaction().start(database_name, 0) as transaction:
             cursor = transaction.cursor
diff --git a/trytond/protocols/jsonrpc.py b/trytond/protocols/jsonrpc.py
index 088d7da..df9f46e 100644
--- a/trytond/protocols/jsonrpc.py
+++ b/trytond/protocols/jsonrpc.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.protocols.sslsocket import SSLSocket
 from trytond.protocols.dispatcher import dispatch
 from trytond.config import config
@@ -55,8 +55,14 @@ JSONDecoder.register('date',
 JSONDecoder.register('time',
     lambda dct: datetime.time(dct['hour'], dct['minute'], dct['second'],
         dct['microsecond']))
-JSONDecoder.register('buffer', lambda dct:
-    buffer(base64.decodestring(dct['base64'])))
+JSONDecoder.register('timedelta',
+    lambda dct: datetime.timedelta(seconds=dct['seconds']))
+
+
+def _bytes_decoder(dct):
+    cast = bytearray if bytes == str else bytes
+    return cast(base64.decodestring(dct['base64']))
+JSONDecoder.register('bytes', _bytes_decoder)
 JSONDecoder.register('Decimal', lambda dct: Decimal(dct['decimal']))
 
 
@@ -105,11 +111,17 @@ JSONEncoder.register(datetime.time,
         'second': o.second,
         'microsecond': o.microsecond,
         })
-JSONEncoder.register(buffer,
+JSONEncoder.register(datetime.timedelta,
     lambda o: {
-        '__class__': 'buffer',
-        'base64': base64.encodestring(o),
+        '__class__': 'timedelta',
+        'seconds': o.total_seconds(),
         })
+_bytes_encoder = lambda o: {
+    '__class__': 'bytes',
+    'base64': base64.encodestring(o),
+    }
+JSONEncoder.register(bytes, _bytes_encoder)
+JSONEncoder.register(bytearray, _bytes_encoder)
 JSONEncoder.register(Decimal,
     lambda o: {
         '__class__': 'Decimal',
@@ -145,7 +157,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
         response = {'id': req_id}
 
         try:
-            #generate response
+            # generate response
             if dispatch_method is not None:
                 response['result'] = dispatch_method(method, params)
             else:
diff --git a/trytond/protocols/sslsocket.py b/trytond/protocols/sslsocket.py
index 9c18a26..4d12720 100644
--- a/trytond/protocols/sslsocket.py
+++ b/trytond/protocols/sslsocket.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.config import config
 
 
diff --git a/trytond/protocols/webdav.py b/trytond/protocols/webdav.py
index 584a7c3..23d4056 100644
--- a/trytond/protocols/webdav.py
+++ b/trytond/protocols/webdav.py
@@ -1,12 +1,11 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 SocketServer
 import socket
 import BaseHTTPServer
 import urlparse
 import time
 import urllib
-import sys
 import logging
 from threading import local
 import xml.dom.minidom
@@ -21,7 +20,7 @@ from pywebdav.lib.davcmd import copyone, copytree, moveone, movetree, \
 from trytond.protocols.sslsocket import SSLSocket
 from trytond.protocols.common import daemon
 from trytond.security import login
-from trytond.version import PACKAGE, VERSION, WEBSITE
+from trytond import __version__
 from trytond.tools.misc import LocalDict
 from trytond import backend
 from trytond.pool import Pool
@@ -134,9 +133,9 @@ class TrytonDAVInterface(iface.dav_interface):
         if isinstance(exception, (NotLogged, ConcurrencyException, UserError,
                     UserWarning, DAV_Error, DAV_NotFound, DAV_Secret,
                     DAV_Forbidden)):
-            logger.debug('Exception', exc_info=sys.exc_info())
+            logger.debug('Exception %s', exception, exc_info=True)
         else:
-            logger.error('Exception', exc_info=sys.exc_info())
+            logger.error('Exception %s', exception, exc_info=True)
 
     @staticmethod
     def get_dburi(uri):
@@ -199,8 +198,7 @@ class TrytonDAVInterface(iface.dav_interface):
             res += '<head>'
             res += ('<meta http-equiv="Content-Type" content="text/html; '
                 'charset=utf-8">')
-            res += ('<title>%s - WebDAV - %s</title>'
-                % (PACKAGE, dbname or 'root'))
+            res += '<title>Tryton - WebDAV - %s</title>' % dbname or 'root'
             res += '</head>'
             res += '<body>'
             res += '<h2>Collection: %s</h2>' % (get_urifilename(uri) or '/')
@@ -220,8 +218,8 @@ class TrytonDAVInterface(iface.dav_interface):
                     % (quote_uri(child), get_urifilename(child)))
             res += '</ul>'
             res += '<hr noshade>'
-            res += ('<em>Powered by <a href="%s">%s</a> version %s</em>'
-                % (quote_uri(WEBSITE), PACKAGE, VERSION))
+            res += ('<em>Powered by <a href="http://www.tryton.org/">'
+                'Tryton</a> version %s</em>' % __version__)
             res += '</body>'
             res += '</html>'
             return res
@@ -574,7 +572,7 @@ class WebDAVAuthRequestHandler(WebDAVServer.DAVRequestHandler):
                 with Transaction().start(dbname, 0) as transaction:
                     database_list = Pool.database_list()
                     pool = Pool(dbname)
-                    if not dbname in database_list:
+                    if dbname not in database_list:
                         pool.init()
                     Share = pool.get('webdav.share')
                     user = Share.get_login(key, command, path)
diff --git a/trytond/protocols/xmlrpc.py b/trytond/protocols/xmlrpc.py
index 7b48c53..9febacf 100644
--- a/trytond/protocols/xmlrpc.py
+++ b/trytond/protocols/xmlrpc.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.protocols.sslsocket import SSLSocket
 from trytond.protocols.dispatcher import dispatch
 from trytond.protocols.common import daemon, RegisterHandlerMixin
@@ -10,7 +10,6 @@ import SimpleXMLRPCServer
 import SocketServer
 import xmlrpclib
 import socket
-import sys
 import base64
 import datetime
 from types import DictType
@@ -29,7 +28,7 @@ def dump_decimal(self, value, write):
     self.dump_struct(value, write)
 
 
-def dump_buffer(self, value, write):
+def dump_bytes(self, value, write):
     self.write = write
     value = xmlrpclib.Binary(value)
     value.encode(self)
@@ -54,12 +53,22 @@ def dump_time(self, value, write):
         }
     self.dump_struct(value, write)
 
+
+def dump_timedelta(self, value, write):
+    value = {'__class__': 'timedelta',
+        'seconds': value.total_seconds(),
+        }
+    self.dump_struct(value, write)
+
 xmlrpclib.Marshaller.dispatch[Decimal] = dump_decimal
 xmlrpclib.Marshaller.dispatch[type(None)] = \
         lambda self, value, write: write("<value><nil/></value>")
 xmlrpclib.Marshaller.dispatch[datetime.date] = dump_date
 xmlrpclib.Marshaller.dispatch[datetime.time] = dump_time
-xmlrpclib.Marshaller.dispatch[buffer] = dump_buffer
+xmlrpclib.Marshaller.dispatch[datetime.timedelta] = dump_timedelta
+if bytes != str:
+    xmlrpclib.Marshaller.dispatch[bytes] = dump_bytes
+xmlrpclib.Marshaller.dispatch[bytearray] = dump_bytes
 
 
 def dump_struct(self, value, write, escape=xmlrpclib.escape):
@@ -94,6 +103,8 @@ XMLRPCDecoder.register('date',
 XMLRPCDecoder.register('time',
     lambda dct: datetime.time(dct['hour'], dct['minute'], dct['second'],
         dct['microsecond']))
+XMLRPCDecoder.register('timedelta',
+    lambda dct: datetime.timedelta(seconds=dct['seconds']))
 XMLRPCDecoder.register('Decimal', lambda dct: Decimal(dct['decimal']))
 
 
@@ -122,7 +133,8 @@ xmlrpclib.Unmarshaller.dispatch["dateTime.iso8601"] = _end_dateTime
 def _end_base64(self, data):
     value = xmlrpclib.Binary()
     value.decode(data)
-    self.append(buffer(value.data))
+    cast = bytearray if bytes == str else bytes
+    self.append(cast(value.data))
     self._value = 0
 xmlrpclib.Unmarshaller.dispatch['base64'] = _end_base64
 
@@ -152,14 +164,14 @@ class GenericXMLRPCRequestHandler:
                 return dispatch(host, port, 'XML-RPC', database_name, user,
                         session, object_type, object_name, method, *params)
             except (NotLogged, ConcurrencyException), exception:
-                logger.debug(exception_message, exc_info=sys.exc_info())
+                logger.debug(exception_message, exc_info=True)
                 raise xmlrpclib.Fault(exception.code, str(exception))
             except (UserError, UserWarning), exception:
-                logger.debug(exception_message, exc_info=sys.exc_info())
+                logger.debug(exception_message, exc_info=True)
                 error, description = exception.args
                 raise xmlrpclib.Fault(exception.code, str(exception))
             except Exception, exception:
-                logger.error(exception_message, exc_info=sys.exc_info())
+                logger.error(exception_message, exc_info=True)
                 raise xmlrpclib.Fault(255, str(exception))
         finally:
             security.logout(database_name, user, session)
diff --git a/trytond/pyson.py b/trytond/pyson.py
index 28979fd..c455588 100644
--- a/trytond/pyson.py
+++ b/trytond/pyson.py
@@ -1,12 +1,28 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 try:
     import simplejson as json
 except ImportError:
     import json
 import datetime
 from dateutil.relativedelta import relativedelta
-from functools import reduce
+from functools import reduce, wraps
+
+
+def reduced_type(types):
+    types = types.copy()
+    for k, r in [(long, int), (str, basestring), (unicode, basestring)]:
+        if k in types:
+            types.remove(k)
+            types.add(r)
+    return types
+
+
+def reduce_type(func):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        return reduced_type(func(*args, **kwargs))
+    return wrapper
 
 
 class PYSON(object):
@@ -79,6 +95,14 @@ class PYSON(object):
     def contains(self, k):
         return In(k, self)
 
+    def __repr__(self):
+        klass = self.__class__.__name__
+        return '%s(%s)' % (klass, ', '.join(map(repr, self.__repr_params__)))
+
+    @property
+    def __repr_params__(self):
+        return NotImplementedError
+
 
 class PYSONEncoder(json.JSONEncoder):
 
@@ -97,24 +121,34 @@ class PYSONEncoder(json.JSONEncoder):
 
 class PYSONDecoder(json.JSONDecoder):
 
-    def __init__(self, context=None):
+    def __init__(self, context=None, noeval=False):
         self.__context = context or {}
+        self.noeval = noeval
         super(PYSONDecoder, self).__init__(object_hook=self._object_hook)
 
     def _object_hook(self, dct):
         if '__class__' in dct:
-            klass = globals().get(dct['__class__'])
-            if klass and hasattr(klass, 'eval'):
-                return klass.eval(dct, self.__context)
+            klass = CONTEXT.get(dct['__class__'])
+            if klass:
+                if not self.noeval:
+                    return klass.eval(dct, self.__context)
+                else:
+                    dct = dct.copy()
+                    del dct['__class__']
+                    return klass(**dct)
         return dct
 
 
 class Eval(PYSON):
 
-    def __init__(self, value, default=''):
+    def __init__(self, v, d=''):
         super(Eval, self).__init__()
-        self._value = value
-        self._default = default
+        self._value = v
+        self._default = d
+
+    @property
+    def __repr_params__(self):
+        return self._value, self._default
 
     def pyson(self):
         return {
@@ -123,6 +157,7 @@ class Eval(PYSON):
             'd': self._default,
             }
 
+    @reduce_type
     def types(self):
         if isinstance(self._default, PYSON):
             return self._default.types()
@@ -136,13 +171,17 @@ class Eval(PYSON):
 
 class Not(PYSON):
 
-    def __init__(self, value):
+    def __init__(self, v):
         super(Not, self).__init__()
-        if isinstance(value, PYSON):
-            assert value.types() == set([bool]), 'value must be boolean'
+        if isinstance(v, PYSON):
+            assert v.types() == set([bool]), 'value must be boolean'
         else:
-            assert isinstance(value, bool), 'value must be boolean'
-        self._value = value
+            assert isinstance(v, bool), 'value must be boolean'
+        self._value = v
+
+    @property
+    def __repr_params__(self):
+        return (self._value,)
 
     def pyson(self):
         return {
@@ -160,9 +199,13 @@ class Not(PYSON):
 
 class Bool(PYSON):
 
-    def __init__(self, value):
+    def __init__(self, v):
         super(Bool, self).__init__()
-        self._value = value
+        self._value = v
+
+    @property
+    def __repr_params__(self):
+        return (self._value,)
 
     def pyson(self):
         return {
@@ -180,8 +223,9 @@ class Bool(PYSON):
 
 class And(PYSON):
 
-    def __init__(self, *statements):
+    def __init__(self, *statements, **kwargs):
         super(And, self).__init__()
+        statements = list(statements) + kwargs.get('s', [])
         for statement in statements:
             if isinstance(statement, PYSON):
                 assert statement.types() == set([bool]), \
@@ -190,7 +234,11 @@ class And(PYSON):
                 assert isinstance(statement, bool), \
                     'statement must be boolean'
         assert len(statements) >= 2, 'must have at least 2 statements'
-        self._statements = list(statements)
+        self._statements = statements
+
+    @property
+    def __repr_params__(self):
+        return tuple(self._statements)
 
     def pyson(self):
         return {
@@ -220,20 +268,25 @@ class Or(And):
 
 class Equal(PYSON):
 
-    def __init__(self, statement1, statement2):
+    def __init__(self, s1, s2):
+        statement1, statement2 = s1, s2
         super(Equal, self).__init__()
         if isinstance(statement1, PYSON):
             types1 = statement1.types()
         else:
-            types1 = set([type(statement1)])
+            types1 = reduced_type(set([type(s1)]))
         if isinstance(statement2, PYSON):
             types2 = statement2.types()
         else:
-            types2 = set([type(statement2)])
+            types2 = reduced_type(set([type(s2)]))
         assert types1 == types2, 'statements must have the same type'
         self._statement1 = statement1
         self._statement2 = statement2
 
+    @property
+    def __repr_params__(self):
+        return (self._statement1, self._statement2)
+
     def pyson(self):
         return {
             '__class__': 'Equal',
@@ -251,7 +304,8 @@ class Equal(PYSON):
 
 class Greater(PYSON):
 
-    def __init__(self, statement1, statement2, equal=False):
+    def __init__(self, s1, s2, e=False):
+        statement1, statement2, equal = s1, s2, e
         super(Greater, self).__init__()
         for i in (statement1, statement2):
             if isinstance(i, PYSON):
@@ -268,6 +322,10 @@ class Greater(PYSON):
         self._statement2 = statement2
         self._equal = equal
 
+    @property
+    def __repr_params__(self):
+        return (self._statement1, self._statement2, self._equal)
+
     def pyson(self):
         return {
             '__class__': 'Greater',
@@ -314,7 +372,8 @@ class Less(Greater):
 
 class If(PYSON):
 
-    def __init__(self, condition, then_statement, else_statement=None):
+    def __init__(self, c, t, e=None):
+        condition, then_statement, else_statement = c, t, e
         super(If, self).__init__()
         if isinstance(condition, PYSON):
             assert condition.types() == set([bool]), \
@@ -324,17 +383,21 @@ class If(PYSON):
         if isinstance(then_statement, PYSON):
             then_types = then_statement.types()
         else:
-            then_types = set([type(then_statement)])
+            then_types = reduced_type(set([type(then_statement)]))
         if isinstance(else_statement, PYSON):
-            assert then_types == else_statement.types(), \
-                'then and else statements must be the same type'
+            else_types = else_statement.types()
         else:
-            assert then_types == set([type(else_statement)]), \
-                'then and else statements must be the same type'
+            else_types = reduced_type(set([type(else_statement)]))
+        assert then_types == else_types, \
+            'then and else statements must be the same type'
         self._condition = condition
         self._then_statement = then_statement
         self._else_statement = else_statement
 
+    @property
+    def __repr_params__(self):
+        return (self._condition, self._then_statement, self._else_statement)
+
     def pyson(self):
         return {
             '__class__': 'If',
@@ -343,6 +406,7 @@ class If(PYSON):
             'e': self._else_statement,
             }
 
+    @reduce_type
     def types(self):
         if isinstance(self._then_statement, PYSON):
             return self._then_statement.types()
@@ -359,7 +423,8 @@ class If(PYSON):
 
 class Get(PYSON):
 
-    def __init__(self, obj, key, default=''):
+    def __init__(self, v, k, d=''):
+        obj, key, default = v, k, d
         super(Get, self).__init__()
         if isinstance(obj, PYSON):
             assert obj.types() == set([dict]), 'obj must be a dict'
@@ -367,12 +432,16 @@ class Get(PYSON):
             assert isinstance(obj, dict), 'obj must be a dict'
         self._obj = obj
         if isinstance(key, PYSON):
-            assert key.types() == set([str]), 'key must be a string'
+            assert key.types() == set([basestring]), 'key must be a string'
         else:
-            assert type(key) == str, 'key must be a string'
+            assert isinstance(key, basestring), 'key must be a string'
         self._key = key
         self._default = default
 
+    @property
+    def __repr_params__(self):
+        return (self._obj, self._key, self._default)
+
     def pyson(self):
         return {
             '__class__': 'Get',
@@ -381,6 +450,7 @@ class Get(PYSON):
             'd': self._default,
             }
 
+    @reduce_type
     def types(self):
         if isinstance(self._default, PYSON):
             return self._default.types()
@@ -394,26 +464,31 @@ class Get(PYSON):
 
 class In(PYSON):
 
-    def __init__(self, key, obj):
+    def __init__(self, k, v):
+        key, obj = k, v
         super(In, self).__init__()
         if isinstance(key, PYSON):
-            assert key.types().issubset(set([str, int, long])), \
+            assert key.types().issubset(set([basestring, int])), \
                 'key must be a string or an integer or a long'
         else:
-            assert type(key) in [str, int, long], \
+            assert isinstance(key, (basestring, int, long)), \
                 'key must be a string or an integer or a long'
         if isinstance(obj, PYSON):
             assert obj.types().issubset(set([dict, list])), \
                 'obj must be a dict or a list'
             if obj.types() == set([dict]):
-                assert type(key) == str, 'key must be a string'
+                assert isinstance(key, basestring), 'key must be a string'
         else:
-            assert type(obj) in [dict, list]
-            if type(obj) == dict:
-                assert type(key) == str, 'key must be a string'
+            assert isinstance(obj, (dict, list))
+            if isinstance(obj, dict):
+                assert isinstance(key, basestring), 'key must be a string'
         self._key = key
         self._obj = obj
 
+    @property
+    def __repr_params__(self):
+        return (self._key, self._obj)
+
     def pyson(self):
         return {
             '__class__': 'In',
@@ -432,7 +507,13 @@ class In(PYSON):
 class Date(PYSON):
 
     def __init__(self, year=None, month=None, day=None,
-            delta_years=0, delta_months=0, delta_days=0):
+            delta_years=0, delta_months=0, delta_days=0, **kwargs):
+        year = kwargs.get('y', year)
+        month = kwargs.get('M', month)
+        day = kwargs.get('d', day)
+        delta_years = kwargs.get('dy', delta_years)
+        delta_months = kwargs.get('dM', delta_months)
+        delta_days = kwargs.get('dd', delta_days)
         super(Date, self).__init__()
         for i in (year, month, day, delta_years, delta_months, delta_days):
             if isinstance(i, PYSON):
@@ -448,6 +529,11 @@ class Date(PYSON):
         self._delta_months = delta_months
         self._delta_days = delta_days
 
+    @property
+    def __repr_params__(self):
+        return (self._year, self._month, self._day,
+            self._delta_years, self._delta_months, self._delta_days)
+
     def pyson(self):
         return {
             '__class__': 'Date',
@@ -480,14 +566,22 @@ class DateTime(Date):
             hour=None, minute=None, second=None, microsecond=None,
             delta_years=0, delta_months=0, delta_days=0,
             delta_hours=0, delta_minutes=0, delta_seconds=0,
-            delta_microseconds=0):
+            delta_microseconds=0, **kwargs):
+        hour = kwargs.get('h', hour)
+        minute = kwargs.get('m', minute)
+        second = kwargs.get('s', second)
+        microsecond = kwargs.get('ms', microsecond)
+        delta_hours = kwargs.get('dh', delta_hours)
+        delta_minutes = kwargs.get('dm', delta_minutes)
+        delta_seconds = kwargs.get('ds', delta_seconds)
+        delta_microseconds = kwargs.get('dms', delta_microseconds)
         super(DateTime, self).__init__(year=year, month=month, day=day,
                 delta_years=delta_years, delta_months=delta_months,
-                delta_days=delta_days)
+                delta_days=delta_days, **kwargs)
         for i in (hour, minute, second, microsecond,
                 delta_hours, delta_minutes, delta_seconds, delta_microseconds):
             if isinstance(i, PYSON):
-                assert i.types() == set([int, long, type(None)]), \
+                assert i.types() == set([int, type(None)]), \
                     '%s must be an integer or None' % (i,)
             else:
                 assert isinstance(i, (int, long, type(None))), \
@@ -501,6 +595,15 @@ class DateTime(Date):
         self._delta_seconds = delta_seconds
         self._delta_microseconds = delta_microseconds
 
+    @property
+    def __repr_params__(self):
+        date_params = super(DateTime, self).__repr_params__
+        return (date_params[:3]
+            + (self._hour, self._minute, self._second, self._microsecond)
+            + date_params[3:]
+            + (self._delta_hours, self._delta_minutes, self._delta_seconds,
+                self._delta_microseconds))
+
     def pyson(self):
         res = super(DateTime, self).pyson()
         res['__class__'] = 'DateTime'
@@ -539,15 +642,19 @@ class DateTime(Date):
 
 class Len(PYSON):
 
-    def __init__(self, value):
+    def __init__(self, v):
         super(Len, self).__init__()
-        if isinstance(value, PYSON):
-            assert value.types().issubset(set([dict, list, str])), \
+        if isinstance(v, PYSON):
+            assert v.types().issubset(set([dict, list, basestring])), \
                 'value must be a dict or a list or a string'
         else:
-            assert type(value) in [dict, list, str], \
+            assert isinstance(v, (dict, list, basestring)), \
                 'value must be a dict or list or a string'
-        self._value = value
+        self._value = v
+
+    @property
+    def __repr_params__(self):
+        return (self._value,)
 
     def pyson(self):
         return {
@@ -556,7 +663,7 @@ class Len(PYSON):
             }
 
     def types(self):
-        return set([int, long])
+        return set([int])
 
     @staticmethod
     def eval(dct, context):
@@ -571,6 +678,10 @@ class Id(PYSON):
         self._module = module
         self._fs_id = fs_id
 
+    @property
+    def __repr_params__(self):
+        return (self._module, self._fs_id)
+
     def pyson(self):
         from trytond.pool import Pool
         ModelData = Pool().get('ir.model.data')
diff --git a/trytond/report/__init__.py b/trytond/report/__init__.py
index e7e310a..15e5214 100644
--- a/trytond/report/__init__.py
+++ b/trytond/report/__init__.py
@@ -1,3 +1,3 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .report import *
diff --git a/trytond/report/report.py b/trytond/report/report.py
index b0a3ef1..9dc99e9 100644
--- a/trytond/report/report.py
+++ b/trytond/report/report.py
@@ -1,14 +1,12 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 try:
     import cStringIO as StringIO
 except ImportError:
     import StringIO
 import zipfile
-import time
 import os
 import datetime
-import inspect
 import tempfile
 import warnings
 import subprocess
@@ -130,20 +128,27 @@ class Report(URLMixin, PoolBase):
         '''
         pool = Pool()
         ActionReport = pool.get('ir.action.report')
-        action_reports = ActionReport.search([
-                ('report_name', '=', cls.__name__)
-                ])
-        if not action_reports:
-            raise Exception('Error', 'Report (%s) not find!' % cls.__name__)
         cls.check_access()
-        action_report = action_reports[0]
+
+        action_id = data.get('action_id')
+        if action_id is None:
+            action_reports = ActionReport.search([
+                    ('report_name', '=', cls.__name__)
+                    ])
+            assert action_reports, '%s not found' % cls
+            action_report = action_reports[0]
+        else:
+            action_report = ActionReport(action_id)
+
         records = None
         model = action_report.model or data.get('model')
         if model:
             records = cls._get_records(ids, model, data)
-        type, data = cls.parse(action_report, records, data, {})
-        return (type, buffer(data), action_report.direct_print,
-            action_report.name)
+        report_context = cls.get_context(records, data)
+        oext, content = cls.convert(action_report,
+            cls.render(action_report, report_context))
+        content = bytearray(content) if bytes == str else bytes(content)
+        return (oext, content, action_report.direct_print, action_report.name)
 
     @classmethod
     def _get_records(cls, ids, model, data):
@@ -157,7 +162,7 @@ class Report(URLMixin, PoolBase):
                 self.id = id
                 self._language = Transaction().language
 
-            def setLang(self, language):
+            def set_lang(self, language):
                 self._language = language
 
             def __getattr__(self, name):
@@ -173,33 +178,29 @@ class Report(URLMixin, PoolBase):
         return [TranslateModel(id) for id in ids]
 
     @classmethod
-    def parse(cls, report, records, data, localcontext):
-        '''
-        Parse the report and return a tuple with report type and report.
-        '''
+    def get_context(cls, records, data):
         pool = Pool()
         User = pool.get('res.user')
-        Translation = pool.get('ir.translation')
 
-        localcontext['data'] = data
-        localcontext['user'] = User(Transaction().user)
-        localcontext['formatLang'] = lambda *args, **kargs: \
-            cls.format_lang(*args, **kargs)
-        localcontext['StringIO'] = StringIO.StringIO
-        localcontext['time'] = time
-        localcontext['datetime'] = datetime
-        localcontext['context'] = Transaction().context
+        report_context = {}
+        report_context['data'] = data
+        report_context['context'] = Transaction().context
+        report_context['user'] = User(Transaction().user)
+        report_context['records'] = records
+        report_context['format_date'] = cls.format_date
+        report_context['format_currency'] = cls.format_currency
+        report_context['format_number'] = cls.format_number
+        report_context['datetime'] = datetime
 
-        translate = TranslateFactory(cls.__name__, Transaction().language,
-            Translation)
-        localcontext['setLang'] = lambda language: translate.set_language(
-            language)
+        return report_context
 
-        # Convert to str as buffer from DB is not supported by StringIO
-        report_content = (str(report.report_content) if report.report_content
-            else False)
-        style_content = (str(report.style_content) if report.style_content
-            else False)
+    @classmethod
+    def _prepare_template_file(cls, report):
+        # Convert to str as value from DB is not supported by StringIO
+        report_content = (bytes(report.report_content) if report.report_content
+            else None)
+        style_content = (bytes(report.style_content) if report.style_content
+            else None)
 
         if not report_content:
             raise Exception('Error', 'Missing report file!')
@@ -268,44 +269,52 @@ class Report(URLMixin, PoolBase):
                 outzip.writestr(file, picture)
 
         if manifest:
-            outzip.writestr(MANIFEST, str(manifest))
+            outzip.writestr(MANIFEST, bytes(manifest))
 
         content_z.close()
         content_io.close()
         outzip.close()
 
-        # Since Genshi >= 0.6, Translator requires a function type
+        return fd, path
+
+    @classmethod
+    def _add_translation_hook(cls, relatorio_report, context):
+        pool = Pool()
+        Translation = pool.get('ir.translation')
+
+        translate = TranslateFactory(cls.__name__, Transaction().language,
+            Translation)
+        context['set_lang'] = lambda language: translate.set_language(language)
         translator = Translator(lambda text: translate(text))
+        relatorio_report.filters.insert(0, translator)
+
+    @classmethod
+    def render(cls, report, report_context):
+        "calls the underlying templating engine to renders the report"
+        fd, path = cls._prepare_template_file(report)
 
         mimetype = MIMETYPES[report.template_extension]
         rel_report = relatorio.reporting.Report(path, mimetype,
                 ReportFactory(), relatorio.reporting.MIMETemplateLoader())
-        rel_report.filters.insert(0, translator)
-        #convert unicode key into str
-        localcontext = dict(map(lambda x: (str(x[0]), x[1]),
-            localcontext.iteritems()))
-        #Test compatibility with old relatorio version <= 0.3.0
-        if len(inspect.getargspec(rel_report.__call__)[0]) == 2:
-            data = rel_report(records, **localcontext).render().getvalue()
-        else:
-            localcontext['objects'] = records  # XXX to remove
-            localcontext['records'] = records
-            data = rel_report(**localcontext).render()
-            if hasattr(data, 'getvalue'):
-                data = data.getvalue()
+        cls._add_translation_hook(rel_report, report_context)
+
+        data = rel_report(**report_context).render()
+        if hasattr(data, 'getvalue'):
+            data = data.getvalue()
         os.close(fd)
         os.remove(path)
-        output_format = report.extension or report.template_extension
-        if output_format not in MIMETYPES:
-            data = cls.unoconv(data, report.template_extension, output_format)
-        oext = FORMAT2EXT.get(output_format, output_format)
-        return (oext, data)
+
+        return data
 
     @classmethod
-    def unoconv(cls, data, input_format, output_format):
-        '''
-        Call unoconv to convert the OpenDocument
-        '''
+    def convert(cls, report, data):
+        "converts the report data to another mimetype if necessary"
+        input_format = report.template_extension
+        output_format = report.extension or report.template_extension
+
+        if output_format in MIMETYPES:
+            return output_format, data
+
         fd, path = tempfile.mkstemp(suffix=(os.extsep + input_format),
             prefix='trytond_')
         oext = FORMAT2EXT.get(output_format, output_format)
@@ -318,31 +327,37 @@ class Report(URLMixin, PoolBase):
             stdoutdata, stderrdata = proc.communicate()
             if proc.wait() != 0:
                 raise Exception(stderrdata)
-            return stdoutdata
+            return oext, stdoutdata
         finally:
             os.remove(path)
 
     @classmethod
-    def format_lang(cls, value, lang, digits=2, grouping=True, monetary=False,
-            date=False, currency=None, symbol=True):
+    def format_date(cls, value, lang):
         pool = Pool()
         Lang = pool.get('ir.lang')
         Config = pool.get('ir.configuration')
 
-        if date or isinstance(value, datetime.date):
-            if date:
-                warnings.warn('date parameter of format_lang is deprecated, '
-                    'use a datetime.date as value instead', DeprecationWarning,
-                    stacklevel=2)
-            if lang:
-                locale_format = lang.date
-                code = lang.code
-            else:
-                locale_format = Lang.default_date()
-                code = Config.get_language()
-            return Lang.strftime(value, code, locale_format)
-        if currency:
-            return Lang.currency(lang, value, currency, grouping=grouping,
-                symbol=symbol)
+        if lang:
+            locale_format = lang.date
+            code = lang.code
+        else:
+            locale_format = Lang.default_date()
+            code = Config.get_language()
+        return Lang.strftime(value, code, locale_format)
+
+    @classmethod
+    def format_currency(cls, value, lang, currency, symbol=True,
+            grouping=True):
+        pool = Pool()
+        Lang = pool.get('ir.lang')
+
+        return Lang.currency(lang, value, currency, symbol, grouping)
+
+    @classmethod
+    def format_number(cls, value, lang, digits=2, grouping=True,
+            monetary=None):
+        pool = Pool()
+        Lang = pool.get('ir.lang')
+
         return Lang.format(lang, '%.' + str(digits) + 'f', value,
             grouping=grouping, monetary=monetary)
diff --git a/trytond/res/__init__.py b/trytond/res/__init__.py
index 481f45a..6b157c1 100644
--- a/trytond/res/__init__.py
+++ b/trytond/res/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 ..pool import Pool
 from .group import *
 from .user import *
diff --git a/trytond/res/group.py b/trytond/res/group.py
index 86649dd..03772c8 100644
--- a/trytond/res/group.py
+++ b/trytond/res/group.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 "Group"
 from itertools import chain
 from ..model import ModelView, ModelSQL, fields
diff --git a/trytond/res/ir.py b/trytond/res/ir.py
index a867611..e6622fa 100644
--- a/trytond/res/ir.py
+++ b/trytond/res/ir.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 ..model import ModelSQL, fields
 from .. import backend
 from ..transaction import Transaction
diff --git a/trytond/res/ir.xml b/trytond/res/ir.xml
index 041e090..a51e5ba 100644
--- a/trytond/res/ir.xml
+++ b/trytond/res/ir.xml
@@ -265,6 +265,14 @@ this repository contains the full copyright notices and license terms. -->
             <field name="perm_create" eval="False"/>
             <field name="perm_delete" eval="False"/>
         </record>
+        <record model="ir.model.access" id="access_ir_model_data_admin">
+            <field name="model" search="[('model', '=', 'ir.model.data')]"/>
+            <field name="group" ref="group_admin"/>
+            <field name="perm_read" eval="True"/>
+            <field name="perm_write" eval="True"/>
+            <field name="perm_create" eval="False"/>
+            <field name="perm_delete" eval="False"/>
+        </record>
         <record model="ir.model.access" id="access_ir_cron">
             <field name="model" search="[('model', '=', 'ir.cron')]"/>
             <field name="perm_read" eval="True"/>
@@ -420,11 +428,13 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_menu1">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_menu"/>
         </record>
         <record model="ir.rule" id="rule_menu2">
-            <field name="domain">[('groups', '=', None)]</field>
+            <field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
             <field name="rule_group" ref="rule_group_menu"/>
         </record>
         <record model="ir.rule.group" id="rule_group_action">
@@ -432,11 +442,13 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_action1">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_action"/>
         </record>
         <record model="ir.rule" id="rule_action2">
-            <field name="domain">[('groups', '=', None)]</field>
+            <field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
             <field name="rule_group" ref="rule_group_action"/>
         </record>
         <record model="ir.rule.group" id="rule_group_action_keyword">
@@ -444,11 +456,13 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_action_keyword1">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_action_keyword"/>
         </record>
         <record model="ir.rule" id="rule_action_keyword2">
-            <field name="domain">[('groups', '=', None)]</field>
+            <field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
             <field name="rule_group" ref="rule_group_action_keyword"/>
         </record>
 
@@ -457,11 +471,13 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_action_report1">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_action_report"/>
         </record>
         <record model="ir.rule" id="rule_action_report2">
-            <field name="domain">[('groups', '=', None)]</field>
+            <field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
             <field name="rule_group" ref="rule_group_action_report"/>
         </record>
 
@@ -470,11 +486,13 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_action_act_window1">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_action_act_window"/>
         </record>
         <record model="ir.rule" id="rule_action_act_window2">
-            <field name="domain">[('groups', '=', None)]</field>
+            <field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
             <field name="rule_group" ref="rule_group_action_act_window"/>
         </record>
 
@@ -483,11 +501,13 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_action_wizard1">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_action_wizard"/>
         </record>
         <record model="ir.rule" id="rule_action_wizard2">
-            <field name="domain">[('groups', '=', None)]</field>
+            <field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
             <field name="rule_group" ref="rule_group_action_wizard"/>
         </record>
 
@@ -496,11 +516,13 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_action_url1">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_action_url"/>
         </record>
         <record model="ir.rule" id="rule_action_url2">
-            <field name="domain">[('groups', '=', None)]</field>
+            <field name="domain" eval="[('groups', '=', None)]" pyson="1"/>
             <field name="rule_group" ref="rule_group_action_url"/>
         </record>
 
@@ -760,7 +782,9 @@ this repository contains the full copyright notices and license terms. -->
             <field name="perm_read" eval="False"/>
         </record>
         <record model="ir.rule" id="rule_sequence">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_sequence"/>
         </record>
 
@@ -770,7 +794,9 @@ this repository contains the full copyright notices and license terms. -->
             <field name="perm_read" eval="False"/>
         </record>
         <record model="ir.rule" id="rule_sequence_strict">
-            <field name="domain">[('groups', 'in', [g.id for g in user.groups])]</field>
+            <field name="domain"
+                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_sequence_strict"/>
         </record>
 
@@ -824,7 +850,9 @@ this repository contains the full copyright notices and license terms. -->
             <field name="perm_delete" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_group_view_search1">
-            <field name="domain">[('user', '=', user.id)]</field>
+            <field name="domain"
+                eval="[('user', '=', Eval('user', {}).get('id', -1))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_view_search"/>
         </record>
 
@@ -850,7 +878,9 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_session1">
-            <field name="domain">[('create_uid', '=', user.id)]</field>
+            <field name="domain"
+                eval="[('create_uid', '=', Eval('user', {}).get('id', -1))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_session"/>
         </record>
 
@@ -860,7 +890,9 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_session_wizard1">
-            <field name="domain">[('create_uid', '=', user.id)]</field>
+            <field name="domain"
+                eval="[('create_uid', '=', Eval('user', {}).get('id', -1))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_session_wizard"/>
         </record>
 
@@ -870,7 +902,9 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_ui_menu_favorite1">
-            <field name="domain">[('user', '=', user.id)]</field>
+            <field name="domain"
+                eval="[('create_uid', '=', Eval('user', {}).get('id', -1))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_ui_menu_favorite"/>
         </record>
 
diff --git a/trytond/res/locale/bg_BG.po b/trytond/res/locale/bg_BG.po
index 83777ba..a2223d7 100644
--- a/trytond/res/locale/bg_BG.po
+++ b/trytond/res/locale/bg_BG.po
@@ -737,7 +737,7 @@ msgid "Cancel"
 msgstr "Отказ"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "Добре"
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/locale/ca_ES.po b/trytond/res/locale/ca_ES.po
index 25cdfb9..e4f0144 100644
--- a/trytond/res/locale/ca_ES.po
+++ b/trytond/res/locale/ca_ES.po
@@ -8,7 +8,7 @@ msgstr "El nom del grup ha de ser únic."
 
 msgctxt "error:res.user:"
 msgid "Wrong password!"
-msgstr "Contrasenya incorrecta."
+msgstr "La contrasenya és incorrecta."
 
 msgctxt "error:res.user:"
 msgid "You can not have two users with the same login!"
@@ -705,7 +705,7 @@ msgid "Cancel"
 msgstr "Cancel·la"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "D'acord"
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/locale/cs_CZ.po b/trytond/res/locale/cs_CZ.po
index 57228c3..1c6df59 100644
--- a/trytond/res/locale/cs_CZ.po
+++ b/trytond/res/locale/cs_CZ.po
@@ -726,7 +726,7 @@ msgid "Cancel"
 msgstr ""
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr ""
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/locale/de_DE.po b/trytond/res/locale/de_DE.po
index 40fed63..607bebf 100644
--- a/trytond/res/locale/de_DE.po
+++ b/trytond/res/locale/de_DE.po
@@ -730,7 +730,7 @@ msgid "Cancel"
 msgstr "Abbrechen"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "OK"
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/locale/es_AR.po b/trytond/res/locale/es_AR.po
index 71897ae..03d5011 100644
--- a/trytond/res/locale/es_AR.po
+++ b/trytond/res/locale/es_AR.po
@@ -12,7 +12,7 @@ msgstr "¡Contraseña incorrecta!"
 
 msgctxt "error:res.user:"
 msgid "You can not have two users with the same login!"
-msgstr "No puede tener dos usuarios con el mismo nombre de usuario"
+msgstr "¡No puede tener dos usuarios con el mismo nombre de usuario!"
 
 msgctxt "error:res.user:"
 msgid ""
@@ -538,7 +538,7 @@ msgstr "Usuario modificación"
 
 msgctxt "help:ir.sequence.type,groups:"
 msgid "Groups allowed to edit the sequences of this type"
-msgstr "Los grupos son autorizados para editar secuencias en este tipo"
+msgstr "Los grupos autorizados para editar las secuencias de este tipo"
 
 msgctxt "help:res.user,actions:"
 msgid "Actions that will be run at login"
@@ -646,7 +646,7 @@ msgstr "Cancelar instalación"
 
 msgctxt "view:ir.module.module:"
 msgid "Cancel Uninstallation"
-msgstr "Cancelar la desinstalación"
+msgstr "Cancelar desinstalación"
 
 msgctxt "view:ir.module.module:"
 msgid "Cancel Upgrade"
@@ -729,7 +729,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/locale/es_CO.po b/trytond/res/locale/es_CO.po
index 4f741c0..9222050 100644
--- a/trytond/res/locale/es_CO.po
+++ b/trytond/res/locale/es_CO.po
@@ -729,7 +729,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/locale/es_EC.po b/trytond/res/locale/es_EC.po
index bdae57a..2dc4995 100644
--- a/trytond/res/locale/es_EC.po
+++ b/trytond/res/locale/es_EC.po
@@ -30,11 +30,11 @@ msgstr "Acción"
 
 msgctxt "field:ir.action-res.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.action-res.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.action-res.group,group:"
 msgid "Group"
@@ -50,11 +50,11 @@ msgstr "Nombre"
 
 msgctxt "field:ir.action-res.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.action-res.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model.button-res.group,active:"
 msgid "Active"
@@ -66,11 +66,11 @@ msgstr "Botón"
 
 msgctxt "field:ir.model.button-res.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.model.button-res.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.model.button-res.group,group:"
 msgid "Group"
@@ -86,23 +86,23 @@ msgstr "Nombre"
 
 msgctxt "field:ir.model.button-res.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.model.button-res.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.model.field-res.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.model.field-res.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.model.field-res.group,field:"
 msgid "Model Field"
-msgstr "Campo del Modelo"
+msgstr "Campo del modelo"
 
 msgctxt "field:ir.model.field-res.group,group:"
 msgid "Group"
@@ -118,19 +118,19 @@ msgstr "Nombre"
 
 msgctxt "field:ir.model.field-res.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.model.field-res.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.rule.group-res.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.rule.group-res.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.rule.group-res.group,group:"
 msgid "Group"
@@ -146,23 +146,23 @@ msgstr "Nombre"
 
 msgctxt "field:ir.rule.group-res.group,rule_group:"
 msgid "Rule Group"
-msgstr "Grupo de Reglas"
+msgstr "Grupo de reglas"
 
 msgctxt "field:ir.rule.group-res.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.rule.group-res.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.rule.group-res.user,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.rule.group-res.user,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.rule.group-res.user,id:"
 msgid "ID"
@@ -174,7 +174,7 @@ msgstr "Nombre"
 
 msgctxt "field:ir.rule.group-res.user,rule_group:"
 msgid "Rule Group"
-msgstr "Grupo de Reglas"
+msgstr "Grupo de reglas"
 
 msgctxt "field:ir.rule.group-res.user,user:"
 msgid "User"
@@ -182,35 +182,35 @@ msgstr "Usuario"
 
 msgctxt "field:ir.rule.group-res.user,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.rule.group-res.user,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.sequence,groups:"
 msgid "User Groups"
-msgstr "Grupos de Usuarios"
+msgstr "Grupos de usuarios"
 
 msgctxt "field:ir.sequence.strict,groups:"
 msgid "User Groups"
-msgstr "Grupos de Usuarios"
+msgstr "Grupos de usuarios"
 
 msgctxt "field:ir.sequence.type,groups:"
 msgid "User Groups"
-msgstr "Grupos de Usuarios"
+msgstr "Grupos de usuarios"
 
 msgctxt "field:ir.sequence.type-res.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.sequence.type-res.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.sequence.type-res.group,group:"
 msgid "User Groups"
-msgstr "Grupos de Usuarios"
+msgstr "Grupos de usuarios"
 
 msgctxt "field:ir.sequence.type-res.group,id:"
 msgid "ID"
@@ -222,23 +222,23 @@ msgstr "Nombre"
 
 msgctxt "field:ir.sequence.type-res.group,sequence_type:"
 msgid "Sequence Type"
-msgstr "Tipo de Secuencia"
+msgstr "Tipo de secuencia"
 
 msgctxt "field:ir.sequence.type-res.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.sequence.type-res.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:ir.ui.menu-res.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:ir.ui.menu-res.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:ir.ui.menu-res.group,group:"
 msgid "Group"
@@ -258,23 +258,23 @@ msgstr "Nombre"
 
 msgctxt "field:ir.ui.menu-res.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:ir.ui.menu-res.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:res.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:res.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:res.group,field_access:"
 msgid "Access Field"
-msgstr "Acceso a Campo"
+msgstr "Acceso a campos"
 
 msgctxt "field:res.group,id:"
 msgid "ID"
@@ -282,11 +282,11 @@ msgstr "ID"
 
 msgctxt "field:res.group,menu_access:"
 msgid "Access Menu"
-msgstr "Acceso a Menú"
+msgstr "Acceso a menús"
 
 msgctxt "field:res.group,model_access:"
 msgid "Access Model"
-msgstr "Acceso a Modelo"
+msgstr "Acceso a modelos"
 
 msgctxt "field:res.group,name:"
 msgid "Name"
@@ -306,11 +306,11 @@ msgstr "Usuarios"
 
 msgctxt "field:res.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:res.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:res.user,actions:"
 msgid "Actions"
@@ -322,11 +322,11 @@ msgstr "Activo"
 
 msgctxt "field:res.user,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:res.user,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:res.user,email:"
 msgid "Email"
@@ -346,7 +346,7 @@ msgstr "Idioma"
 
 msgctxt "field:res.user,language_direction:"
 msgid "Language Direction"
-msgstr "Dirección del Idioma"
+msgstr "Dirección del idioma"
 
 msgctxt "field:res.user,login:"
 msgid "Login"
@@ -354,7 +354,7 @@ msgstr "Nombre de usuario"
 
 msgctxt "field:res.user,menu:"
 msgid "Menu Action"
-msgstr "Menú de Acciones"
+msgstr "Acción Menú"
 
 msgctxt "field:res.user,name:"
 msgid "Name"
@@ -366,7 +366,7 @@ msgstr "Contraseña"
 
 msgctxt "field:res.user,password_hash:"
 msgid "Password Hash"
-msgstr "Hash de Contraseña"
+msgstr "Hash de la contraseña"
 
 msgctxt "field:res.user,pyson_menu:"
 msgid "PySON Menu"
@@ -390,7 +390,7 @@ msgstr "Firma"
 
 msgctxt "field:res.user,status_bar:"
 msgid "Status Bar"
-msgstr "Barra de Estado"
+msgstr "Barra de estado"
 
 msgctxt "field:res.user,warnings:"
 msgid "Warnings"
@@ -398,11 +398,11 @@ msgstr "Advertencias"
 
 msgctxt "field:res.user,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:res.user,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:res.user-ir.action,action:"
 msgid "Action"
@@ -410,11 +410,11 @@ msgstr "Acción"
 
 msgctxt "field:res.user-ir.action,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:res.user-ir.action,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:res.user-ir.action,id:"
 msgid "ID"
@@ -430,19 +430,19 @@ msgstr "Usuario"
 
 msgctxt "field:res.user-ir.action,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:res.user-ir.action,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:res.user-res.group,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:res.user-res.group,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:res.user-res.group,group:"
 msgid "Group"
@@ -462,11 +462,11 @@ msgstr "Usuario"
 
 msgctxt "field:res.user-res.group,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:res.user-res.group,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:res.user.config.start,id:"
 msgid "ID"
@@ -474,11 +474,11 @@ msgstr "ID"
 
 msgctxt "field:res.user.login.attempt,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:res.user.login.attempt,create_uid:"
 msgid "Create User"
-msgstr "Usuario creación"
+msgstr "Creado por usuario"
 
 msgctxt "field:res.user.login.attempt,id:"
 msgid "ID"
@@ -494,11 +494,11 @@ msgstr "Nombre"
 
 msgctxt "field:res.user.login.attempt,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:res.user.login.attempt,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:res.user.warning,always:"
 msgid "Always"
@@ -506,11 +506,11 @@ msgstr "Siempre"
 
 msgctxt "field:res.user.warning,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:res.user.warning,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:res.user.warning,id:"
 msgid "ID"
@@ -530,15 +530,15 @@ msgstr "Usuario"
 
 msgctxt "field:res.user.warning,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:res.user.warning,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "help:ir.sequence.type,groups:"
 msgid "Groups allowed to edit the sequences of this type"
-msgstr "Grupos autorizados para editar las secuencias de este tipo"
+msgstr "Los grupos autorizados para editar las secuencias de este tipo"
 
 msgctxt "help:res.user,actions:"
 msgid "Actions that will be run at login"
@@ -550,7 +550,7 @@ msgstr "Grupos"
 
 msgctxt "model:ir.action,name:act_user_config"
 msgid "Configure Users"
-msgstr "Configurar Usuarios"
+msgstr "Configurar usuarios"
 
 msgctxt "model:ir.action,name:act_user_form"
 msgid "Users"
@@ -562,7 +562,7 @@ msgstr "Acción - Grupo"
 
 msgctxt "model:ir.cron,name:cron_trigger_time"
 msgid "Run On Time Triggers"
-msgstr "Ejecutar Disparadores a Tiempo"
+msgstr "Ejecutar Disparadores \"A tiempo\""
 
 msgctxt "model:ir.model.button-res.group,name:"
 msgid "Model Button - Group"
@@ -574,15 +574,15 @@ msgstr "Relación entre grupo y campo del modelo"
 
 msgctxt "model:ir.rule.group-res.group,name:"
 msgid "Rule Group - Group"
-msgstr "Grupo de Reglas - Grupo"
+msgstr "Grupo de reglas - Grupo"
 
 msgctxt "model:ir.rule.group-res.user,name:"
 msgid "Rule Group - User"
-msgstr "Grupo de Reglas - Usuario"
+msgstr "Grupo de reglas - Usuario"
 
 msgctxt "model:ir.sequence.type-res.group,name:"
 msgid "Sequence Type - Group"
-msgstr "Tipo de Secuencia - Grupo"
+msgstr "Tipo de secuencia - Grupo"
 
 msgctxt "model:ir.ui.menu,name:menu_group_form"
 msgid "Groups"
@@ -598,7 +598,7 @@ msgstr "Usuarios"
 
 msgctxt "model:ir.ui.menu-res.group,name:"
 msgid "UI Menu - Group"
-msgstr "Menú UI - Grupo"
+msgstr "Menú de la UI - Grupo"
 
 msgctxt "model:res.group,name:"
 msgid "Group"
@@ -618,7 +618,7 @@ msgstr "Administrador"
 
 msgctxt "model:res.user,name:user_trigger"
 msgid "Cron Trigger"
-msgstr "Disparador del Programador de tareas"
+msgstr "Disparador del programador de tareas"
 
 msgctxt "model:res.user-ir.action,name:"
 msgid "User - Action"
@@ -630,7 +630,7 @@ msgstr "Usuario - Grupo"
 
 msgctxt "model:res.user.config.start,name:"
 msgid "User Config Init"
-msgstr "Configuración Inicial de Usuario"
+msgstr "Configuración inicial de usuario"
 
 msgctxt "model:res.user.login.attempt,name:"
 msgid "Login Attempt"
@@ -638,35 +638,35 @@ msgstr "Intento de Inicio de Sesión"
 
 msgctxt "model:res.user.warning,name:"
 msgid "User Warning"
-msgstr "Advertencia al Usuario"
+msgstr "Aviso al usuario"
 
 msgctxt "view:ir.module.module:"
 msgid "Cancel Installation"
-msgstr "Cancelar Instalación"
+msgstr "Cancelar instalación"
 
 msgctxt "view:ir.module.module:"
 msgid "Cancel Uninstallation"
-msgstr "Cancelar la Desinstalación"
+msgstr "Cancelar desinstalación"
 
 msgctxt "view:ir.module.module:"
 msgid "Cancel Upgrade"
-msgstr "Cancelar Actualización"
+msgstr "Cancelar actualización"
 
 msgctxt "view:ir.module.module:"
 msgid "Mark for Installation"
-msgstr "Marcar para Instalar"
+msgstr "Marcar para instalar"
 
 msgctxt "view:ir.module.module:"
 msgid "Mark for Uninstallation (beta)"
-msgstr "Marcar para Desinstalar (beta)"
+msgstr "Marcar para desinstalar (beta)"
 
 msgctxt "view:ir.module.module:"
 msgid "Mark for Upgrade"
-msgstr "Marcar para Actualizar"
+msgstr "Marcar para actualizar"
 
 msgctxt "view:res.group:"
 msgid "Access Permissions"
-msgstr "Permisos de Acceso"
+msgstr "Permisos de acceso"
 
 msgctxt "view:res.group:"
 msgid "Group"
@@ -682,27 +682,27 @@ msgstr "Miembros"
 
 msgctxt "view:res.user.config.start:"
 msgid "Add Users"
-msgstr "Agregar Usuarios"
+msgstr "Añadir usuarios"
 
 msgctxt "view:res.user.config.start:"
 msgid "Be careful that the login must be unique!"
-msgstr "¡Asegúrese de que el nombre de usuario sea único!"
+msgstr "El identificador debe ser único."
 
 msgctxt "view:res.user.config.start:"
 msgid "You can now add some users into the system."
-msgstr "Ahora puede agregar algunos usuarios al sistema."
+msgstr "Si quiere, ahora puede añadir más usuarios en el sistema."
 
 msgctxt "view:res.user.warning:"
 msgid "Warning"
-msgstr "Advertencia"
+msgstr "Aviso"
 
 msgctxt "view:res.user.warning:"
 msgid "Warnings"
-msgstr "Advertencias"
+msgstr "Avisos"
 
 msgctxt "view:res.user:"
 msgid "Access Permissions"
-msgstr "Permisos de Acceso"
+msgstr "Permisos de acceso"
 
 msgctxt "view:res.user:"
 msgid "Actions"
@@ -729,12 +729,12 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:res.user.config,user,add:"
 msgid "Add"
-msgstr "Añadir"
+msgstr "Agregar"
 
 msgctxt "wizard_button:res.user.config,user,end:"
 msgid "End"
diff --git a/trytond/res/locale/es_ES.po b/trytond/res/locale/es_ES.po
index 18997e6..4f7bf0a 100644
--- a/trytond/res/locale/es_ES.po
+++ b/trytond/res/locale/es_ES.po
@@ -8,7 +8,7 @@ msgstr "El nombre del grupo debe ser único."
 
 msgctxt "error:res.user:"
 msgid "Wrong password!"
-msgstr "Contraseña incorrecta."
+msgstr "La contraseña es incorrecta."
 
 msgctxt "error:res.user:"
 msgid "You can not have two users with the same login!"
@@ -729,7 +729,7 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "Aceptar"
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/locale/fr_FR.po b/trytond/res/locale/fr_FR.po
index 7ed7195..a6f059d 100644
--- a/trytond/res/locale/fr_FR.po
+++ b/trytond/res/locale/fr_FR.po
@@ -729,8 +729,8 @@ msgid "Cancel"
 msgstr "Annuler"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
-msgstr "Ok"
+msgid "OK"
+msgstr "OK"
 
 msgctxt "wizard_button:res.user.config,user,add:"
 msgid "Add"
diff --git a/trytond/res/locale/nl_NL.po b/trytond/res/locale/nl_NL.po
index 3bf2b91..bff5213 100644
--- a/trytond/res/locale/nl_NL.po
+++ b/trytond/res/locale/nl_NL.po
@@ -785,7 +785,7 @@ msgstr "Annuleren"
 
 #, fuzzy
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "Oké"
 
 #, fuzzy
diff --git a/trytond/res/locale/ru_RU.po b/trytond/res/locale/ru_RU.po
index 22ebae5..f6cae7f 100644
--- a/trytond/res/locale/ru_RU.po
+++ b/trytond/res/locale/ru_RU.po
@@ -730,7 +730,7 @@ msgid "Cancel"
 msgstr "Отменить"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "Ок"
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/locale/sl_SI.po b/trytond/res/locale/sl_SI.po
index 8b96eb7..c8f9186 100644
--- a/trytond/res/locale/sl_SI.po
+++ b/trytond/res/locale/sl_SI.po
@@ -728,7 +728,7 @@ msgid "Cancel"
 msgstr "Prekliči"
 
 msgctxt "wizard_button:res.user.config,start,user:"
-msgid "Ok"
+msgid "OK"
 msgstr "V redu"
 
 msgctxt "wizard_button:res.user.config,user,add:"
diff --git a/trytond/res/user.py b/trytond/res/user.py
index 19e1ba8..662d77b 100644
--- a/trytond/res/user.py
+++ b/trytond/res/user.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 "User"
 import copy
 import string
@@ -202,17 +202,18 @@ class User(ModelSQL, ModelView):
         timeout = datetime.timedelta(
             seconds=config.getint('session', 'timeout'))
         result = dict((u.id, 0) for u in users)
-        for sub_ids in grouped_slice(users):
-            sessions = Session.search([
-                    ('create_uid', 'in', sub_ids),
-                    ], order=[('create_uid', 'ASC')])
-
-            def filter_(session):
-                timestamp = session.write_date or session.create_date
-                return abs(timestamp - now) < timeout
-            result.update(dict((i, len(list(g)))
-                    for i, g in groupby(ifilter(filter_, sessions),
-                        attrgetter('create_uid.id'))))
+        with Transaction().set_user(0):
+            for sub_ids in grouped_slice(users):
+                sessions = Session.search([
+                        ('create_uid', 'in', sub_ids),
+                        ], order=[('create_uid', 'ASC')])
+
+                def filter_(session):
+                    timestamp = session.write_date or session.create_date
+                    return abs(timestamp - now) < timeout
+                result.update(dict((i, len(list(g)))
+                        for i, g in groupby(ifilter(filter_, sessions),
+                            attrgetter('create_uid.id'))))
         return result
 
     @staticmethod
@@ -273,12 +274,14 @@ class User(ModelSQL, ModelView):
 
     @classmethod
     def search_rec_name(cls, name, clause):
-        users = cls.search([
-            ('login', '=', clause[2]),
-            ], order=[])
-        if len(users) == 1:
-            return [('id', '=', users[0].id)]
-        return [(cls._rec_name,) + tuple(clause[1:])]
+        if clause[1].startswith('!') or clause[1].startswith('not '):
+            bool_op = 'AND'
+        else:
+            bool_op = 'OR'
+        return [bool_op,
+            ('login',) + tuple(clause[1:]),
+            (cls._rec_name,) + tuple(clause[1:]),
+            ]
 
     @classmethod
     def copy(cls, users, default=None):
@@ -301,7 +304,6 @@ class User(ModelSQL, ModelView):
         ModelData = pool.get('ir.model.data')
         Action = pool.get('ir.action')
         ConfigItem = pool.get('ir.module.module.config_wizard.item')
-        Config = pool.get('ir.configuration')
 
         res = {}
         if context_only:
@@ -314,7 +316,7 @@ class User(ModelSQL, ModelView):
                     if user.language:
                         res['language'] = user.language.code
                     else:
-                        res['language'] = Config.get_language()
+                        res['language'] = None
                 else:
                     res[field] = None
                     if getattr(user, field):
@@ -666,7 +668,7 @@ class UserConfig(Wizard):
     start = StateView('res.user.config.start',
         'res.user_config_start_view_form', [
             Button('Cancel', 'end', 'tryton-cancel'),
-            Button('Ok', 'user', 'tryton-ok', default=True),
+            Button('OK', 'user', 'tryton-ok', default=True),
             ])
     user = StateView('res.user',
         'res.user_view_form', [
diff --git a/trytond/res/user.xml b/trytond/res/user.xml
index 5d39768..1c2993f 100644
--- a/trytond/res/user.xml
+++ b/trytond/res/user.xml
@@ -101,7 +101,8 @@ this repository contains the full copyright notices and license terms. -->
             <field name="global_p" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_user_warning1">
-            <field name="domain">[('user', '=', user.id)]</field>
+            <field name="domain"
+                eval="[('user', '=', Eval('user', {}).get('id', -1))]" pyson="1"/>
             <field name="rule_group" ref="rule_group_user_warning"/>
         </record>
 
diff --git a/trytond/rpc.py b/trytond/rpc.py
index 4fc1f7a..d346b11 100644
--- a/trytond/rpc.py
+++ b/trytond/rpc.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 
 __all__ = ['RPC']
 
@@ -32,9 +32,12 @@ class RPC(object):
         else:
             context = args.pop()
         timestamp = None
-        if '_timestamp' in context:
-            timestamp = context['_timestamp']
-            del context['_timestamp']
+        for key in context.keys():
+            if key == '_timestamp':
+                timestamp = context[key]
+            # Remove all private keyword but _datetime for history
+            if key.startswith('_') and key != '_datetime':
+                del context[key]
         if self.check_access:
             context['_check_access'] = True
         if self.instantiate is not None:
diff --git a/trytond/security.py b/trytond/security.py
index 4e92b00..70d594e 100644
--- a/trytond/security.py
+++ b/trytond/security.py
@@ -1,6 +1,5 @@
-#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 os
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 try:
     import crypt
 except ImportError:
@@ -15,7 +14,7 @@ from trytond.exceptions import NotLogged
 def _get_pool(dbname):
     database_list = Pool.database_list()
     pool = Pool(dbname)
-    if not dbname in database_list:
+    if dbname not in database_list:
         pool.init()
     return pool
 
diff --git a/trytond/server.py b/trytond/server.py
index 7cf414c..b792460 100644
--- a/trytond/server.py
+++ b/trytond/server.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 """
 %prog [options]
 """
@@ -31,16 +31,22 @@ class TrytonServer(object):
             logging.getLogger('server').info('using %s as logging '
                 'configuration file', options.logconf)
         else:
-            logformat = '[%(asctime)s] %(levelname)s:%(name)s:%(message)s'
-            datefmt = '%a %b %d %H:%M:%S %Y'
-            logging.basicConfig(level=logging.INFO, format=logformat,
-                datefmt=datefmt)
+            logformat = ('%(process)s %(thread)s [%(asctime)s] '
+                '%(levelname)s %(name)s %(message)s')
+            if options.verbose:
+                if options.dev:
+                    level = logging.DEBUG
+                else:
+                    level = logging.INFO
+            else:
+                level = logging.ERROR
+            logging.basicConfig(level=level, format=logformat)
 
         self.logger = logging.getLogger(__name__)
 
         if options.configfile:
-            self.logger.info('using %s as configuration file'
-                % options.configfile)
+            self.logger.info('using %s as configuration file',
+                options.configfile)
         else:
             self.logger.info('using default configuration')
         self.logger.info('initialising distributed objects services')
@@ -72,16 +78,21 @@ class TrytonServer(object):
 
         for db_name in self.options.database_names:
             init[db_name] = False
-            with Transaction().start(db_name, 0) as transaction:
-                cursor = transaction.cursor
-                if self.options.update:
-                    if not cursor.test():
-                        self.logger.info("init db")
-                        backend.get('Database').init(cursor)
-                        init[db_name] = True
-                    cursor.commit()
-                elif not cursor.test():
-                    raise Exception("'%s' is not a Tryton database!" % db_name)
+            try:
+                with Transaction().start(db_name, 0) as transaction:
+                    cursor = transaction.cursor
+                    if self.options.update:
+                        if not cursor.test():
+                            self.logger.info("init db")
+                            backend.get('Database').init(cursor)
+                            init[db_name] = True
+                        cursor.commit()
+                    elif not cursor.test():
+                        raise Exception("'%s' is not a Tryton database!" %
+                            db_name)
+            except Exception:
+                self.stop(False)
+                raise
 
         for db_name in self.options.database_names:
             if self.options.update:
@@ -149,9 +160,10 @@ class TrytonServer(object):
                     if not pool.lock.acquire(0):
                         continue
                     try:
-                        if 'ir.cron' not in pool.object_name_list():
+                        try:
+                            Cron = pool.get('ir.cron')
+                        except KeyError:
                             continue
-                        Cron = pool.get('ir.cron')
                     finally:
                         pool.lock.release()
                     thread = threading.Thread(
@@ -176,24 +188,24 @@ class TrytonServer(object):
             for hostname, port in parse_listen(
                     config.get('jsonrpc', 'listen')):
                 self.jsonrpcd.append(JSONRPCDaemon(hostname, port, ssl))
-                self.logger.info("starting JSON-RPC%s protocol on %s:%d" %
-                    (ssl and ' SSL' or '', hostname or '*', port))
+                self.logger.info("starting JSON-RPC%s protocol on %s:%d",
+                    ssl and ' SSL' or '', hostname or '*', port)
 
         if config.get('xmlrpc', 'listen'):
             from trytond.protocols.xmlrpc import XMLRPCDaemon
             for hostname, port in parse_listen(
                     config.get('xmlrpc', 'listen')):
                 self.xmlrpcd.append(XMLRPCDaemon(hostname, port, ssl))
-                self.logger.info("starting XML-RPC%s protocol on %s:%d" %
-                    (ssl and ' SSL' or '', hostname or '*', port))
+                self.logger.info("starting XML-RPC%s protocol on %s:%d",
+                    ssl and ' SSL' or '', hostname or '*', port)
 
         if config.get('webdav', 'listen'):
             from trytond.protocols.webdav import WebDAVServerThread
             for hostname, port in parse_listen(
                     config.get('webdav', 'listen')):
                 self.webdavd.append(WebDAVServerThread(hostname, port, ssl))
-                self.logger.info("starting WebDAV%s protocol on %s:%d" %
-                    (ssl and ' SSL' or '', hostname or '*', port))
+                self.logger.info("starting WebDAV%s protocol on %s:%d",
+                    ssl and ' SSL' or '', hostname or '*', port)
 
         for servers in (self.xmlrpcd, self.jsonrpcd, self.webdavd):
             for server in servers:
diff --git a/trytond/test_loader.py b/trytond/test_loader.py
index 22ad30f..81d91fc 100644
--- a/trytond/test_loader.py
+++ b/trytond/test_loader.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest import TestLoader
 
diff --git a/trytond/tests/__init__.py b/trytond/tests/__init__.py
index 6e456ca..9482db7 100644
--- a/trytond/tests/__init__.py
+++ b/trytond/tests/__init__.py
@@ -1,9 +1,10 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 ..pool import Pool
 from .test import *
 from .model import *
+from .modelview import *
 from .mptt import *
 from .import_data import *
 from .export_data import *
@@ -53,6 +54,9 @@ def register():
         TimeDefault,
         TimeRequired,
         TimeFormat,
+        TimeDelta,
+        TimeDeltaDefault,
+        TimeDeltaRequired,
         One2One,
         One2OneTarget,
         One2OneRelation,
@@ -88,6 +92,7 @@ def register():
         Property,
         Selection,
         SelectionRequired,
+        DictSchema,
         Dict,
         DictDefault,
         DictRequired,
@@ -96,6 +101,7 @@ def register():
         BinaryRequired,
         Singleton,
         URLObject,
+        ModelStorage,
         ModelSQLRequiredField,
         ModelSQLTimestamp,
         Model4Union1,
@@ -107,6 +113,8 @@ def register():
         Model4UnionTree1,
         Model4UnionTree2,
         UnionTree,
+        ModelViewChangedValues,
+        ModelViewChangedValuesTarget,
         MPTT,
         ImportDataBoolean,
         ImportDataInteger,
@@ -150,6 +158,7 @@ def register():
         CopyMany2ManyReferenceRelation,
         Many2OneTarget,
         Many2OneDomainValidation,
+        Many2OneOrderBy,
         TestHistory,
         TestHistoryLine,
         FieldContextChild,
diff --git a/trytond/tests/access.py b/trytond/tests/access.py
index 07e2c3b..36113da 100644
--- a/trytond/tests/access.py
+++ b/trytond/tests/access.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.model import ModelSQL, fields
 
 __all__ = [
diff --git a/trytond/tests/copy_.py b/trytond/tests/copy_.py
index d0d92cc..ffcaa0e 100644
--- a/trytond/tests/copy_.py
+++ b/trytond/tests/copy_.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 "Test for copy"
 from trytond.model import ModelSQL, fields
 
diff --git a/trytond/tests/export_data.py b/trytond/tests/export_data.py
index 5f61332..e084c2b 100644
--- a/trytond/tests/export_data.py
+++ b/trytond/tests/export_data.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 "Test for export_data"
 from trytond.model import ModelSQL, fields
 from trytond.pool import PoolMeta
diff --git a/trytond/tests/history.py b/trytond/tests/history.py
index ed1b24b..6421ad8 100644
--- a/trytond/tests/history.py
+++ b/trytond/tests/history.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.model import ModelSQL, fields
 
 __all__ = ['TestHistory', 'TestHistoryLine']
diff --git a/trytond/tests/import_data.py b/trytond/tests/import_data.py
index ecc7a94..bebd892 100644
--- a/trytond/tests/import_data.py
+++ b/trytond/tests/import_data.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 "Test for import_data"
 from trytond.model import ModelSQL, fields
 
diff --git a/trytond/tests/model.py b/trytond/tests/model.py
index 1815389..835eb99 100644
--- a/trytond/tests/model.py
+++ b/trytond/tests/model.py
@@ -1,9 +1,11 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.model import ModelSingleton, ModelSQL, UnionMixin, fields
 
 __all__ = [
-    'Singleton', 'URLObject', 'ModelSQLRequiredField', 'ModelSQLTimestamp',
+    'Singleton', 'URLObject',
+    'ModelStorage',
+    'ModelSQLRequiredField', 'ModelSQLTimestamp',
     'Model4Union1', 'Model4Union2', 'Model4Union3', 'Model4Union4',
     'Union', 'UnionUnion',
     'Model4UnionTree1', 'Model4UnionTree2', 'UnionTree',
@@ -26,6 +28,12 @@ class URLObject(ModelSQL):
     name = fields.Char('Name')
 
 
+class ModelStorage(ModelSQL):
+    'Model stored'
+    __name__ = 'test.modelstorage'
+    name = fields.Char('Name')
+
+
 class ModelSQLRequiredField(ModelSQL):
     'model with a required field'
     __name__ = 'test.modelsql'
diff --git a/trytond/tests/modelview.py b/trytond/tests/modelview.py
new file mode 100644
index 0000000..5688021
--- /dev/null
+++ b/trytond/tests/modelview.py
@@ -0,0 +1,32 @@
+# 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 trytond.model import ModelView, fields
+
+
+__all__ = [
+    'ModelViewChangedValues',
+    'ModelViewChangedValuesTarget',
+    ]
+
+
+class ModelViewChangedValues(ModelView):
+    'ModelView Changed Values'
+    __name__ = 'test.modelview.changed_values'
+    name = fields.Char('Name')
+    target = fields.Many2One('test.modelview.changed_values.target', 'Target')
+    ref_target = fields.Reference('Target Reference', [
+            ('test.modelview.changed_values.target', 'Target'),
+            ])
+    targets = fields.One2Many('test.modelview.changed_values.target', 'model',
+        'Targets')
+    m2m_targets = fields.Many2Many('test.modelview.changed_values.target',
+        None, None, 'Targets')
+
+
+class ModelViewChangedValuesTarget(ModelView):
+    'ModelView Changed Values Target'
+    __name__ = 'test.modelview.changed_values.target'
+    name = fields.Char('Name')
+    parent = fields.Many2One('test.modelview.changed_values', 'Parent')
diff --git a/trytond/tests/mptt.py b/trytond/tests/mptt.py
index ee9486d..62e8ee2 100644
--- a/trytond/tests/mptt.py
+++ b/trytond/tests/mptt.py
@@ -1,6 +1,8 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 "Test for Tree"
+from sql import Null
+
 from trytond.model import ModelView, ModelSQL, fields
 
 __all__ = [
@@ -27,7 +29,7 @@ class MPTT(ModelSQL, ModelView):
     @staticmethod
     def order_sequence(tables):
         table, _ = tables[None]
-        return [table.sequence == None, table.sequence]
+        return [table.sequence == Null, table.sequence]
 
     @staticmethod
     def default_active():
diff --git a/trytond/tests/run-tests.py b/trytond/tests/run-tests.py
index f6dd242..1bbc3a9 100755
--- a/trytond/tests/run-tests.py
+++ b/trytond/tests/run-tests.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 logging
 import argparse
 import os
@@ -20,6 +20,8 @@ parser.add_argument("-c", "--config", dest="config",
     help="specify config file")
 parser.add_argument("-m", "--modules", action="store_true", dest="modules",
     default=False, help="Run also modules tests")
+parser.add_argument("--no-doctest", action="store_false", dest="doctest",
+    default=True, help="Don't run doctest")
 parser.add_argument("-v", action="count", default=0, dest="verbosity",
     help="Increase verbosity")
 parser.add_argument('tests', metavar='test', nargs='*')
@@ -39,6 +41,6 @@ from trytond.tests.test_tryton import all_suite, modules_suite
 if not opt.modules:
     suite = all_suite(opt.tests)
 else:
-    suite = modules_suite(opt.tests)
+    suite = modules_suite(opt.tests, doc=opt.doctest)
 result = unittest.TextTestRunner(verbosity=opt.verbosity).run(suite)
 sys.exit(not result.wasSuccessful())
diff --git a/trytond/tests/test.py b/trytond/tests/test.py
index 5f812f2..8b4b201 100644
--- a/trytond/tests/test.py
+++ b/trytond/tests/test.py
@@ -1,9 +1,9 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 datetime
 from decimal import Decimal
-from trytond.model import ModelSQL, fields
+from trytond.model import ModelSQL, DictSchemaMixin, fields
 from trytond.pyson import Eval
 
 __all__ = [
@@ -16,6 +16,7 @@ __all__ = [
     'Date', 'DateDefault', 'DateRequired',
     'DateTime', 'DateTimeDefault', 'DateTimeRequired', 'DateTimeFormat',
     'Time', 'TimeDefault', 'TimeRequired', 'TimeFormat',
+    'TimeDelta', 'TimeDeltaDefault', 'TimeDeltaRequired',
     'One2One', 'One2OneTarget', 'One2OneRelation', 'One2OneRequired',
     'One2OneRequiredRelation', 'One2OneDomain', 'One2OneDomainRelation',
     'One2Many', 'One2ManyTarget',
@@ -32,9 +33,9 @@ __all__ = [
     'Reference', 'ReferenceTarget', 'ReferenceRequired',
     'Property',
     'Selection', 'SelectionRequired',
-    'Dict', 'DictDefault', 'DictRequired',
+    'DictSchema', 'Dict', 'DictDefault', 'DictRequired',
     'Binary', 'BinaryDefault', 'BinaryRequired',
-    'Many2OneDomainValidation', 'Many2OneTarget',
+    'Many2OneDomainValidation', 'Many2OneTarget', 'Many2OneOrderBy',
     ]
 
 
@@ -315,6 +316,31 @@ class TimeFormat(ModelSQL):
     time = fields.Time(string='Time', format='%H:%M')
 
 
+class TimeDelta(ModelSQL):
+    'TimeDelta'
+    __name__ = 'test.timedelta'
+    timedelta = fields.TimeDelta(string='TimeDelta', help='Test timedelta',
+        required=False)
+
+
+class TimeDeltaDefault(ModelSQL):
+    'TimeDelta Default'
+    __name__ = 'test.timedelta_default'
+    timedelta = fields.TimeDelta(string='TimeDelta', help='Test timedelta',
+        required=False)
+
+    @staticmethod
+    def default_timedelta():
+        return datetime.timedelta(seconds=3600)
+
+
+class TimeDeltaRequired(ModelSQL):
+    'TimeDelta Required'
+    __name__ = 'test.timedelta_required'
+    timedelta = fields.TimeDelta(string='TimeDelta', help='Test timedelta',
+        required=True)
+
+
 class One2One(ModelSQL):
     'One2One'
     __name__ = 'test.one2one'
@@ -629,10 +655,17 @@ class SelectionRequired(ModelSQL):
         'Selection', required=True)
 
 
+class DictSchema(DictSchemaMixin, ModelSQL):
+    'Dict Schema'
+    __name__ = 'test.dict.schema'
+
+
 class Dict(ModelSQL):
     'Dict'
     __name__ = 'test.dict'
-    dico = fields.Dict(None, 'Test Dict')
+    dico = fields.Dict('test.dict.schema', 'Test Dict')
+    dico_string = dico.translated('dico')
+    dico_string_keys = dico.translated('dico', 'keys')
 
 
 class DictDefault(ModelSQL):
@@ -664,7 +697,7 @@ class BinaryDefault(ModelSQL):
 
     @staticmethod
     def default_binary():
-        return buffer('default')
+        return b'default'
 
 
 class BinaryRequired(ModelSQL):
@@ -676,6 +709,7 @@ class BinaryRequired(ModelSQL):
 class Many2OneTarget(ModelSQL):
     "Many2One Domain Validation Target"
     __name__ = 'test.many2one_target'
+    _order_name = 'value'
 
     active = fields.Boolean('Active')
     value = fields.Integer('Value')
@@ -694,3 +728,9 @@ class Many2OneDomainValidation(ModelSQL):
             ('value', '>', 5),
             ])
     dummy = fields.Char('Dummy')
+
+
+class Many2OneOrderBy(ModelSQL):
+    "Many2One OrderBy"
+    __name__ = 'test.many2one_orderby'
+    many2one = fields.Many2One('test.many2one_target', 'many2one')
diff --git a/trytond/tests/test_access.py b/trytond/tests/test_access.py
index 3251a77..fc780c5 100644
--- a/trytond/tests/test_access.py
+++ b/trytond/tests/test_access.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
         install_module
diff --git a/trytond/tests/test_cache.py b/trytond/tests/test_cache.py
index 9aa859c..236885e 100644
--- a/trytond/tests/test_cache.py
+++ b/trytond/tests/test_cache.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 
diff --git a/trytond/tests/test_copy.py b/trytond/tests/test_copy.py
index d6c3156..ceb642c 100644
--- a/trytond/tests/test_copy.py
+++ b/trytond/tests/test_copy.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
         install_module
diff --git a/trytond/tests/test_descriptors.py b/trytond/tests/test_descriptors.py
new file mode 100644
index 0000000..0bcca2b
--- /dev/null
+++ b/trytond/tests/test_descriptors.py
@@ -0,0 +1,12 @@
+# 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 unittest
+import doctest
+
+from trytond.model import descriptors
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocTestSuite(descriptors))
+    return suite
diff --git a/trytond/tests/test_exportdata.py b/trytond/tests/test_exportdata.py
index 417b52b..d95921c 100644
--- a/trytond/tests/test_exportdata.py
+++ b/trytond/tests/test_exportdata.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 sys
 try:
     import cdecimal
diff --git a/trytond/tests/test_fields.py b/trytond/tests/test_fields.py
index 2c436f6..bda99ba 100644
--- a/trytond/tests/test_fields.py
+++ b/trytond/tests/test_fields.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 sys
 try:
     import cdecimal
@@ -16,6 +16,7 @@ from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
         install_module
 from trytond.transaction import Transaction
 from trytond.exceptions import UserError
+from trytond.model import fields
 
 
 class FieldsTestCase(unittest.TestCase):
@@ -67,6 +68,10 @@ class FieldsTestCase(unittest.TestCase):
         self.time_required = POOL.get('test.time_required')
         self.time_format = POOL.get('test.time_format')
 
+        self.timedelta = POOL.get('test.timedelta')
+        self.timedelta_default = POOL.get('test.timedelta_default')
+        self.timedelta_required = POOL.get('test.timedelta_required')
+
         self.one2one = POOL.get('test.one2one')
         self.one2one_target = POOL.get('test.one2one.target')
         self.one2one_required = POOL.get('test.one2one_required')
@@ -102,6 +107,7 @@ class FieldsTestCase(unittest.TestCase):
         self.selection_required = POOL.get('test.selection_required')
 
         self.dict_ = POOL.get('test.dict')
+        self.dict_schema = POOL.get('test.dict.schema')
         self.dict_default = POOL.get('test.dict_default')
         self.dict_required = POOL.get('test.dict_required')
 
@@ -110,6 +116,7 @@ class FieldsTestCase(unittest.TestCase):
         self.binary_required = POOL.get('test.binary_required')
 
         self.m2o_domain_validation = POOL.get('test.many2one_domainvalidation')
+        self.m2o_orderby = POOL.get('test.many2one_orderby')
         self.m2o_target = POOL.get('test.many2one_target')
 
     def test0010boolean(self):
@@ -2269,6 +2276,21 @@ class FieldsTestCase(unittest.TestCase):
                         'name': 'one2one5',
                         'one2one': target5.id,
                         }])
+            targets = self.one2one_target.create([{
+                        'name': 'multiple1',
+                        }, {
+                        'name': 'multiple2',
+                        }])
+            one2ones = self.one2one.create([{
+                        'name': 'origin6',
+                        'one2one': targets[0].id,
+                        }, {
+                        'name': 'origin7',
+                        'one2one': targets[1].id,
+                        }])
+            for one2one, target in zip(one2ones, targets):
+                self.assert_(one2one)
+                self.assertEqual(one2one.one2one, target)
 
             transaction.cursor.rollback()
 
@@ -2648,7 +2670,7 @@ class FieldsTestCase(unittest.TestCase):
                         }])
             self.assert_(origin3_id)
 
-            size_targets = self.many2many_size_target.create([{
+            self.many2many_size_target.create([{
                         'name': str(i),
                         } for i in range(6)])
 
@@ -3108,6 +3130,23 @@ class FieldsTestCase(unittest.TestCase):
         'Test Dict'
         with Transaction().start(DB_NAME, USER,
                 context=CONTEXT) as transaction:
+
+            self.dict_schema.create([{
+                        'name': 'a',
+                        'string': 'A',
+                        'type_': 'integer',
+                        }, {
+                        'name': 'b',
+                        'string': 'B',
+                        'type_': 'integer',
+                        }, {
+                        'name': 'type',
+                        'string': 'Type',
+                        'type_': 'selection',
+                        'selection': ('arabic: Arabic\n'
+                            'hexa: Hexadecimal'),
+                        }])
+
             dict1, = self.dict_.create([{
                         'dico': {'a': 1, 'b': 2},
                         }])
@@ -3116,6 +3155,21 @@ class FieldsTestCase(unittest.TestCase):
             self.dict_.write([dict1], {'dico': {'z': 26}})
             self.assert_(dict1.dico == {'z': 26})
 
+            dict1.dico = {
+                'a': 1,
+                'type': 'arabic',
+                }
+            dict1.save()
+            self.assertEqual(dict1.dico, {'a': 1, 'type': 'arabic'})
+            self.assertEqual(dict1.dico_string, {
+                    'a': 1,
+                    'type': 'Arabic',
+                    })
+            self.assertEqual(dict1.dico_string_keys, {
+                    'a': 'A',
+                    'type': 'Type',
+                    })
+
             dict2, = self.dict_.create([{}])
             self.assert_(dict2.dico is None)
 
@@ -3137,32 +3191,34 @@ class FieldsTestCase(unittest.TestCase):
         with Transaction().start(DB_NAME, USER,
                 context=CONTEXT) as transaction:
             bin1, = self.binary.create([{
-                        'binary': buffer('foo'),
+                        'binary': fields.Binary.cast(b'foo'),
                         }])
-            self.assert_(bin1.binary == buffer('foo'))
+            self.assert_(bin1.binary == fields.Binary.cast(b'foo'))
 
-            self.binary.write([bin1], {'binary': buffer('bar')})
-            self.assert_(bin1.binary == buffer('bar'))
+            self.binary.write([bin1], {'binary': fields.Binary.cast(b'bar')})
+            self.assert_(bin1.binary == fields.Binary.cast(b'bar'))
 
             with transaction.set_context({'test.binary.binary': 'size'}):
                 bin1_size = self.binary(bin1.id)
-                self.assert_(bin1_size.binary == len('bar'))
-                self.assert_(bin1_size.binary != buffer('bar'))
+                self.assert_(bin1_size.binary == len(b'bar'))
+                self.assert_(bin1_size.binary != fields.Binary.cast(b'bar'))
 
             bin2, = self.binary.create([{}])
             self.assert_(bin2.binary is None)
 
             bin3, = self.binary_default.create([{}])
-            self.assert_(bin3.binary == buffer('default'))
+            self.assert_(bin3.binary == fields.Binary.cast(b'default'))
 
             self.assertRaises(UserError, self.binary_required.create, [{}])
             transaction.cursor.rollback()
 
-            bin4, = self.binary_required.create([{'binary': buffer('baz')}])
-            self.assert_(bin4.binary == buffer('baz'))
+            bin4, = self.binary_required.create([{
+                        'binary': fields.Binary.cast(b'baz'),
+                        }])
+            self.assert_(bin4.binary == fields.Binary.cast(b'baz'))
 
             self.assertRaises(UserError, self.binary_required.create,
-                [{'binary': buffer('')}])
+                [{'binary': fields.Binary.cast(b'')}])
 
             transaction.cursor.rollback()
 
@@ -3188,6 +3244,241 @@ class FieldsTestCase(unittest.TestCase):
             domain.dummy = 'Dummy'
             domain.save()
 
+            # Testing order_by
+            for value in (5, 3, 2):
+                m2o, = self.m2o_target.create([{'value': value}])
+                self.m2o_orderby.create([{'many2one': m2o}])
+
+            search = self.m2o_orderby.search([], order=[('many2one', 'ASC')])
+            self.assertTrue(all(x.many2one.value <= y.many2one.value
+                    for x, y in zip(search, search[1:])))
+
+            search = self.m2o_orderby.search([],
+                order=[('many2one.id', 'ASC')])
+            self.assertTrue(all(x.many2one.id <= y.many2one.id
+                    for x, y in zip(search, search[1:])))
+
+            search = self.m2o_orderby.search([],
+                order=[('many2one.value', 'ASC')])
+            self.assertTrue(all(x.many2one.value <= y.many2one.value
+                    for x, y in zip(search, search[1:])))
+
+            transaction.cursor.rollback()
+
+    def test0200timedelta(self):
+        'Test timedelta'
+        with Transaction().start(DB_NAME, USER,
+                context=CONTEXT) as transaction:
+
+            minute = datetime.timedelta(minutes=1)
+            hour = datetime.timedelta(hours=1)
+            day = datetime.timedelta(days=1)
+            default_timedelta = datetime.timedelta(seconds=3600)
+
+            timedelta1, = self.timedelta.create([{
+                        'timedelta': hour,
+                        }])
+            self.assert_(timedelta1)
+            self.assertEqual(timedelta1.timedelta, hour)
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '=', hour),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '=', day),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '=', None),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '!=', day),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '!=', None),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'in', [hour]),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'in', [day]),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'in', [minute]),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'in', [None]),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'in', []),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'not in', [hour]),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'not in', [day]),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'not in', [None]),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'not in', []),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '<', day),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '<', minute),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '<', hour),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '<=', hour),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '<=', minute),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '<=', day),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '>', day),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '>', minute),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '>', hour),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '>=', day),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '>=', minute),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '>=', hour),
+                    ])
+            self.assertEqual(timedelta, [timedelta1])
+
+            timedelta2, = self.timedelta.create([{
+                        'timedelta': minute,
+                        }])
+            self.assert_(timedelta2)
+            self.assertEqual(timedelta2.timedelta, minute)
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', '=', minute),
+                    ])
+            self.assertEqual(timedelta, [timedelta2])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'in', [minute, hour]),
+                    ])
+            self.assertEqual(timedelta, [timedelta1, timedelta2])
+
+            timedelta = self.timedelta.search([
+                    ('timedelta', 'not in', [minute, hour]),
+                    ])
+            self.assertEqual(timedelta, [])
+
+            timedelta3, = self.timedelta.create([{}])
+            self.assert_(timedelta3)
+            self.assertEqual(timedelta3.timedelta, None)
+
+            timedelta4, = self.timedelta_default.create([{}])
+            self.assert_(timedelta4)
+            self.assertEqual(timedelta4.timedelta, default_timedelta)
+
+            self.timedelta.write([timedelta1], {
+                    'timedelta': minute,
+                    })
+            self.assertEqual(timedelta1.timedelta, minute)
+
+            self.timedelta.write([timedelta2], {
+                    'timedelta': day,
+                    })
+            self.assertEqual(timedelta2.timedelta, day)
+
+            self.assertRaises(Exception, self.timedelta.create, [{
+                        'timedelta': 'test',
+                        }])
+
+            self.assertRaises(Exception, self.timedelta.write, [timedelta1], {
+                    'timedelta': 'test',
+                    })
+
+            self.assertRaises(Exception, self.timedelta.create, [{
+                        'timedelta': 1,
+                        }])
+
+            self.assertRaises(Exception, self.timedelta.write, [timedelta1], {
+                    'timedelta': 1,
+                    })
+
+            self.assertRaises(UserError, self.timedelta_required.create, [{}])
+            transaction.cursor.rollback()
+
+            timedelta6, = self.timedelta_required.create([{
+                        'timedelta': day,
+                        }])
+            self.assert_(timedelta6)
+
+            timedelta7, = self.timedelta.create([{
+                        'timedelta': None,
+                        }])
+            self.assert_(timedelta7)
+
             transaction.cursor.rollback()
 
 
diff --git a/trytond/tests/test_history.py b/trytond/tests/test_history.py
index 74c9176..d0f6e7a 100644
--- a/trytond/tests/test_history.py
+++ b/trytond/tests/test_history.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 import datetime
 
@@ -193,6 +193,40 @@ class HistoryTestCase(unittest.TestCase):
             History.restore_history([history_id], datetime.datetime.max)
             self.assertRaises(UserError, History.read, [history_id])
 
+    def test0041restore_history_before(self):
+        'Test restore history before'
+        History = POOL.get('test.history')
+
+        with Transaction().start(DB_NAME, USER,
+                context=CONTEXT) as transaction:
+            history = History(value=1)
+            history.save()
+            history_id = history.id
+
+            transaction.cursor.commit()
+
+        with Transaction().start(DB_NAME, USER,
+                context=CONTEXT) as transaction:
+            history = History(history_id)
+            history.value = 2
+            history.save()
+            second = history.write_date
+
+            transaction.cursor.commit()
+
+        with Transaction().start(DB_NAME, USER,
+                context=CONTEXT) as transaction:
+            history = History(history_id)
+            history.value = 3
+            history.save()
+
+            transaction.cursor.commit()
+
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            History.restore_history_before([history_id], second)
+            history = History(history_id)
+            self.assertEqual(history.value, 1)
+
     @unittest.skipIf(backend.name() in ('sqlite', 'mysql'),
         'now() is not the start of the transaction')
     def test0045restore_history_same_timestamp(self):
@@ -382,6 +416,63 @@ class HistoryTestCase(unittest.TestCase):
             self.assertEqual(history.value, 2)
             self.assertEqual([l.name for l in history.lines], ['c'])
 
+    def test0080_search_cursor_max(self):
+        'Test search with number of history entries at cursor.IN_MAX'
+        History = POOL.get('test.history')
+
+        with Transaction().start(DB_NAME, USER,
+                context=CONTEXT) as transaction:
+            cursor = transaction.cursor
+
+            history = History(value=-1)
+            history.save()
+
+            for history.value in range(cursor.IN_MAX + 1):
+                history.save()
+
+            with transaction.set_context(_datetime=datetime.datetime.max):
+                record, = History.search([])
+
+                self.assertEqual(record.value, cursor.IN_MAX)
+
+    def test0090_search_cursor_max_entries(self):
+        'Test search for skipping first history entries at cursor.IN_MAX'
+        History = POOL.get('test.history')
+
+        with Transaction().start(DB_NAME, USER,
+                context=CONTEXT) as transaction:
+            cursor = transaction.cursor
+
+            for i in xrange(0, 2):
+                history = History(value=-1)
+                history.save()
+
+                for history.value in range(cursor.IN_MAX + 1):
+                    history.save()
+
+            with transaction.set_context(_datetime=datetime.datetime.max):
+                records = History.search([])
+
+                self.assertEqual({r.value for r in records}, {cursor.IN_MAX})
+                self.assertEqual(len(records), 2)
+
+    def test0100_search_cursor_max_histories(self):
+        'Test search with number of histories at cursor.IN_MAX'
+        History = POOL.get('test.history')
+
+        with Transaction().start(DB_NAME, USER,
+                                 context=CONTEXT) as transaction:
+            cursor = transaction.cursor
+
+            n = cursor.IN_MAX + 1
+            History.create([{'value': 1}] * n)
+
+            with transaction.set_context(_datetime=datetime.datetime.max):
+                records = History.search([])
+
+                self.assertEqual({r.value for r in records}, {1})
+                self.assertEqual(len(records), n)
+
 
 def suite():
     return unittest.TestLoader().loadTestsFromTestCase(HistoryTestCase)
diff --git a/trytond/tests/test_importdata.py b/trytond/tests/test_importdata.py
index 8718032..594b34f 100644
--- a/trytond/tests/test_importdata.py
+++ b/trytond/tests/test_importdata.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 from decimal import InvalidOperation
 
diff --git a/trytond/tests/test_ir.py b/trytond/tests/test_ir.py
new file mode 100644
index 0000000..7079397
--- /dev/null
+++ b/trytond/tests/test_ir.py
@@ -0,0 +1,14 @@
+# 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 unittest
+
+from .test_tryton import ModuleTestCase
+
+
+class IrTestCase(ModuleTestCase):
+    'Test ir module'
+    module = 'ir'
+
+
+def suite():
+    return unittest.TestLoader().loadTestsFromTestCase(IrTestCase)
diff --git a/trytond/tests/test_mixins.py b/trytond/tests/test_mixins.py
index a699f7b..087e40d 100644
--- a/trytond/tests/test_mixins.py
+++ b/trytond/tests/test_mixins.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 import urllib
diff --git a/trytond/tests/test_modelsingleton.py b/trytond/tests/test_modelsingleton.py
index 673e668..ff4d135 100644
--- a/trytond/tests/test_modelsingleton.py
+++ b/trytond/tests/test_modelsingleton.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 from datetime import datetime
 from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
diff --git a/trytond/tests/test_modelstorage.py b/trytond/tests/test_modelstorage.py
new file mode 100644
index 0000000..1be6597
--- /dev/null
+++ b/trytond/tests/test_modelstorage.py
@@ -0,0 +1,39 @@
+# 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 unittest
+
+from trytond.transaction import Transaction
+from trytond.pool import Pool
+from trytond.tests.test_tryton import DB_NAME, USER, CONTEXT, install_module
+
+
+class ModelStorageTestCase(unittest.TestCase):
+    'Test ModelStorage'
+
+    def setUp(self):
+        install_module('tests')
+
+    def test_search_read_order(self):
+        'Test search_read order'
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            pool = Pool()
+            ModelStorage = pool.get('test.modelstorage')
+
+            ModelStorage.create([{'name': i} for i in ['foo', 'bar', 'test']])
+
+            rows = ModelStorage.search_read([])
+            self.assertTrue(
+                all(x['id'] < y['id'] for x, y in zip(rows, rows[1:])))
+
+            rows = ModelStorage.search_read([], order=[('name', 'ASC')])
+            self.assertTrue(
+                all(x['name'] <= y['name'] for x, y in zip(rows, rows[1:])))
+
+            rows = ModelStorage.search_read([], order=[('name', 'DESC')])
+            self.assertTrue(
+                all(x['name'] >= y['name'] for x, y in zip(rows, rows[1:])))
+
+
+def suite():
+    return unittest.TestLoader().loadTestsFromTestCase(ModelStorageTestCase)
diff --git a/trytond/tests/test_modelview.py b/trytond/tests/test_modelview.py
new file mode 100644
index 0000000..300978e
--- /dev/null
+++ b/trytond/tests/test_modelview.py
@@ -0,0 +1,81 @@
+# 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 unittest
+
+from trytond.tests.test_tryton import DB_NAME, USER, CONTEXT, install_module
+from trytond.pool import Pool
+from trytond.transaction import Transaction
+
+
+class ModelView(unittest.TestCase):
+    "Test ModelView"
+
+    def setUp(self):
+        install_module('tests')
+
+    def test_changed_values(self):
+        "Test ModelView._changed_values"
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            pool = Pool()
+            Model = pool.get('test.modelview.changed_values')
+            Target = pool.get('test.modelview.changed_values.target')
+
+            record = Model()
+
+            self.assertEqual(record._changed_values, {})
+
+            record.name = 'foo'
+            record.target = Target(1)
+            record.ref_target = Target(2)
+            record.targets = [Target(name='bar')]
+            self.assertEqual(record._changed_values, {
+                    'name': 'foo',
+                    'target': 1,
+                    'ref_target': 'test.modelview.changed_values.target,2',
+                    'targets': {
+                        'add': [
+                            (0, {'name': 'bar'}),
+                            ],
+                        },
+                    })
+
+            record = Model(name='test', target=1, targets=[
+                    {'id': 1, 'name': 'foo'},
+                    {'id': 2},
+                    ], m2m_targets=[5, 6, 7])
+
+            self.assertEqual(record._changed_values, {})
+
+            target = record.targets[0]
+            target.name = 'bar'
+            record.targets = [target]
+            record.m2m_targets = [Target(9), Target(10)]
+            self.assertEqual(record._changed_values, {
+                    'targets': {
+                        'update': [{'id': 1, 'name': 'bar'}],
+                        'remove': [2],
+                        },
+                    'm2m_targets': [9, 10],
+                    })
+
+            # change only one2many record
+            record = Model(targets=[{'id': 1, 'name': 'foo'}])
+            self.assertEqual(record._changed_values, {})
+
+            target, = record.targets
+            target.name = 'bar'
+            record.targets = record.targets
+            self.assertEqual(record._changed_values, {
+                    'targets': {
+                        'update': [{'id': 1, 'name': 'bar'}],
+                        },
+                    })
+
+
+def suite():
+    func = unittest.TestLoader().loadTestsFromTestCase
+    suite = unittest.TestSuite()
+    for testcase in (ModelView,):
+        suite.addTests(func(testcase))
+    return suite
diff --git a/trytond/tests/test_mptt.py b/trytond/tests/test_mptt.py
index 6bbd37b..5cc3373 100644
--- a/trytond/tests/test_mptt.py
+++ b/trytond/tests/test_mptt.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 sys
 import unittest
 from mock import patch
diff --git a/trytond/tests/test_protocols.py b/trytond/tests/test_protocols.py
index ee445ca..28ae666 100644
--- a/trytond/tests/test_protocols.py
+++ b/trytond/tests/test_protocols.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 import json
@@ -30,9 +30,10 @@ class JSONTestCase(unittest.TestCase):
         'Test time'
         self.dumps_loads(datetime.datetime.now().time())
 
-    def test_buffer(self):
-        'Test buffer'
-        self.dumps_loads(buffer('foo'))
+    def test_bytes(self):
+        'Test bytes'
+        self.dumps_loads(bytes(b'foo'))
+        self.dumps_loads(bytearray(b'foo'))
 
     def test_decimal(self):
         'Test Decimal'
@@ -52,9 +53,10 @@ class XMLTestCase(unittest.TestCase):
         'Test Decimal'
         self.dumps_loads(Decimal('3.141592653589793'))
 
-    def test_buffer(self):
-        'Test buffer'
-        self.dumps_loads(buffer('foo'))
+    def test_bytes(self):
+        'Test bytes'
+        self.dumps_loads(bytes(b'foo'))
+        self.dumps_loads(bytearray(b'foo'))
 
     def test_date(self):
         'Test date'
diff --git a/trytond/tests/test_pyson.py b/trytond/tests/test_pyson.py
index d295b98..5cf65c2 100644
--- a/trytond/tests/test_pyson.py
+++ b/trytond/tests/test_pyson.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 import datetime
@@ -12,34 +12,37 @@ class PYSONTestCase(unittest.TestCase):
 
     def test0010Eval(self):
         'Test pyson.Eval'
-        self.assert_(pyson.Eval('test').pyson() == {
+        self.assertEqual(pyson.Eval('test').pyson(), {
             '__class__': 'Eval',
             'v': 'test',
             'd': '',
             })
-        self.assert_(pyson.Eval('test', 'foo').pyson() == {
+        self.assertEqual(pyson.Eval('test', 'foo').pyson(), {
             '__class__': 'Eval',
             'v': 'test',
             'd': 'foo',
             })
 
-        self.assert_(pyson.Eval('test', 'foo').types() == set([type('foo')]))
-        self.assert_(pyson.Eval('test', 1).types() == set([type(1)]))
+        self.assertEqual(pyson.Eval('test', 'foo').types(), set([basestring]))
+        self.assertEqual(pyson.Eval('test', 1).types(), set([int]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Eval('test', 0))
-        self.assert_(pyson.PYSONDecoder({'test': 1}).decode(eval) == 1)
-        self.assert_(pyson.PYSONDecoder().decode(eval) == 0)
+        self.assertEqual(pyson.PYSONDecoder({'test': 1}).decode(eval), 1)
+        self.assertEqual(pyson.PYSONDecoder().decode(eval), 0)
+
+        self.assertEqual(repr(pyson.Eval('test', 'foo')),
+            "Eval('test', 'foo')")
 
     def test0020Not(self):
         'Test pyson.Not'
-        self.assert_(pyson.Not(True).pyson() == {
+        self.assertEqual(pyson.Not(True).pyson(), {
             '__class__': 'Not',
             'v': True,
             })
 
         self.assertRaises(AssertionError, pyson.Not, 'foo')
 
-        self.assert_(pyson.Not(True).types() == set([bool]))
+        self.assertEqual(pyson.Not(True).types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Not(True))
         self.assertFalse(pyson.PYSONDecoder().decode(eval))
@@ -47,14 +50,16 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.Not(False))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
 
+        self.assertEqual(repr(pyson.Not(True)), 'Not(True)')
+
     def test0030Bool(self):
         'Test pyson.Bool'
-        self.assert_(pyson.Bool('test').pyson() == {
+        self.assertEqual(pyson.Bool('test').pyson(), {
             '__class__': 'Bool',
             'v': 'test',
             })
 
-        self.assert_(pyson.Bool('test').types() == set([bool]))
+        self.assertEqual(pyson.Bool('test').types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Bool(True))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
@@ -86,9 +91,11 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.Bool({}))
         self.assertFalse(pyson.PYSONDecoder().decode(eval))
 
+        self.assertEqual(repr(pyson.Bool('test')), "Bool('test')")
+
     def test0040And(self):
         'Test pyson.And'
-        self.assert_(pyson.And(True, False).pyson() == {
+        self.assertEqual(pyson.And(True, False).pyson(), {
             '__class__': 'And',
             's': [True, False],
             })
@@ -99,7 +106,7 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.And, True)
         self.assertRaises(AssertionError, pyson.And)
 
-        self.assert_(pyson.And(True, False).types() == set([bool]))
+        self.assertEqual(pyson.And(True, False).types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.And(True, True))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
@@ -128,9 +135,12 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.And(False, False, True))
         self.assertFalse(pyson.PYSONDecoder().decode(eval))
 
+        self.assertEqual(repr(pyson.And(False, True, True)),
+            'And(False, True, True)')
+
     def test0050Or(self):
         'Test pyson.Or'
-        self.assert_(pyson.Or(True, False).pyson() == {
+        self.assertEqual(pyson.Or(True, False).pyson(), {
             '__class__': 'Or',
             's': [True, False],
             })
@@ -141,7 +151,7 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.Or, True)
         self.assertRaises(AssertionError, pyson.Or)
 
-        self.assert_(pyson.Or(True, False).types() == set([bool]))
+        self.assertEqual(pyson.Or(True, False).types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Or(True, True))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
@@ -170,9 +180,12 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.Or(False, False, True))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
 
+        self.assertEqual(repr(pyson.Or(False, True, True)),
+            'Or(False, True, True)')
+
     def test0060Equal(self):
         'Test pyson.Equal'
-        self.assert_(pyson.Equal('test', 'test').pyson() == {
+        self.assertEqual(pyson.Equal('test', 'test').pyson(), {
             '__class__': 'Equal',
             's1': 'test',
             's2': 'test',
@@ -180,7 +193,7 @@ class PYSONTestCase(unittest.TestCase):
 
         self.assertRaises(AssertionError, pyson.Equal, 'test', True)
 
-        self.assert_(pyson.Equal('test', 'test').types() == set([bool]))
+        self.assertEqual(pyson.Equal('test', 'test').types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Equal('test', 'test'))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
@@ -188,9 +201,12 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.Equal('foo', 'bar'))
         self.assertFalse(pyson.PYSONDecoder().decode(eval))
 
+        self.assertEqual(repr(pyson.Equal('foo', 'bar')),
+            "Equal('foo', 'bar')")
+
     def test0070Greater(self):
         'Test pyson.Greater'
-        self.assert_(pyson.Greater(1, 0).pyson() == {
+        self.assertEqual(pyson.Greater(1, 0).pyson(), {
             '__class__': 'Greater',
             's1': 1,
             's2': 0,
@@ -201,7 +217,7 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.Greater, 1, 'test')
         self.assertRaises(AssertionError, pyson.Greater, 1, 0, 'test')
 
-        self.assert_(pyson.Greater(1, 0).types() == set([bool]))
+        self.assertEqual(pyson.Greater(1, 0).types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Greater(1, 0))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
@@ -221,9 +237,11 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.Greater(1, 1, True))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
 
+        self.assertEqual(repr(pyson.Greater(1, 0)), 'Greater(1, 0, False)')
+
     def test0080Less(self):
         'Test pyson.Less'
-        self.assert_(pyson.Less(0, 1).pyson() == {
+        self.assertEqual(pyson.Less(0, 1).pyson(), {
             '__class__': 'Less',
             's1': 0,
             's2': 1,
@@ -234,7 +252,7 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.Less, 0, 'test')
         self.assertRaises(AssertionError, pyson.Less, 0, 1, 'test')
 
-        self.assert_(pyson.Less(0, 1).types() == set([bool]))
+        self.assertEqual(pyson.Less(0, 1).types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Less(0, 1))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
@@ -254,9 +272,11 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.Less(1, 1, True))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
 
+        self.assertEqual(repr(pyson.Less(0, 1)), 'Less(0, 1, False)')
+
     def test0090If(self):
         'Test pyson.If'
-        self.assert_(pyson.If(True, 'foo', 'bar').pyson() == {
+        self.assertEqual(pyson.If(True, 'foo', 'bar').pyson(), {
             '__class__': 'If',
             'c': True,
             't': 'foo',
@@ -267,18 +287,21 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.If, True, 'foo', False)
 
         self.assertEqual(pyson.If(True, 'foo', 'bar').types(),
-            set([type('foo')]))
-        self.assert_(pyson.If(True, False, True).types() == set([bool]))
+            set([basestring]))
+        self.assertEqual(pyson.If(True, False, True).types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.If(True, 'foo', 'bar'))
-        self.assert_(pyson.PYSONDecoder().decode(eval) == 'foo')
+        self.assertEqual(pyson.PYSONDecoder().decode(eval), 'foo')
 
         eval = pyson.PYSONEncoder().encode(pyson.If(False, 'foo', 'bar'))
-        self.assert_(pyson.PYSONDecoder().decode(eval) == 'bar')
+        self.assertEqual(pyson.PYSONDecoder().decode(eval), 'bar')
+
+        self.assertEqual(repr(pyson.If(True, 'foo', 'bar')),
+            "If(True, 'foo', 'bar')")
 
     def test0100Get(self):
         'Test pyson.Get'
-        self.assert_(pyson.Get({'foo': 'bar'}, 'foo', 'default').pyson() == {
+        self.assertEqual(pyson.Get({'foo': 'bar'}, 'foo', 'default').pyson(), {
             '__class__': 'Get',
             'v': {'foo': 'bar'},
             'k': 'foo',
@@ -288,24 +311,28 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.Get, 'test', 'foo', 'default')
         self.assertRaises(AssertionError, pyson.Get, {}, 1, 'default')
 
-        self.assert_(pyson.Get({}, 'foo', 'default').types() == set([str]))
-        self.assert_(pyson.Get({}, 'foo', True).types() == set([bool]))
+        self.assertEqual(pyson.Get({}, 'foo', 'default').types(),
+            set([basestring]))
+        self.assertEqual(pyson.Get({}, 'foo', True).types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Get(
             {'foo': 'bar'}, 'foo', 'default'))
-        self.assert_(pyson.PYSONDecoder().decode(eval) == 'bar')
+        self.assertEqual(pyson.PYSONDecoder().decode(eval), 'bar')
 
         eval = pyson.PYSONEncoder().encode(pyson.Get(
             {'foo': 'bar'}, 'test', 'default'))
-        self.assert_(pyson.PYSONDecoder().decode(eval) == 'default')
+        self.assertEqual(pyson.PYSONDecoder().decode(eval), 'default')
 
         eval = pyson.PYSONEncoder().encode(pyson.Get(
             {}, 'foo', 'default'))
-        self.assert_(pyson.PYSONDecoder().decode(eval) == 'default')
+        self.assertEqual(pyson.PYSONDecoder().decode(eval), 'default')
+
+        self.assertEqual(repr(pyson.Get({'foo': 'bar'}, 'foo', 'default')),
+            "Get({'foo': 'bar'}, 'foo', 'default')")
 
     def test0110In(self):
         'Test pyson.In'
-        self.assert_(pyson.In('foo', {'foo': 'bar'}).pyson() == {
+        self.assertEqual(pyson.In('foo', {'foo': 'bar'}).pyson(), {
             '__class__': 'In',
             'k': 'foo',
             'v': {'foo': 'bar'},
@@ -314,7 +341,7 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.In, object(), {})
         self.assertRaises(AssertionError, pyson.In, 'test', 'foo')
 
-        self.assert_(pyson.In('foo', {}).types() == set([bool]))
+        self.assertEqual(pyson.In('foo', {}).types(), set([bool]))
 
         eval = pyson.PYSONEncoder().encode(pyson.In('foo', {'foo': 'bar'}))
         self.assertTrue(pyson.PYSONDecoder().decode(eval))
@@ -346,9 +373,12 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.In('test', []))
         self.assertFalse(pyson.PYSONDecoder().decode(eval))
 
+        self.assertEqual(repr(pyson.In('foo', ['foo', 'bar'])),
+            "In('foo', ['foo', 'bar'])")
+
     def test0120Date(self):
         'Test pyson.Date'
-        self.assert_(pyson.Date(2010, 1, 12, -1, 12, -7).pyson() == {
+        self.assertEqual(pyson.Date(2010, 1, 12, -1, 12, -7).pyson(), {
             '__class__': 'Date',
             'y': 2010,
             'M': 1,
@@ -371,37 +401,40 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.Date, 2010, 1, 12, -1, 12,
                 'test')
 
-        self.assert_(pyson.Date(2010, 1, 12, -1, 12, -7).types()
-                == set([datetime.date]))
+        self.assertEqual(pyson.Date(2010, 1, 12, -1, 12, -7).types(),
+            set([datetime.date]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Date())
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.date.today())
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.date.today())
 
         eval = pyson.PYSONEncoder().encode(pyson.Date(2010, 1, 12))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.date(2010, 1, 12))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.date(2010, 1, 12))
 
         eval = pyson.PYSONEncoder().encode(pyson.Date(2010, 1, 12, -1))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.date(2009, 1, 12))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.date(2009, 1, 12))
 
         eval = pyson.PYSONEncoder().encode(pyson.Date(2010, 1, 12, 0, 12))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.date(2011, 1, 12))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.date(2011, 1, 12))
 
         eval = pyson.PYSONEncoder().encode(pyson.Date(2010, 1, 12, 0, 0, -7))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.date(2010, 1, 5))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.date(2010, 1, 5))
 
         eval = pyson.PYSONEncoder().encode(datetime.date(2010, 2, 22))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.date(2010, 2, 22))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.date(2010, 2, 22))
+
+        self.assertEqual(repr(pyson.Date(2010, 1, 12, -1, 12, -7)),
+            'Date(2010, 1, 12, -1, 12, -7)')
 
     def test0130DateTime(self):
         'Test pyson.DateTime'
-        self.assert_(pyson.DateTime(2010, 1, 12, 10, 30, 20, 0,
-            -1, 12, -7, 2, 15, 30, 1).pyson() == {
+        self.assertEqual(pyson.DateTime(2010, 1, 12, 10, 30, 20, 0,
+            -1, 12, -7, 2, 15, 30, 1).pyson(), {
                 '__class__': 'DateTime',
                 'y': 2010,
                 'M': 1,
@@ -448,64 +481,68 @@ class PYSONTestCase(unittest.TestCase):
         self.assertRaises(AssertionError, pyson.DateTime, 2010, 1, 12, 10, 30,
                 20, 0, -1, 12, -7, 2, 15, 30, 'test')
 
-        self.assert_(pyson.DateTime(2010, 1, 12, 10, 30, 20, 0,
-            -1, 12, -7, 2, 15, 30, 1).types() == set([datetime.datetime]))
+        self.assertEqual(pyson.DateTime(2010, 1, 12, 10, 30, 20, 0,
+            -1, 12, -7, 2, 15, 30, 1).types(), set([datetime.datetime]))
 
         eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
             10, 30, 20, 0))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2010, 1, 12, 10, 30, 20, 0))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2010, 1, 12, 10, 30, 20, 0))
 
         eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
             10, 30, 20, 0, -1))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2009, 1, 12, 10, 30, 20, 0))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2009, 1, 12, 10, 30, 20, 0))
 
         eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
             10, 30, 20, 0, 0, 12))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2011, 1, 12, 10, 30, 20, 0))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2011, 1, 12, 10, 30, 20, 0))
 
         eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
             10, 30, 20, 0, 0, 0, -7))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2010, 1, 5, 10, 30, 20, 0))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2010, 1, 5, 10, 30, 20, 0))
 
         eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
             10, 30, 20, 0, 0, 0, 0, 12))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2010, 1, 12, 22, 30, 20, 0))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2010, 1, 12, 22, 30, 20, 0))
 
         eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
             10, 30, 20, 0, 0, 0, 0, 0, -30))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2010, 1, 12, 10, 0, 20, 0))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2010, 1, 12, 10, 0, 20, 0))
 
         eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
             10, 30, 20, 0, 0, 0, 0, 0, 0, 30))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2010, 1, 12, 10, 30, 50, 0))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2010, 1, 12, 10, 30, 50, 0))
 
         eval = pyson.PYSONEncoder().encode(pyson.DateTime(2010, 1, 12,
             10, 30, 20, 0, 0, 0, 0, 0, 0, 0, 200))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2010, 1, 12, 10, 30, 20, 200))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2010, 1, 12, 10, 30, 20, 200))
 
         eval = pyson.PYSONEncoder().encode(datetime.datetime(
             2010, 2, 22, 10, 30, 20, 200))
-        self.assert_(pyson.PYSONDecoder().decode(eval)
-                == datetime.datetime(2010, 2, 22, 10, 30, 20, 200))
+        self.assertEqual(pyson.PYSONDecoder().decode(eval),
+            datetime.datetime(2010, 2, 22, 10, 30, 20, 200))
+
+        self.assertEqual(repr(pyson.DateTime(2010, 1, 12, 10, 30, 20, 0,
+                    -1, 12, -7, 2, 15, 30, 1)),
+            'DateTime(2010, 1, 12, 10, 30, 20, 0, -1, 12, -7, 2, 15, 30, 1)')
 
     def test0140Len(self):
         'Test pyson.Len'
-        self.assert_(pyson.Len([1, 2, 3]).pyson() == {
+        self.assertEqual(pyson.Len([1, 2, 3]).pyson(), {
                 '__class__': 'Len',
                 'v': [1, 2, 3],
                 })
 
         self.assertRaises(AssertionError, pyson.Len, object())
 
-        self.assert_(pyson.Len([1, 2, 3]).types() == set([int, long]))
+        self.assertEqual(pyson.Len([1, 2, 3]).types(), set([int]))
 
         eval = pyson.PYSONEncoder().encode(pyson.Len([1, 2, 3]))
         self.assertEqual(pyson.PYSONDecoder().decode(eval), 3)
@@ -516,15 +553,44 @@ class PYSONTestCase(unittest.TestCase):
         eval = pyson.PYSONEncoder().encode(pyson.Len('foo bar'))
         self.assertEqual(pyson.PYSONDecoder().decode(eval), 7)
 
+        self.assertEqual(repr(pyson.Len([1, 2, 3])), 'Len([1, 2, 3])')
+
     def test0900Composite(self):
         'Test Composite'
-        eval = pyson.PYSONEncoder().encode(['id', pyson.If(pyson.Not(
-            pyson.In('company', pyson.Eval('context', {}))), '=', '!='),
+        expr = pyson.If(pyson.Not(
+                pyson.In('company', pyson.Eval('context', {}))), '=', '!=')
+        eval = pyson.PYSONEncoder().encode(['id', expr,
             pyson.Get(pyson.Eval('context', {}), 'company', -1)])
-        self.assert_(pyson.PYSONDecoder({'context': {'company': 1}}
-            ).decode(eval) == ['id', '!=', 1])
-        self.assert_(pyson.PYSONDecoder({'context': {}}
-            ).decode(eval) == ['id', '=', -1])
+        self.assertEqual(pyson.PYSONDecoder({'context': {'company': 1}}
+            ).decode(eval), ['id', '!=', 1])
+        self.assertEqual(pyson.PYSONDecoder({'context': {}}
+            ).decode(eval), ['id', '=', -1])
+
+        self.assertEqual(repr(expr),
+            "If(Not(In('company', Eval('context', {}))), '=', '!=')")
+
+    def test_noeval(self):
+        decoder = pyson.PYSONDecoder(noeval=True)
+        encoder = pyson.PYSONEncoder()
+
+        for instance in [
+                pyson.Eval('test', 0),
+                pyson.Not(True),
+                pyson.Bool('test'),
+                pyson.And(True, False, True),
+                pyson.Or(False, True, True),
+                pyson.Equal('foo', 'bar'),
+                pyson.Greater(1, 0),
+                pyson.Less(0, 1),
+                pyson.If(True, 'foo', 'bar'),
+                pyson.Get({'foo': 'bar'}, 'foo', 'default'),
+                pyson.In('foo', ['foo', 'bar']),
+                pyson.Date(),
+                pyson.DateTime(),
+                pyson.Len([1, 2, 3]),
+                ]:
+            self.assertEqual(decoder.decode(encoder.encode(instance)).pyson(),
+                instance.pyson())
 
 
 def suite():
diff --git a/trytond/tests/test_res.py b/trytond/tests/test_res.py
new file mode 100644
index 0000000..46db413
--- /dev/null
+++ b/trytond/tests/test_res.py
@@ -0,0 +1,14 @@
+# 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 unittest
+
+from .test_tryton import ModuleTestCase
+
+
+class ResTestCase(ModuleTestCase):
+    'Test res module'
+    module = 'res'
+
+
+def suite():
+    return unittest.TestLoader().loadTestsFromTestCase(ResTestCase)
diff --git a/trytond/tests/test_sequence.py b/trytond/tests/test_sequence.py
index 802372d..e4aa58f 100644
--- a/trytond/tests/test_sequence.py
+++ b/trytond/tests/test_sequence.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 import datetime
 from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
diff --git a/trytond/tests/test_tools.py b/trytond/tests/test_tools.py
index 3a5c3ad..28d77e8 100644
--- a/trytond/tests/test_tools.py
+++ b/trytond/tests/test_tools.py
@@ -1,14 +1,15 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
+import doctest
 import datetime
 import sql
 import sql.operators
 
-from trytond.tools import reduce_ids, safe_eval, datetime_strftime, \
-    reduce_domain
+from trytond.tools import reduce_ids, datetime_strftime, \
+    reduce_domain, decimal_, is_instance_method
 
 
 class ToolsTestCase(unittest.TestCase):
@@ -58,24 +59,6 @@ class ToolsTestCase(unittest.TestCase):
                 | (self.table.id.in_([15.0, 18.0, 19.0, 21.0]))))
         self.assertRaises(AssertionError, reduce_ids, self.table.id, [1.1])
 
-    def test0060safe_eval_builtin(self):
-        'Attempt to access a unsafe builtin'
-        self.assertRaises(NameError, safe_eval, "open('test.txt', 'w')")
-
-    def test0061safe_eval_getattr(self):
-        'Attempt to get arround direct attr access'
-        self.assertRaises(NameError, safe_eval, "getattr(int, 'real')")
-
-    def test0062safe_eval_func_globals(self):
-        'Attempt to access global enviroment where fun was defined'
-        self.assertRaises(SyntaxError, safe_eval,
-                "def x(): pass; print x.func_globals")
-
-    def test0063safe_eval_lowlevel(self):
-        "Lowlevel tricks to access 'object'"
-        self.assertRaises(ValueError, safe_eval,
-                "().__class__.mro()[1].__subclasses__()")
-
     def test0070datetime_strftime(self):
         'Test datetime_strftime'
         self.assert_(datetime_strftime(datetime.date(2005, 3, 2),
@@ -111,10 +94,31 @@ class ToolsTestCase(unittest.TestCase):
             self.assertEqual(reduce_domain(i), j,
                     '%s -> %s != %s' % (i, reduce_domain(i), j))
 
+    def test_is_instance_method(self):
+        'Test is_instance_method'
+
+        class Foo(object):
+
+            @staticmethod
+            def static():
+                pass
+
+            @classmethod
+            def klass(cls):
+                pass
+
+            def instance(self):
+                pass
+
+        self.assertFalse(is_instance_method(Foo, 'static'))
+        self.assertFalse(is_instance_method(Foo, 'klass'))
+        self.assertTrue(is_instance_method(Foo, 'instance'))
+
 
 def suite():
     func = unittest.TestLoader().loadTestsFromTestCase
     suite = unittest.TestSuite()
     for testcase in (ToolsTestCase,):
         suite.addTests(func(testcase))
+    suite.addTest(doctest.DocTestSuite(decimal_))
     return suite
diff --git a/trytond/tests/test_transaction.py b/trytond/tests/test_transaction.py
index 311d806..10aef42 100644
--- a/trytond/tests/test_transaction.py
+++ b/trytond/tests/test_transaction.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 from trytond.tests.test_tryton import DB_NAME, USER, CONTEXT, install_module
 from trytond.transaction import Transaction
diff --git a/trytond/tests/test_trigger.py b/trytond/tests/test_trigger.py
index 200aedd..4f24741 100644
--- a/trytond/tests/test_trigger.py
+++ b/trytond/tests/test_trigger.py
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 import time
-from xmlrpclib import MAXINT
+import datetime
 from itertools import combinations
 
 from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
@@ -11,6 +11,7 @@ from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
 from trytond.tests.trigger import TRIGGER_LOGS
 from trytond.transaction import Transaction
 from trytond.exceptions import UserError
+from trytond.pyson import PYSONEncoder, Eval
 
 
 class TriggerTestCase(unittest.TestCase):
@@ -37,7 +38,7 @@ class TriggerTestCase(unittest.TestCase):
                 'name': 'Test',
                 'model': model.id,
                 'on_time': True,
-                'condition': 'True',
+                'condition': 'true',
                 'action_model': action_model.id,
                 'action_function': 'test',
                 }
@@ -80,7 +81,7 @@ class TriggerTestCase(unittest.TestCase):
                         'name': 'Test',
                         'model': model.id,
                         'on_create': True,
-                        'condition': 'True',
+                        'condition': 'true',
                         'action_model': action_model.id,
                         'action_function': 'trigger',
                         }])
@@ -93,8 +94,10 @@ class TriggerTestCase(unittest.TestCase):
             TRIGGER_LOGS.pop()
 
             # Trigger with condition
+            condition = PYSONEncoder().encode(
+                Eval('self', {}).get('name') == 'Bar')
             self.trigger.write([trigger], {
-                    'condition': 'self.name == "Bar"',
+                    'condition': condition,
                     })
 
             # Matching condition
@@ -112,7 +115,7 @@ class TriggerTestCase(unittest.TestCase):
 
             # With limit number
             self.trigger.write([trigger], {
-                    'condition': 'True',
+                    'condition': 'true',
                     'limit_number': 1,
                     })
             triggered, = self.triggered.create([{
@@ -124,7 +127,7 @@ class TriggerTestCase(unittest.TestCase):
             # With minimum delay
             self.trigger.write([trigger], {
                     'limit_number': 0,
-                    'minimum_delay': 1,
+                    'minimum_time_delay': datetime.timedelta(hours=1),
                     })
             triggered, = self.triggered.create([{
                         'name': 'Test',
@@ -151,7 +154,7 @@ class TriggerTestCase(unittest.TestCase):
                         'name': 'Test',
                         'model': model.id,
                         'on_write': True,
-                        'condition': 'True',
+                        'condition': 'true',
                         'action_model': action_model.id,
                         'action_function': 'trigger',
                         }])
@@ -166,8 +169,10 @@ class TriggerTestCase(unittest.TestCase):
             self.assertEqual(TRIGGER_LOGS, [])
 
             # Trigger with condition
+            condition = PYSONEncoder().encode(
+                Eval('self', {}).get('name') == 'Bar')
             self.trigger.write([trigger], {
-                    'condition': 'self.name == "Bar"',
+                    'condition': condition,
                     })
 
             # Matching condition
@@ -190,8 +195,10 @@ class TriggerTestCase(unittest.TestCase):
             self.assertEqual(TRIGGER_LOGS, [])
 
             # With limit number
+            condition = PYSONEncoder().encode(
+                Eval('self', {}).get('name') == 'Bar')
             self.trigger.write([trigger], {
-                    'condition': 'self.name == "Bar"',
+                    'condition': condition,
                     'limit_number': 1,
                     })
             triggered, = self.triggered.create([{
@@ -212,7 +219,7 @@ class TriggerTestCase(unittest.TestCase):
             # With minimum delay
             self.trigger.write([trigger], {
                     'limit_number': 0,
-                    'minimum_delay': MAXINT,
+                    'minimum_time_delay': datetime.timedelta.max,
                     })
             triggered, = self.triggered.create([{
                         'name': 'Foo',
@@ -225,7 +232,7 @@ class TriggerTestCase(unittest.TestCase):
             TRIGGER_LOGS.pop()
 
             self.trigger.write([trigger], {
-                    'minimum_delay': 0.02,
+                    'minimum_time_delay': datetime.timedelta(seconds=1),
                     })
             triggered, = self.triggered.create([{
                         'name': 'Foo',
@@ -266,7 +273,7 @@ class TriggerTestCase(unittest.TestCase):
                         'name': 'Test',
                         'model': model.id,
                         'on_delete': True,
-                        'condition': 'True',
+                        'condition': 'true',
                         'action_model': action_model.id,
                         'action_function': 'trigger',
                         }])
@@ -277,8 +284,10 @@ class TriggerTestCase(unittest.TestCase):
             Transaction().delete = {}
 
             # Trigger with condition
+            condition = PYSONEncoder().encode(
+                Eval('self', {}).get('name') == 'Bar')
             self.trigger.write([trigger], {
-                    'condition': 'self.name == "Bar"',
+                    'condition': condition,
                     })
 
             triggered, = self.triggered.create([{
@@ -306,7 +315,7 @@ class TriggerTestCase(unittest.TestCase):
 
             # With limit number
             self.trigger.write([trigger], {
-                    'condition': 'True',
+                    'condition': 'true',
                     'limit_number': 1,
                     })
             self.triggered.delete([triggered])
@@ -325,7 +334,7 @@ class TriggerTestCase(unittest.TestCase):
             # With minimum delay
             self.trigger.write([trigger], {
                     'limit_number': 0,
-                    'minimum_delay': 1,
+                    'minimum_time_delay': datetime.timedelta(hours=1),
                     })
             self.triggered.delete([triggered])
             self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
@@ -351,7 +360,7 @@ class TriggerTestCase(unittest.TestCase):
                         'name': 'Test',
                         'model': model.id,
                         'on_time': True,
-                        'condition': 'True',
+                        'condition': 'true',
                         'action_model': action_model.id,
                         'action_function': 'trigger',
                         }])
@@ -364,8 +373,10 @@ class TriggerTestCase(unittest.TestCase):
             TRIGGER_LOGS.pop()
 
             # Trigger with condition
+            condition = PYSONEncoder().encode(
+                Eval('self', {}).get('name') == 'Bar')
             self.trigger.write([trigger], {
-                    'condition': 'self.name == "Bar"',
+                    'condition': condition,
                     })
 
             # Matching condition
@@ -385,7 +396,7 @@ class TriggerTestCase(unittest.TestCase):
 
             # With limit number
             self.trigger.write([trigger], {
-                    'condition': 'True',
+                    'condition': 'true',
                     'limit_number': 1,
                     })
             self.trigger.trigger_time()
@@ -401,7 +412,7 @@ class TriggerTestCase(unittest.TestCase):
             # With minimum delay
             self.trigger.write([trigger], {
                     'limit_number': 0,
-                    'minimum_delay': MAXINT,
+                    'minimum_time_delay': datetime.timedelta.max,
                     })
             self.trigger.trigger_time()
             self.trigger.trigger_time()
@@ -415,7 +426,7 @@ class TriggerTestCase(unittest.TestCase):
                         ]))
 
             self.trigger.write([trigger], {
-                    'minimum_delay': 0.02,
+                    'minimum_time_delay': datetime.timedelta(seconds=1),
                     })
             self.trigger.trigger_time()
             time.sleep(1.2)
diff --git a/trytond/tests/test_tryton.py b/trytond/tests/test_tryton.py
index 94bfab9..ec91336 100644
--- a/trytond/tests/test_tryton.py
+++ b/trytond/tests/test_tryton.py
@@ -1,22 +1,25 @@
 # -*- coding: utf-8 -*-
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 os
 import sys
 import unittest
 import doctest
+from itertools import chain
+import operator
 from lxml import etree
 
 from trytond.pool import Pool
 from trytond import backend
+from trytond.model import Workflow
+from trytond.model.fields import get_eval_fields
 from trytond.protocols.dispatcher import create, drop
+from trytond.tools import is_instance_method
 from trytond.transaction import Transaction
-from trytond.pyson import PYSONEncoder, Eval
-from trytond.exceptions import UserError
 from trytond import security
 
 __all__ = ['POOL', 'DB_NAME', 'USER', 'USER_PASSWORD', 'CONTEXT',
-    'install_module', 'test_view', 'test_depends',
+    'install_module', 'ModuleTestCase',
     'doctest_setup', 'doctest_teardown',
     'suite', 'all_suite', 'modules_suite']
 
@@ -31,45 +34,6 @@ POOL = Pool(DB_NAME)
 security.check_super = lambda *a, **k: True
 
 
-class ModelViewTestCase(unittest.TestCase):
-    'Test ModelView'
-
-    def setUp(self):
-        install_module('ir')
-        install_module('res')
-        install_module('webdav')
-
-    def test0000test(self):
-        'Test test'
-        self.assertRaises(UserError, install_module, 'nosuchmodule')
-        self.assertRaises(UserError, test_view, 'nosuchmodule')
-
-    def test0010ir(self):
-        'Test ir'
-        test_view('ir')
-
-    def test0020res(self):
-        'Test res'
-        test_view('res')
-
-    def test0040webdav(self):
-        'Test webdav'
-        test_view('webdav')
-
-
-class FieldDependsTestCase(unittest.TestCase):
-    'Test Field depends'
-
-    def setUp(self):
-        install_module('ir')
-        install_module('res')
-        install_module('webdav')
-
-    def test0010depends(self):
-        'Test depends'
-        test_depends()
-
-
 def install_module(name):
     '''
     Install module for the tested database
@@ -82,7 +46,7 @@ def install_module(name):
         modules = Module.search([
                 ('name', '=', name),
                 ])
-        assert modules
+        assert modules, "%s not found" % name
 
         modules = Module.search([
                 ('name', '=', name),
@@ -104,73 +68,152 @@ def install_module(name):
         transaction.cursor.commit()
 
 
-def test_view(module_name):
-    '''
-    Test validity of all views of the module
-    '''
-    with Transaction().start(DB_NAME, USER,
-            context=CONTEXT) as transaction:
-        View = POOL.get('ir.ui.view')
-        views = View.search([
-                ('module', '=', module_name),
-                ('model', '!=', ''),
-                ])
-        assert views, "No views for %s" % module_name
-        for view in views:
-            view_id = view.inherit and view.inherit.id or view.id
-            model = view.model
-            Model = POOL.get(model)
-            res = Model.fields_view_get(view_id)
-            assert res['model'] == model
-            tree = etree.fromstring(res['arch'])
-            tree_root = tree.getroottree().getroot()
-
-            for element in tree_root.iter():
-                if element.tag in ('field', 'label', 'separator', 'group'):
-                    for attr in ('name', 'icon'):
-                        field = element.get(attr)
-                        if field:
-                            assert field in res['fields'], ('Missing field: %s'
-                                % field)
-        transaction.cursor.rollback()
-
-
-def test_depends():
-    '''
-    Test for missing depends
-    '''
-    class Encoder(PYSONEncoder):
-
-        def __init__(self, *args, **kwargs):
-            super(Encoder, self).__init__(*args, **kwargs)
-            self.fields = set()
-
-        def default(self, obj):
-            if isinstance(obj, Eval):
-                fname = obj._value
-                if not fname.startswith('_parent_'):
-                    self.fields.add(fname)
-            return super(Encoder, self).default(obj)
-
-    with Transaction().start(DB_NAME, USER, context=CONTEXT):
-        for mname, model in Pool().iterobject():
-            for fname, field in model._fields.iteritems():
-                encoder = Encoder()
-                encoder.encode(field.domain)
-                if hasattr(field, 'digits'):
-                    encoder.encode(field.digits)
-                if hasattr(field, 'add_remove'):
-                    encoder.encode(field.add_remove)
-                encoder.fields.discard(fname)
-                encoder.fields.discard('context')
-                encoder.fields.discard('_user')
-                depends = set(field.depends)
-                assert encoder.fields <= depends, (
-                    'Missing depends %s in "%s"."%s"' % (
-                        list(encoder.fields - depends), mname, fname))
-                assert depends <= set(model._fields), (
-                    'Unknown depends %s in "%s"."%s"' % (
-                        list(depends - set(model._fields)), mname, fname))
+class ModuleTestCase(unittest.TestCase):
+    'Trytond Test Case'
+    module = None
+
+    def setUp(self):
+        install_module(self.module)
+
+    def test_rec_name(self):
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            for mname, model in Pool().iterobject():
+                # Don't test model not registered by the module
+                if not any(issubclass(model, cls) for cls in
+                        Pool().classes['model'].get(self.module, [])):
+                    continue
+                # Skip testing default value even if the field doesn't exist
+                # as there is a fallback to id
+                if model._rec_name == 'name':
+                    continue
+                assert model._rec_name in model._fields, (
+                    'Wrong _rec_name "%s" for %s'
+                    % (model._rec_name, mname))
+
+    def test_view(self):
+        'Test validity of all views of the module'
+        with Transaction().start(DB_NAME, USER,
+                context=CONTEXT) as transaction:
+            View = POOL.get('ir.ui.view')
+            views = View.search([
+                    ('module', '=', self.module),
+                    ('model', '!=', ''),
+                    ])
+            for view in views:
+                view_id = view.inherit and view.inherit.id or view.id
+                model = view.model
+                Model = POOL.get(model)
+                res = Model.fields_view_get(view_id)
+                assert res['model'] == model
+                tree = etree.fromstring(res['arch'])
+                tree_root = tree.getroottree().getroot()
+
+                for element in tree_root.iter():
+                    if element.tag in ('field', 'label', 'separator', 'group'):
+                        for attr in ('name', 'icon'):
+                            field = element.get(attr)
+                            if field:
+                                assert field in res['fields'], (
+                                    'Missing field: %s' % field)
+            transaction.cursor.rollback()
+
+    def test_depends(self):
+        'Test for missing depends'
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            for mname, model in Pool().iterobject():
+                # Don't test model not registered by the module
+                if not any(issubclass(model, cls) for cls in
+                        Pool().classes['model'].get(self.module, [])):
+                    continue
+                for fname, field in model._fields.iteritems():
+                    fields = set()
+                    fields |= get_eval_fields(field.domain)
+                    if hasattr(field, 'digits'):
+                        fields |= get_eval_fields(field.digits)
+                    if hasattr(field, 'add_remove'):
+                        fields |= get_eval_fields(field.add_remove)
+                    fields.discard(fname)
+                    fields.discard('context')
+                    fields.discard('_user')
+                    depends = set(field.depends)
+                    assert fields <= depends, (
+                        'Missing depends %s in "%s"."%s"' % (
+                            list(fields - depends), mname, fname))
+                    assert depends <= set(model._fields), (
+                        'Unknown depends %s in "%s"."%s"' % (
+                            list(depends - set(model._fields)), mname, fname))
+
+    def test_menu_action(self):
+        'Test that menu actions are accessible to menu\'s group'
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            pool = Pool()
+            Menu = pool.get('ir.ui.menu')
+            ModelData = pool.get('ir.model.data')
+
+            module_menus = ModelData.search([
+                    ('model', '=', 'ir.ui.menu'),
+                    ('module', '=', self.module),
+                    ])
+            menus = Menu.browse([mm.db_id for mm in module_menus])
+            for menu, module_menu in zip(menus, module_menus):
+                if not menu.action_keywords:
+                    continue
+                menu_groups = set(menu.groups)
+                actions_groups = reduce(operator.or_,
+                    (set(k.action.groups) for k in menu.action_keywords
+                        if k.keyword == 'tree_open'))
+                if not actions_groups:
+                    continue
+                assert menu_groups <= actions_groups, (
+                    'Menu "%(menu_xml_id)s" actions are not accessible to '
+                    '%(groups)s' % {
+                        'menu_xml_id': module_menu.fs_id,
+                        'groups': ','.join(g.name
+                            for g in menu_groups - actions_groups),
+                        })
+
+    def test_model_access(self):
+        'Test missing default model access'
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            Access = POOL.get('ir.model.access')
+            no_groups = {a.model.name for a in Access.search([
+                        ('group', '=', None),
+                        ])}
+            with_groups = {a.model.name for a in Access.search([
+                        ('group', '!=', None),
+                        ])}
+
+            assert no_groups >= with_groups, (
+                'Model "%(models)s" are missing a default access' % {
+                    'models': list(with_groups - no_groups),
+                    })
+
+    def test_workflow_transitions(self):
+        'Test all workflow transitions exist'
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            for mname, model in Pool().iterobject():
+                # Don't test model not registered by the module
+                if not any(issubclass(model, cls) for cls in
+                        Pool().classes['model'].get(self.module, [])):
+                    continue
+                if not issubclass(model, Workflow):
+                    continue
+                field = getattr(model, model._transition_state)
+                if isinstance(field.selection, (tuple, list)):
+                    values = field.selection
+                else:
+                    # instance method may not return all the possible values
+                    if is_instance_method(model, field.selection):
+                        continue
+                    values = getattr(model, field.selection)()
+                states = set(dict(values))
+                transition_states = set(chain(*model._transitions))
+                assert transition_states <= states, (
+                    ('Unknown transition states "%(states)s" '
+                        'in model "%(model)s". ') % {
+                        'states': list(transition_states - states),
+                        'model': model.__name__,
+                        })
 
 
 def db_exist():
@@ -193,7 +236,7 @@ def drop_db():
 
 
 def drop_create():
-    if db_exist:
+    if db_exist():
         drop_db()
     create_db()
 
@@ -224,7 +267,7 @@ def all_suite(modules=None):
     return suite_
 
 
-def modules_suite(modules=None):
+def modules_suite(modules=None, doc=True):
     '''
     Return all tests suite of all modules
     '''
@@ -265,7 +308,8 @@ def modules_suite(modules=None):
     doc_tests = []
     for test in suite_:
         if isinstance(test, doctest.DocTestCase):
-            doc_tests.append(test)
+            if doc:
+                doc_tests.append(test)
         else:
             tests.append(test)
     tests.extend(doc_tests)
diff --git a/trytond/tests/test_union.py b/trytond/tests/test_union.py
index 723c38c..fd05707 100644
--- a/trytond/tests/test_union.py
+++ b/trytond/tests/test_union.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 unittest
 
 from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
diff --git a/trytond/tests/test_webdav.py b/trytond/tests/test_webdav.py
new file mode 100644
index 0000000..4957b36
--- /dev/null
+++ b/trytond/tests/test_webdav.py
@@ -0,0 +1,14 @@
+# 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 unittest
+
+from .test_tryton import ModuleTestCase
+
+
+class WebDAVTestCase(ModuleTestCase):
+    'Test ir module'
+    module = 'webdav'
+
+
+def suite():
+    return unittest.TestLoader().loadTestsFromTestCase(WebDAVTestCase)
diff --git a/trytond/tests/trigger.py b/trytond/tests/trigger.py
index 3ec71e6..977fbf9 100644
--- a/trytond/tests/trigger.py
+++ b/trytond/tests/trigger.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.model import ModelSQL, fields
 
 __all__ = [
diff --git a/trytond/tests/wizard.py b/trytond/tests/wizard.py
index 5ba5485..c4760b7 100644
--- a/trytond/tests/wizard.py
+++ b/trytond/tests/wizard.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 trytond.model import ModelView, ModelSQL, fields
 from trytond.wizard import Wizard, StateView, StateTransition, StateAction, \
     Button
@@ -15,7 +15,7 @@ class TestWizardStart(ModelSQL, ModelView):
     __name__ = 'test.test_wizard.start'
     name = fields.Char('Test me')
     user = fields.Many2One('res.user', 'User')
-    groups = fields.One2Many('res.group', None, 'Groups')
+    groups = fields.Many2Many('res.group', None, None, 'Groups')
 
     @staticmethod
     def default_user():
diff --git a/trytond/tools/StringMatcher.py b/trytond/tools/StringMatcher.py
index 2258a99..ff785f7 100644
--- a/trytond/tools/StringMatcher.py
+++ b/trytond/tools/StringMatcher.py
@@ -1,9 +1,9 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 
 # Code come from python-Levenshtein
 
-from Levenshtein import *
+from Levenshtein import opcodes, editops, matching_blocks, ratio, distance
 from warnings import warn
 
 
diff --git a/trytond/tools/__init__.py b/trytond/tools/__init__.py
index e209cff..c5c9170 100644
--- a/trytond/tools/__init__.py
+++ b/trytond/tools/__init__.py
@@ -1,7 +1,8 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .misc import *
 from .datetime_strftime import *
+from .decimal_ import *
 
 
 class ClassProperty(property):
diff --git a/trytond/tools/datetime_strftime.py b/trytond/tools/datetime_strftime.py
index 2f7f3d4..9bd01fc 100644
--- a/trytond/tools/datetime_strftime.py
+++ b/trytond/tools/datetime_strftime.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.
-#Copyright (c) 2002-2007 John D. Hunter; All Rights Reserved
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+# Copyright (c) 2002-2007 John D. Hunter; All Rights Reserved
 import time
 
 
diff --git a/trytond/tools/decimal_.py b/trytond/tools/decimal_.py
new file mode 100644
index 0000000..ee156c0
--- /dev/null
+++ b/trytond/tools/decimal_.py
@@ -0,0 +1,36 @@
+# 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 tokenize
+from io import StringIO
+
+# code snippet taken from http://docs.python.org/library/tokenize.html
+
+
+def decistmt(s):
+    """Substitute Decimals for floats in a string of statements.
+
+    >>> from decimal import Decimal
+    >>> s = 'print +21.3e-5*-.1234/81.7'
+    >>> decistmt(s)
+    u"print +Decimal (u'21.3e-5')*-Decimal (u'.1234')/Decimal (u'81.7')"
+
+    >>> exec(s)
+    -3.21716034272e-07
+    >>> exec(decistmt(s))
+    -3.217160342717258261933904529E-7
+    """
+    result = []
+    # tokenize the string
+    g = tokenize.generate_tokens(StringIO(s.decode('utf-8')).readline)
+    for toknum, tokval, _, _, _ in g:
+        # replace NUMBER tokens
+        if toknum == tokenize.NUMBER and '.' in tokval:
+            result.extend([
+                (tokenize.NAME, 'Decimal'),
+                (tokenize.OP, '('),
+                (tokenize.STRING, repr(tokval)),
+                (tokenize.OP, ')')
+            ])
+        else:
+            result.append((toknum, tokval))
+    return tokenize.untokenize(result)
diff --git a/trytond/tools/misc.py b/trytond/tools/misc.py
index ebe7c66..a590395 100644
--- a/trytond/tools/misc.py
+++ b/trytond/tools/misc.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.
 # -*- coding: utf-8 -*-
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 """
 Miscelleanous tools used by tryton
 """
@@ -9,10 +9,10 @@ import sys
 import subprocess
 from threading import local
 import smtplib
-import dis
-from decimal import Decimal
 from array import array
 from itertools import islice
+import types
+
 from sql import Literal
 from sql.operators import Or
 
@@ -333,58 +333,6 @@ def reduce_ids(field, ids):
         sql.append(field.in_(discontinue_list))
     return sql
 
-_ALLOWED_CODES = set(dis.opmap[x] for x in [
-        'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', 'BUILD_LIST',
-        'BUILD_MAP', 'BUILD_TUPLE', 'LOAD_CONST', 'RETURN_VALUE',
-        'STORE_SUBSCR', 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
-        'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY', 'BINARY_DIVIDE',
-        'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE', 'BINARY_MODULO',
-        'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_LSHIFT', 'BINARY_RSHIFT',
-        'BINARY_AND', 'BINARY_XOR', 'BINARY_OR', 'STORE_MAP', 'LOAD_NAME',
-        'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR', 'STORE_NAME', 'GET_ITER',
-        'FOR_ITER', 'LIST_APPEND', 'JUMP_ABSOLUTE', 'DELETE_NAME',
-        'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_IF_FALSE_OR_POP',
-        'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE', 'POP_JUMP_IF_TRUE',
-        'BINARY_SUBSCR', 'JUMP_FORWARD',
-        ] if x in dis.opmap)
-
-
- at memoize(1000)
-def _compile_source(source):
-    comp = compile(source, '', 'eval')
-    codes = []
-    co_code = comp.co_code
-    i = 0
-    while i < len(co_code):
-        code = ord(co_code[i])
-        codes.append(code)
-        if code >= dis.HAVE_ARGUMENT:
-            i += 3
-        else:
-            i += 1
-    for code in codes:
-        if code not in _ALLOWED_CODES:
-            raise ValueError('opcode %s not allowed' % dis.opname[code])
-    return comp
-
-
-def safe_eval(source, data=None):
-    if '__' in source:
-        raise ValueError('Double underscores not allowed')
-
-    comp = _compile_source(source)
-    return eval(comp, {'__builtins__': {
-        'True': True,
-        'False': False,
-        'str': str,
-        'globals': locals,
-        'locals': locals,
-        'bool': bool,
-        'dict': dict,
-        'round': round,
-        'Decimal': Decimal,
-        }}, data)
-
 
 def reduce_domain(domain):
     '''
@@ -402,10 +350,10 @@ def reduce_domain(domain):
                 (isinstance(arg, list) and
                     len(arg) > 2 and
                     arg[1] in OPERATORS)):
-            #clause
+            # clause
             result.append(arg)
         elif isinstance(arg, list) and arg:
-            #sub-domain
+            # sub-domain
             sub_domain = reduce_domain(arg)
             sub_operator = sub_domain[0]
             if sub_operator == operator:
@@ -423,3 +371,10 @@ def grouped_slice(records, count=None):
         count = Transaction().cursor.IN_MAX
     for i in xrange(0, len(records), count):
         yield islice(records, i, i + count)
+
+
+def is_instance_method(cls, method):
+    for klass in cls.__mro__:
+        type_ = klass.__dict__.get(method)
+        if type_ is not None:
+            return isinstance(type_, types.FunctionType)
diff --git a/trytond/tools/singleton.py b/trytond/tools/singleton.py
index 78de8f0..0066990 100644
--- a/trytond/tools/singleton.py
+++ b/trytond/tools/singleton.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 
 
 class Singleton(type):
diff --git a/trytond/transaction.py b/trytond/transaction.py
index 39b5aab..ef4ca63 100644
--- a/trytond/transaction.py
+++ b/trytond/transaction.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 threading import local
 from sql import Flavor
 
diff --git a/trytond/url.py b/trytond/url.py
index be17878..2e40fad 100644
--- a/trytond/url.py
+++ b/trytond/url.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 encodings.idna
 import urllib
diff --git a/trytond/version.py b/trytond/version.py
deleted file mode 100644
index d6fb88c..0000000
--- a/trytond/version.py
+++ /dev/null
@@ -1,6 +0,0 @@
-#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 = "trytond"
-VERSION = "3.4.3"
-LICENSE = "GPL-3"
-WEBSITE = "http://www.tryton.org/"
diff --git a/trytond/webdav/__init__.py b/trytond/webdav/__init__.py
index 424fe2c..c1fe716 100644
--- a/trytond/webdav/__init__.py
+++ b/trytond/webdav/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 ..pool import Pool
 from .webdav import *
 
diff --git a/trytond/webdav/locale/bg_BG.po b/trytond/webdav/locale/bg_BG.po
index 1703c6f..3c7cb47 100644
--- a/trytond/webdav/locale/bg_BG.po
+++ b/trytond/webdav/locale/bg_BG.po
@@ -4,7 +4,7 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
 
diff --git a/trytond/webdav/locale/ca_ES.po b/trytond/webdav/locale/ca_ES.po
index cd15ec2..8eff949 100644
--- a/trytond/webdav/locale/ca_ES.po
+++ b/trytond/webdav/locale/ca_ES.po
@@ -4,10 +4,10 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
-"No podeu crear un adjunt anomenat \"%(attachment)s al directori "
+"No podeu crear un adjunt anomenat \"%(attachment)s\" al directori "
 "\"%(collection)s\" perquè ja existeix un altre amb aquest nom."
 
 msgctxt "error:webdav.collection:"
diff --git a/trytond/webdav/locale/cs_CZ.po b/trytond/webdav/locale/cs_CZ.po
index eebd971..d437783 100644
--- a/trytond/webdav/locale/cs_CZ.po
+++ b/trytond/webdav/locale/cs_CZ.po
@@ -4,7 +4,7 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
 
diff --git a/trytond/webdav/locale/de_DE.po b/trytond/webdav/locale/de_DE.po
index bbfea9c..8335794 100644
--- a/trytond/webdav/locale/de_DE.po
+++ b/trytond/webdav/locale/de_DE.po
@@ -4,11 +4,11 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
-"Anhang \"%(attachment)s\" in Sammlung \"%(collection)s\" kann nicht angelegt"
-" werden, da es bereits eine Sammlung mit diesem Namen gibt."
+"Erstellung von Anhang \"%(attachment)s\" in Sammlung \"%(collection)s\" "
+"nicht möglich, weil es bereits eine Sammlung mit diesem Namen gibt"
 
 msgctxt "error:webdav.collection:"
 msgid "The collection name must be unique inside a collection!"
diff --git a/trytond/webdav/locale/es_AR.po b/trytond/webdav/locale/es_AR.po
index 593b79d..3174fa4 100644
--- a/trytond/webdav/locale/es_AR.po
+++ b/trytond/webdav/locale/es_AR.po
@@ -4,7 +4,7 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
 "No puede crear un archivo adjunto llamado «%(attachment)s» en la colección "
@@ -92,7 +92,7 @@ msgstr "Usuario creación"
 
 msgctxt "field:webdav.share,expiration_date:"
 msgid "Expiration Date"
-msgstr "Fecha expiración"
+msgstr "Fecha vencimiento"
 
 msgctxt "field:webdav.share,id:"
 msgid "ID"
diff --git a/trytond/webdav/locale/es_CO.po b/trytond/webdav/locale/es_CO.po
index c2d65ff..3e97429 100644
--- a/trytond/webdav/locale/es_CO.po
+++ b/trytond/webdav/locale/es_CO.po
@@ -4,11 +4,11 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
-"No puede crear un adjunto llamado \"%(attachment)s\" en la colección "
-"\"%(collections)s\" con el porque hay una colección con el ese nombre."
+"No puede crear un adjunto nombrado \"%(attachments)s\" en el repositorio "
+"\"%(collections)s\" porque hay actualmente un repositorio con ese nombre."
 
 msgctxt "error:webdav.collection:"
 msgid "The collection name must be unique inside a collection!"
diff --git a/trytond/webdav/locale/es_EC.po b/trytond/webdav/locale/es_EC.po
index cac92a0..eeadcc9 100644
--- a/trytond/webdav/locale/es_EC.po
+++ b/trytond/webdav/locale/es_EC.po
@@ -4,11 +4,11 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
 "No puede crear un adjunto llamado \"%(attachment)s\" en la colección "
-"\"%(collections)s\" porque ya existe una colección con este nombre."
+"\"%(collection)s\" porque ya existe una colección con ese nombre."
 
 msgctxt "error:webdav.collection:"
 msgid "The collection name must be unique inside a collection!"
@@ -28,7 +28,7 @@ msgstr "Ruta"
 
 msgctxt "field:ir.attachment,shares:"
 msgid "Shares"
-msgstr "Recursos Compartidos"
+msgstr "Recursos compartidos"
 
 msgctxt "field:ir.attachment,url:"
 msgid "URL"
@@ -40,15 +40,15 @@ msgstr "Hijos"
 
 msgctxt "field:webdav.collection,complete_name:"
 msgid "Complete Name"
-msgstr "Nombre Completo"
+msgstr "Nombre completo"
 
 msgctxt "field:webdav.collection,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:webdav.collection,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:webdav.collection,domain:"
 msgid "Domain"
@@ -76,23 +76,23 @@ msgstr "Nombre"
 
 msgctxt "field:webdav.collection,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:webdav.collection,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "field:webdav.share,create_date:"
 msgid "Create Date"
-msgstr "Fecha de Creación"
+msgstr "Fecha de creación"
 
 msgctxt "field:webdav.share,create_uid:"
 msgid "Create User"
-msgstr "Creado por Usuario"
+msgstr "Creado por usuario"
 
 msgctxt "field:webdav.share,expiration_date:"
 msgid "Expiration Date"
-msgstr "Fecha de Expiración"
+msgstr "Fecha de expiración"
 
 msgctxt "field:webdav.share,id:"
 msgid "ID"
@@ -124,11 +124,11 @@ msgstr "Usuario"
 
 msgctxt "field:webdav.share,write_date:"
 msgid "Write Date"
-msgstr "Fecha de Modificación"
+msgstr "Fecha de modificación"
 
 msgctxt "field:webdav.share,write_uid:"
 msgid "Write User"
-msgstr "Modificado por Usuario"
+msgstr "Modificado por usuario"
 
 msgctxt "model:ir.action,name:act_collection_list"
 msgid "Collections"
@@ -140,7 +140,7 @@ msgstr "Colecciones"
 
 msgctxt "model:ir.action,name:act_share_list"
 msgid "Shares"
-msgstr "Recursos Compartidos"
+msgstr "Recursos compartidos"
 
 msgctxt "model:ir.ui.menu,name:menu_collection_list"
 msgid "Collections"
@@ -152,7 +152,7 @@ msgstr "Colecciones"
 
 msgctxt "model:ir.ui.menu,name:menu_share_list"
 msgid "Shares"
-msgstr "Recursos Compartidos"
+msgstr "Recursos compartidos"
 
 msgctxt "model:ir.ui.menu,name:menu_webdav"
 msgid "WebDAV"
@@ -164,7 +164,7 @@ msgstr "Colección"
 
 msgctxt "model:webdav.share,name:"
 msgid "Share"
-msgstr "Recurso Compartido"
+msgstr "Recurso compartido"
 
 msgctxt "view:ir.attachment:"
 msgid "WebDAV"
@@ -180,8 +180,8 @@ msgstr "Colecciones"
 
 msgctxt "view:webdav.share:"
 msgid "Share"
-msgstr "Recurso Compartido"
+msgstr "Recurso compartido"
 
 msgctxt "view:webdav.share:"
 msgid "Shares"
-msgstr "Recursos Compartidos"
+msgstr "Recursos compartidos"
diff --git a/trytond/webdav/locale/es_ES.po b/trytond/webdav/locale/es_ES.po
index 3b7312a..2dbda01 100644
--- a/trytond/webdav/locale/es_ES.po
+++ b/trytond/webdav/locale/es_ES.po
@@ -4,11 +4,11 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
-"No puede crear el adjunto con el nombre \"%(attachment)s en el directorio "
-"\"%(collection)s\" porque ya existe otro con este nombre."
+"No puede crear un adjunto llamado \"%(attachment)s\" en el directorio "
+"\"%(collection)s\" porque ya existe un directorio con este nombre."
 
 msgctxt "error:webdav.collection:"
 msgid "The collection name must be unique inside a collection!"
diff --git a/trytond/webdav/locale/fr_FR.po b/trytond/webdav/locale/fr_FR.po
index 1d0062d..512036e 100644
--- a/trytond/webdav/locale/fr_FR.po
+++ b/trytond/webdav/locale/fr_FR.po
@@ -4,10 +4,10 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
-"Vous ne pouvez créer une pièce jointe nommée « %(attachment)s » dans la "
+"Vous ne pouvez pas créer un attachement nommé « %(attachment)s » dans la "
 "collection « %(collection)s » car il y a déjà une collection avec ce nom."
 
 msgctxt "error:webdav.collection:"
diff --git a/trytond/webdav/locale/nl_NL.po b/trytond/webdav/locale/nl_NL.po
index 91d0c52..139c028 100644
--- a/trytond/webdav/locale/nl_NL.po
+++ b/trytond/webdav/locale/nl_NL.po
@@ -4,7 +4,7 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
 
diff --git a/trytond/webdav/locale/ru_RU.po b/trytond/webdav/locale/ru_RU.po
index 153c248..8cda1ed 100644
--- a/trytond/webdav/locale/ru_RU.po
+++ b/trytond/webdav/locale/ru_RU.po
@@ -4,11 +4,9 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
-"Вы не можете создать вложение \"%(attachment)s\" в коллекции "
-"\"%(collection)s\" так как существует коллекция с таким именем."
 
 msgctxt "error:webdav.collection:"
 msgid "The collection name must be unique inside a collection!"
diff --git a/trytond/webdav/locale/sl_SI.po b/trytond/webdav/locale/sl_SI.po
index b1f2bb1..ffa39d4 100644
--- a/trytond/webdav/locale/sl_SI.po
+++ b/trytond/webdav/locale/sl_SI.po
@@ -4,11 +4,11 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
 
 msgctxt "error:ir.attachment:"
 msgid ""
-"You can not create an attachment named \"%(attachment)s in collection "
+"You can not create an attachment named \"%(attachment)s\" in collection "
 "\"%(collection)s\" because there is already a collection with that name."
 msgstr ""
-"Priloge z imenom \"%(attachment)s\" v zbirki \"%(collection)s\" ni možno "
-"izdelati, ker že obstaja zbirka z istim imenom."
+"Priloge \"%(attachment)s\" ni možno pripeti v zbirko \"%(collection)s\", ker"
+" že obstaja zbirka s tem imenom."
 
 msgctxt "error:webdav.collection:"
 msgid "The collection name must be unique inside a collection!"
diff --git a/trytond/webdav/webdav.py b/trytond/webdav/webdav.py
index 9817f49..f34561c 100644
--- a/trytond/webdav/webdav.py
+++ b/trytond/webdav/webdav.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 os
 import time
 import urllib
@@ -709,7 +709,7 @@ class Attachment(ModelSQL, ModelView):
         super(Attachment, cls).__setup__()
         cls._error_messages.update({
                 'collection_attachment_name': ('You can not create an '
-                    'attachment named "%(attachment)s in collection '
+                    'attachment named "%(attachment)s" in collection '
                     '"%(collection)s" because there is already a collection '
                     'with that name.')
                 })
diff --git a/trytond/webdav/webdav.xml b/trytond/webdav/webdav.xml
index 2a6ab87..5345da9 100644
--- a/trytond/webdav/webdav.xml
+++ b/trytond/webdav/webdav.xml
@@ -32,7 +32,7 @@ this repository contains the full copyright notices and license terms. -->
             <field name="name">Collections</field>
             <field name="type">ir.action.act_window</field>
             <field name="res_model">webdav.collection</field>
-            <field name="domain">[('parent', '=', None)]</field>
+            <field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
         </record>
         <record model="ir.action.act_window.view"
             id="act_collection_tree_view1">
@@ -116,7 +116,9 @@ this repository contains the full copyright notices and license terms. -->
             <field name="perm_delete" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_share">
-            <field name="domain">[('user', '=', user.id)]</field>
+            <field name="domain"
+                eval="[('user', '=', Eval('user', {}).get('id', -1))]"
+                pyson="1"/>
             <field name="rule_group" ref="rule_group_share"/>
         </record>
 
diff --git a/trytond/wizard/__init__.py b/trytond/wizard/__init__.py
index 47c219d..79a16bd 100644
--- a/trytond/wizard/__init__.py
+++ b/trytond/wizard/__init__.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# 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 .wizard import Wizard, StateView, StateTransition, StateAction, Button
 
 __all__ = ['Wizard', 'StateView', 'StateTransition', 'StateAction', 'Button']
diff --git a/trytond/wizard/wizard.py b/trytond/wizard/wizard.py
index e92c35c..6d92ed3 100644
--- a/trytond/wizard/wizard.py
+++ b/trytond/wizard/wizard.py
@@ -1,5 +1,5 @@
-#This file is part of Tryton.  The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
 
 __all__ = ['Wizard', 'StateView', 'StateTransition', 'StateAction', 'Button']
 
@@ -67,14 +67,17 @@ class StateView(State):
         assert len(self.buttons) == len(set(b.state for b in self.buttons))
         assert len([b for b in self.buttons if b.default]) <= 1
 
-    def get_view(self):
+    def get_view(self, wizard, state_name):
         '''
         Returns the view definition
         '''
         Model_ = Pool().get(self.model_name)
         ModelData = Pool().get('ir.model.data')
-        module, fs_id = self.view.split('.')
-        view_id = ModelData.get_id(module, fs_id)
+        if self.view:
+            module, fs_id = self.view.split('.')
+            view_id = ModelData.get_id(module, fs_id)
+        else:
+            view_id = None
         return Model_.fields_view_get(view_id=view_id, view_type='form')
 
     def get_defaults(self, wizard, state_name, fields):
@@ -256,7 +259,7 @@ class Wizard(WarningErrorMixin, URLMixin, PoolBase):
         result = {}
 
         if isinstance(state, StateView):
-            view = state.get_view()
+            view = state.get_view(self, state_name)
             defaults = state.get_defaults(self, state_name,
                 view['fields'].keys())
             buttons = state.get_buttons(self, state_name)
-- 
tryton-server



More information about the tryton-debian-vcs mailing list