[tryton-debian-vcs] tryton-server branch upstream updated. upstream/3.2.3-1-g61781f3
Mathias Behrle
tryton-debian-vcs at alioth.debian.org
Thu Oct 23 12:19:46 UTC 2014
The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/tryton-server.git;a=commitdiff;h=upstream/3.2.3-1-g61781f3
commit 61781f32a142e12d249411d63b65026bb1079865
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Tue Oct 21 11:29:26 2014 +0200
Adding upstream version 3.4.0.
Signed-off-by: Mathias Behrle <mathiasb at m9s.biz>
diff --git a/CHANGELOG b/CHANGELOG
index be6f898..6a2811c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,13 +1,28 @@
-Version 3.2.3 - 2014-09-29
+Version 3.4.0 - 2014-10-20
* Bug fixes (see mercurial logs for details)
* Use literal_eval instead of safe_eval (CVE-2014-6633)
* Prevent double underscore in safe_eval (CVE-2014-6633)
-
-Version 3.2.2 - 2014-08-03
-* Bug fixes (see mercurial logs for details)
-
-Version 3.2.1 - 2014-07-01
-* Bug fixes (see mercurial logs for details)
+* Add pre-validation on button
+* Model and Field access checked only if _check_access is set
+* Add check_access to RPC
+* Add check_access to Wizard and Report
+* Add support for domain_<field name> method
+* Refactor configuration file and command line
+* Use the context of the relation field for instanciation
+* Use a configuration field for logging
+* Add translated descriptor for Selection field
+* Add tree_state attribute on tree view
+* Allow to sync XML data
+* Remove on_change calls in Model.default_get
+* Add group call to on_change
+* Add UnionMixin
+* Allow to disable sorting of dictionary field's selection
+* Add active field to views of action window
+* Make global cache depends on explicit context keys
+* Don't add to global cache Binary fields
+* Add MatchMixin
+* Add image widget to tree
+* Remove context, current_date and time from record rule evaluation
Version 3.2.0 - 2014-04-21
* Bug fixes (see mercurial logs for details)
diff --git a/MANIFEST.in b/MANIFEST.in
index 15940c4..b5f249f 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -8,7 +8,6 @@ include doc/*
recursive-include doc *.rst
recursive-include doc *.po
recursive-include doc *.pot
-include etc/*
include trytond/backend/*/init.sql
include trytond/ir/tryton.cfg
include trytond/ir/*.xml
diff --git a/PKG-INFO b/PKG-INFO
index 40f97fa..c6ba62e 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,12 @@
Metadata-Version: 1.1
Name: trytond
-Version: 3.2.3
+Version: 3.4.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.2/
+Download-URL: http://downloads.tryton.org/3.4/
Description: trytond
=======
diff --git a/bin/trytond b/bin/trytond
index d8b84ef..6f03fe7 100755
--- a/bin/trytond
+++ b/bin/trytond
@@ -21,72 +21,39 @@ def parse_commandline():
parser.add_argument('--version', action='version',
version='%(prog)s ' + VERSION)
- parser.add_argument("-c", "--config", dest="config",
- help="specify config file")
- parser.add_argument('--debug', dest='debug_mode', action='store_true',
- help='enable debug mode (start post-mortem debugger if exceptions'
- ' occur)')
+ 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',
+ help='enable development mode')
parser.add_argument("-v", "--verbose", action="store_true",
dest="verbose", help="enable verbose mode")
- parser.add_argument("-d", "--database", dest="db_name",
- help="specify the database name")
- parser.add_argument("-i", "--init", dest="init",
- help="init a module (use \"all\" for all modules)")
- parser.add_argument("-u", "--update", dest="update",
- help="update a module (use \"all\" for all modules)")
+ parser.add_argument("-d", "--database", dest="database_names", nargs='+',
+ default=[], metavar='DATABASE', help="specify the database name")
+ parser.add_argument("-u", "--update", dest="update", nargs='+', default=[],
+ metavar='MODULE', help="update a module")
+ parser.add_argument("--all", dest="update", action="append_const",
+ const="ir", help="update all installed modules")
- parser.add_argument("--pidfile", dest="pidfile",
+ parser.add_argument("--pidfile", dest="pidfile", metavar='FILE',
help="file where the server pid will be stored")
- parser.add_argument("--logfile", dest="logfile",
- help="file where the server log will be stored")
- parser.add_argument("--disable-cron", dest="cron",
- action="store_false", help="disable cron")
-
- parser.epilog = ('The first time a database is initialized with "-i" admin'
- ' password is read from file defined by TRYTONPASSFILE '
- 'environment variable or interactively ask user. '
+ parser.add_argument("--logconf", dest="logconf", metavar='FILE',
+ help="logging configuration file (ConfigParser format)")
+ parser.add_argument("--cron", dest="cron", action="store_true",
+ help="enable cron")
+
+ parser.epilog = ('The first time a database is initialized admin '
+ 'password is read from file defined by TRYTONPASSFILE '
+ 'environment variable or interactively ask user.\n'
'The config file can be specified in the TRYTOND_CONFIG '
+ 'environment variable.\n'
+ 'The database URI can be specified in the TRYTOND_DATABASE_URI '
'environment variable.')
- opt = parser.parse_args()
-
- if opt.config:
- options['configfile'] = opt.config
- else:
- # No config file speficified, it will be guessed
- options['configfile'] = None
-
- for arg in (
- 'verbose',
- 'debug_mode',
- 'pidfile',
- 'logfile',
- 'cron',
- ):
- if getattr(opt, arg) is not None:
- options[arg] = getattr(opt, arg)
-
- db_name = []
- if opt.db_name:
- for i in opt.db_name.split(','):
- db_name.append(i)
- options['db_name'] = db_name
-
- init = {}
- if opt.init:
- for i in opt.init.split(','):
- if i != 'test':
- init[i] = 1
- options['init'] = init
-
- update = {}
- if opt.update:
- for i in opt.update.split(','):
- if i != 'test':
- update[i] = 1
- options['update'] = update
+ options = parser.parse_args()
+ if not options.database_names and options.update:
+ parser.error('Missing database option')
return options
diff --git a/doc/conf.py b/doc/conf.py
index 71c121e..18e62c7 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,9 +48,9 @@ copyright = (u'2008-2011, Bertrand Chenal, Cédric Krier, Ian Wilson, '
# built documents.
#
# The short X.Y version.
-version = '3.2'
+version = '3.4'
# The full version, including alpha/beta/rc tags.
-release = '3.2'
+release = '3.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/index.rst b/doc/index.rst
index aa1c79b..5ee2f0d 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -24,7 +24,9 @@ First steps
===========
* **Installation:**
- :ref:`Installation <topics-install>`
+ :ref:`Installation <topics-install>` |
+ :ref:`Configuration <topics-configuration>` |
+ :ref:`Setup a database <topics-setup-database>`
The model layer
===============
@@ -32,7 +34,8 @@ The model layer
* **Models:**
:ref:`Model syntax <topics-models>` |
:ref:`Field types <ref-models-fields>` |
- :ref:`Domain syntax <topics-domain>`
+ :ref:`Domain syntax <topics-domain>` |
+ :ref:`Access rights <topics-access_rights>`
The view layer
==============
diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst
index 85fd0ca..82b4b1e 100644
--- a/doc/ref/models/fields.rst
+++ b/doc/ref/models/fields.rst
@@ -129,8 +129,12 @@ client will also read these fields even if they are not defined on the view.
.. attribute:: Field.context
-A dictionary which will update the current context when opening a *relation
-field*.
+A dictionary which will update the current context for *relation field*.
+
+.. warning::
+ The context could only depend on direct field of the record and without
+ context.
+..
``loading``
-----------
@@ -184,6 +188,18 @@ Default value
See :ref:`default value <topics-fields_default_value>`
+Searching
+=========
+
+A class method could be defined for each field which must return a SQL
+expression for the given domain instead of the default one.
+The method signature is::
+
+ domain_<field name>(domain, tables)
+
+Where ``domain`` is the simple :ref:`domain <topics-domain>` clause and
+``tables`` is a nested dictionary, see :meth:`~Field.convert_domain`.
+
Ordering
========
@@ -413,6 +429,13 @@ A string field with limited values to choice.
If true, the human-readable values will be translated. Default value is
``True``.
+Instance methods:
+
+.. method:: Selection.translated([name])
+
+ Returns a descriptor for the translated value of the field. The descriptor
+ must be used on the same class as the field.
+
Reference
---------
@@ -600,6 +623,10 @@ This field accepts as written value a list of tuples like the :class:`One2Many`.
An integer or a PYSON expression denoting the maximum number of records
allowed in the relation.
+.. attribute:: Many2Many.add_remove
+
+ An alias to the :attr:`domain` for compatibility with the :class:`One2Many`.
+
Instance methods:
.. method:: Many2Many.get_target()
diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst
index 5ac7918..110636e 100644
--- a/doc/ref/models/models.rst
+++ b/doc/ref/models/models.rst
@@ -76,13 +76,12 @@ Class methods:
warning states by users.
..
-.. classmethod:: Model.default_get(fields_names[, with_rec_name[, with_on_change]])
+.. classmethod:: Model.default_get(fields_names[, with_rec_name])
- Return a dictionary with the default values for each field in
+ Returns a dictionary with the default values for each field in
``fields_names``. Default values are defined by the returned value of each
instance method with the pattern ``default_`field_name`()``.
``with_rec_name`` allow to add `rec_name` value for each many2one field.
- ``with_on_change`` allow to add ``on_change`` value for each default value.
.. classmethod:: Model.fields_get([fields_names])
@@ -90,6 +89,15 @@ Class methods:
Instance methods:
+.. method:: Model.on_change(fieldnames)
+
+ Returns the list of changes by calling `on_change` method of each field.
+
+.. method:: Model.on_change_with(fieldnames)
+
+ Returns the new values of all fields by calling `on_change_with` method of
+ each field.
+
.. method:: Model.pre_validate()
This method is called by the client to validate the instance.
@@ -301,6 +309,8 @@ CLass methods:
Return a list of list of values for each ``records``.
The list of values follows ``fields_names``.
Relational fields are defined with ``/`` at any depth.
+ Descriptor on fields are available by appending ``.`` and the name of the
+ method on the field that returns the descriptor.
.. classmethod:: ModelStorage.import_data(fields_names, data)
@@ -520,6 +530,10 @@ Class attributes are:
couple of key and label when the type is `selection`.
The format is a key/label separated by ":" per line.
+.. attribute:: DictSchemaMixin.selection_sorted
+
+ If the :attr:`selection` must be sorted on label by the client.
+
.. attribute:: DictSchemaMixin.selection_json
The definition of the :class:`trytond.model.fields.Function` field to
@@ -543,5 +557,58 @@ Instance methods:
Getter for the :attr:`selection_json`.
+==========
+MatchMixin
+==========
+
+.. class:: MatchMixin
+
+A mixin_ to add to a :class:`Model` a match method on pattern.
+The pattern is a dictionary with field name as key and the value to compare.
+The record matches the pattern if for all dictionary entries, the value of the
+record is equal or not defined.
+
+Instance methods:
+
+.. method:: MatchMixin.match(pattern)
+
+ Return if the instance match the pattern
+
+==========
+UnionMixin
+==========
+
+.. class:: UnionMixin
+
+A mixin_ to create a :class:`ModelSQL` which is the UNION_ of some
+:class:`ModelSQL`'s. The ids of each models are sharded to be unique.
+
+Static methods:
+
+.. staticmethod:: UnionMixin.union_models()
+
+ Return the list of :class:`ModelSQL`'s names
+
+Class methods:
+
+.. classmethod:: UnionMixin.union_shard(column, model)
+
+ Return a SQL expression that shards the column containing record id of
+ model name.
+
+.. classmethod:: UnionMixin.union_unshard(record_id)
+
+ Return the original instance of the record for the sharded id.
+
+.. classmethod:: UnionMixin.union_column(name, field, table, Model)
+
+ Return the SQL column that corresponds to the field on the union model.
+
+.. classmethod:: UnionMixin.union_columns(model)
+
+ Return the SQL table and columns to use for the UNION for the model name.
+
+
.. _mixin: http://en.wikipedia.org/wiki/Mixin
.. _JSON: http://en.wikipedia.org/wiki/Json
+.. _UNION: http://en.wikipedia.org/wiki/Union_(SQL)#UNION_operator
diff --git a/doc/ref/rpc.rst b/doc/ref/rpc.rst
index ba0e6f9..d34e26f 100644
--- a/doc/ref/rpc.rst
+++ b/doc/ref/rpc.rst
@@ -5,7 +5,7 @@
RPC
===
-.. class:: RPC([readonly[, instantiate[, result]]])
+.. class:: RPC([readonly[, instantiate[, result[, check_access]]]])
RPC is an object to define the behavior of Remote Procedur Call.
@@ -22,3 +22,8 @@ Instance attributes are:
.. attribute:: RPC.result
The function to transform the result
+
+.. attribute:: RPC.check_access
+
+ Set `_check_access` in the context to activate the access right on model
+ and field. Default is `True`.
diff --git a/doc/topics/access_rights.rst b/doc/topics/access_rights.rst
new file mode 100644
index 0000000..96c0d22
--- /dev/null
+++ b/doc/topics/access_rights.rst
@@ -0,0 +1,35 @@
+.. _topics-access_rights:
+
+=============
+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.
+
+Model Access
+============
+
+They are defined by records of `ir.model.access` which define for each couple
+of model and group, the read, write, create and delete permission. If any group
+of the user has the permission activated, then the user is granted this
+permission.
+
+Field Access
+============
+
+Same as for model access but applied on the field. It uses records of
+`ir.model.field.access`.
+
+Button
+======
+
+For each button of a model the records of 'ir.model.button` define the list of
+groups that are allowed to call it.
+
+Record Rule
+===========
+
+.. TODO
diff --git a/doc/topics/configuration.rst b/doc/topics/configuration.rst
new file mode 100644
index 0000000..a187c0b
--- /dev/null
+++ b/doc/topics/configuration.rst
@@ -0,0 +1,193 @@
+.. _topics-configuration:
+
+=============================
+Configuration file for Tryton
+=============================
+
+The configuration file control 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:
+
+ [database]
+ uri = postgresql://user:password@localhost/
+ path = /var/lib/trytond
+
+For more information see ConfigParser_.
+
+.. _ConfigParser: http://docs.python.org/2/library/configparser.html
+
+Sections
+========
+
+This section describes the different main sections that may appear in a Tryton
+configuration file, the purpose of each section, its possible keys, and their
+possible values.
+Some modules could request the usage of other sections for which the guideline
+asks them to be named like their module.
+
+jsonrpc
+-------
+
+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`.
+
+hostname
+~~~~~~~~
+
+Defines the hostname for this network interface.
+
+data
+~~~~
+
+Defines the root path to retrieve data for `GET` request.
+
+xmlrpc
+------
+
+Defines the behavior of the XML-RPC_ network interface.
+
+listen
+~~~~~~
+
+Same as for `jsonrpc` except it have no default value.
+
+webdav
+------
+
+Define the behavior of the WebDAV_ network interface.
+
+listen
+~~~~~~
+
+Same as for `jsonrpc` except it have no default value.
+
+database
+--------
+
+Defines how database is managed.
+
+uri
+~~~
+
+Contains the URI to connect to the SQL database. The URI follows the RFC-3986_.
+The typical form is:
+
+ database://username:password@host:port/
+
+The default available databases are:
+
+PostgreSQL
+**********
+
+`pyscopg2` supports two type of connections:
+
+ - TCP/IP connection: `postgresql://user:password@localhost:5432/`
+ - Unix domain connection: `postgresql://username:password@/`
+
+SQLite
+******
+
+The only possible URI is: `sqlite://`
+
+MySQL
+*****
+
+Same as for PostgreSQL.
+
+path
+~~~~
+
+The directory where Tryton should store files and so the user running `trytond`
+must have write access on this directory.
+The default value is `/var/lib/trytond/`.
+
+list
+~~~~
+
+A boolean value (default: `True`) to list available databases.
+
+retry
+~~~~~
+
+The number of retries when a database operation error occurs during a request.
+
+language
+~~~~~~~~
+
+The main language (default: `en_US`) of the database that will be stored in the
+main table for translatable fields.
+
+ssl
+---
+
+Activates the SSL_ on all network protocol.
+
+privatekey
+~~~~~~~~~~
+
+The path to the private key.
+
+certificate
+~~~~~~~~~~~
+
+The path to the certificate.
+
+email
+-----
+
+uri
+~~~
+
+The SMTP-URL_ to connect to the SMTP server which is extended to support SSL_
+and STARTTLS_.
+The available protocols are:
+
+ - `smtp`: simple SMTP
+ - `smtp+tls`: SMTP with STARTTLS
+ - `smtps`: SMTP with SSL
+
+The default value is: `smtp://localhost:25`
+
+from
+~~~~
+
+Defines the default `From` address when Tryton send emails.
+
+session
+-------
+
+timeout
+~~~~~~~
+
+The time in second before a session expires.
+
+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::
+
+ python -c 'import getpass,crypt,random,string; print crypt.crypt(getpass.getpass(), "".join(random.sample(string.ascii_letters + string.digits, 8)))'
+
+report
+------
+
+unoconv
+~~~~~~~
+
+The parameters for `unoconv`.
+
+.. _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
+.. _STARTTLS: http://en.wikipedia.org/wiki/STARTTLS
diff --git a/doc/topics/index.rst b/doc/topics/index.rst
index bae4107..939dacd 100644
--- a/doc/topics/index.rst
+++ b/doc/topics/index.rst
@@ -10,11 +10,15 @@ Introduction to all the key parts of trytond:
:maxdepth: 1
install
+ configuration
+ setup_database
+ logs
models/index
models/fields_default_value
models/fields_on_change
domain
pyson
+ access_rights
actions
views/index
views/extension
diff --git a/doc/topics/install.rst b/doc/topics/install.rst
index a5eb993..0836c30 100644
--- a/doc/topics/install.rst
+++ b/doc/topics/install.rst
@@ -8,7 +8,7 @@ Prerequisites
=============
* Python 2.7 or later (http://www.python.org/)
- * lxml 2.0 or later (http://codespeak.net/lxml/)
+ * lxml 2.0 or later (http://lxml.de/)
* relatorio 0.2.0 or later (http://code.google.com/p/python-relatorio/)
* python-dateutil (http://labix.org/python-dateutil)
* polib (https://bitbucket.org/izi/polib/wiki/Home)
diff --git a/doc/topics/logs.rst b/doc/topics/logs.rst
new file mode 100644
index 0000000..ccd4e6d
--- /dev/null
+++ b/doc/topics/logs.rst
@@ -0,0 +1,49 @@
+.. _topics-logs:
+
+=====================
+Logging configuration
+=====================
+
+Without any configuration, trytond write INFO messages to standard output.
+
+Logs can be configured using a `configparser-format`_ file. The filename can
+be specified using trytond ``logconf`` parameter.
+
+.. _`configparser-format`: https://docs.python.org/2/library/logging.config.html#configuration-file-format
+
+Example
+=======
+
+This example allows to write INFO messages on standard output and on a disk log
+file rotated every day.
+
+.. highlight:: ini
+
+::
+
+ [formatters]
+ keys: simple
+
+ [handlers]
+ keys: rotate, console
+
+ [loggers]
+ keys: root
+
+ [formatter_simple]
+ format: %(asctime)s] %(levelname)s:%(name)s:%(message)s
+ datefmt: %a %b %d %H:%M:%S %Y
+
+ [handler_rotate]
+ class: handlers.TimedRotatingFileHandler
+ args: ('/tmp/tryton.log', 'D', 1, 30)
+ formatter: simple
+
+ [handler_console]
+ class: StreamHandler
+ formatter: simple
+ args: (sys.stdout,)
+
+ [logger_root]
+ level: INFO
+ handlers: rotate, console
diff --git a/doc/topics/setup_database.rst b/doc/topics/setup_database.rst
new file mode 100644
index 0000000..c1fa543
--- /dev/null
+++ b/doc/topics/setup_database.rst
@@ -0,0 +1,26 @@
+.. _topics-setup-database:
+
+=======================
+How to setup a database
+=======================
+
+The database section of the `configuration <topics-configuration>` must be set
+before starting.
+
+Create a database
+=================
+
+Depending of the database backend choosen, you must create a database (see the
+documentation of the choosen backend). The user running `trytond` must be
+granted the priviledge to create tables. For backend that has the option, the
+encoding of the database must be set to `UTF-8`.
+
+Initialize a database
+=====================
+
+A database can be initialized using this command line::
+
+ trytond -c <config file> -d <database name> --all
+
+At the end of the process, `trytond` will ask to set the password for the
+`admin` user.
diff --git a/doc/topics/views/index.rst b/doc/topics/views/index.rst
index 04203c5..0f79504 100644
--- a/doc/topics/views/index.rst
+++ b/doc/topics/views/index.rst
@@ -113,6 +113,9 @@ List of attributes shared by many form elements:
* ``icon``: Only for button, it must return the icon name to use or
False.
+ * ``pre_validate``: Only for button, it contains a domain to apply
+ on the record before calling the button.
+
.. _common-attributes-help:
* ``help``: The string that will be displayed when the cursor hovers over
@@ -491,6 +494,9 @@ Each tree view must start with this tag.
* ``keyword_open``: A boolean to specify if the client should look for a
tree_open action on double click instead of switching view.
+ * ``tree_state``: A boolean to specify if the client should save the state
+ of the tree.
+
field
^^^^^
diff --git a/etc/trytond.conf b/etc/trytond.conf
deleted file mode 100644
index f70a6d6..0000000
--- a/etc/trytond.conf
+++ /dev/null
@@ -1,88 +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.
-[options]
-
-# Activate the json-rpc protocol
-jsonrpc = localhost:8000
-#ssl_jsonrpc = False
-
-# This is the hostname used when generating tryton URI
-#hostname_jsonrpc =
-
-# Configure the path of json-rpc data
-#jsondata_path = /var/www/localhost/tryton
-
-# Activate the xml-rpc protocol
-#xmlrpc = *:8069
-#ssl_xmlrpc = False
-
-# Activate the webdav protocol
-#webdav = *:8080
-#ssl_webdav = False
-
-# This is the hostname used when generating WebDAV URI
-#hostname_webdav =
-
-# Configure the database type
-# allowed values are postgresql, sqlite, mysql
-#db_type = postgresql
-
-# Configure the database connection
-## Note: Only databases owned by db_user will be displayed in the connection dialog
-## of the Tryton client. db_user must have create permission for new databases
-## to be able to use automatic database creation with the Tryton client.
-#db_host = False
-#db_port = False
-#db_user = False
-#db_password = False
-#db_minconn = 1
-#db_maxconn = 64
-
-# Configure the postgresql path for the executable
-#pg_path = None
-
-# Configure the Tryton server password
-#admin_passwd = admin
-
-# Configure the path of the files for the pid and the logs
-#pidfile = False
-#logfile = False
-
-#privatekey = server.pem
-#certificate = server.pem
-
-# Configure the SMTP connection
-#smtp_server = localhost
-#smtp_port = 25
-#smtp_ssl = False
-#smtp_tls = False
-#smtp_password = False
-#smtp_user = False
-#smtp_default_from_email = False
-
-# Configure the path to store attachments and sqlite database
-#data_path = /var/lib/trytond
-
-# Allow to run more than one instance of trytond
-#multi_server = False
-
-# Configure the session timeout (inactivity of the client in sec)
-#session_timeout = 600
-
-# Enable auto-reload of modules if changed
-#auto_reload = True
-
-# Prevent database listing
-#prevent_dblist = False
-
-# Enable cron
-# cron = True
-
-# unoconv connection
-#unoconv = pipe,name=trytond;urp;StarOffice.ComponentContext
-
-# Number of retries on database operational error
-# retry = 5
-
-# Default language code
-# language = en_US
diff --git a/setup.py b/setup.py
index 793dac8..8fee48d 100644
--- a/setup.py
+++ b/setup.py
@@ -91,4 +91,5 @@ setup(name=PACKAGE,
zip_safe=False,
test_suite='trytond.tests',
test_loader='trytond.test_loader:Loader',
+ tests_require=['mock'],
)
diff --git a/trytond.egg-info/PKG-INFO b/trytond.egg-info/PKG-INFO
index 40f97fa..c6ba62e 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.2.3
+Version: 3.4.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.2/
+Download-URL: http://downloads.tryton.org/3.4/
Description: trytond
=======
diff --git a/trytond.egg-info/SOURCES.txt b/trytond.egg-info/SOURCES.txt
index d3ca803..f5a045e 100644
--- a/trytond.egg-info/SOURCES.txt
+++ b/trytond.egg-info/SOURCES.txt
@@ -21,11 +21,15 @@ doc/ref/models/fields.rst
doc/ref/models/index.rst
doc/ref/models/models.rst
doc/ref/tools/singleton.rst
+doc/topics/access_rights.rst
doc/topics/actions.rst
+doc/topics/configuration.rst
doc/topics/domain.rst
doc/topics/index.rst
doc/topics/install.rst
+doc/topics/logs.rst
doc/topics/pyson.rst
+doc/topics/setup_database.rst
doc/topics/wizard.rst
doc/topics/models/fields_default_value.rst
doc/topics/models/fields_on_change.rst
@@ -34,7 +38,6 @@ doc/topics/modules/index.rst
doc/topics/reports/index.rst
doc/topics/views/extension.rst
doc/topics/views/index.rst
-etc/trytond.conf
trytond/__init__.py
trytond/cache.py
trytond/config.py
@@ -110,6 +113,7 @@ trytond/ir/locale/cs_CZ.po
trytond/ir/locale/de_DE.po
trytond/ir/locale/es_AR.po
trytond/ir/locale/es_CO.po
+trytond/ir/locale/es_EC.po
trytond/ir/locale/es_ES.po
trytond/ir/locale/fr_FR.po
trytond/ir/locale/nl_NL.po
@@ -177,6 +181,8 @@ trytond/ir/view/model_access_form.xml
trytond/ir/view/model_access_list.xml
trytond/ir/view/model_button_form.xml
trytond/ir/view/model_button_list.xml
+trytond/ir/view/model_data_form.xml
+trytond/ir/view/model_data_list.xml
trytond/ir/view/model_field_access_form.xml
trytond/ir/view/model_field_access_list.xml
trytond/ir/view/model_field_form.xml
@@ -230,11 +236,13 @@ trytond/ir/view/ui_view_tree_width_form.xml
trytond/ir/view/ui_view_tree_width_list.xml
trytond/model/__init__.py
trytond/model/dictschema.py
+trytond/model/match.py
trytond/model/model.py
trytond/model/modelsingleton.py
trytond/model/modelsql.py
trytond/model/modelstorage.py
trytond/model/modelview.py
+trytond/model/union.py
trytond/model/workflow.py
trytond/model/fields/__init__.py
trytond/model/fields/binary.py
@@ -281,6 +289,7 @@ trytond/res/locale/cs_CZ.po
trytond/res/locale/de_DE.po
trytond/res/locale/es_AR.po
trytond/res/locale/es_CO.po
+trytond/res/locale/es_EC.po
trytond/res/locale/es_ES.po
trytond/res/locale/fr_FR.po
trytond/res/locale/nl_NL.po
@@ -299,6 +308,7 @@ trytond/tests/__init__.py
trytond/tests/access.py
trytond/tests/copy_.py
trytond/tests/export_data.py
+trytond/tests/field_context.py
trytond/tests/history.py
trytond/tests/import_data.py
trytond/tests/import_data.xml
@@ -311,6 +321,7 @@ trytond/tests/test_access.py
trytond/tests/test_cache.py
trytond/tests/test_copy.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
@@ -318,12 +329,14 @@ trytond/tests/test_mixins.py
trytond/tests/test_modelsingleton.py
trytond/tests/test_modelsql.py
trytond/tests/test_mptt.py
+trytond/tests/test_protocols.py
trytond/tests/test_pyson.py
trytond/tests/test_sequence.py
trytond/tests/test_tools.py
trytond/tests/test_transaction.py
trytond/tests/test_trigger.py
trytond/tests/test_tryton.py
+trytond/tests/test_union.py
trytond/tests/test_user.py
trytond/tests/test_wizard.py
trytond/tests/test_workflow.py
@@ -348,6 +361,7 @@ trytond/webdav/locale/cs_CZ.po
trytond/webdav/locale/de_DE.po
trytond/webdav/locale/es_AR.po
trytond/webdav/locale/es_CO.po
+trytond/webdav/locale/es_EC.po
trytond/webdav/locale/es_ES.po
trytond/webdav/locale/fr_FR.po
trytond/webdav/locale/nl_NL.po
diff --git a/trytond/backend/__init__.py b/trytond/backend/__init__.py
index 5620291..6b9abc4 100644
--- a/trytond/backend/__init__.py
+++ b/trytond/backend/__init__.py
@@ -1,15 +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.
import sys
+import urlparse
-from trytond.config import CONFIG
+from trytond.config import config
-__all__ = ['get']
+__all__ = ['name', 'get']
-def get(name):
- db_type = CONFIG['db_type']
+def name():
+ return urlparse.urlparse(config.get('database', 'uri', '')).scheme
+
+
+def get(prop):
+ db_type = name()
modname = 'trytond.backend.%s' % db_type
__import__(modname)
module = sys.modules[modname]
- return getattr(module, name)
+ return getattr(module, prop)
diff --git a/trytond/backend/database.py b/trytond/backend/database.py
index 2979c66..dae7908 100644
--- a/trytond/backend/database.py
+++ b/trytond/backend/database.py
@@ -105,29 +105,19 @@ class CursorInterface(object):
Define generic interface for database cursor
'''
IN_MAX = 1000
+ cache_keys = {'language', 'fuzzy_translation', '_datetime'}
def __init__(self):
self.cache = {}
- def get_cache(self, context=None):
- '''
- Return cache for the context
-
- :param context: the context
- :return: the cache dictionary
- '''
+ def get_cache(self):
from trytond.cache import LRUDict
from trytond.transaction import Transaction
user = Transaction().user
- if context is None:
- context = {}
- cache_ctx = context.copy()
- for i in ('_timestamp', '_delete', '_create_records',
- '_delete_records'):
- if i in cache_ctx:
- del cache_ctx[i]
- return self.cache.setdefault((user, repr(cache_ctx)),
- LRUDict(MODEL_CACHE_SIZE))
+ 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))
def execute(self, sql, params=None):
'''
diff --git a/trytond/backend/mysql/database.py b/trytond/backend/mysql/database.py
index f61136f..96627d5 100644
--- a/trytond/backend/mysql/database.py
+++ b/trytond/backend/mysql/database.py
@@ -1,7 +1,7 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.backend.database import DatabaseInterface, CursorInterface
-from trytond.config import CONFIG
+from trytond.config import config, parse_uri
import MySQLdb
import MySQLdb.cursors
import MySQLdb.converters
@@ -91,14 +91,16 @@ class Database(DatabaseInterface):
'charset': 'utf8',
'conv': conv,
}
- if CONFIG['db_host']:
- args['host'] = CONFIG['db_host']
- if CONFIG['db_port']:
- args['port'] = int(CONFIG['db_port'])
- if CONFIG['db_user']:
- args['user'] = CONFIG['db_user']
- if CONFIG['db_password']:
- args['passwd'] = CONFIG['db_password']
+ uri = parse_uri(config.get('database', 'uri'))
+ assert uri.scheme == 'mysql'
+ if uri.hostname:
+ args['host'] = uri.hostname
+ if uri.port:
+ args['port'] = uri.port
+ if uri.username:
+ args['user'] = uri.username
+ if uri.password:
+ args['passwd'] = uri.password
conn = MySQLdb.connect(**args)
cursor = Cursor(conn, self.database_name)
cursor.execute('SET time_zone = `UTC`')
@@ -120,20 +122,21 @@ class Database(DatabaseInterface):
@staticmethod
def dump(database_name):
- from trytond.tools import exec_pg_command_pipe
+ from trytond.tools import exec_command_pipe
cmd = ['mysqldump', '--no-create-db']
- if CONFIG['db_user']:
- cmd.append('--user=' + CONFIG['db_user'])
- if CONFIG['db_host']:
- cmd.append('--host=' + CONFIG['db_host'])
- if CONFIG['db_port']:
- cmd.append('--port=' + CONFIG['db_port'])
- if CONFIG['db_password']:
- cmd.append('--password=' + CONFIG['db_password'])
+ uri = parse_uri(config.get('database', 'uri'))
+ if uri.username:
+ cmd.append('--user=' + uri.username)
+ if uri.hostname:
+ cmd.append('--host=' + uri.hostname)
+ if uri.port:
+ cmd.append('--port=' + uri.port)
+ if uri.password:
+ cmd.append('--password=' + uri.password)
cmd.append(database_name)
- pipe = exec_pg_command_pipe(*tuple(cmd))
+ pipe = exec_command_pipe(*tuple(cmd))
pipe.stdin.close()
data = pipe.stdout.read()
res = pipe.wait()
@@ -143,7 +146,7 @@ class Database(DatabaseInterface):
@staticmethod
def restore(database_name, data):
- from trytond.tools import exec_pg_command_pipe
+ from trytond.tools import exec_command_pipe
database = Database().connect()
cursor = database.cursor(autocommit=True)
@@ -152,14 +155,15 @@ class Database(DatabaseInterface):
cursor.close()
cmd = ['mysql']
- if CONFIG['db_user']:
- cmd.append('--user=' + CONFIG['db_user'])
- if CONFIG['db_host']:
- cmd.append('--host=' + CONFIG['db_host'])
- if CONFIG['db_port']:
- cmd.append('--port=' + CONFIG['db_port'])
- if CONFIG['db_password']:
- cmd.append('--password=' + CONFIG['db_password'])
+ uri = parse_uri(config.get('database', 'uri'))
+ if uri.username:
+ cmd.append('--user=' + uri.username)
+ if uri.hostname:
+ cmd.append('--host=' + uri.hostname)
+ if uri.port:
+ cmd.append('--port=' + uri.port)
+ if uri.password:
+ cmd.append('--password=' + uri.password)
cmd.append(database_name)
fd, file_name = tempfile.mkstemp()
@@ -171,7 +175,7 @@ class Database(DatabaseInterface):
args2 = tuple(cmd)
- pipe = exec_pg_command_pipe(*args2)
+ pipe = exec_command_pipe(*args2)
pipe.stdin.close()
res = pipe.wait()
os.remove(file_name)
@@ -192,7 +196,7 @@ class Database(DatabaseInterface):
@staticmethod
def list(cursor):
now = time.time()
- timeout = int(CONFIG['session_timeout'])
+ timeout = config.getint('session', 'timeout')
res = Database._list_cache
if res and abs(Database._list_cache_timestamp - now) < timeout:
return res
@@ -338,4 +342,4 @@ class Cursor(CursorInterface):
def update_auto_increment(self, table, value):
self.cursor.execute('ALTER TABLE `%s` AUTO_INCREMENT = %%s' % table,
- value)
+ (value,))
diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py
index ca444cd..8323557 100644
--- a/trytond/backend/postgresql/database.py
+++ b/trytond/backend/postgresql/database.py
@@ -1,7 +1,7 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.backend.database import DatabaseInterface, CursorInterface
-from trytond.config import CONFIG
+from trytond.config import config, parse_uri
from psycopg2.pool import ThreadedConnectionPool
from psycopg2.extensions import cursor as PsycopgCursor
from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ
@@ -54,14 +54,15 @@ class Database(DatabaseInterface):
return self
logger = logging.getLogger('database')
logger.info('connect to "%s"' % self.database_name)
- host = CONFIG['db_host'] and "host=%s" % CONFIG['db_host'] or ''
- port = CONFIG['db_port'] and "port=%s" % CONFIG['db_port'] or ''
+ uri = parse_uri(config.get('database', 'uri'))
+ assert uri.scheme == 'postgresql'
+ host = uri.hostname and "host=%s" % uri.hostname or ''
+ port = uri.port and "port=%s" % uri.port or ''
name = "dbname=%s" % self.database_name
- user = CONFIG['db_user'] and "user=%s" % CONFIG['db_user'] or ''
- password = (CONFIG['db_password']
- and "password=%s" % CONFIG['db_password'] or '')
- minconn = int(CONFIG['db_minconn']) or 1
- maxconn = int(CONFIG['db_maxconn']) or 64
+ 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)
dsn = '%s %s %s %s %s' % (host, port, name, user, password)
self._connpool = ThreadedConnectionPool(minconn, maxconn, dsn)
return self
@@ -106,18 +107,25 @@ class Database(DatabaseInterface):
@staticmethod
def dump(database_name):
- from trytond.tools import exec_pg_command_pipe
+ from trytond.tools import exec_command_pipe
cmd = ['pg_dump', '--format=c', '--no-owner']
- if CONFIG['db_user']:
- cmd.append('--username=' + CONFIG['db_user'])
- if CONFIG['db_host']:
- cmd.append('--host=' + CONFIG['db_host'])
- if CONFIG['db_port']:
- cmd.append('--port=' + CONFIG['db_port'])
+ env = {}
+ uri = parse_uri(config.get('database', 'uri'))
+ if uri.username:
+ cmd.append('--username=' + uri.username)
+ if uri.hostname:
+ cmd.append('--host=' + uri.hostname)
+ if uri.port:
+ cmd.append('--port=' + uri.port)
+ if uri.password:
+ # if db_password is set in configuration we should pass
+ # an environment variable PGPASSWORD to our subprocess
+ # see libpg documentation
+ env['PGPASSWORD'] = uri.password
cmd.append(database_name)
- pipe = exec_pg_command_pipe(*tuple(cmd))
+ pipe = exec_command_pipe(*tuple(cmd), env=env)
pipe.stdin.close()
data = pipe.stdout.read()
res = pipe.wait()
@@ -127,7 +135,7 @@ class Database(DatabaseInterface):
@staticmethod
def restore(database_name, data):
- from trytond.tools import exec_pg_command_pipe
+ from trytond.tools import exec_command_pipe
database = Database().connect()
cursor = database.cursor(autocommit=True)
@@ -136,12 +144,16 @@ class Database(DatabaseInterface):
cursor.close()
cmd = ['pg_restore', '--no-owner']
- if CONFIG['db_user']:
- cmd.append('--username=' + CONFIG['db_user'])
- if CONFIG['db_host']:
- cmd.append('--host=' + CONFIG['db_host'])
- if CONFIG['db_port']:
- cmd.append('--port=' + CONFIG['db_port'])
+ env = {}
+ uri = parse_uri(config.get('database', 'uri'))
+ if uri.username:
+ cmd.append('--username=' + uri.username)
+ if uri.hostname:
+ cmd.append('--host=' + uri.hostname)
+ if uri.port:
+ cmd.append('--port=' + uri.port)
+ if uri.password:
+ env['PGPASSWORD'] = uri.password
cmd.append('--dbname=' + database_name)
args2 = tuple(cmd)
@@ -153,7 +165,7 @@ class Database(DatabaseInterface):
args2.append(' ' + tmpfile)
args2 = tuple(args2)
- pipe = exec_pg_command_pipe(*args2)
+ pipe = exec_command_pipe(*args2, env=env)
if not os.name == "nt":
pipe.stdin.write(data)
pipe.stdin.close()
@@ -175,23 +187,14 @@ class Database(DatabaseInterface):
@staticmethod
def list(cursor):
now = time.time()
- timeout = int(CONFIG['session_timeout'])
+ timeout = config.getint('session', 'timeout')
res = Database._list_cache
if res and abs(Database._list_cache_timestamp - now) < timeout:
return res
- db_user = CONFIG['db_user']
+ uri = parse_uri(config.get('database', 'uri'))
+ db_user = uri.username
if not db_user and os.name == 'posix':
db_user = pwd.getpwuid(os.getuid())[0]
- if not db_user:
- cursor.execute("SELECT usename "
- "FROM pg_user "
- "WHERE usesysid = ("
- "SELECT datdba "
- "FROM pg_database "
- "WHERE datname = %s)",
- (CONFIG["db_name"],))
- res = cursor.fetchone()
- db_user = res and res[0]
if db_user:
cursor.execute("SELECT datname "
"FROM pg_database "
diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py
index 9f132a0..7ee1e2b 100644
--- a/trytond/backend/sqlite/database.py
+++ b/trytond/backend/sqlite/database.py
@@ -1,7 +1,7 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.backend.database import DatabaseInterface, CursorInterface
-from trytond.config import CONFIG
+from trytond.config import config
import os
from decimal import Decimal
import datetime
@@ -22,7 +22,8 @@ except ImportError:
from sqlite3 import IntegrityError as DatabaseIntegrityError
from sqlite3 import OperationalError as DatabaseOperationalError
from sql import Flavor, Table
-from sql.functions import Function, Extract, Position, Now, Substring, Overlay
+from sql.functions import (Function, Extract, Position, Now, Substring,
+ Overlay, CharLength)
__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError',
'Cursor']
@@ -133,6 +134,11 @@ class SQLiteOverlay(Function):
return string[:from_ - 1] + placing_string + string[from_ - 1 + for_:]
+class SQLiteCharLength(Function):
+ __slots__ = ()
+ _function = 'LENGTH'
+
+
def sign(value):
return math.copysign(1, value)
@@ -142,6 +148,7 @@ MAPPING = {
Position: SQLitePosition,
Substring: SQLiteSubstring,
Overlay: SQLiteOverlay,
+ CharLength: SQLiteCharLength,
}
@@ -167,7 +174,7 @@ class Database(DatabaseInterface):
path = ':memory:'
else:
db_filename = self.database_name + '.sqlite'
- path = os.path.join(CONFIG['data_path'], db_filename)
+ path = os.path.join(config.get('database', 'path'), db_filename)
if not os.path.isfile(path):
raise IOError('Database "%s" doesn\'t exist!' % db_filename)
if self._conn is not None:
@@ -209,7 +216,7 @@ class Database(DatabaseInterface):
else:
if os.sep in database_name:
return
- path = os.path.join(CONFIG['data_path'],
+ path = os.path.join(config.get('database', 'path'),
database_name + '.sqlite')
with sqlite.connect(path) as conn:
cursor = conn.cursor()
@@ -222,7 +229,7 @@ class Database(DatabaseInterface):
return
if os.sep in database_name:
return
- os.remove(os.path.join(CONFIG['data_path'],
+ os.remove(os.path.join(config.get('database', 'path'),
database_name + '.sqlite'))
@staticmethod
@@ -231,7 +238,7 @@ class Database(DatabaseInterface):
raise Exception('Unable to dump memory database!')
if os.sep in database_name:
raise Exception('Wrong database name!')
- path = os.path.join(CONFIG['data_path'],
+ path = os.path.join(config.get('database', 'path'),
database_name + '.sqlite')
with open(path, 'rb') as file_p:
data = file_p.read()
@@ -243,7 +250,7 @@ class Database(DatabaseInterface):
raise Exception('Unable to restore memory database!')
if os.sep in database_name:
raise Exception('Wrong database name!')
- path = os.path.join(CONFIG['data_path'],
+ path = os.path.join(config.get('database', 'path'),
database_name + '.sqlite')
if os.path.isfile(path):
raise Exception('Database already exists!')
@@ -255,7 +262,7 @@ class Database(DatabaseInterface):
res = []
listdir = [':memory:']
try:
- listdir += os.listdir(CONFIG['data_path'])
+ listdir += os.listdir(config.get('database', 'path'))
except OSError:
pass
for db_file in listdir:
diff --git a/trytond/cache.py b/trytond/cache.py
index c6d7adb..6ccd039 100644
--- a/trytond/cache.py
+++ b/trytond/cache.py
@@ -1,12 +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 datetime
from threading import Lock
from collections import OrderedDict
+from sql import Table
+from sql.functions import Now
+
from trytond.transaction import Transaction
-from trytond.config import CONFIG
-from trytond import backend
__all__ = ['Cache', 'LRUDict']
@@ -74,18 +74,13 @@ class Cache(object):
@staticmethod
def clean(dbname):
- if not CONFIG['multi_server']:
- return
- database = backend.get('Database')(dbname).connect()
- cursor = database.cursor()
- try:
- cursor.execute('SELECT "timestamp", "name" FROM ir_cache')
+ with Transaction().new_cursor():
+ cursor = Transaction().cursor
+ table = Table('ir_cache')
+ cursor.execute(*table.select(table.timestamp, table.name))
timestamps = {}
for timestamp, name in cursor.fetchall():
timestamps[name] = timestamp
- finally:
- cursor.commit()
- cursor.close()
for inst in Cache._cache_instance:
if inst._name in timestamps:
with inst._lock:
@@ -96,36 +91,30 @@ class Cache(object):
@staticmethod
def reset(dbname, name):
- if not CONFIG['multi_server']:
- return
with Cache._resets_lock:
Cache._resets.setdefault(dbname, set())
Cache._resets[dbname].add(name)
@staticmethod
def resets(dbname):
- if not CONFIG['multi_server']:
- return
- database = backend.get('Database')(dbname).connect()
- cursor = database.cursor()
- try:
+ with Transaction().new_cursor():
+ cursor = Transaction().cursor
+ table = Table('ir_cache')
with Cache._resets_lock:
Cache._resets.setdefault(dbname, set())
for name in Cache._resets[dbname]:
- cursor.execute('SELECT name FROM ir_cache WHERE name = %s',
- (name,))
+ cursor.execute(*table.select(table.name,
+ where=table.name == name))
if cursor.fetchone():
# It would be better to insert only
- cursor.execute('UPDATE ir_cache SET "timestamp" = %s '
- 'WHERE name = %s', (datetime.datetime.now(), name))
+ cursor.execute(*table.update([table.timestamp],
+ [Now()], where=table.name == name))
else:
- cursor.execute('INSERT INTO ir_cache '
- '("timestamp", "name") '
- 'VALUES (%s, %s)', (datetime.datetime.now(), name))
+ cursor.execute(*table.insert(
+ [table.timestamp, table.name],
+ [[Now(), name]]))
Cache._resets[dbname].clear()
- finally:
cursor.commit()
- cursor.close()
class LRUDict(OrderedDict):
diff --git a/trytond/config.py b/trytond/config.py
index ba67332..99e3057 100644
--- a/trytond/config.py
+++ b/trytond/config.py
@@ -1,18 +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.
-import sys
-try:
- import cdecimal
- # Use cdecimal globally
- if 'decimal' not in sys.modules:
- sys.modules['decimal'] = cdecimal
-except ImportError:
- import decimal
- sys.modules['cdecimal'] = decimal
import os
import ConfigParser
-import getpass
-import socket
+import urlparse
+
+__all__ = ['config', 'get_hostname', 'get_port', 'split_netloc',
+ 'parse_listen', 'parse_uri']
def get_hostname(netloc):
@@ -24,119 +17,80 @@ def get_hostname(netloc):
return netloc
-def get_port(netloc, protocol):
+def get_port(netloc):
netloc = netloc.split(']')[-1]
- if ':' in netloc:
- return int(netloc.split(':')[1])
- else:
- return {
- 'jsonrpc': 8000,
- 'xmlrpc': 8069,
- 'webdav': 8080,
- }.get(protocol)
-
-
-class ConfigManager(object):
- def __init__(self, fname=None):
- self.options = {
- 'jsonrpc': [('localhost', 8000)],
- 'ssl_jsonrpc': False,
- 'hostname_jsonrpc': None,
- 'xmlrpc': [],
- 'ssl_xmlrpc': False,
- 'jsondata_path': '/var/www/localhost/tryton',
- 'webdav': [],
- 'ssl_webdav': False,
- 'hostname_webdav': None,
- 'db_type': 'postgresql',
- 'db_host': False,
- 'db_port': False,
- 'db_name': False,
- 'db_user': False,
- 'db_password': False,
- 'db_minconn': 1,
- 'db_maxconn': 64,
- 'pg_path': None,
- 'admin_passwd': 'admin',
- 'verbose': False,
- 'debug_mode': False,
- 'pidfile': None,
- 'logfile': None,
- 'privatekey': '/etc/ssl/trytond/server.key',
- 'certificate': '/etc/ssl/trytond/server.pem',
- 'smtp_server': 'localhost',
- 'smtp_port': 25,
- 'smtp_ssl': False,
- 'smtp_tls': False,
- 'smtp_user': False,
- 'smtp_password': False,
- 'smtp_default_from_email': '%s@%s' % (
- getpass.getuser(), socket.getfqdn()),
- 'data_path': '/var/lib/trytond',
- 'multi_server': False,
- 'session_timeout': 600,
- 'auto_reload': True,
- 'prevent_dblist': False,
- 'init': {},
- 'update': {},
- 'cron': True,
- 'unoconv': 'pipe,name=trytond;urp;StarOffice.ComponentContext',
- 'retry': 5,
- 'language': 'en_US',
- }
- self.configfile = None
-
- def update_cmdline(self, cmdline_options):
- self.options.update(cmdline_options)
-
- # Verify that we want to log or not,
- # if not the output will go to stdout
- if self.options['logfile'] in ('None', 'False'):
- self.options['logfile'] = False
- # the same for the pidfile
- if self.options['pidfile'] in ('None', 'False'):
- self.options['pidfile'] = False
- if self.options['data_path'] in ('None', 'False'):
- self.options['data_path'] = False
-
- def update_etc(self, configfile=None):
- if configfile is None:
- configfile = os.environ.get('TRYTOND_CONFIG')
- if not configfile:
- prefixdir = os.path.abspath(os.path.normpath(os.path.join(
- os.path.dirname(sys.prefix), '..')))
- configfile = os.path.join(prefixdir, 'etc', 'trytond.conf')
- if not os.path.isfile(configfile):
- configdir = os.path.abspath(os.path.normpath(os.path.join(
- os.path.dirname(__file__), '..')))
- configfile = os.path.join(configdir, 'etc', 'trytond.conf')
- if not os.path.isfile(configfile):
- configfile = None
-
- self.configfile = configfile
- if not self.configfile:
+ return int(netloc.split(':')[1])
+
+
+def split_netloc(netloc):
+ return get_hostname(netloc).replace('*', ''), get_port(netloc)
+
+
+def parse_listen(value):
+ for netloc in value.split(','):
+ yield split_netloc(netloc)
+
+
+def parse_uri(uri):
+ return urlparse.urlparse(uri)
+
+
+class TrytonConfigParser(ConfigParser.RawConfigParser):
+
+ def __init__(self):
+ ConfigParser.RawConfigParser.__init__(self)
+ self.add_section('jsonrpc')
+ self.set('jsonrpc', 'listen', 'localhost:8000')
+ self.set('jsonrpc', 'data', '/var/www/localhost/tryton')
+ self.add_section('xmlrpc')
+ self.add_section('webdav')
+ self.add_section('database')
+ self.set('database', 'uri',
+ os.environ.get('TRYTOND_DATABASE_URI', 'sqlite://'))
+ self.set('database', 'path', '/var/lib/trytond')
+ self.set('database', 'list', 'True')
+ self.set('database', 'retry', 5)
+ self.set('database', 'language', 'en_US')
+ self.add_section('ssl')
+ self.add_section('email')
+ self.set('email', 'uri', 'smtp://localhost:25')
+ self.add_section('session')
+ self.set('session', 'timeout', 600)
+ self.add_section('report')
+ self.set('report', 'unoconv',
+ 'pipe,name=trytond;urp;StarOffice.ComponentContext')
+
+ def update_etc(self, configfile=os.environ.get('TRYTOND_CONFIG')):
+ if not configfile:
return
+ self.read(configfile)
+
+ def get(self, section, option, default=None):
+ try:
+ return ConfigParser.RawConfigParser.get(self, section, option)
+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+ return default
+
+ def getint(self, section, option, default=None):
+ try:
+ return ConfigParser.RawConfigParser.getint(self, section, option)
+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError,
+ TypeError):
+ return default
+
+ def getfloat(self, section, option, default=None):
+ try:
+ return ConfigParser.RawConfigParser.getfloat(self, section, option)
+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError,
+ TypeError):
+ return default
+
+ def getboolean(self, section, option, default=None):
+ try:
+ return ConfigParser.RawConfigParser.getboolean(
+ self, section, option)
+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError,
+ AttributeError):
+ return default
- parser = ConfigParser.ConfigParser()
- with open(self.configfile) as fp:
- parser.readfp(fp)
- for (name, value) in parser.items('options'):
- if value == 'True' or value == 'true':
- value = True
- if value == 'False' or value == 'false':
- value = False
- if name in ('xmlrpc', 'jsonrpc', 'webdav') and value:
- value = [(get_hostname(netloc).replace('*', ''),
- get_port(netloc, name)) for netloc in value.split(',')]
- self.options[name] = value
-
- def get(self, key, default=None):
- return self.options.get(key, default)
-
- def __setitem__(self, key, value):
- self.options[key] = value
-
- def __getitem__(self, key):
- return self.options[key]
-
-CONFIG = ConfigManager()
+config = TrytonConfigParser()
diff --git a/trytond/convert.py b/trytond/convert.py
index 378da55..8d5515c 100644
--- a/trytond/convert.py
+++ b/trytond/convert.py
@@ -2,8 +2,6 @@
#this repository contains the full copyright notices and license terms.
import time
from xml import sax
-from decimal import Decimal
-import datetime
import logging
import traceback
import sys
@@ -13,7 +11,7 @@ from itertools import izip
from collections import defaultdict
from .version import VERSION
-from .tools import safe_eval
+from .tools import safe_eval, grouped_slice
from .transaction import Transaction
CDATA_START = re.compile('^\s*\<\!\[cdata\[', re.IGNORECASE)
@@ -354,7 +352,6 @@ class Fs2bdAccessor:
self.browserecord[module][model_name][model.id] = model
def fetch_new_module(self, module):
- cursor = Transaction().cursor
self.fs2db[module] = {}
module_data_ids = self.ModelData.search([
('module', '=', module),
@@ -377,12 +374,11 @@ class Fs2bdAccessor:
continue
Model = self.pool.get(model_name)
self.browserecord[module][model_name] = {}
- for i in range(0, len(record_ids[model_name]), cursor.IN_MAX):
- sub_record_ids = record_ids[model_name][i:i + cursor.IN_MAX]
+ for sub_record_ids in grouped_slice(record_ids[model_name]):
with Transaction().set_context(active_test=False):
records = Model.search([
- ('id', 'in', sub_record_ids),
- ])
+ ('id', 'in', list(sub_record_ids)),
+ ], order=[('id', 'ASC')])
with Transaction().set_context(language='en_US'):
models = Model.browse(map(int, records))
for model in models:
@@ -493,7 +489,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
module, model = key
self.write_records(module, model, *actions)
self.grouped_write.clear()
- if self.grouped_model_data:
+ if name == 'data' and self.grouped_model_data:
self.ModelData.write(*self.grouped_model_data)
del self.grouped_model_data[:]
@@ -604,10 +600,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
if not old_values:
old_values = {}
else:
- old_values = safe_eval(old_values, {
- 'Decimal': Decimal,
- 'datetime': datetime,
- })
+ old_values = self.ModelData.load_values(old_values)
for key in old_values:
if isinstance(old_values[key], str):
@@ -691,6 +684,9 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
else:
self.write_records(module, model, record, to_update,
old_values, fs_id, mdata_id)
+ self.grouped_model_data.extend(([self.ModelData(mdata_id)], {
+ 'fs_values': self.ModelData.dump_values(values),
+ }))
else:
if self.grouped:
self.grouped_creations[model][fs_id] = values
@@ -713,7 +709,8 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
'model': model,
'module': self.module,
'db_id': record.id,
- 'values': str(values),
+ 'values': self.ModelData.dump_values(values),
+ 'fs_values': self.ModelData.dump_values(values),
'noupdate': self.noupdate,
})
@@ -725,7 +722,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
'db_id': record.id,
'model': model,
'id': mdata.id,
- 'values': str(values),
+ 'values': self.ModelData.dump_values(values),
})
self.fs2db.reset_browsercord(self.module, model,
[r.id for r in records])
@@ -774,8 +771,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
'model': model,
'module': module,
'db_id': record.id,
- 'values': str(values),
- 'date_update': datetime.datetime.now(),
+ 'values': self.ModelData.dump_values(values),
}))
# reset_browsercord to keep cache memory low
diff --git a/trytond/ir/action.py b/trytond/ir/action.py
index 873a131..606796a 100644
--- a/trytond/ir/action.py
+++ b/trytond/ir/action.py
@@ -170,11 +170,7 @@ class ActionKeyword(ModelSQL, ModelView):
def models_get():
pool = Pool()
Model = pool.get('ir.model')
- models = Model.search([])
- res = []
- for model in models:
- res.append([model.model, model.name])
- return res
+ return [(m.model, m.name) for m in Model.search([])]
@classmethod
def delete(cls, keywords):
@@ -241,6 +237,7 @@ class ActionKeyword(ModelSQL, ModelView):
class ActionMixin(ModelSQL):
_order_name = 'action'
+ _action_name = 'name'
@classmethod
def __setup__(cls):
@@ -298,6 +295,8 @@ class ActionMixin(ModelSQL):
@classmethod
def create(cls, vlist):
pool = Pool()
+ ModelView._fields_view_get_cache.clear()
+ ModelView._view_toolbar_get_cache.clear()
Action = pool.get('ir.action')
ir_action = cls.__table__()
new_records = []
@@ -336,11 +335,15 @@ class ActionMixin(ModelSQL):
pool = Pool()
ActionKeyword = pool.get('ir.action.keyword')
super(ActionMixin, cls).write(records, values, *args)
+ ModelView._fields_view_get_cache.clear()
+ ModelView._view_toolbar_get_cache.clear()
ActionKeyword._get_keyword_cache.clear()
@classmethod
def delete(cls, records):
pool = Pool()
+ ModelView._fields_view_get_cache.clear()
+ ModelView._view_toolbar_get_cache.clear()
Action = pool.get('ir.action')
actions = [x.action for x in records]
super(ActionMixin, cls).delete(records)
@@ -360,10 +363,23 @@ class ActionMixin(ModelSQL):
default=default))
return new_records
+ @classmethod
+ def get_groups(cls, name, action_id=None):
+ # TODO add cache
+ domain = [
+ (cls._action_name, '=', name),
+ ]
+ if action_id:
+ domain.append(('id', '=', action_id))
+ actions = cls.search(domain)
+ groups = {g.id for a in actions for g in a.groups}
+ return groups
+
class ActionReport(ActionMixin, ModelSQL, ModelView):
"Action report"
__name__ = 'ir.action.report'
+ _action_name = 'report_name'
model = fields.Char('Model')
report_name = fields.Char('Internal Name', required=True)
report = fields.Char('Path')
@@ -491,10 +507,9 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
where=outputformat.format == 'pdf'))
ids = [x[0] for x in cursor.fetchall()]
- with Transaction().set_user(0):
- cls.write(cls.browse(ids), {'extension': 'pdf'})
- ids = cls.search([('id', 'not in', ids)])
- cls.write(cls.browse(ids), {'extension': 'odt'})
+ cls.write(cls.browse(ids), {'extension': 'pdf'})
+ ids = cls.search([('id', 'not in', ids)])
+ cls.write(cls.browse(ids), {'extension': 'odt'})
table.drop_column("output_format")
TableHandler.dropTable(cursor, 'ir.action.report.outputformat',
@@ -902,12 +917,17 @@ class ActionActWindowView(ModelSQL, ModelView):
ondelete='CASCADE')
act_window = fields.Many2One('ir.action.act_window', 'Action',
ondelete='CASCADE')
+ active = fields.Boolean('Active', select=True)
@classmethod
def __setup__(cls):
super(ActionActWindowView, cls).__setup__()
cls._order.insert(0, ('sequence', 'ASC'))
+ @staticmethod
+ def default_active():
+ return True
+
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
@@ -960,6 +980,7 @@ class ActionActWindowDomain(ModelSQL, ModelView):
class ActionWizard(ActionMixin, ModelSQL, ModelView):
"Action wizard"
__name__ = 'ir.action.wizard'
+ _action_name = 'wiz_name'
wiz_name = fields.Char('Wizard name', required=True)
action = fields.Many2One('ir.action', 'Action', required=True,
ondelete='CASCADE')
diff --git a/trytond/ir/attachment.py b/trytond/ir/attachment.py
index d261509..0195c70 100644
--- a/trytond/ir/attachment.py
+++ b/trytond/ir/attachment.py
@@ -5,7 +5,7 @@ import hashlib
from sql.operators import Concat
from ..model import ModelView, ModelSQL, fields
-from ..config import CONFIG
+from ..config import config
from .. import backend
from ..transaction import Transaction
from ..pyson import Eval
@@ -117,7 +117,7 @@ class Attachment(ModelSQL, ModelView):
filename = self.digest
if self.collision:
filename = filename + '-' + str(self.collision)
- filename = os.path.join(CONFIG['data_path'], db_name,
+ filename = os.path.join(config.get('database', 'path'), db_name,
filename[0:2], filename[2:4], filename)
if name == 'data_size' or format_ == 'size':
try:
@@ -140,7 +140,7 @@ class Attachment(ModelSQL, ModelView):
cursor = Transaction().cursor
table = cls.__table__()
db_name = cursor.dbname
- directory = os.path.join(CONFIG['data_path'], db_name)
+ directory = os.path.join(config.get('database', 'path'), db_name)
if not os.path.isdir(directory):
os.makedirs(directory, 0770)
digest = hashlib.md5(value).hexdigest()
@@ -191,22 +191,19 @@ class Attachment(ModelSQL, ModelView):
return (self.write_date if self.write_date else self.create_date
).replace(microsecond=0)
- @classmethod
- def get_last_user(cls, attachments, name):
- with Transaction().set_user(0):
- return dict(
- (x.id, x.write_uid.rec_name
- if x.write_uid else x.create_uid.rec_name)
- for x in cls.browse(attachments))
+ def get_last_user(self, name):
+ return (self.write_uid.rec_name if self.write_uid
+ else self.create_uid.rec_name)
@classmethod
def check_access(cls, ids, mode='read'):
pool = Pool()
ModelAccess = pool.get('ir.model.access')
- if Transaction().user == 0:
+ if ((Transaction().user == 0)
+ or not Transaction().context.get('_check_access')):
return
model_names = set()
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
for attachment in cls.browse(ids):
if attachment.resource:
model_names.add(attachment.resource.__name__)
diff --git a/trytond/ir/configuration.py b/trytond/ir/configuration.py
index abe72db..4ee7307 100644
--- a/trytond/ir/configuration.py
+++ b/trytond/ir/configuration.py
@@ -2,7 +2,7 @@
#this repository contains the full copyright notices and license terms.
from ..model import ModelSQL, ModelSingleton, fields
from ..cache import Cache
-from ..config import CONFIG
+from ..config import config
__all__ = ['Configuration']
@@ -15,7 +15,7 @@ class Configuration(ModelSingleton, ModelSQL):
@staticmethod
def default_language():
- return CONFIG['language']
+ return config.get('database', 'language')
@classmethod
def get_language(cls):
@@ -25,6 +25,6 @@ class Configuration(ModelSingleton, ModelSQL):
config = cls(1)
language = config.language
if not language:
- language = CONFIG['language']
+ language = config.get('database', 'language')
cls._get_language_cache.set(None, language)
return language
diff --git a/trytond/ir/cron.py b/trytond/ir/cron.py
index 10f7e3f..438a0d2 100644
--- a/trytond/ir/cron.py
+++ b/trytond/ir/cron.py
@@ -14,7 +14,7 @@ from ..tools import get_smtp_server
from ..transaction import Transaction
from ..pool import Pool
from .. import backend
-from ..config import CONFIG
+from ..config import config
__all__ = [
'Cron',
@@ -133,7 +133,7 @@ class Cron(ModelSQL, ModelView):
(cron.name, cron.__url__, tb_s),
raise_exception=False)
- from_addr = CONFIG['smtp_default_from_email']
+ from_addr = config.get('email', 'from')
to_addr = cron.request_user.email
msg = MIMEText(body, _charset='utf-8')
diff --git a/trytond/ir/gen_time_locale.py b/trytond/ir/gen_time_locale.py
index b0f5eaa..9f78290 100644
--- a/trytond/ir/gen_time_locale.py
+++ b/trytond/ir/gen_time_locale.py
@@ -195,6 +195,7 @@ if __name__ == '__main__':
'de_DE',
'en_US',
'es_AR',
+ 'es_EC',
'es_ES',
'es_CO',
'fr_FR',
diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py
index f5fd4f0..c0e175b 100644
--- a/trytond/ir/lang.py
+++ b/trytond/ir/lang.py
@@ -10,11 +10,14 @@ from ..tools import datetime_strftime
from ..transaction import Transaction
from ..pool import Pool
from .time_locale import TIME_LOCALE
+from ..backend.database import CursorInterface
warnings.filterwarnings('ignore', "", ImportWarning)
from locale import CHAR_MAX
warnings.resetwarnings()
+CursorInterface.cache_keys.add('translate_name')
+
__all__ = [
'Lang',
]
diff --git a/trytond/ir/lang.xml b/trytond/ir/lang.xml
index 7be3725..00b333d 100644
--- a/trytond/ir/lang.xml
+++ b/trytond/ir/lang.xml
@@ -48,6 +48,14 @@ this repository contains the full copyright notices and license terms. -->
<field name="decimal_point">,</field>
<field name="thousands_sep">.</field>
</record>
+ <record model="ir.lang" id="lang_ec">
+ <field name="code">es_EC</field>
+ <field name="name">Spanish (Ecuador)</field>
+ <field name="date">%d/%m/%Y</field>
+ <field name="grouping">[3, 3, 0]</field>
+ <field name="decimal_point">.</field>
+ <field name="thousands_sep">,</field>
+ </record>
<record model="ir.lang" id="lang_es">
<field name="code">es_ES</field>
<field name="name">Spanish (Spain)</field>
diff --git a/trytond/ir/locale/bg_BG.po b/trytond/ir/locale/bg_BG.po
index 56ebd1b..4209638 100644
--- a/trytond/ir/locale/bg_BG.po
+++ b/trytond/ir/locale/bg_BG.po
@@ -23,7 +23,7 @@ msgstr "Нямате права да изтривате този запис."
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
msgctxt "error:domain_validation_record:"
@@ -570,6 +570,11 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Действие"
+#, fuzzy
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Активен"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Създадено на"
@@ -1394,14 +1399,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Създадено от"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Начална дата"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Дата на обновяване"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "ID на ресурс"
@@ -1410,6 +1407,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Идентификатор от файлова система"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr ""
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr "ID"
@@ -1426,6 +1427,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "Без обновяване"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr ""
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Име"
@@ -2356,6 +2361,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Действие"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr ""
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Активен"
@@ -2870,6 +2879,11 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Бутони"
+#, fuzzy
+msgctxt "model:ir.action,name:act_model_data_form"
+msgid "Data"
+msgstr "Данни"
+
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr "Достъп до полета"
@@ -2982,6 +2996,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr ""
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr ""
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Изглед на действие на активния на прозорец"
@@ -3055,6 +3079,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Немски"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr ""
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Англииски"
@@ -3299,6 +3327,11 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Бутони"
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_model_data_form"
+msgid "Data"
+msgstr "Данни"
+
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
msgstr "Достъп до полета"
@@ -3758,6 +3791,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Бутони"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr ""
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr ""
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Достъп до поле"
diff --git a/trytond/ir/locale/ca_ES.po b/trytond/ir/locale/ca_ES.po
index 1f2be61..9e9483f 100644
--- a/trytond/ir/locale/ca_ES.po
+++ b/trytond/ir/locale/ca_ES.po
@@ -25,16 +25,18 @@ msgstr "No podeu eliminar aquest registre."
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
-"El nombre de dígits \"%(digits)s\" del camp \"%(field)s\" de %\"(value)s\" "
-"és superior al límit."
+"El nombre de dígits \"%(digits)s\" del camp \"%(field)s\" de \"%(value)s\" "
+"és superior al seu límit."
msgctxt "error:domain_validation_record:"
msgid ""
"The value of the field \"%(field)s\" on \"%(model)s\" is not valid according"
" to its domain."
-msgstr "El valor del camp \"%s\" de \"%s\" no és correcte segons aquest domini."
+msgstr ""
+"El valor del camp \"%(field)s\" de \"%(model)s\" no és correcte segons "
+"aquest domini."
msgctxt "error:foreign_model_exist:"
msgid ""
@@ -42,7 +44,7 @@ msgid ""
" \"%(model)s\"."
msgstr ""
"No es poden eliminar els registres perquè es fan servir al camp "
-"\"%(field)s\" de \"%(models)\"."
+"\"%(field)s\" de \"%(model)s\"."
msgctxt "error:foreign_model_missing:"
msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
@@ -73,7 +75,7 @@ msgstr ""
msgctxt "error:ir.action.report:"
msgid "The internal name must be unique by module!"
-msgstr "El nom intern ha de ser únic del mòdul."
+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!"
@@ -237,7 +239,7 @@ msgstr "L'arrodoniment de data-hora ha de ser més gran que 0."
msgctxt "error:ir.translation:"
msgid "Translation must be unique"
-msgstr "La traducció ha de ser únic."
+msgstr "La traducció ha de ser única."
msgctxt "error:ir.translation:"
msgid ""
@@ -289,11 +291,11 @@ msgid ""
" was configured as ancestor of itself."
msgstr ""
"Error de recursivitat: El registre \"%(rec_name)s\" amb el pare "
-"\"%(parent_rec_name)s\" es va configurar com pare de si mateix."
+"\"%(parent_rec_name)s\" s'ha configurat com a pare de si mateix."
msgctxt "error:reference_syntax_error:"
msgid "Syntax error for reference %r in %s"
-msgstr "Error de sintaxi en la referència %r a %s."
+msgstr "Error de sintaxi en la referència %r de %s."
msgctxt "error:relation_not_found:"
msgid "Relation not found: %r in %s"
@@ -301,11 +303,11 @@ msgstr "No s'ha trobat la relació: %r a %s."
msgctxt "error:required_field:"
msgid "The field \"%(field)s\" on \"%(model)s\" is required."
-msgstr "El camp \"%(field)s\" de \"%(model)s\" es obligatori."
+msgstr "El camp \"%(field)s\" de \"%(model)s\" és obligatori."
msgctxt "error:required_validation_record:"
msgid "The field \"%(field)s\" on \"%(model)s\" is required."
-msgstr "El camp \"%(field)s\" de \"%(model)s\" es obligatori."
+msgstr "El camp \"%(field)s\" de \"%(model)s\" és obligatori."
msgctxt "error:search_function_missing:"
msgid "Missing search function on field \"%s\"."
@@ -359,7 +361,7 @@ msgstr "No podeu modificar aquest registre."
msgctxt "error:xml_id_syntax_error:"
msgid "Syntax error for XML id %r in %s"
-msgstr "Error de sintaxi XML id %r a %s."
+msgstr "Error de sintaxi en el XML id %r de %s."
msgctxt "error:xml_record_desc:"
msgid "This record is part of the base configuration."
@@ -581,6 +583,10 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Acció"
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Actiu"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Data creació"
@@ -671,7 +677,7 @@ msgstr "Usuari creació"
msgctxt "field:ir.action.report,direct_print:"
msgid "Direct Print"
-msgstr "Direcció impressió"
+msgstr "Impressió directa"
msgctxt "field:ir.action.report,email:"
msgid "Email"
@@ -1259,7 +1265,7 @@ msgstr "Informació"
msgctxt "field:ir.model,model:"
msgid "Model Name"
-msgstr "Nom model"
+msgstr "Nom del model"
msgctxt "field:ir.model,module:"
msgid "Module"
@@ -1377,14 +1383,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Usuari creació"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Data d'inicialització"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Data actualització"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "ID recurs"
@@ -1393,6 +1391,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Identificador en el sistema de fitxers"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr "Valors en sistema de fitxers"
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr "ID"
@@ -1409,6 +1411,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "No actualitzat"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr "Sense sincronitzar"
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Nom"
@@ -1891,7 +1897,7 @@ msgstr "Desfasament de data-hora"
msgctxt "field:ir.sequence,timestamp_rounding:"
msgid "Timestamp Rounding"
-msgstr "Arronodiment de data-hora"
+msgstr "Arrodoniment de data-hora"
msgctxt "field:ir.sequence,type:"
msgid "Type"
@@ -1967,7 +1973,7 @@ msgstr "Desfasament de data-hora"
msgctxt "field:ir.sequence.strict,timestamp_rounding:"
msgid "Timestamp Rounding"
-msgstr "Arronodiment de data-hora"
+msgstr "Arrodoniment de data-hora"
msgctxt "field:ir.sequence.strict,type:"
msgid "Type"
@@ -2099,7 +2105,7 @@ msgstr "Mòdul"
msgctxt "field:ir.translation,name:"
msgid "Field Name"
-msgstr "Nom"
+msgstr "Nom del camp"
msgctxt "field:ir.translation,overriding_module:"
msgid "Overriding Module"
@@ -2333,6 +2339,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Acció"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr "Accions de teclat"
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Actiu"
@@ -2451,7 +2461,7 @@ msgstr "Domini"
msgctxt "field:ir.ui.view,field_childs:"
msgid "Children Field"
-msgstr "Camp fill"
+msgstr "Camp fills"
msgctxt "field:ir.ui.view,id:"
msgid "ID"
@@ -2635,7 +2645,7 @@ msgstr "Criteri de cerca per defecte en les vistes de llista."
msgctxt "help:ir.action.act_window,window_name:"
msgid "Use the action name as window name"
-msgstr "Utilitza el nom de l'acció pel nom de la finestra."
+msgstr "Utilitzeu el nom de l'acció pel nom de la finestra."
msgctxt "help:ir.action.report,email:"
msgid ""
@@ -2744,8 +2754,8 @@ msgid ""
"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
"0 for no delay."
msgstr ""
-"Interval mínim en minuts entre les crides \"Acció de funció\" pel mateix registre.\n"
-"0 per no interval."
+"Marge mínim en minuts entre les crides \"Acció de funció\" pel mateix registre.\n"
+"0 per no deixar marge."
msgctxt "help:ir.ui.view_search,domain:"
msgid "The PYSON domain"
@@ -2815,6 +2825,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Botons"
+msgctxt "model:ir.action,name:act_model_data_form"
+msgid "Data"
+msgstr "Dades"
+
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr "Permisos dels camps"
@@ -2927,6 +2941,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Domini acció de finestra"
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr "Tots"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr "Sense sincronitzar"
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Vista acció de finestra"
@@ -2999,6 +3023,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Alemany"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr "Espanyol (Ecuador)"
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Anglès"
@@ -3045,7 +3073,7 @@ msgstr "Botó model"
msgctxt "model:ir.model.data,name:"
msgid "Model data"
-msgstr "Data model"
+msgstr "Dades del model"
msgctxt "model:ir.model.field,name:"
msgid "Model field"
@@ -3243,6 +3271,10 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Botons"
+msgctxt "model:ir.ui.menu,name:menu_model_data_form"
+msgid "Data"
+msgstr "Dades"
+
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
msgstr "Permisos camps"
@@ -3699,6 +3731,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Botons"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr "Dades del model"
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr "Sincronitza"
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Permisos dels camps"
diff --git a/trytond/ir/locale/cs_CZ.po b/trytond/ir/locale/cs_CZ.po
index 78ce3a0..a149887 100644
--- a/trytond/ir/locale/cs_CZ.po
+++ b/trytond/ir/locale/cs_CZ.po
@@ -21,7 +21,7 @@ msgstr ""
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
msgctxt "error:domain_validation_record:"
@@ -545,6 +545,10 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr ""
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr ""
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr ""
@@ -1341,14 +1345,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr ""
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr ""
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr ""
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr ""
@@ -1357,6 +1353,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr ""
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr ""
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr ""
@@ -1373,6 +1373,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr ""
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr ""
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr ""
@@ -2297,6 +2301,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr ""
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr ""
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr ""
@@ -2765,6 +2773,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr ""
+msgctxt "model:ir.action,name:act_model_data_form"
+msgid "Data"
+msgstr ""
+
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr ""
@@ -2877,6 +2889,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr ""
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr ""
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr ""
@@ -2949,6 +2971,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr ""
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr ""
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr ""
@@ -3193,6 +3219,10 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr ""
+msgctxt "model:ir.ui.menu,name:menu_model_data_form"
+msgid "Data"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
msgstr ""
@@ -3649,6 +3679,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr ""
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr ""
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr ""
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr ""
diff --git a/trytond/ir/locale/de_DE.po b/trytond/ir/locale/de_DE.po
index 22986fe..7009184 100644
--- a/trytond/ir/locale/de_DE.po
+++ b/trytond/ir/locale/de_DE.po
@@ -25,7 +25,7 @@ msgstr "Keine Löschberechtigung für diesen Datensatz"
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
"Die Anzahl der Nachkommastellen \"%(digits)s\" in Feld \"%(field)s\" in "
"\"%(value)s\" überschreitet die erlaubte Größe."
@@ -591,6 +591,10 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Aktion"
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Aktiv"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Erstellungsdatum"
@@ -965,7 +969,7 @@ msgstr "Ressource"
msgctxt "field:ir.attachment,summary:"
msgid "Summary"
-msgstr "Zusammenfassung"
+msgstr "Beschreibung"
msgctxt "field:ir.attachment,type:"
msgid "Type"
@@ -1387,14 +1391,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Erstellt durch"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Initialdatum"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Aktualisierungsdatum"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "ID Ressource"
@@ -1403,6 +1399,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Identifikator im Dateisystem"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr "Werte im Dateisystem"
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr "ID"
@@ -1417,7 +1417,11 @@ msgstr "Modul"
msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
-msgstr "Ohne Update"
+msgstr "Kein Update"
+
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr "Nicht synchronisiert"
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
@@ -2343,6 +2347,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Aktion"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr "Aktionsschlüsselwörter"
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Aktiv"
@@ -2830,6 +2838,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Knöpfe"
+msgctxt "model:ir.action,name:act_model_data_form"
+msgid "Data"
+msgstr "Daten"
+
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr "Zugriffsberechtigungen Felder"
@@ -2896,11 +2908,11 @@ msgstr "Übersetzungen"
msgctxt "model:ir.action,name:act_translation_set"
msgid "Set Report Translations"
-msgstr "Übersetzungen für Berichte aktualisieren"
+msgstr "Übersetzungen aktualisieren"
msgctxt "model:ir.action,name:act_translation_update"
msgid "Synchronize Translations"
-msgstr "Übersetzungen aktualisieren"
+msgstr "Übersetzungen eingeben"
msgctxt "model:ir.action,name:act_trigger_form"
msgid "Triggers"
@@ -2942,6 +2954,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Wertebereich Aktion Aktuelles Fenster"
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr "Alle"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr "Nicht synchronisiert"
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Aktion aktives Fenster Sicht"
@@ -3014,6 +3036,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Deutsch"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr "Spanisch (Ecuador)"
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Englisch"
@@ -3258,6 +3284,10 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Knöpfe"
+msgctxt "model:ir.ui.menu,name:menu_model_data_form"
+msgid "Data"
+msgstr "Daten"
+
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
msgstr "Zugriffsberechtigungen Felder"
@@ -3324,11 +3354,11 @@ msgstr "Übersetzungen"
msgctxt "model:ir.ui.menu,name:menu_translation_set"
msgid "Set Translations"
-msgstr "Übersetzungen für Berichte aktualisieren"
+msgstr "Übersetzungen aktualisieren"
msgctxt "model:ir.ui.menu,name:menu_translation_update"
msgid "Synchronize Translations"
-msgstr "Übersetzungen aktualisieren"
+msgstr "Übersetzungen eingeben"
msgctxt "model:ir.ui.menu,name:menu_trigger_form"
msgid "Triggers"
@@ -3714,6 +3744,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Knöpfe"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr "Modelldaten"
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr "Synchronisation"
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Zugriffsberechtigung Felder"
@@ -3939,7 +3977,7 @@ msgstr "Übersetzungen bereinigen"
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations Succeed!"
-msgstr "Bereinigung der Übersetzungen erfolgreich durchgeführt!"
+msgstr "Bereinigung der Übersetzungen erfolgreich durchgeführt"
msgctxt "view:ir.translation.export.result:"
msgid "Export Translation"
@@ -3951,7 +3989,7 @@ msgstr "Übersetzung exportieren"
msgctxt "view:ir.translation.set.start:"
msgid "Set Translations"
-msgstr "Übersetzungen für Berichte aktualisieren"
+msgstr "Übersetzungen aktualisieren"
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
@@ -3959,15 +3997,15 @@ msgstr "Übersetzungen aktualisieren?"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
-msgstr "Aktualisierung erfolgreich."
+msgstr "Aktualisierung erfolgreich"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
-msgstr "Übersetzungen für Berichte aktualisieren"
+msgstr "Übersetzungen aktualisieren"
msgctxt "view:ir.translation.update.start:"
msgid "Synchronize Translations"
-msgstr "Übersetzungen aktualisieren"
+msgstr "Übersetzungen eingeben"
msgctxt "view:ir.translation:"
msgid "Translations"
@@ -4115,7 +4153,7 @@ msgstr "Abbrechen"
msgctxt "wizard_button:ir.translation.update,start,update:"
msgid "Update"
-msgstr "Aktualisieren"
+msgstr "Eingeben"
msgctxt "wizard_button:ir.ui.view.show,start,end:"
msgid "Close"
diff --git a/trytond/ir/locale/es_AR.po b/trytond/ir/locale/es_AR.po
index d727c5e..d59aedf 100644
--- a/trytond/ir/locale/es_AR.po
+++ b/trytond/ir/locale/es_AR.po
@@ -25,7 +25,7 @@ msgstr "No está autorizado a borrar este registro."
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
"El número de decimales «%(digits)s» del campo «%(field)s» en «%(value)s» "
"excede su límite."
@@ -254,7 +254,7 @@ msgstr ""
msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
-msgstr "¡\"Al Tiempo\" y otros son mutuamente excluyentes!"
+msgstr "¡«Al Tiempo» y otros son mutuamente excluyentes!"
msgctxt "error:ir.trigger:"
msgid ""
@@ -398,7 +398,7 @@ msgstr "ID"
msgctxt "field:ir.action,keywords:"
msgid "Keywords"
-msgstr "Palabras clave"
+msgstr "Acciones de teclado"
msgctxt "field:ir.action,name:"
msgid "Name"
@@ -474,7 +474,7 @@ msgstr "ID"
msgctxt "field:ir.action.act_window,keywords:"
msgid "Keywords"
-msgstr "Palabras clave"
+msgstr "Acciones de teclado"
msgctxt "field:ir.action.act_window,limit:"
msgid "Limit"
@@ -588,6 +588,10 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Acción"
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Activo"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
@@ -642,7 +646,7 @@ msgstr "ID"
msgctxt "field:ir.action.keyword,keyword:"
msgid "Keyword"
-msgstr "Palabra clave"
+msgstr "Acción de teclado"
msgctxt "field:ir.action.keyword,model:"
msgid "Model"
@@ -702,7 +706,7 @@ msgstr "ID"
msgctxt "field:ir.action.report,keywords:"
msgid "Keywords"
-msgstr "Palabras clave"
+msgstr "Acciones de teclado"
msgctxt "field:ir.action.report,model:"
msgid "Model"
@@ -802,7 +806,7 @@ msgstr "ID"
msgctxt "field:ir.action.url,keywords:"
msgid "Keywords"
-msgstr "Palabras clave"
+msgstr "Acciones de teclado"
msgctxt "field:ir.action.url,name:"
msgid "Name"
@@ -866,7 +870,7 @@ msgstr "ID"
msgctxt "field:ir.action.wizard,keywords:"
msgid "Keywords"
-msgstr "Palabras clave"
+msgstr "Acciones de teclado"
msgctxt "field:ir.action.wizard,model:"
msgid "Model"
@@ -950,7 +954,7 @@ msgstr "Enlace"
msgctxt "field:ir.attachment,name:"
msgid "Name"
-msgstr "Nombre del adjunto"
+msgstr "Nombre"
msgctxt "field:ir.attachment,rec_name:"
msgid "Name"
@@ -1384,21 +1388,17 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Usuario creación"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Fecha de inicio"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Fecha de actualización"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "ID del recurso"
msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
-msgstr "Identificador en el sistema de archivos"
+msgstr "Identificador en el Sistema de Archivos"
+
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr "Valores en el Sistema de Archivos"
msgctxt "field:ir.model.data,id:"
msgid "ID"
@@ -1416,6 +1416,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "No Actualizar"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr "Sin sincronizar"
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Nombre"
@@ -2340,6 +2344,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Acción"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr "Acciones de teclado"
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Activo"
@@ -2824,6 +2832,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Botones"
+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 "Campos de Acceso"
@@ -2862,7 +2874,7 @@ msgstr "Propiedades predeterminadas"
msgctxt "model:ir.action,name:act_rule_group_form"
msgid "Record Rules"
-msgstr "Grabar reglas"
+msgstr "Reglas de registros"
msgctxt "model:ir.action,name:act_sequence_form"
msgid "Sequences"
@@ -2936,13 +2948,23 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Dominio de acción de ventana"
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr "Todo"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr "Sin sincronizar"
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Acción de vista de ventana"
msgctxt "model:ir.action.keyword,name:"
msgid "Action keyword"
-msgstr "Palabra clave de acción"
+msgstr "Acción de teclado"
msgctxt "model:ir.action.report,name:"
msgid "Action report"
@@ -3008,6 +3030,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Alemán"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr "Español (Ecuador)"
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Inglés"
@@ -3066,7 +3092,7 @@ msgstr "Modelo de Acceso a Campo"
msgctxt "model:ir.model.print_model_graph.start,name:"
msgid "Print Model Graph"
-msgstr "Imprimir Gráfico del Modelo"
+msgstr "Imprimir gráfico de modelos"
msgctxt "model:ir.module.module,name:"
msgid "Module"
@@ -3253,9 +3279,13 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Botones"
+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 "Campos de Acceso"
+msgstr "Acceso a los campos"
msgctxt "model:ir.ui.menu,name:menu_model_form"
msgid "Models"
@@ -3287,7 +3317,7 @@ msgstr "Propiedades predeterminadas"
msgctxt "model:ir.ui.menu,name:menu_rule_group_form"
msgid "Record Rules"
-msgstr "Grabar reglas"
+msgstr "Reglas de registros"
msgctxt "model:ir.ui.menu,name:menu_scheduler"
msgid "Scheduler"
@@ -3379,11 +3409,11 @@ msgstr "Ancho vista de árbol"
msgctxt "selection:ir.action.keyword,keyword:"
msgid "Action form"
-msgstr "Formulario de acción"
+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"
@@ -3619,11 +3649,11 @@ msgstr "Abrir una ventana"
msgctxt "view:ir.action.keyword:"
msgid "Keyword"
-msgstr "Palabra clave"
+msgstr "Acción de teclado"
msgctxt "view:ir.action.keyword:"
msgid "Keywords"
-msgstr "Palabras clave"
+msgstr "Acciones de teclado"
msgctxt "view:ir.action.report:"
msgid "General"
@@ -3709,6 +3739,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Botones"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr "Modelo de Datos"
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr "Sincronizar"
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Acceso al Campo"
@@ -3723,7 +3761,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 de modelos"
msgctxt "view:ir.model:"
msgid "Model Description"
@@ -3833,7 +3871,7 @@ msgstr ""
msgctxt "view:ir.rule.group:"
msgid "Record rules"
-msgstr "Grabar reglas"
+msgstr "Reglas de registros"
msgctxt "view:ir.rule.group:"
msgid "The rule is satisfied if at least one test is True"
diff --git a/trytond/ir/locale/es_CO.po b/trytond/ir/locale/es_CO.po
index 01c59ac..7b69425 100644
--- a/trytond/ir/locale/es_CO.po
+++ b/trytond/ir/locale/es_CO.po
@@ -23,7 +23,7 @@ msgstr "No está autorizado a borrar este registro."
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
"El numero de decimales \"%(digits)s\" del campo \"%(field)s\" en el valor "
"\"%(value)s\" excede el limite."
@@ -120,19 +120,19 @@ msgstr "decimal_point y «thousands_sep» deben ser distintos"
msgctxt "error:ir.model.access:"
msgid "You can not create this kind of document! (%s)"
-msgstr "No puede crear este tipo de documento (%s)"
+msgstr "No puede crear este tipo de documento! (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not delete this document! (%s)"
-msgstr "No puede borrar este documento (%s)"
+msgstr "No puede borrar este documento! (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not read this document! (%s)"
-msgstr "No puede leer este documento (%s)"
+msgstr "No puede leer este documento! (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not write in this document! (%s)"
-msgstr "No puede escribir en este documento (%s)"
+msgstr "No puede escribir en este documento! (%s)"
msgctxt "error:ir.model.button:"
msgid "The button name in model must be unique!"
@@ -586,6 +586,10 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Acción"
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Activo"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Fecha de Creación"
@@ -1382,14 +1386,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Creado por Usuario"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Fecha Inicial"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Fecha de Actualización"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "ID del recurso"
@@ -1398,6 +1394,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Identificador en Sistema de Archivos"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr "Valores en el Sistema de Archivos"
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr "ID"
@@ -1414,6 +1414,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "No Actualizar"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr "Sin Sincronizar"
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Nombre"
@@ -2338,6 +2342,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Acción"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr "Teclas de Acción"
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Activo"
@@ -2772,7 +2780,7 @@ msgstr "Acciones"
msgctxt "model:ir.action,name:act_action_report_form"
msgid "Reports"
-msgstr "Informes"
+msgstr "Reportes"
msgctxt "model:ir.action,name:act_action_url_form"
msgid "URLs"
@@ -2822,6 +2830,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Botones"
+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 Campos"
@@ -2934,6 +2946,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Accion en dominio de ventana de acción"
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr "Todo"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr "Sin Sincronizar"
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Acción en vista de ventana de acción"
@@ -3006,6 +3028,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Alemán"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr "Español (Ecuador)"
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Inglés"
@@ -3048,7 +3074,7 @@ msgstr "Permisos de modelo"
msgctxt "model:ir.model.button,name:"
msgid "Model Button"
-msgstr "Modelo de Botón"
+msgstr "Modelo Botón"
msgctxt "model:ir.model.data,name:"
msgid "Model data"
@@ -3145,11 +3171,11 @@ msgstr "Iniciar limpieza de traducciones"
msgctxt "model:ir.translation.export.result,name:"
msgid "Export translation"
-msgstr "Exportar traducción - archivo"
+msgstr "Exportar traducción"
msgctxt "model:ir.translation.export.start,name:"
msgid "Export translation"
-msgstr "Exportar traducción - archivo"
+msgstr "Exportar traducción"
msgctxt "model:ir.translation.set.start,name:"
msgid "Set Translation"
@@ -3193,7 +3219,7 @@ msgstr "Ventana de Acciones"
msgctxt "model:ir.ui.menu,name:menu_action_report_form"
msgid "Reports"
-msgstr "Informes"
+msgstr "Reportes"
msgctxt "model:ir.ui.menu,name:menu_action_url"
msgid "URLs"
@@ -3251,6 +3277,10 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Botones"
+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"
@@ -3629,11 +3659,11 @@ msgstr "General"
msgctxt "view:ir.action.report:"
msgid "Report"
-msgstr "Informe"
+msgstr "Reporte"
msgctxt "view:ir.action.report:"
msgid "Report xml"
-msgstr "Informe xml"
+msgstr "Reporte xml"
msgctxt "view:ir.action.url:"
msgid "General"
@@ -3697,7 +3727,7 @@ msgstr "Formato de Números"
msgctxt "view:ir.model.access:"
msgid "Access controls"
-msgstr "Acceso a controles"
+msgstr "Controles de acceso"
msgctxt "view:ir.model.button:"
msgid "Button"
@@ -3707,6 +3737,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Botones"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr "Modelo de Datos"
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr "Syncronizar"
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Permiso de Campo"
diff --git a/trytond/ir/locale/es_CO.po b/trytond/ir/locale/es_EC.po
similarity index 91%
copy from trytond/ir/locale/es_CO.po
copy to trytond/ir/locale/es_EC.po
index 01c59ac..de8595c 100644
--- a/trytond/ir/locale/es_CO.po
+++ b/trytond/ir/locale/es_EC.po
@@ -7,26 +7,28 @@ msgid ""
"You try to bypass an access rule!\n"
"(Document type: %s)"
msgstr ""
-"Está tratando de saltarse una regla de acceso!\n"
+"¡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 "Esta intentando evitar una regla de acceso."
+msgstr ""
+"Esta intentando evitar una regla de acceso.\n"
+"(Tipo de documento: %s)"
msgctxt "error:delete_xml_record:"
msgid "You are not allowed to delete this record."
-msgstr "No está autorizado a borrar este registro."
+msgstr "No está autorizado para eliminar este registro."
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
-"El numero de decimales \"%(digits)s\" del campo \"%(field)s\" en el valor "
-"\"%(value)s\" excede el limite."
+"El número de decimales \"%(digits)s\" del campo \"%(field)s\" en el "
+"valor\"%(value)s\" excede su limite."
msgctxt "error:domain_validation_record:"
msgid ""
@@ -41,40 +43,38 @@ msgid ""
"Could not delete the records because they are used on field \"%(field)s\" of"
" \"%(model)s\"."
msgstr ""
-"No se pudieron borrar los registros porque son usados en el campo "
+"No se pudieron eliminar los registros porque son utilizados en el campo "
"\"%(field)s\" de \"%(model)s\"."
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\" en el campo \"%(field)s\" en el modelo \"%(model)s\" "
-"no existe."
+msgstr "El valor \"%(value)s\" del campo \"%(field)s\" en \"%(model)s\" no existe."
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\"."
+msgstr "El contexto \"%(context)s\" en la acción \"%(action)s\" no es válido."
msgctxt "error:ir.action.act_window:"
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\"."
+"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 view \"%(view)s\" for action \"%(action)s\"."
-msgstr "Vista \"%(view)s\" inválida para la acción \"%(action)s\"."
+msgstr "La vista \"%(view)s\" para la acción \"%(action)s\" no es válida."
msgctxt "error:ir.action.keyword:"
msgid "Wrong wizard model in keyword action \"%s\"."
-msgstr "Errado modelo asistente para acción la \"%s\"."
+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 "El correo electronico definido en el informe \"%s\" es inválido."
+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 de los módulos debe ser único!"
+msgstr "¡El nombre interno debe ser único por módulo!"
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource!"
@@ -92,23 +92,25 @@ msgid ""
"\n"
"%s\n"
msgstr ""
-"La siguiente acción falló al ejecutar apropiadamente: \"%s\"\n"
+"La siguiente acción falló cuando se ejecutaba: \"%s\"\n"
"%s\n"
-" Traceback:\n"
+" Traza del programa:\n"
"\n"
-"%s"
+"%s\n"
msgctxt "error:ir.lang:"
msgid "Default language can not be deleted."
-msgstr "Los lenguajes por defecto no pueden ser borrados."
+msgstr "El idioma por defecto no puede ser eliminado."
msgctxt "error:ir.lang:"
msgid "Invalid date format \"%(format)s\" on \"%(language)s\" language."
-msgstr "Formato de fecha inválido \"%(format)s\" para el lenguaje \"%(languages)s\"."
+msgstr ""
+"El formato de la fecha \"%(format)s\" para el idioma \"%(languages)s\" no es"
+" válido."
msgctxt "error:ir.lang:"
msgid "Invalid grouping \"%(grouping)s\" on \"%(language)s\" language."
-msgstr "Agrupamiento inválido \"%(grouping)s\" en lenguaje \"%(languages)s\"."
+msgstr "Agrupamiento no válido \"%(grouping)s\" en idioma \"%(languages)s\"."
msgctxt "error:ir.lang:"
msgid "The default language must be translatable."
@@ -116,7 +118,7 @@ msgstr "El idioma por defecto debe ser traducible."
msgctxt "error:ir.lang:"
msgid "decimal_point and thousands_sep must be different!"
-msgstr "decimal_point y «thousands_sep» deben ser distintos"
+msgstr "¡el punto decimal y el separador de miles deben ser distintos!"
msgctxt "error:ir.model.access:"
msgid "You can not create this kind of document! (%s)"
@@ -124,7 +126,7 @@ msgstr "No puede crear este tipo de documento (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not delete this document! (%s)"
-msgstr "No puede borrar este documento (%s)"
+msgstr "No puede eliminar este documento (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not read this document! (%s)"
@@ -136,7 +138,7 @@ msgstr "No puede escribir en este documento (%s)"
msgctxt "error:ir.model.button:"
msgid "The button name in model must be unique!"
-msgstr "El nombre del botón en el modelo debe ser único!"
+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!"
@@ -152,7 +154,7 @@ 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 Modelo Campo \"%s\" no es un identificador python válido."
+msgstr "El nombre del Campo Modelo \"%s\" no es un identificador python válido."
msgctxt "error:ir.model.field:"
msgid "The field name in model must be unique!"
@@ -194,21 +196,19 @@ msgstr "Global y Predeterminado son mutuamente excluyentes"
msgctxt "error:ir.rule:"
msgid "Invalid domain in rule \"%s\"."
-msgstr "Dominio inválido en regla \"%s\"."
+msgstr "Dominio no válido en la regla \"%s\"."
msgctxt "error:ir.sequence.strict:"
msgid "Invalid prefix \"%(prefix)s\" on sequence \"%(sequence)s\"."
-msgstr "Prefijo inválido \"%(prefix)s\" en secuencia \"%(sequence)s\"."
+msgstr "El prefijo \"%(prefix)s\" en la secuencia \"%(sequence)s\" no es válido."
msgctxt "error:ir.sequence.strict:"
msgid "Invalid suffix \"%(suffix)s\" on sequence \"%(sequence)s\"."
-msgstr "Sufijo inválido \"%(suffix)s\" en secuencia \"%(sequence)s\"."
+msgstr "El sufijo \"%(sufix)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 marca de tiempo no puede estar en el futuro para la secuencia "
-"\"%s\"."
+msgstr "La ultima fecha-hora no puede ser del futuro para la secuencia \"%s\"."
msgctxt "error:ir.sequence.strict:"
msgid "Missing sequence."
@@ -216,21 +216,20 @@ msgstr "Falta la secuencia."
msgctxt "error:ir.sequence.strict:"
msgid "Timestamp rounding should be greater than 0"
-msgstr "El redondeo de la marca de tiempo debe ser mayor que 0"
+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 "Prefijo inválido \"%(prefix)s\" en secuencia \"%(sequence)s\"."
+msgstr "El prefijo \"%(prefix)s\" en la secuencia \"%(sequence)s\" no es válido."
msgctxt "error:ir.sequence:"
msgid "Invalid suffix \"%(suffix)s\" on sequence \"%(sequence)s\"."
-msgstr "Sufijo inválido \"%(suffix)s\" en secuencia \"%(sequence)s\"."
+msgstr "El sufijo \"%(sufix)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 marca de tiempo no puede estar en el futuro para la secuencia "
-"\"%s\"."
+"La ultima fecha-hora no puede estar en el futuro para la secuencia \"%s\"."
msgctxt "error:ir.sequence:"
msgid "Missing sequence."
@@ -238,7 +237,7 @@ msgstr "Falta la secuencia."
msgctxt "error:ir.sequence:"
msgid "Timestamp rounding should be greater than 0"
-msgstr "El redondeo de la marca de tiempo debe ser mayor que 0"
+msgstr "El redondeo de la fecha-hora debe ser mayor que 0"
msgctxt "error:ir.translation:"
msgid "Translation must be unique"
@@ -249,19 +248,19 @@ 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 esta es una traducción "
+"No puede exportar la traduccion %(name)s porque es una traducción "
"sobreescrita por el módulo %(overrinding_module)s"
msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
-msgstr "\"Al Tiempo\" y otros son mutuamente excluyentes!"
+msgstr "¡\"A Tiempo\" y otros son mutuamente excluyentes!"
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid python expression on trigger "
"\"%(trigger)s\"."
msgstr ""
-"Condición \"%(condition)s\" no es una expressión python válida en el "
+"La condición \"%(condition)s\" no es una expressión python válida en el "
"disparador \"%s(trigger)s\"."
msgctxt "error:ir.ui.menu:"
@@ -270,21 +269,23 @@ msgstr "\"%s\" no es un nombre válido de menú porque no esta permitido contene
msgctxt "error:ir.ui.view:"
msgid "Invalid XML for view \"%s\"."
-msgstr "XML inválido para la vista \"%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"
+"¡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 "Esta intentando leer registros que ya no existen."
+msgstr ""
+"Está intentando leer registros que ya no existen.\n"
+"(Tipo de documento: %s)"
msgctxt "error:recursion_error:"
msgid ""
@@ -292,7 +293,7 @@ msgid ""
" was configured as ancestor of itself."
msgstr ""
"Error de recursión: El registro \"%(rec_name)s\" con padre \"%(rec_name)s\" "
-"fue configurado como antecesor del mismo."
+"fue configurado como antecesor de si mismo."
msgctxt "error:reference_syntax_error:"
msgid "Syntax error for reference %r in %s"
@@ -304,33 +305,33 @@ 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 el modelo \"%(model)s\" es obligatorio."
+msgstr "El campo \"%(field)s\" en \"%(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 el modelo \"%(model)s\" es obligatorio."
+msgstr "El campo \"%(field)s\" en \"%(model)s\" es requerido."
msgctxt "error:search_function_missing:"
msgid "Missing search function on field \"%s\"."
-msgstr "Falta la función en el campo \"%s\"."
+msgstr "Falta la función de búsqueda en el campo \"%s\"."
msgctxt "error:selection_validation_record:"
msgid ""
"The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" is not in "
"the selection."
msgstr ""
-"El valor \"%(value)s\" en el campo \"%(field)s\" en el modelo \"%(model)s\" "
-"no esta en la selección."
+"El valor \"%(value)s\" del campo \"%(field)s\" en el modelo \"%(model)s\" no"
+" esta en la selección."
msgctxt "error:selection_value_notfound:"
msgid "Value not in the selection for field \"%s\"."
-msgstr "El valor no esta en la selección para el campo \"%s\"."
+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 el modelo \"%(model)s\" is"
-" demasiado largo."
+"El tamaño \"%(size)s\" del campo \"%(field)s\" en \"%(model)s\" is demasiado"
+" largo."
msgctxt "error:time_format_validation_record:"
msgid "The time value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" is not valid."
@@ -347,7 +348,7 @@ 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"
+"¡Está tratando de escribir en registros que ya no existen!\n"
"(Tipo de documento: %s)"
msgctxt "error:write_error:"
@@ -364,7 +365,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 id XML %r en %s"
+msgstr "Error de sintaxis para XML id %r en %s"
msgctxt "error:xml_record_desc:"
msgid "This record is part of the base configuration."
@@ -396,7 +397,7 @@ msgstr "ID"
msgctxt "field:ir.action,keywords:"
msgid "Keywords"
-msgstr "Teclas"
+msgstr "Acciones de teclado"
msgctxt "field:ir.action,name:"
msgid "Name"
@@ -472,7 +473,7 @@ msgstr "ID"
msgctxt "field:ir.action.act_window,keywords:"
msgid "Keywords"
-msgstr "Teclas"
+msgstr "Acciones de teclado"
msgctxt "field:ir.action.act_window,limit:"
msgid "Limit"
@@ -586,6 +587,10 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Acción"
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Activo"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Fecha de Creación"
@@ -700,7 +705,7 @@ msgstr "ID"
msgctxt "field:ir.action.report,keywords:"
msgid "Keywords"
-msgstr "Teclas"
+msgstr "Acciones de teclado"
msgctxt "field:ir.action.report,model:"
msgid "Model"
@@ -752,7 +757,7 @@ msgstr "Estilo"
msgctxt "field:ir.action.report,template_extension:"
msgid "Template Extension"
-msgstr "Plantilla de Extensión"
+msgstr "Extensión de Plantilla"
msgctxt "field:ir.action.report,type:"
msgid "Type"
@@ -1060,11 +1065,11 @@ msgstr "ID"
msgctxt "field:ir.cron,interval_number:"
msgid "Interval Number"
-msgstr "Número de Intervalo"
+msgstr "Número de Intervalos"
msgctxt "field:ir.cron,interval_type:"
msgid "Interval Unit"
-msgstr "Unidad Intervalo"
+msgstr "Unidad de Intervalo"
msgctxt "field:ir.cron,model:"
msgid "Model"
@@ -1092,7 +1097,7 @@ msgstr "Repetir Fallidos"
msgctxt "field:ir.cron,request_user:"
msgid "Request User"
-msgstr "Petición de Usuario"
+msgstr "Solicitud de Usuario"
msgctxt "field:ir.cron,user:"
msgid "Execution User"
@@ -1382,21 +1387,17 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Creado por Usuario"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Fecha Inicial"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Fecha de Actualización"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "ID del recurso"
msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
-msgstr "Identificador en Sistema de Archivos"
+msgstr "Identificador en el Sistema de Archivos"
+
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr "Valores del Sistema de Archivos"
msgctxt "field:ir.model.data,id:"
msgid "ID"
@@ -1414,6 +1415,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "No Actualizar"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr "Fuera de Sincronización"
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Nombre"
@@ -1472,7 +1477,7 @@ msgstr "Nombre"
msgctxt "field:ir.model.field,relation:"
msgid "Model Relation"
-msgstr "Modelo Relación"
+msgstr "Relación del Modelo"
msgctxt "field:ir.model.field,ttype:"
msgid "Field Type"
@@ -1516,7 +1521,7 @@ msgstr "Permiso para Crear"
msgctxt "field:ir.model.field.access,perm_delete:"
msgid "Delete Access"
-msgstr "Permiso para Borrar"
+msgstr "Permiso para Eliminar"
msgctxt "field:ir.model.field.access,perm_read:"
msgid "Read Access"
@@ -1804,7 +1809,7 @@ msgstr "Permiso para Crear"
msgctxt "field:ir.rule.group,perm_delete:"
msgid "Delete Access"
-msgstr "Permiso para Borrar"
+msgstr "Permiso para Eliminar"
msgctxt "field:ir.rule.group,perm_read:"
msgid "Read Access"
@@ -1856,7 +1861,7 @@ msgstr "ID"
msgctxt "field:ir.sequence,last_timestamp:"
msgid "Last Timestamp"
-msgstr "Última Marca de Tiempo"
+msgstr "Última Fecha-hora"
msgctxt "field:ir.sequence,name:"
msgid "Sequence Name"
@@ -1868,11 +1873,11 @@ msgstr "Número de Incremento"
msgctxt "field:ir.sequence,number_next:"
msgid "Next Number"
-msgstr "Número Siguiente"
+msgstr "Siguiente Número"
msgctxt "field:ir.sequence,number_next_internal:"
msgid "Next Number"
-msgstr "Número Siguiente"
+msgstr "Siguiente Número"
msgctxt "field:ir.sequence,padding:"
msgid "Number padding"
@@ -1892,11 +1897,11 @@ msgstr "Sufijo"
msgctxt "field:ir.sequence,timestamp_offset:"
msgid "Timestamp Offset"
-msgstr "Compensación de Marca de Tiempo"
+msgstr "Desfase de Fecha-hora"
msgctxt "field:ir.sequence,timestamp_rounding:"
msgid "Timestamp Rounding"
-msgstr "Redondeo de Marca de Tiempo"
+msgstr "Redondeo de Fecha-hora"
msgctxt "field:ir.sequence,type:"
msgid "Type"
@@ -1932,7 +1937,7 @@ msgstr "ID"
msgctxt "field:ir.sequence.strict,last_timestamp:"
msgid "Last Timestamp"
-msgstr "Última Marca de Tiempo"
+msgstr "Última Fecha-hora"
msgctxt "field:ir.sequence.strict,name:"
msgid "Sequence Name"
@@ -1944,11 +1949,11 @@ msgstr "Número de Incremento"
msgctxt "field:ir.sequence.strict,number_next:"
msgid "Next Number"
-msgstr "Número Siguiente"
+msgstr "Siguiente Número"
msgctxt "field:ir.sequence.strict,number_next_internal:"
msgid "Next Number"
-msgstr "Número Siguiente"
+msgstr "Siguiente Número"
msgctxt "field:ir.sequence.strict,padding:"
msgid "Number padding"
@@ -1968,11 +1973,11 @@ msgstr "Sufijo"
msgctxt "field:ir.sequence.strict,timestamp_offset:"
msgid "Timestamp Offset"
-msgstr "Compensación de Marca de Tiempo"
+msgstr "Desfase de Fecha-hora"
msgctxt "field:ir.sequence.strict,timestamp_rounding:"
msgid "Timestamp Rounding"
-msgstr "Redondeo de Marca de Tiempo"
+msgstr "Redondeo de Fecha-hora"
msgctxt "field:ir.sequence.strict,type:"
msgid "Type"
@@ -2084,7 +2089,7 @@ msgstr "Creado por Usuario"
msgctxt "field:ir.translation,fuzzy:"
msgid "Fuzzy"
-msgstr "Vago"
+msgstr "Confuso"
msgctxt "field:ir.translation,id:"
msgid "ID"
@@ -2188,11 +2193,11 @@ msgstr "Idioma"
msgctxt "field:ir.trigger,action_function:"
msgid "Action Function"
-msgstr "Acción Función"
+msgstr "Función de la Acción"
msgctxt "field:ir.trigger,action_model:"
msgid "Action Model"
-msgstr "Acción en Modelo"
+msgstr "Acción de Modelo"
msgctxt "field:ir.trigger,active:"
msgid "Active"
@@ -2338,6 +2343,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Acción"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr "Teclas de Acción"
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Activo"
@@ -2436,7 +2445,7 @@ msgstr "Modificado por Usuario"
msgctxt "field:ir.ui.view,arch:"
msgid "View Architecture"
-msgstr "Ver arquitectura"
+msgstr "Ver Arquitectura"
msgctxt "field:ir.ui.view,create_date:"
msgid "Create Date"
@@ -2632,11 +2641,11 @@ 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 en lista"
+msgstr "Límite predeterminado para la vista de lista"
msgctxt "help:ir.action.act_window,search_value:"
msgid "Default search criteria for the list view"
-msgstr "Criterio de búsqueda por defecto para la vista en lista"
+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"
@@ -2648,7 +2657,7 @@ msgid ""
"Example: {'to': 'test at example.com', 'cc': 'user at example.com'}"
msgstr ""
"Diccionario de Python donde las claves definen \"to\" \"cc\" \"subject\"\n"
-"Ejemplo: {'to': 'test at example.com', 'cc': 'user at example.com'}"
+"Ejemplo: {'to': 'test at ejemplo.com', 'cc': 'usuario at ejemplo.com'}"
msgctxt "help:ir.action.report,extension:"
msgid ""
@@ -2664,7 +2673,7 @@ msgstr "Define el estilo a aplicar en el informe."
msgctxt "help:ir.action.wizard,window:"
msgid "Run wizard in a new window"
-msgstr "Ejecutar asistente en una nueva ventana"
+msgstr "Ejecutar el asistente en una nueva ventana"
msgctxt "help:ir.cron,number_calls:"
msgid ""
@@ -2684,7 +2693,7 @@ msgstr "El usuario que se utilizará para ejecutar esta acción"
msgctxt "help:ir.lang,code:"
msgid "RFC 4646 tag: http://tools.ietf.org/html/rfc4646"
-msgstr "RFC 4646 tag: http://tools.ietf.org/html/rfc4646"
+msgstr "Etiqueta RFC 4646: http://tools.ietf.org/html/rfc4646"
msgctxt "help:ir.model,module:"
msgid "Module in which this model is defined"
@@ -2707,8 +2716,8 @@ msgid ""
"Entering a Python Regular Expression will exclude matching models from the "
"graph."
msgstr ""
-"Ingresando una Expresión Regular de Python se excluirán modelos que "
-"concuerden con el gráfico."
+"Ingresando una Expresión Regular de Python se excluirán del gráfico modelos "
+"que concuerden."
msgctxt "help:ir.rule,domain:"
msgid "Domain is evaluated with \"user\" as the current user"
@@ -2716,7 +2725,7 @@ msgstr "El dominio es evaluado con el \"usuario\" como el usuario actual"
msgctxt "help:ir.rule.group,default_p:"
msgid "Add this rule to all users by default"
-msgstr "Añadir la regla a todos los usuarios por defecto"
+msgstr "Añadir esta regla a todos los usuarios por defecto"
msgctxt "help:ir.rule.group,global_p:"
msgid ""
@@ -2728,7 +2737,7 @@ msgstr ""
msgctxt "help:ir.rule.group,rules:"
msgid "The rule is satisfied if at least one test is True"
-msgstr "La regla se satisface si al menos una condición es cierta"
+msgstr "La regla se satisface si al menos una condición es verdadera"
msgctxt "help:ir.trigger,condition:"
msgid ""
@@ -2788,7 +2797,7 @@ msgstr "Adjuntos"
msgctxt "model:ir.action,name:act_config_wizard_item_form"
msgid "Config Wizard Items"
-msgstr "Asistentes de Configuración"
+msgstr "Elementos del Asistente de Configuración"
msgctxt "model:ir.action,name:act_cron_form"
msgid "Scheduled Actions"
@@ -2822,9 +2831,14 @@ 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 Campos"
+msgstr "Permisos de los Campos"
msgctxt "model:ir.action,name:act_model_fields_form"
msgid "Fields"
@@ -2836,7 +2850,7 @@ msgstr "Modelos"
msgctxt "model:ir.action,name:act_module_config"
msgid "Configure Modules"
-msgstr "Configuración de Modulos"
+msgstr "Configuración de Módulos"
msgctxt "model:ir.action,name:act_module_config_wizard"
msgid "Module Configuration"
@@ -2916,7 +2930,7 @@ msgstr "Estado de Arbol"
msgctxt "model:ir.action,name:act_view_tree_width_form"
msgid "View Tree Width"
-msgstr "Ancho de Columna"
+msgstr "Ancho de la Vista de Arbol"
msgctxt "model:ir.action,name:print_model_graph"
msgid "Graph"
@@ -2934,13 +2948,24 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Accion en dominio de ventana de acción"
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+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"
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Acción en vista de ventana de acción"
msgctxt "model:ir.action.keyword,name:"
msgid "Action keyword"
-msgstr "Teclas de acción "
+msgstr "Tecla de acción "
msgctxt "model:ir.action.report,name:"
msgid "Action report"
@@ -2952,7 +2977,7 @@ msgstr "URL de la acción"
msgctxt "model:ir.action.wizard,name:"
msgid "Action wizard"
-msgstr "Acción del Asistente"
+msgstr "Asistente de la acción"
msgctxt "model:ir.attachment,name:"
msgid "Attachment"
@@ -3006,6 +3031,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Alemán"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr "Español (Ecuador)"
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Inglés"
@@ -3048,19 +3077,19 @@ msgstr "Permisos de modelo"
msgctxt "model:ir.model.button,name:"
msgid "Model Button"
-msgstr "Modelo de Botón"
+msgstr "Botón de Modelo"
msgctxt "model:ir.model.data,name:"
msgid "Model data"
-msgstr "Modelo de datos"
+msgstr "Datos de modelo"
msgctxt "model:ir.model.field,name:"
msgid "Model field"
-msgstr "Modelo de campo"
+msgstr "Campo de modelo"
msgctxt "model:ir.model.field.access,name:"
msgid "Model Field Access"
-msgstr "Permiso de Modelo Campo"
+msgstr "Permiso Campo de Modelo"
msgctxt "model:ir.model.print_model_graph.start,name:"
msgid "Print Model Graph"
@@ -3072,20 +3101,19 @@ msgstr "Módulo"
msgctxt "model:ir.module.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr "Configuración del Módulo Terminada"
+msgstr "Asitente de Configuración del Módulo - Realizado"
msgctxt "model:ir.module.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr "Primer Asistente de Configuración de 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"
-msgstr ""
-"Asistente de configuración a ejecutar después de la instalaciń del módulo"
+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 "Siguiente Asistente de Configuración de Módulo"
+msgstr "Asistente de Configuración del Módulo - Otro"
msgctxt "model:ir.module.module.dependency,name:"
msgid "Module dependency"
@@ -3093,11 +3121,11 @@ msgstr "Dependencias del módulo"
msgctxt "model:ir.module.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr "Actualización del Módulo Terminada"
+msgstr "Instalación / Actualización del Módulo - Realizada"
msgctxt "model:ir.module.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
-msgstr "Inicio de Asistente de Instalación del Módulo"
+msgstr "Instalación / Actualización del Módulo - Iniciar"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3109,7 +3137,7 @@ msgstr "Regla"
msgctxt "model:ir.rule.group,name:"
msgid "Rule group"
-msgstr "Regla de grupo"
+msgstr "Grupo de reglas"
msgctxt "model:ir.sequence,name:"
msgid "Sequence"
@@ -3213,7 +3241,7 @@ msgstr "Adjuntos"
msgctxt "model:ir.ui.menu,name:menu_config_wizard_item_form"
msgid "Config Wizard Items"
-msgstr "Asistentes de Configuración"
+msgstr "Elementos del Asistente de Configuración"
msgctxt "model:ir.ui.menu,name:menu_cron_form"
msgid "Scheduled Actions"
@@ -3251,6 +3279,11 @@ 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"
@@ -3317,7 +3350,7 @@ msgstr "Traducciones"
msgctxt "model:ir.ui.menu,name:menu_translation_set"
msgid "Set Translations"
-msgstr "Establecer Traducción"
+msgstr "Establecer Traducciones"
msgctxt "model:ir.ui.menu,name:menu_translation_update"
msgid "Synchronize Translations"
@@ -3345,7 +3378,7 @@ msgstr "Estado de Arbol"
msgctxt "model:ir.ui.menu,name:menu_view_tree_width"
msgid "View Tree Width"
-msgstr "Ancho de Columna"
+msgstr "Ancho de la Vista de Arbol"
msgctxt "model:ir.ui.menu,name:model_model_fields_form"
msgid "Fields"
@@ -3373,7 +3406,7 @@ msgstr "Estado de Vista de Arbol"
msgctxt "model:ir.ui.view_tree_width,name:"
msgid "View Tree Width"
-msgstr "Ancho de Columna"
+msgstr "Ancho de la Vista de Arbol"
msgctxt "selection:ir.action.keyword,keyword:"
msgid "Action form"
@@ -3433,7 +3466,7 @@ msgstr "De izquierda a derecha"
msgctxt "selection:ir.lang,direction:"
msgid "Right-to-left"
-msgstr "Derecha-a-Izquierda"
+msgstr "De derecha a Izquierda"
msgctxt "selection:ir.module.module,state:"
msgid "Installed"
@@ -3445,15 +3478,15 @@ msgstr "No instalado"
msgctxt "selection:ir.module.module,state:"
msgid "To be installed"
-msgstr "Pendiente de ser instalado"
+msgstr "Para ser instalado"
msgctxt "selection:ir.module.module,state:"
msgid "To be removed"
-msgstr "Pendiente de ser eliminado"
+msgstr "Para ser eliminado"
msgctxt "selection:ir.module.module,state:"
msgid "To be upgraded"
-msgstr "Pendiente de ser actualizado"
+msgstr "Para ser actualizado"
msgctxt "selection:ir.module.module.config_wizard.item,state:"
msgid "Done"
@@ -3473,15 +3506,15 @@ msgstr "No instalado"
msgctxt "selection:ir.module.module.dependency,state:"
msgid "To be installed"
-msgstr "Pendiente de instalar"
+msgstr "Para ser instalado"
msgctxt "selection:ir.module.module.dependency,state:"
msgid "To be removed"
-msgstr "Pendiente de ser eliminado"
+msgstr "Para ser eliminado"
msgctxt "selection:ir.module.module.dependency,state:"
msgid "To be upgraded"
-msgstr "Pendiente de ser actualizado"
+msgstr "Para ser actualizado"
msgctxt "selection:ir.module.module.dependency,state:"
msgid "Unknown"
@@ -3489,11 +3522,11 @@ msgstr "Desconocido"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
-msgstr "Marca de Tiempo Decimal"
+msgstr "Fecha-hora Decimal"
msgctxt "selection:ir.sequence,type:"
msgid "Hexadecimal Timestamp"
-msgstr "Marca de Tiempo Hexadecimal"
+msgstr "Fecha-hora Hexadecimal"
msgctxt "selection:ir.sequence,type:"
msgid "Incremental"
@@ -3501,11 +3534,11 @@ msgstr "Incremental"
msgctxt "selection:ir.sequence.strict,type:"
msgid "Decimal Timestamp"
-msgstr "Marca de Tiempo Decimal"
+msgstr "Fecha-hora Decimal"
msgctxt "selection:ir.sequence.strict,type:"
msgid "Hexadecimal Timestamp"
-msgstr "Marca de Tiempo Hexadecimal"
+msgstr "Fecha-hora Hexadecimal"
msgctxt "selection:ir.sequence.strict,type:"
msgid "Incremental"
@@ -3617,11 +3650,11 @@ msgstr "Abrir una Ventana"
msgctxt "view:ir.action.keyword:"
msgid "Keyword"
-msgstr "Tecla"
+msgstr "Acción de teclado"
msgctxt "view:ir.action.keyword:"
msgid "Keywords"
-msgstr "Teclas"
+msgstr "Acciones de teclado"
msgctxt "view:ir.action.report:"
msgid "General"
@@ -3633,7 +3666,7 @@ msgstr "Informe"
msgctxt "view:ir.action.report:"
msgid "Report xml"
-msgstr "Informe xml"
+msgstr "Informe XML"
msgctxt "view:ir.action.url:"
msgid "General"
@@ -3697,7 +3730,7 @@ msgstr "Formato de Números"
msgctxt "view:ir.model.access:"
msgid "Access controls"
-msgstr "Acceso a controles"
+msgstr "Controles de acceso"
msgctxt "view:ir.model.button:"
msgid "Button"
@@ -3707,6 +3740,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Botones"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr "Datos de Modelo"
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr "Sincronizar"
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Permiso de Campo"
@@ -3733,7 +3774,7 @@ msgstr "Configuración del módulo"
msgctxt "view:ir.module.module.config_wizard.done:"
msgid "The configuration is done."
-msgstr "La configuracion ha terminado."
+msgstr "La configuracion se ha realizado."
msgctxt "view:ir.module.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
@@ -3744,12 +3785,12 @@ msgid ""
"You will be able to configure your installation depending on the modules you"
" have installed."
msgstr ""
-"Usted será capaz de configurar la instalación en función de los módulos que "
-"ha instalado."
+"Usted será capaz de 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 "Asistentes de Configuración"
+msgstr "Elementos del Asistente de Configuración"
msgctxt "view:ir.module.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
@@ -3765,15 +3806,15 @@ msgstr "Dependencia"
msgctxt "view:ir.module.module.install_upgrade.done:"
msgid "System Upgrade Done"
-msgstr "Actualización del Sistema Finalizada"
+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 se han actualizado / instalado!"
msgctxt "view:ir.module.module.install_upgrade.start:"
msgid "Note that this operation may take a few minutes."
-msgstr "Esta operación puede demorar varios minutos."
+msgstr "Tenga en cuenta que esta operación puede demorar varios minutos."
msgctxt "view:ir.module.module.install_upgrade.start:"
msgid "System Upgrade"
@@ -3836,7 +3877,7 @@ 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 por lo menos una condición es verdadera"
+msgstr "La regla se satisface si al menos una condición es verdadera"
msgctxt "view:ir.rule:"
msgid "Test"
@@ -3860,7 +3901,7 @@ msgstr "Día:"
msgctxt "view:ir.sequence.strict:"
msgid "Legend (for prefix, suffix)"
-msgstr "Leyenda (variables para usar en prefijo y/o sufijo)"
+msgstr "Leyenda (para prefijo y/o sufijo)"
msgctxt "view:ir.sequence.strict:"
msgid "Month:"
@@ -3900,7 +3941,7 @@ msgstr "Incremental"
msgctxt "view:ir.sequence:"
msgid "Legend (Placeholders for prefix, suffix)"
-msgstr "Leyenda (variables para usar en prefijo y/o sufijo)"
+msgstr "Leyenda (variables para utilizar en prefijo y/o sufijo)"
msgctxt "view:ir.sequence:"
msgid "Month:"
@@ -3912,7 +3953,7 @@ msgstr "Secuencias"
msgctxt "view:ir.sequence:"
msgid "Timestamp"
-msgstr "Marca de tiempo"
+msgstr "Fecha-hora"
msgctxt "view:ir.sequence:"
msgid "Year:"
@@ -3924,7 +3965,7 @@ msgstr "Limpiar Traducciones"
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations?"
-msgstr "Limpiar Traducciones?"
+msgstr "¿Limpiar las Traducciones?"
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations"
@@ -3944,19 +3985,19 @@ msgstr "Exportar Traducción"
msgctxt "view:ir.translation.set.start:"
msgid "Set Translations"
-msgstr "Establecer Traducción"
+msgstr "Establecer Traducciones"
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
-msgstr "Sincronizar Traducciones?"
+msgstr "¿Sincronizar las Traducciones?"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
-msgstr "Realizado Exitosamente!"
+msgstr "¡Realizado Exitosamente!"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
-msgstr "Establecer Traducción"
+msgstr "Establecer Traducciones"
msgctxt "view:ir.translation.update.start:"
msgid "Synchronize Translations"
@@ -4016,15 +4057,15 @@ msgstr "Estado de Vista de Arbol"
msgctxt "view:ir.ui.view_tree_state:"
msgid "Views Tree State"
-msgstr "Estado de Vista de Arbol"
+msgstr "Estado de la Vista de Arbol"
msgctxt "view:ir.ui.view_tree_width:"
msgid "View Tree Width"
-msgstr "Ancho de Columna"
+msgstr "Ancho de la Vista de Arbol"
msgctxt "view:ir.ui.view_tree_width:"
msgid "Views Tree Width"
-msgstr "Anchos de Columna"
+msgstr "Ancho de la Vista de Arbol"
msgctxt "wizard_button:ir.model.print_model_graph,start,end:"
msgid "Cancel"
@@ -4064,7 +4105,7 @@ msgstr "Cancelar"
msgctxt "wizard_button:ir.module.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
-msgstr "Iniciar Mejora"
+msgstr "Iniciar Actualización"
msgctxt "wizard_button:ir.translation.clean,start,clean:"
msgid "Clean"
diff --git a/trytond/ir/locale/es_ES.po b/trytond/ir/locale/es_ES.po
index bdb021d..987d042 100644
--- a/trytond/ir/locale/es_ES.po
+++ b/trytond/ir/locale/es_ES.po
@@ -25,7 +25,7 @@ msgstr "No está autorizado para eliminar este registro."
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"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."
@@ -43,8 +43,8 @@ msgid ""
"Could not delete the records because they are used on field \"%(field)s\" of"
" \"%(model)s\"."
msgstr ""
-"No se pueden eliminar los registros porqué se utilizan en el campo "
-"\"%(field)s\" del \"%(model)s\". "
+"No se pueden eliminar los registros porque se utilizan en el campo "
+"\"%(field)s\" de \"%(model)s\". "
msgctxt "error:foreign_model_missing:"
msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
@@ -291,11 +291,11 @@ msgid ""
" was configured as ancestor of itself."
msgstr ""
"Error de recursividad: El registro \"%(rec_name)s\" con el padre "
-"\"%(parent_rec_name)s\" se configuró como padre de si mismo."
+"\"%(parent_rec_name)s\" se ha configurado como padre de si mismo."
msgctxt "error:reference_syntax_error:"
msgid "Syntax error for reference %r in %s"
-msgstr "Error de sintaxis para la 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"
@@ -363,7 +363,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 el 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."
@@ -585,6 +585,10 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Acción"
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Activo"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
@@ -891,7 +895,7 @@ msgstr "Ventana"
msgctxt "field:ir.action.wizard,wiz_name:"
msgid "Wizard name"
-msgstr "Nombre asistente"
+msgstr "Nombre del asistente"
msgctxt "field:ir.action.wizard,write_date:"
msgid "Write Date"
@@ -1263,7 +1267,7 @@ msgstr "Información"
msgctxt "field:ir.model,model:"
msgid "Model Name"
-msgstr "Nombre modelo"
+msgstr "Nombre del modelo"
msgctxt "field:ir.model,module:"
msgid "Module"
@@ -1381,14 +1385,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Usuario creación"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Fecha de inicio"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Fecha de actualización"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "ID del recurso"
@@ -1397,6 +1393,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Identificador en el sistema de archivos"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr "Valores en sistema de ficheros"
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr "ID"
@@ -1413,6 +1413,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "No actualizar"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr "Sin sincronizar"
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Nombre"
@@ -2103,7 +2107,7 @@ msgstr "Módulo"
msgctxt "field:ir.translation,name:"
msgid "Field Name"
-msgstr "Nombre campo"
+msgstr "Nombre del campo"
msgctxt "field:ir.translation,overriding_module:"
msgid "Overriding Module"
@@ -2337,6 +2341,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Acción"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr "Acciones de teclado"
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Activo"
@@ -2455,7 +2463,7 @@ msgstr "Dominio"
msgctxt "field:ir.ui.view,field_childs:"
msgid "Children Field"
-msgstr "Campo de los hijos"
+msgstr "Campo hijos"
msgctxt "field:ir.ui.view,id:"
msgid "ID"
@@ -2543,7 +2551,7 @@ msgstr "Usuario modificación"
msgctxt "field:ir.ui.view_tree_state,child_name:"
msgid "Child Name"
-msgstr "Nombre hijo"
+msgstr "Nombre del hijo"
msgctxt "field:ir.ui.view_tree_state,create_date:"
msgid "Create Date"
@@ -2821,6 +2829,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Botones"
+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 "Permiso de los campos"
@@ -2933,6 +2945,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Dominio acción de ventana"
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr "Todos"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr "Sin sincronizar"
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Acción vista de ventana"
@@ -3005,6 +3027,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Alemán"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr "Español (Ecuador)"
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Inglés"
@@ -3051,7 +3077,7 @@ msgstr "Botón modelo"
msgctxt "model:ir.model.data,name:"
msgid "Model data"
-msgstr "Datos modelo"
+msgstr "Datos del modelo"
msgctxt "model:ir.model.field,name:"
msgid "Model field"
@@ -3249,6 +3275,10 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Botones"
+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 campos"
@@ -3705,6 +3735,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Botones"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr "Datos del modelo"
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr "Sincroniza"
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Permiso del campo"
diff --git a/trytond/ir/locale/fr_FR.po b/trytond/ir/locale/fr_FR.po
index 8cc677d..44e6984 100644
--- a/trytond/ir/locale/fr_FR.po
+++ b/trytond/ir/locale/fr_FR.po
@@ -25,9 +25,9 @@ msgstr "Vous n'êtes pas autorisé à supprimer cet enregistrement"
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
-"Le nombre de décimales \"%(digits)s\" du champ \"%(field)s\" dépasse sa "
+"Le nombre de décimales « %(digits)s » du champ « %(field)s » dépasse sa "
"limite."
msgctxt "error:domain_validation_record:"
@@ -35,7 +35,7 @@ msgid ""
"The value of the field \"%(field)s\" on \"%(model)s\" is not valid according"
" to its domain."
msgstr ""
-"La valeur du champ \"%(field)s\" sur \"%(model)s\" n'est pas valide selon "
+"La valeur du champ « %(field)s » sur « %(model)s » n'est pas valide selon "
"son domaine."
msgctxt "error:foreign_model_exist:"
@@ -44,42 +44,44 @@ msgid ""
" \"%(model)s\"."
msgstr ""
"Impossible de supprimer les enregistrements car ils sont utilisés dans le "
-"champ \"%(field)s\" de \"%(model)s\"."
+"champ « %(field)s » de « %(model)s »."
msgctxt "error:foreign_model_missing:"
msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
-msgstr "La valeur \"%(value)s\" du champ \"%(field)s\" sur \"%(model)s\" n'existe pas."
+msgstr ""
+"La valeur « %(value)s » du champ « %(field)s » sur « %(model)s » n'existe "
+"pas."
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."
+msgstr "Le contexte « %(context)s » de l'action « %(action)s » est invalide."
msgctxt "error:ir.action.act_window:"
msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
msgstr ""
-"Le critère de recherche ou le domaine \"%(domain)s\" de l'action "
-"\"%(action)s\" est invalide."
+"Le critère de recherche ou le domaine « %(domain)s » de l'action « "
+"%(action)s » est invalide."
msgctxt "error:ir.action.act_window:"
msgid "Invalid view \"%(view)s\" for action \"%(action)s\"."
-msgstr "La vue \"%(view)s\" de l'action \"%(action)s\" est invalide."
+msgstr "La vue « %(view)s » de l'action « %(action)s » est invalide."
msgctxt "error:ir.action.keyword:"
msgid "Wrong wizard model in keyword action \"%s\"."
-msgstr "Modèle d'assistant incorrect pour le mot-clé d'action \"%s\"."
+msgstr "Modèle d'assistant incorrect pour le mot-clé d'action « %s »."
msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
-msgstr "Définition de mail incorrecte sur le rapport \"%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 !"
+msgstr "Le nom interne doit être unique par module !"
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource!"
msgstr ""
-"Le nom des pièces jointes doivent être unique pour une même ressource !"
+"Le nom des pièces jointes doivent être unique pour une même ressource !"
msgctxt "error:ir.cron:"
msgid "Scheduled action failed"
@@ -93,7 +95,7 @@ msgid ""
"\n"
"%s\n"
msgstr ""
-"L'action suivante n'a pas pu s'exécuter correctement: \"%s\"\n"
+"L'action suivante n'a pas pu s'exécuter correctement: « %s »\n"
"%s\n"
" Retraçage:\n"
"\n"
@@ -105,11 +107,13 @@ msgstr "Le langage par défaut ne peut pas être supprimé"
msgctxt "error:ir.lang:"
msgid "Invalid date format \"%(format)s\" on \"%(language)s\" language."
-msgstr "Le format de date \"%(format)s\" du langage \"%(language)s\" est invalide."
+msgstr ""
+"Le format de date « %(format)s » du langage « %(language)s » est invalide."
msgctxt "error:ir.lang:"
msgid "Invalid grouping \"%(grouping)s\" on \"%(language)s\" language."
-msgstr "Le groupement \"%(grouping)s\" de la langye \"%(language)s\" est invalide."
+msgstr ""
+"Le groupement « %(grouping)s » de la langye « %(language)s » est invalide."
msgctxt "error:ir.lang:"
msgid "The default language must be translatable."
@@ -119,23 +123,23 @@ msgctxt "error:ir.lang:"
msgid "decimal_point and thousands_sep must be different!"
msgstr ""
"Le séparateur de décimale et le séparateur des centaines doivent être "
-"différents !"
+"différents !"
msgctxt "error:ir.model.access:"
msgid "You can not create this kind of document! (%s)"
-msgstr "Vous ne pouvez pas créer ce type de document ! (%s)"
+msgstr "Vous ne pouvez pas créer ce type de document ! (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not delete this document! (%s)"
-msgstr "Vous ne pouvez pas supprimer ce document ! (%s)"
+msgstr "Vous ne pouvez pas supprimer ce document ! (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not read this document! (%s)"
-msgstr "Vous ne pouvez pas lire ce document ! (%s)"
+msgstr "Vous ne pouvez pas lire ce document ! (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not write in this document! (%s)"
-msgstr "Vous ne pouvez pas écrire dans ce document ! (%s)"
+msgstr "Vous ne pouvez pas écrire dans ce document ! (%s)"
msgctxt "error:ir.model.button:"
msgid "The button name in model must be unique!"
@@ -143,45 +147,45 @@ msgstr "Le nom du bouton dans le modèle doit être unique"
msgctxt "error:ir.model.data:"
msgid "The triple (fs_id, module, model) must be unique!"
-msgstr "Le triplet (fs_id, module, model) doit être unique !"
+msgstr "Le triplet (fs_id, module, model) doit être unique !"
msgctxt "error:ir.model.field.access:"
msgid "You can not read the field! (%s.%s)"
-msgstr "Vous ne pouvez lire le champs (%s.%s) !"
+msgstr "Vous ne pouvez lire le champs ! (%s.%s)"
msgctxt "error:ir.model.field.access:"
msgid "You can not write on the field! (%s.%s)"
-msgstr "Vous ne pouvez écrire sur le champs (%s.%s) !"
+msgstr "Vous ne pouvez écrire sur le champs ! (%s.%s)"
msgctxt "error:ir.model.field:"
msgid "Model Field name \"%s\" is not a valid python identifier."
-msgstr "Le nom de champ modèle \"%s\" n'est pas un identifiant Python valide."
+msgstr "Le nom de champ modèle « %s » n'est pas un identifiant Python valide."
msgctxt "error:ir.model.field:"
msgid "The field name in model must be unique!"
-msgstr "Le nom du champ doit être unique sur le modèle !"
+msgstr "Le nom du champ doit être unique sur le modèle !"
msgctxt "error:ir.model:"
msgid "Module name \"%s\" is not a valid python identifier."
-msgstr "Le nom de module \"%s\" n'est pas un identifiant Python valide."
+msgstr "Le nom de module « %s » n'est pas un identifiant Python valide."
msgctxt "error:ir.model:"
msgid "The model must be unique!"
-msgstr "Le modèle doit être unique !"
+msgstr "Le modèle doit être unique !"
msgctxt "error:ir.module.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr "Une dépendance doit être unique par module !"
+msgstr "Une dépendance doit être unique par module !"
msgctxt "error:ir.module.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr "Dépendences %s manquante pour le module \"%s\""
+msgstr "Dépendences %s manquante pour le module « %s »"
msgctxt "error:ir.module.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
msgstr ""
"Les modules que vous essayez de dés-installer dépendent de modules installés"
-" :"
+" :"
msgctxt "error:ir.module.module:"
msgid "The name of the module must be unique!"
@@ -194,25 +198,27 @@ msgstr ""
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
-msgstr "Global et Défaut sont exclusifs !"
+msgstr "Global et Défaut sont exclusifs !"
msgctxt "error:ir.rule:"
msgid "Invalid domain in rule \"%s\"."
-msgstr "Domaine invalide sur la règle \"%s\"."
+msgstr "Domaine invalide sur la règle « %s »."
msgctxt "error:ir.sequence.strict:"
msgid "Invalid prefix \"%(prefix)s\" on sequence \"%(sequence)s\"."
-msgstr "Le préfixe \"%(prefix)s\" de la séquence \"%(sequence)s\" est invalide"
+msgstr ""
+"Le préfixe « %(prefix)s » de la séquence « %(sequence)s » est invalide"
msgctxt "error:ir.sequence.strict:"
msgid "Invalid suffix \"%(suffix)s\" on sequence \"%(sequence)s\"."
-msgstr "Le suffixe \"%(suffix)s\" de la séquence \"%(sequence)s\" est invalide"
+msgstr ""
+"Le suffixe « %(suffix)s » de la séquence « %(sequence)s » est invalide"
msgctxt "error:ir.sequence.strict:"
msgid "Last Timestamp cannot be in the future on sequence \"%s\"."
msgstr ""
-"La dernière estampille ne peut pas être dans le futur pour la séquence "
-"\"%s\""
+"La dernière estampille ne peut pas être dans le futur pour la séquence « %s "
+"»."
msgctxt "error:ir.sequence.strict:"
msgid "Missing sequence."
@@ -224,17 +230,19 @@ msgstr "L'estampille doit être plus grande que 0"
msgctxt "error:ir.sequence:"
msgid "Invalid prefix \"%(prefix)s\" on sequence \"%(sequence)s\"."
-msgstr "Le préfixe \"%(prefix)s\" de la séquence \"%(sequence)s\" est invalide"
+msgstr ""
+"Le préfixe « %(prefix)s » de la séquence « %(sequence)s » est invalide."
msgctxt "error:ir.sequence:"
msgid "Invalid suffix \"%(suffix)s\" on sequence \"%(sequence)s\"."
-msgstr "Le suffixe \"%(suffix)s\" de la séquence \"%(sequence)s\" est invalide"
+msgstr ""
+"Le suffixe « %(suffix)s » de la séquence « %(sequence)s » est invalide."
msgctxt "error:ir.sequence:"
msgid "Last Timestamp cannot be in the future on sequence \"%s\"."
msgstr ""
-"La dernière estampille ne peut pas être dans le futur pour la séquence "
-"\"%s\""
+"La dernière estampille ne peut pas être dans le futur pour la séquence « %s "
+"»."
msgctxt "error:ir.sequence:"
msgid "Missing sequence."
@@ -258,30 +266,30 @@ msgstr ""
msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
-msgstr "\"À temps\" exclus les autres type de déclencheurs !"
+msgstr "« À temps » exclus les autres type de déclencheurs !"
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid python expression on trigger "
"\"%(trigger)s\"."
msgstr ""
-"la condition \"%(condition)s\" du déclencheur \"%(trigger)s\" n'est pas une "
+"la condition « %(condition)s » du déclencheur « %(trigger)s » n'est pas une "
"expression python valide."
msgctxt "error:ir.ui.menu:"
msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
-msgstr "\"%s\" n'est pas un nom de menu valide car il contient \"/\"."
+msgstr "« %s » n'est pas un nom de menu valide car il contient « / »."
msgctxt "error:ir.ui.view:"
msgid "Invalid XML for view \"%s\"."
-msgstr "XML invalide sur la vue \"%s\"."
+msgstr "XML invalide sur la vue « %s »."
msgctxt "error:read_error:"
msgid ""
"You try to read records that don't exist anymore!\n"
"(Document type: %s)"
msgstr ""
-"Vous essayez de lire un enregistrement qui n'existe plus !\n"
+"Vous essayez de lire un enregistrement qui n'existe plus !\n"
"(Type du document: %s)"
msgctxt "error:read_error:"
@@ -289,7 +297,7 @@ msgid ""
"You try to read records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
-"Vous essayez de lire des enregistrements qui n'existent plus !\n"
+"Vous essayez de lire des enregistrements qui n'existent plus.\n"
"(Type du document: %s)"
msgctxt "error:recursion_error:"
@@ -297,8 +305,8 @@ msgid ""
"Recursion error: Record \"%(rec_name)s\" with parent \"%(parent_rec_name)s\""
" was configured as ancestor of itself."
msgstr ""
-"Erreur de récursion: L'enregistrement \"%(rec_name)s\" avec le parent "
-"\"%(parent_rec_name)s\" a été configuré comme ancêtre de lui même."
+"Erreur de récursion: L'enregistrement « %(rec_name)s » avec le parent « "
+"%(parent_rec_name)s » a été configuré comme ancêtre de lui même."
msgctxt "error:reference_syntax_error:"
msgid "Syntax error for reference %r in %s"
@@ -306,50 +314,54 @@ msgstr "Erreur de syntaxe pour la référence %r de %r"
msgctxt "error:relation_not_found:"
msgid "Relation not found: %r in %s"
-msgstr "Relation non trouvée : %r dans %r"
+msgstr "Relation non trouvée : %r dans %r"
msgctxt "error:required_field:"
msgid "The field \"%(field)s\" on \"%(model)s\" is required."
-msgstr "Le champ \"%(field)s\" sur \"%(model)s\" est requis."
+msgstr "Le champ « %(field)s » sur « %(model)s » est requis."
msgctxt "error:required_validation_record:"
msgid "The field \"%(field)s\" on \"%(model)s\" is required."
-msgstr "Le champ \"%(field)s\" sur \"%(model)s\" est requis."
+msgstr "Le champ « %(field)s » sur « %(model)s » est requis."
msgctxt "error:search_function_missing:"
msgid "Missing search function on field \"%s\"."
-msgstr "Fonction de recherche absente pour le champ \"%s\"."
+msgstr "Fonction de recherche absente pour le champ « %s »."
msgctxt "error:selection_validation_record:"
msgid ""
"The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" is not in "
"the selection."
msgstr ""
-"La valeur \"%(value)s\" du champ \"%(field)s\" sur \"%(model)s\" n'est pas "
+"La valeur « %(value)s » du champ « %(field)s » sur « %(model)s » n'est pas "
"dans la sélection."
msgctxt "error:selection_value_notfound:"
msgid "Value not in the selection for field \"%s\"."
-msgstr "Valeur absente de la sélection pour le champ \"%s\"."
+msgstr "Valeur absente de la sélection pour le champ « %s »."
msgctxt "error:size_validation_record:"
msgid "The size \"%(size)s\" of the field \"%(field)s\" on \"%(model)s\" is too long."
-msgstr "La taille \"%(size)s\" du champ \"%(field)s\" sur \"%(model)s\" est trop longue."
+msgstr ""
+"La taille « %(size)s » du champ « %(field)s » sur « %(model)s » est trop "
+"longue."
msgctxt "error:time_format_validation_record:"
msgid "The time value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" is not valid."
-msgstr "L'heure \"%(value)s\" du champ \"%(field)s\" sur \"%(model)s\" n'est pas valide."
+msgstr ""
+"L'heure « %(value)s » du champ « %(field)s » sur « %(model)s » n'est pas "
+"valide."
msgctxt "error:too_many_relations_found:"
msgid "Too many relations found: %r in %s"
-msgstr "Trop de relations pour : %r dans %s"
+msgstr "Trop de relations pour : %r dans %s"
msgctxt "error:write_error:"
msgid ""
"You try to write on records that don't exist anymore!\n"
"(Document type: %s)"
msgstr ""
-"Vous essayez d'écrire sur un enregistrement qui n'existe plus !\n"
+"Vous essayez d'écrire sur un enregistrement qui n'existe plus !\n"
"(Type du document: %s)"
msgctxt "error:write_error:"
@@ -588,6 +600,10 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Action"
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Actif"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Date de création"
@@ -682,7 +698,7 @@ msgstr "Imprimer directement"
msgctxt "field:ir.action.report,email:"
msgid "Email"
-msgstr "E-mail"
+msgstr "Email"
msgctxt "field:ir.action.report,extension:"
msgid "Extension"
@@ -850,7 +866,7 @@ msgstr "Créé par"
msgctxt "field:ir.action.wizard,email:"
msgid "Email"
-msgstr "E-mail"
+msgstr "Email"
msgctxt "field:ir.action.wizard,groups:"
msgid "Groups"
@@ -1384,14 +1400,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Créé par"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Date d'initialisation"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Date de mis à jour"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "ID de la ressource"
@@ -1400,6 +1408,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Identifiant sur le système de fichier"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr "Valeurs sur le système de fichier"
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr "ID"
@@ -1416,6 +1428,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "Pas de mise à jour"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr "Désynchronisé"
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Nom"
@@ -2340,6 +2356,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Action"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr "Mots-clés d'action"
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Actif"
@@ -2649,7 +2669,7 @@ msgid ""
"Python dictonary where keys define \"to\" \"cc\" \"subject\"\n"
"Example: {'to': 'test at example.com', 'cc': 'user at example.com'}"
msgstr ""
-"Dictionnaire Python définissant \"to\", \"cc\" et \"subject\".\n"
+"Dictionnaire Python définissant « to », « cc » et « subject ».\n"
" Exemple: {'to': 'test at example.com', 'cc': 'user at example.com'}"
msgctxt "help:ir.action.report,extension:"
@@ -2714,7 +2734,7 @@ msgstr ""
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"
+msgstr "Le domaine est évalué avec « user » comme utilisateur courant"
msgctxt "help:ir.rule.group,default_p:"
msgid "Add this rule to all users by default"
@@ -2736,7 +2756,7 @@ msgid ""
"A Python statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
-"Une expression Python ou l'enregistrement est représenté par \"self\".\n"
+"Une expression Python ou l'enregistrement est représenté par « self ».\n"
"Déclenche une action si vrai."
msgctxt "help:ir.trigger,limit_number:"
@@ -2744,7 +2764,7 @@ msgid ""
"Limit the number of call to \"Action Function\" by records.\n"
"0 for no limit."
msgstr ""
-"Limite le nombre d'appel aux \"Fonction action\" par enregistrement.\n"
+"Limite le nombre d'appel aux « Fonction action » par enregistrement.\n"
"0 signifie pas de limite."
msgctxt "help:ir.trigger,minimum_delay:"
@@ -2752,7 +2772,7 @@ msgid ""
"Set a minimum delay in minutes between call to \"Action Function\" for the same record.\n"
"0 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"
+"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."
msgctxt "help:ir.ui.view_search,domain:"
@@ -2823,6 +2843,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Boutons"
+msgctxt "model:ir.action,name:act_model_data_form"
+msgid "Data"
+msgstr "Données"
+
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr "Droits d'accès"
@@ -2935,6 +2959,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Action act window domain"
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr "Toutes"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr "Désynchronisé"
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Action ouvrir fenêtre vue"
@@ -3007,6 +3041,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Allemand"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr "Espagnol (Équateur)"
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Anglais"
@@ -3251,6 +3289,10 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Boutons"
+msgctxt "model:ir.ui.menu,name:menu_model_data_form"
+msgid "Data"
+msgstr "Données"
+
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
msgstr "Droits d'accès au champs"
@@ -3707,6 +3749,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Boutons"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr "Données de modèle"
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr "Synchroniser"
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Droits d'accès au champs"
@@ -3737,7 +3787,7 @@ msgstr "La configuration est faite."
msgctxt "view:ir.module.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
-msgstr "Bienvenu dans l'assistant de configuration de module !"
+msgstr "Bienvenu dans l'assistant de configuration de module !"
msgctxt "view:ir.module.module.config_wizard.first:"
msgid ""
@@ -3753,7 +3803,7 @@ msgstr "Élements de l'assistant de configuration"
msgctxt "view:ir.module.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
-msgstr "Prochaine étape du wizard de configuration !"
+msgstr "Prochaine étape du wizard de configuration !"
msgctxt "view:ir.module.module.dependency:"
msgid "Dependencies"
@@ -3769,7 +3819,7 @@ msgstr "Mise à jour du système terminée"
msgctxt "view:ir.module.module.install_upgrade.done:"
msgid "The modules have been upgraded / installed !"
-msgstr "Les modules ont été mis à jour / installés !"
+msgstr "Les modules ont été mis à jour / installés !"
msgctxt "view:ir.module.module.install_upgrade.start:"
msgid "Note that this operation may take a few minutes."
@@ -3864,7 +3914,7 @@ msgstr "Légende (préfixe et suffixe)"
msgctxt "view:ir.sequence.strict:"
msgid "Month:"
-msgstr "Mois :"
+msgstr "Mois :"
msgctxt "view:ir.sequence.strict:"
msgid "Sequences Strict"
@@ -3872,7 +3922,7 @@ msgstr "Séquence stricte"
msgctxt "view:ir.sequence.strict:"
msgid "Year:"
-msgstr "Année :"
+msgstr "Année :"
msgctxt "view:ir.sequence.type:"
msgid "Sequence Type"
@@ -3904,7 +3954,7 @@ msgstr "Légende (charactère pour le préfixe, le suffixe)"
msgctxt "view:ir.sequence:"
msgid "Month:"
-msgstr "Mois :"
+msgstr "Mois :"
msgctxt "view:ir.sequence:"
msgid "Sequences"
@@ -3916,7 +3966,7 @@ msgstr "Estampille"
msgctxt "view:ir.sequence:"
msgid "Year:"
-msgstr "Année :"
+msgstr "Année :"
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations"
@@ -3924,7 +3974,7 @@ msgstr "Nettoyer les traductions"
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations?"
-msgstr "Nettoyer les traductions ?"
+msgstr "Nettoyer les traductions ?"
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations"
@@ -3932,7 +3982,7 @@ msgstr "Nettoyer les traductions"
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations Succeed!"
-msgstr "Nettoyage des traductions réussi !"
+msgstr "Nettoyage des traductions réussi !"
msgctxt "view:ir.translation.export.result:"
msgid "Export Translation"
@@ -3948,11 +3998,11 @@ msgstr "Définir les traductions"
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
-msgstr "Synchroniser les traductions ?"
+msgstr "Synchroniser les traductions ?"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
-msgstr "Mise à jour réussie !"
+msgstr "Mise à jour réussie !"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
diff --git a/trytond/ir/locale/nl_NL.po b/trytond/ir/locale/nl_NL.po
index daa70aa..a34a383 100644
--- a/trytond/ir/locale/nl_NL.po
+++ b/trytond/ir/locale/nl_NL.po
@@ -23,7 +23,7 @@ msgstr "Het is u niet toegestaan dit item te verwijderen."
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
msgctxt "error:domain_validation_record:"
@@ -566,6 +566,11 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Actie"
+#, fuzzy
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Actief"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr ""
@@ -1392,14 +1397,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr ""
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Datum aanmaken"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Datum bijgewerkt"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "Middel ID"
@@ -1408,6 +1405,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Kenmerk voor bestandssysteem"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr ""
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr ""
@@ -1424,6 +1425,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "Niet bij te werken"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr ""
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Naam"
@@ -2371,6 +2376,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Actie"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr ""
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Actief"
@@ -2861,6 +2870,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr ""
+msgctxt "model:ir.action,name:act_model_data_form"
+msgid "Data"
+msgstr ""
+
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr ""
@@ -2973,6 +2986,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr ""
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr ""
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Actie uitvoerend schermaanzicht"
@@ -3046,6 +3069,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Duits"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr ""
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Engels"
@@ -3296,6 +3323,10 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr ""
+msgctxt "model:ir.ui.menu,name:menu_model_data_form"
+msgid "Data"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
msgstr ""
@@ -3758,6 +3789,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr ""
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr ""
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr ""
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr ""
diff --git a/trytond/ir/locale/ru_RU.po b/trytond/ir/locale/ru_RU.po
index 6af0951..687f877 100644
--- a/trytond/ir/locale/ru_RU.po
+++ b/trytond/ir/locale/ru_RU.po
@@ -25,7 +25,7 @@ msgstr "Вы не можете удалить эту запись."
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
msgctxt "error:domain_validation_record:"
@@ -562,6 +562,11 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Действие"
+#, fuzzy
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Действующий"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
msgstr "Дата создания"
@@ -1358,14 +1363,6 @@ msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
msgstr "Создано пользователем"
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Дата уставки"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Дата обновления"
-
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
msgstr "Ресурс ID"
@@ -1374,6 +1371,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Идентификатор в файловой системе"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr ""
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr "ID"
@@ -1390,6 +1391,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "Нет обновлений"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr ""
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Наименование"
@@ -2315,6 +2320,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Действие"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr ""
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Действующий"
@@ -2808,6 +2817,11 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Кнопки"
+#, fuzzy
+msgctxt "model:ir.action,name:act_model_data_form"
+msgid "Data"
+msgstr "Данные"
+
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr "Доступ к полям"
@@ -2920,6 +2934,17 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Действие окна домен"
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr "Все"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr ""
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Действие окна вида"
@@ -2992,6 +3017,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Немецкий"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr ""
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Английский"
@@ -3236,6 +3265,11 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Кнопки"
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_model_data_form"
+msgid "Data"
+msgstr "Данные"
+
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
msgstr "Доступ к полям"
@@ -3693,6 +3727,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Кнопки"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr ""
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr ""
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Доступ к полю"
diff --git a/trytond/ir/locale/sl_SI.po b/trytond/ir/locale/sl_SI.po
index 59694de..8b73500 100644
--- a/trytond/ir/locale/sl_SI.po
+++ b/trytond/ir/locale/sl_SI.po
@@ -25,7 +25,7 @@ msgstr "Nimate dovoljenja za brisanje tega zapisa"
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
-"exceeds it's limit."
+"exceeds its limit."
msgstr ""
"Število decimalk \"%(digits)s\" polja \"%(field)s\" pri \"%(model)s\" je "
"preseženo."
@@ -120,7 +120,7 @@ msgstr "decimal_point in thousands_sep morata biti različna."
msgctxt "error:ir.model.access:"
msgid "You can not create this kind of document! (%s)"
-msgstr "Te vrste dokumenta ni možno ustvariti. (%s)"
+msgstr "Te vrste dokumenta ni možno izdelati. (%s)"
msgctxt "error:ir.model.access:"
msgid "You can not delete this document! (%s)"
@@ -369,11 +369,11 @@ msgstr "Aktivno"
msgctxt "field:ir.action,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action,groups:"
msgid "Groups"
@@ -437,11 +437,11 @@ msgstr "Vrednost konteksta"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action.act_window,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action.act_window,domain:"
msgid "Domain Value"
@@ -541,11 +541,11 @@ msgstr "Aktivno"
msgctxt "field:ir.action.act_window.domain,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action.act_window.domain,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action.act_window.domain,domain:"
msgid "Domain"
@@ -579,13 +579,17 @@ msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
msgstr "Ukrep"
+msgctxt "field:ir.action.act_window.view,active:"
+msgid "Active"
+msgstr "Aktivno"
+
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action.act_window.view,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action.act_window.view,id:"
msgid "ID"
@@ -617,11 +621,11 @@ msgstr "Ukrep"
msgctxt "field:ir.action.keyword,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action.keyword,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action.keyword,groups:"
msgid "Groups"
@@ -661,11 +665,11 @@ msgstr "Aktivno"
msgctxt "field:ir.action.report,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action.report,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action.report,direct_print:"
msgid "Direct Print"
@@ -773,11 +777,11 @@ msgstr "Aktivno"
msgctxt "field:ir.action.url,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action.url,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action.url,groups:"
msgid "Groups"
@@ -833,11 +837,11 @@ msgstr "Aktivno"
msgctxt "field:ir.action.wizard,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action.wizard,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action.wizard,email:"
msgid "Email"
@@ -901,11 +905,11 @@ msgstr "Prekrivanje"
msgctxt "field:ir.attachment,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.attachment,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.attachment,data:"
msgid "Data"
@@ -969,11 +973,11 @@ msgstr "Zapisal"
msgctxt "field:ir.cache,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.cache,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.cache,id:"
msgid "ID"
@@ -1001,11 +1005,11 @@ msgstr "Zapisal"
msgctxt "field:ir.configuration,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.configuration,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.configuration,id:"
msgid "ID"
@@ -1037,11 +1041,11 @@ msgstr "Argumenti"
msgctxt "field:ir.cron,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.cron,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.cron,function:"
msgid "Function"
@@ -1105,11 +1109,11 @@ msgstr "ID"
msgctxt "field:ir.export,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.export,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.export,export_fields:"
msgid "Fields"
@@ -1141,11 +1145,11 @@ msgstr "Zapisal"
msgctxt "field:ir.export.line,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.export.line,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.export.line,export:"
msgid "Export"
@@ -1181,11 +1185,11 @@ msgstr "Šifra"
msgctxt "field:ir.lang,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.lang,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.lang,date:"
msgid "Date"
@@ -1233,11 +1237,11 @@ msgstr "Zapisal"
msgctxt "field:ir.model,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.model,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.model,fields:"
msgid "Fields"
@@ -1281,11 +1285,11 @@ msgstr "Zapisal"
msgctxt "field:ir.model.access,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.model.access,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.model.access,description:"
msgid "Description"
@@ -1305,7 +1309,7 @@ msgstr "Model"
msgctxt "field:ir.model.access,perm_create:"
msgid "Create Access"
-msgstr "Ustvarjanje"
+msgstr "Izdelava"
msgctxt "field:ir.model.access,perm_delete:"
msgid "Delete Access"
@@ -1333,11 +1337,11 @@ msgstr "Zapisal"
msgctxt "field:ir.model.button,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.model.button,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.model.button,groups:"
msgid "Groups"
@@ -1369,19 +1373,11 @@ msgstr "Zapisal"
msgctxt "field:ir.model.data,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
-
-msgctxt "field:ir.model.data,date_init:"
-msgid "Init Date"
-msgstr "Začetni datum"
-
-msgctxt "field:ir.model.data,date_update:"
-msgid "Update Date"
-msgstr "Datum posodobitve"
+msgstr "Izdelal"
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
@@ -1391,6 +1387,10 @@ msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
msgstr "Identifikator v datotečnem sistemu"
+msgctxt "field:ir.model.data,fs_values:"
+msgid "Values on File System"
+msgstr "Vrednosti v datotečnem sistemu"
+
msgctxt "field:ir.model.data,id:"
msgid "ID"
msgstr "ID"
@@ -1407,6 +1407,10 @@ msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
msgstr "Brez posodabljanja"
+msgctxt "field:ir.model.data,out_of_sync:"
+msgid "Out of Sync"
+msgstr "Izven sinhronizacije"
+
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
msgstr "Ime"
@@ -1425,11 +1429,11 @@ msgstr "Zapisal"
msgctxt "field:ir.model.field,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.model.field,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.model.field,field_description:"
msgid "Field Description"
@@ -1481,11 +1485,11 @@ msgstr "Zapisal"
msgctxt "field:ir.model.field.access,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.model.field.access,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.model.field.access,description:"
msgid "Description"
@@ -1505,7 +1509,7 @@ msgstr "ID"
msgctxt "field:ir.model.field.access,perm_create:"
msgid "Create Access"
-msgstr "Ustvarjanje"
+msgstr "Izdelava"
msgctxt "field:ir.model.field.access,perm_delete:"
msgid "Delete Access"
@@ -1549,11 +1553,11 @@ msgstr "Podmoduli"
msgctxt "field:ir.module.module,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.module.module,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.module.module,dependencies:"
msgid "Dependencies"
@@ -1605,11 +1609,11 @@ msgstr "Ukrep"
msgctxt "field:ir.module.module.config_wizard.item,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.module.module.config_wizard.item,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.module.module.config_wizard.item,id:"
msgid "ID"
@@ -1645,11 +1649,11 @@ msgstr "Odstotek"
msgctxt "field:ir.module.module.dependency,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.module.module.dependency,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.module.module.dependency,id:"
msgid "ID"
@@ -1693,11 +1697,11 @@ msgstr "Moduli za posodobitev"
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.property,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.property,field:"
msgid "Field"
@@ -1729,11 +1733,11 @@ msgstr "Zapisal"
msgctxt "field:ir.rule,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.rule,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.rule,domain:"
msgid "Domain"
@@ -1761,11 +1765,11 @@ msgstr "Zapisal"
msgctxt "field:ir.rule.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.rule.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.rule.group,default_p:"
msgid "Default"
@@ -1793,7 +1797,7 @@ msgstr "Naziv"
msgctxt "field:ir.rule.group,perm_create:"
msgid "Create Access"
-msgstr "Ustvarjanje"
+msgstr "Izdelava"
msgctxt "field:ir.rule.group,perm_delete:"
msgid "Delete Access"
@@ -1837,11 +1841,11 @@ msgstr "Šifra"
msgctxt "field:ir.sequence,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.sequence,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.sequence,id:"
msgid "ID"
@@ -1913,11 +1917,11 @@ msgstr "Šifra"
msgctxt "field:ir.sequence.strict,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.sequence.strict,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.sequence.strict,id:"
msgid "ID"
@@ -1985,11 +1989,11 @@ msgstr "Šifra"
msgctxt "field:ir.sequence.type,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.sequence.type,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.sequence.type,id:"
msgid "ID"
@@ -2013,11 +2017,11 @@ msgstr "Zapisal"
msgctxt "field:ir.session,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.session,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.session,id:"
msgid "ID"
@@ -2041,11 +2045,11 @@ msgstr "Zapisal"
msgctxt "field:ir.session.wizard,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.session.wizard,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.session.wizard,data:"
msgid "Data"
@@ -2069,11 +2073,11 @@ msgstr "Zapisal"
msgctxt "field:ir.translation,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.translation,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.translation,fuzzy:"
msgid "Fuzzy"
@@ -2197,11 +2201,11 @@ msgstr "Pogoj"
msgctxt "field:ir.trigger,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.trigger,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.trigger,id:"
msgid "ID"
@@ -2225,7 +2229,7 @@ msgstr "Naziv"
msgctxt "field:ir.trigger,on_create:"
msgid "On Create"
-msgstr "Ob ustvarjanju"
+msgstr "Ob izdelavi"
msgctxt "field:ir.trigger,on_delete:"
msgid "On Delete"
@@ -2253,11 +2257,11 @@ msgstr "Zapisal"
msgctxt "field:ir.trigger.log,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.trigger.log,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.trigger.log,id:"
msgid "ID"
@@ -2285,11 +2289,11 @@ msgstr "Zapisal"
msgctxt "field:ir.ui.icon,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.ui.icon,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.ui.icon,icon:"
msgid "Icon"
@@ -2331,6 +2335,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Ukrep"
+msgctxt "field:ir.ui.menu,action_keywords:"
+msgid "Action Keywords"
+msgstr "Ključne besede ukrepov"
+
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
msgstr "Aktivno"
@@ -2345,11 +2353,11 @@ msgstr "Polno ime"
msgctxt "field:ir.ui.menu,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.ui.menu,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.ui.menu,favorite:"
msgid "Favorite"
@@ -2393,11 +2401,11 @@ msgstr "Zapisal"
msgctxt "field:ir.ui.menu.favorite,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.ui.menu.favorite,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.ui.menu.favorite,id:"
msgid "ID"
@@ -2433,11 +2441,11 @@ msgstr "Zgradba"
msgctxt "field:ir.ui.view,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.ui.view,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.ui.view,data:"
msgid "Data"
@@ -2497,11 +2505,11 @@ msgstr "ID"
msgctxt "field:ir.ui.view_search,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.ui.view_search,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.ui.view_search,domain:"
msgid "Domain"
@@ -2541,11 +2549,11 @@ msgstr "Ime poddrevesa"
msgctxt "field:ir.ui.view_tree_state,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.ui.view_tree_state,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.ui.view_tree_state,domain:"
msgid "Domain"
@@ -2585,11 +2593,11 @@ msgstr "Zapisal"
msgctxt "field:ir.ui.view_tree_width,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.ui.view_tree_width,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.ui.view_tree_width,field:"
msgid "Field"
@@ -2813,6 +2821,10 @@ msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
msgstr "Gumbi"
+msgctxt "model:ir.action,name:act_model_data_form"
+msgid "Data"
+msgstr "Podatki"
+
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr "Dostop do polj"
@@ -2925,6 +2937,16 @@ msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
msgstr "Domena okna za ukrepe"
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
+msgid "All"
+msgstr "Vse"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
+msgid "Out of Sync"
+msgstr "Nesinhronizirano"
+
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
msgstr "Pogled ukrepa za okna"
@@ -2997,6 +3019,10 @@ msgctxt "model:ir.lang,name:lang_de"
msgid "German"
msgstr "Nemščina"
+msgctxt "model:ir.lang,name:lang_ec"
+msgid "Spanish (Ecuador)"
+msgstr "Španščina (Ekvador)"
+
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
msgstr "Angleščina"
@@ -3241,6 +3267,10 @@ msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
msgstr "Gumbi"
+msgctxt "model:ir.ui.menu,name:menu_model_data_form"
+msgid "Data"
+msgstr "Podatki"
+
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
msgstr "Dostop do polj"
@@ -3697,6 +3727,14 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Gumbi"
+msgctxt "view:ir.model.data:"
+msgid "Model Data"
+msgstr "Podatki modela"
+
+msgctxt "view:ir.model.data:"
+msgid "Sync"
+msgstr "Sinhronizacija"
+
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr "Dostop do polj"
diff --git a/trytond/ir/model.py b/trytond/ir/model.py
index cb63303..53855a1 100644
--- a/trytond/ir/model.py
+++ b/trytond/ir/model.py
@@ -1,11 +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 datetime
import re
import heapq
from sql.aggregate import Max
from sql.conditionals import Case
from collections import defaultdict
+try:
+ import simplejson as json
+except ImportError:
+ import json
from ..model import ModelView, ModelSQL, fields
from ..report import Report
@@ -16,6 +19,7 @@ from ..pool import Pool
from ..pyson import Bool, Eval
from ..rpc import RPC
from .. import backend
+from ..protocols.jsonrpc import JSONDecoder, JSONEncoder
try:
from ..tools.StringMatcher import StringMatcher
except ImportError:
@@ -122,13 +126,12 @@ class Model(ModelSQL, ModelView):
@classmethod
def list_models(cls):
'Return a list of all models names'
- with Transaction().set_user(0):
- models = cls.search([], order=[
- ('module', 'ASC'), # Optimization assumption
- ('model', 'ASC'),
- ('id', 'ASC'),
- ])
- return [m.model for m in models]
+ models = cls.search([], order=[
+ ('module', 'ASC'), # Optimization assumption
+ ('model', 'ASC'),
+ ('id', 'ASC'),
+ ])
+ return [m.model for m in models]
@classmethod
def list_history(cls):
@@ -522,7 +525,9 @@ class ModelAccess(ModelSQL, ModelView):
'Check access for model_name and mode'
assert mode in ['read', 'write', 'create', 'delete'], \
'Invalid access mode for security'
- if Transaction().user == 0:
+ if ((Transaction().user == 0)
+ or (raise_exception
+ and not Transaction().context.get('_check_access'))):
return True
access = cls.get_access([model_name])[model_name][mode]
@@ -600,7 +605,7 @@ class ModelFieldAccess(ModelSQL, ModelView):
perm_create = fields.Boolean('Create Access')
perm_delete = fields.Boolean('Delete Access')
description = fields.Text('Description')
- _get_access_cache = Cache('ir_model_field_access.check')
+ _get_access_cache = Cache('ir_model_field_access.check', context=False)
@classmethod
def __setup__(cls):
@@ -702,7 +707,9 @@ class ModelFieldAccess(ModelSQL, ModelView):
'''
assert mode in ('read', 'write', 'create', 'delete'), \
'Invalid access mode'
- if Transaction().user == 0:
+ if ((Transaction().user == 0)
+ or (raise_exception
+ and not Transaction().context.get('_check_access'))):
if access:
return dict((x, True) for x in fields)
return True
@@ -813,10 +820,11 @@ class ModelData(ModelSQL, ModelView):
db_id = fields.Integer('Resource ID',
help="The id of the record in the database.", select=True,
required=True)
- date_update = fields.DateTime('Update Date')
- date_init = fields.DateTime('Init Date')
values = fields.Text('Values')
+ fs_values = fields.Text('Values on File System')
noupdate = fields.Boolean('No Update')
+ out_of_sync = fields.Function(fields.Boolean('Out of Sync'),
+ 'get_out_of_sync', searcher='search_out_of_sync')
_get_id_cache = Cache('ir_model_data.get_id', context=False)
@classmethod
@@ -826,6 +834,11 @@ class ModelData(ModelSQL, ModelView):
('fs_id_module_model_uniq', 'UNIQUE("fs_id", "module", "model")',
'The triple (fs_id, module, model) must be unique!'),
]
+ cls._buttons.update({
+ 'sync': {
+ 'invisible': ~Eval('out_of_sync'),
+ },
+ })
@classmethod
def __register__(cls, module_name):
@@ -844,13 +857,23 @@ class ModelData(ModelSQL, ModelView):
table.drop_column('inherit', True)
@staticmethod
- def default_date_init():
- return datetime.datetime.now()
-
- @staticmethod
def default_noupdate():
return False
+ def get_out_of_sync(self, name):
+ return self.values != self.fs_values and self.fs_values is not None
+
+ @classmethod
+ def search_out_of_sync(cls, name, clause):
+ table = cls.__table__()
+ name, operator, value = clause
+ Operator = fields.SQL_OPERATORS[operator]
+ query = table.select(table.id,
+ where=Operator(
+ (table.fs_values != table.values) & (table.fs_values != None),
+ value))
+ return [('id', 'in', query)]
+
@classmethod
def write(cls, data, values, *args):
super(ModelData, cls).write(data, values, *args)
@@ -877,6 +900,45 @@ class ModelData(ModelSQL, ModelView):
cls._get_id_cache.set(key, id_)
return id_
+ @classmethod
+ def dump_values(cls, values):
+ return json.dumps(sorted(values.iteritems()), cls=JSONEncoder)
+
+ @classmethod
+ def load_values(cls, values):
+ try:
+ 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, {
+ 'Decimal': Decimal,
+ 'datetime': datetime,
+ })
+
+ @classmethod
+ @ModelView.button
+ def sync(cls, records):
+ pool = Pool()
+ to_write = []
+ for data in records:
+ Model = pool.get(data.model)
+ values = cls.load_values(data.values)
+ fs_values = cls.load_values(data.fs_values)
+ # values could be the same once loaded
+ # if they come from version < 3.2
+ if values != fs_values:
+ record = Model(data.db_id)
+ Model.write([record], fs_values)
+ values.update(fs_values)
+ to_write.extend([[data], {
+ 'values': cls.dump_values(values),
+ }])
+ if to_write:
+ cls.write(*to_write)
+
class PrintModelGraphStart(ModelView):
'Print Model Graph'
diff --git a/trytond/ir/model.xml b/trytond/ir/model.xml
index d2806ac..9c5641a 100644
--- a/trytond/ir/model.xml
+++ b/trytond/ir/model.xml
@@ -173,5 +173,49 @@ this repository contains the full copyright notices and license terms. -->
<menuitem parent="menu_model_access_form"
action="act_model_button_form" id="menu_model_button_form"/>
+ <record model="ir.ui.view" id="model_data_view_list">
+ <field name="model">ir.model.data</field>
+ <field name="type">tree</field>
+ <field name="name">model_data_list</field>
+ </record>
+ <record model="ir.ui.view" id="model_data_view_form">
+ <field name="model">ir.model.data</field>
+ <field name="type">form</field>
+ <field name="name">model_data_form</field>
+ </record>
+
+ <record model="ir.action.act_window" id="act_model_data_form">
+ <field name="name">Data</field>
+ <field name="res_model">ir.model.data</field>
+ </record>
+ <record model="ir.action.act_window.view"
+ id="act_model_data_form_view1">
+ <field name="sequence" eval="10"/>
+ <field name="view" ref="model_data_view_list"/>
+ <field name="act_window" ref="act_model_data_form"/>
+ </record>
+ <record model="ir.action.act_window.view"
+ id="act_model_data_form_view2">
+ <field name="sequence" eval="20"/>
+ <field name="view" ref="model_data_view_form"/>
+ <field name="act_window" ref="act_model_data_form"/>
+ </record>
+ <record model="ir.action.act_window.domain"
+ 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="act_window" ref="act_model_data_form"/>
+ </record>
+ <record model="ir.action.act_window.domain"
+ id="act_model_data_form_domain_all">
+ <field name="name">All</field>
+ <field name="sequence" eval="9999"/>
+ <field name="domain"></field>
+ <field name="act_window" ref="act_model_data_form"/>
+ </record>
+ <menuitem parent="menu_model_form" action="act_model_data_form"
+ id="menu_model_data_form"/>
+
</data>
</tryton>
diff --git a/trytond/ir/module/module.py b/trytond/ir/module/module.py
index 605a614..8153bde 100644
--- a/trytond/ir/module/module.py
+++ b/trytond/ir/module/module.py
@@ -483,6 +483,12 @@ class ModuleInstallUpgrade(Wizard):
])
config = StateAction('ir.act_module_config_wizard')
+ @classmethod
+ def check_access(cls):
+ # Use new cursor to prevent lock when installing modules
+ with Transaction().new_cursor():
+ super(ModuleInstallUpgrade, cls).check_access()
+
@staticmethod
def default_start(fields):
pool = Pool()
@@ -509,13 +515,14 @@ class ModuleInstallUpgrade(Wizard):
modules = Module.search([
('state', 'in', ['to upgrade', 'to remove', 'to install']),
])
+ update = [m.name for m in modules]
langs = Lang.search([
('translatable', '=', True),
])
lang = [x.code for x in langs]
transaction.cursor.commit()
- if modules:
- pool.init(update=True, lang=lang)
+ if update:
+ pool.init(update=update, lang=lang)
return 'done'
diff --git a/trytond/ir/property.py b/trytond/ir/property.py
index 9567c45..f5c79ff 100644
--- a/trytond/ir/property.py
+++ b/trytond/ir/property.py
@@ -135,8 +135,7 @@ class Property(ModelSQL, ModelView):
('field', '=', model_field.id),
('res', 'in', [model + ',' + str(res_id) for res_id in ids]),
], order=[])
- with Transaction().set_user(0, set_context=True):
- cls.delete(properties)
+ cls.delete(properties)
defaults = cls.search([
('field', '=', model_field.id),
@@ -160,5 +159,4 @@ class Property(ModelSQL, ModelView):
if (val != default_val):
for res_id in ids:
vals = cls._set_values(model, res_id, val, model_field.id)
- with Transaction().set_user(0, set_context=True):
- cls.create([vals])
+ cls.create([vals])
diff --git a/trytond/ir/rule.py b/trytond/ir/rule.py
index 479f162..8491514 100644
--- a/trytond/ir/rule.py
+++ b/trytond/ir/rule.py
@@ -1,8 +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 time
-import datetime
-
from ..model import ModelView, ModelSQL, fields
from ..tools import safe_eval
from ..transaction import Transaction
@@ -29,6 +26,7 @@ class RuleGroup(ModelSQL, ModelView):
help="The rule is satisfied if at least one test is True")
groups = fields.Many2Many('ir.rule.group-res.group',
'rule_group', 'group', 'Groups')
+ # TODO remove to only use groups
users = fields.Many2Many('ir.rule.group-res.user',
'rule_group', 'user', 'Users')
perm_read = fields.Boolean('Read Access')
@@ -99,7 +97,7 @@ class Rule(ModelSQL, ModelView):
required=True, ondelete="CASCADE")
domain = fields.Char('Domain', required=True,
help='Domain is evaluated with "user" as the current user')
- _domain_get_cache = Cache('ir_rule.domain_get')
+ _domain_get_cache = Cache('ir_rule.domain_get', context=False)
@classmethod
def __setup__(cls):
@@ -144,15 +142,16 @@ class Rule(ModelSQL, ModelView):
def _get_context():
User = Pool().get('res.user')
user_id = Transaction().user
- with Transaction().set_user(0, set_context=True):
+ with Transaction().set_context(_check_access=False):
user = User(user_id)
return {
'user': user,
- 'current_date': datetime.datetime.today(),
- 'time': time,
- 'context': Transaction().context,
}
+ @staticmethod
+ def _get_cache_key():
+ return (Transaction().user,)
+
@classmethod
def domain_get(cls, model_name, mode='read'):
assert mode in ['read', 'write', 'create', 'delete'], \
@@ -165,7 +164,7 @@ class Rule(ModelSQL, ModelView):
with Transaction().set_user(Transaction().context['user']):
return cls.domain_get(model_name, mode=mode)
- key = (model_name, mode)
+ key = (model_name, mode) + cls._get_cache_key()
domain = cls._domain_get_cache.get(key, False)
if domain is not False:
return domain
diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py
index d1b3c73..93bae63 100644
--- a/trytond/ir/sequence.py
+++ b/trytond/ir/sequence.py
@@ -10,14 +10,13 @@ from ..tools import datetime_strftime
from ..pyson import Eval, And
from ..transaction import Transaction
from ..pool import Pool
-from ..config import CONFIG
from .. import backend
__all__ = [
'SequenceType', 'Sequence', 'SequenceStrict',
]
-sql_sequence = CONFIG.options['db_type'] == 'postgresql'
+sql_sequence = backend.name() == 'postgresql'
class SequenceType(ModelSQL, ModelView):
@@ -314,20 +313,18 @@ class Sequence(ModelSQL, ModelView):
#Pre-fetch number_next
number_next = sequence.number_next_internal
- with Transaction().set_user(0):
- cls.write([sequence], {
- 'number_next_internal': (number_next
- + sequence.number_increment),
- })
+ cls.write([sequence], {
+ 'number_next_internal': (number_next
+ + sequence.number_increment),
+ })
return '%%0%sd' % sequence.padding % number_next
elif sequence.type in ('decimal timestamp', 'hexadecimal timestamp'):
timestamp = sequence.last_timestamp
while timestamp == sequence.last_timestamp:
timestamp = cls._timestamp(sequence)
- with Transaction().set_user(0):
- cls.write([sequence], {
- 'last_timestamp': timestamp,
- })
+ cls.write([sequence], {
+ 'last_timestamp': timestamp,
+ })
if sequence.type == 'decimal timestamp':
return '%d' % timestamp
else:
@@ -345,18 +342,18 @@ class Sequence(ModelSQL, ModelView):
domain = [('id', '=', domain)]
# bypass rules on sequences
- with Transaction().set_context(user=False):
+ with Transaction().set_context(user=False, _check_access=False):
with Transaction().set_user(0):
try:
sequence, = cls.search(domain, limit=1)
except TypeError:
cls.raise_user_error('missing')
- date = Transaction().context.get('date')
- return '%s%s%s' % (
- cls._process(sequence.prefix, date=date),
- cls._get_sequence(sequence),
- cls._process(sequence.suffix, date=date),
- )
+ date = Transaction().context.get('date')
+ return '%s%s%s' % (
+ cls._process(sequence.prefix, date=date),
+ cls._get_sequence(sequence),
+ cls._process(sequence.suffix, date=date),
+ )
@classmethod
def get(cls, code):
diff --git a/trytond/ir/session.py b/trytond/ir/session.py
index e6512ae..d9beae9 100644
--- a/trytond/ir/session.py
+++ b/trytond/ir/session.py
@@ -8,7 +8,7 @@ import uuid
import datetime
from trytond.model import ModelSQL, fields
-from trytond.config import CONFIG
+from trytond.config import config
from .. import backend
from ..transaction import Transaction
@@ -45,7 +45,8 @@ class Session(ModelSQL):
def check(cls, user, key):
"Check user key and delete old one"
now = datetime.datetime.now()
- timeout = datetime.timedelta(seconds=int(CONFIG['session_timeout']))
+ timeout = datetime.timedelta(
+ seconds=config.getint('session', 'timeout'))
sessions = cls.search([
('create_uid', '=', user),
])
diff --git a/trytond/ir/time_locale.py b/trytond/ir/time_locale.py
index e977b76..1371e77 100644
--- a/trytond/ir/time_locale.py
+++ b/trytond/ir/time_locale.py
@@ -265,6 +265,47 @@ TIME_LOCALE = \
u'nov',
u'dic'],
'%p': [u'AM', u'PM']},
+ 'es_EC': {'%A': [u'lunes',
+ u'martes',
+ u'mi\xe9rcoles',
+ u'jueves',
+ u'viernes',
+ u's\xe1bado',
+ u'domingo'],
+ '%B': [None,
+ u'enero',
+ u'febrero',
+ u'marzo',
+ u'abril',
+ u'mayo',
+ u'junio',
+ u'julio',
+ u'agosto',
+ u'septiembre',
+ u'octubre',
+ u'noviembre',
+ u'diciembre'],
+ '%a': [u'lun',
+ u'mar',
+ u'mi\xe9',
+ u'jue',
+ u'vie',
+ u's\xe1b',
+ u'dom'],
+ '%b': [None,
+ u'ene',
+ u'feb',
+ u'mar',
+ u'abr',
+ u'may',
+ u'jun',
+ u'jul',
+ u'ago',
+ u'sep',
+ u'oct',
+ u'nov',
+ u'dic'],
+ '%p': [u'AM', u'PM']},
'es_ES': {'%A': [u'lunes',
u'martes',
u'mi\xe9rcoles',
diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py
index 17e7079..b0c8a54 100644
--- a/trytond/ir/translation.py
+++ b/trytond/ir/translation.py
@@ -21,7 +21,7 @@ from sql.aggregate import Max
from ..model import ModelView, ModelSQL, fields
from ..wizard import Wizard, StateView, StateTransition, StateAction, \
Button
-from ..tools import file_open, reduce_ids
+from ..tools import file_open, reduce_ids, grouped_slice
from .. import backend
from ..pyson import PYSONEncoder
from ..transaction import Transaction
@@ -124,13 +124,10 @@ class Translation(ModelSQL, ModelView):
table = TableHandler(cursor, cls, module_name)
table.not_null_action('src_md5', action='add')
- # Migration from 2.2
+ # Migration from 2.2 and 2.8
cursor.execute(*ir_translation.update([ir_translation.res_id],
- [None], where=ir_translation.res_id == 0))
-
- # Migration from 2.8
- cursor.execute(*ir_translation.update([ir_translation.res_id],
- [-1], where=ir_translation.res_id == None))
+ [-1], where=(ir_translation.res_id == None)
+ | (ir_translation.res_id == 0)))
table = TableHandler(Transaction().cursor, cls, module_name)
table.index_action(['lang', 'type', 'name'], 'add')
@@ -387,7 +384,7 @@ class Translation(ModelSQL, ModelView):
lang = unicode(lang)
if name.split(',')[0] in ('ir.model.field', 'ir.model'):
field_name = name.split(',')[1]
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
if name.split(',')[0] == 'ir.model.field':
if field_name == 'field_description':
ttype = u'field'
@@ -433,8 +430,7 @@ class Translation(ModelSQL, ModelView):
if Transaction().context.get('fuzzy_translation', False):
fuzzy_sql = None
in_max = cursor.IN_MAX / 7
- for i in range(0, len(to_fetch), in_max):
- sub_to_fetch = to_fetch[i:i + in_max]
+ for sub_to_fetch in grouped_slice(to_fetch, in_max):
red_sql = reduce_ids(table.res_id, sub_to_fetch)
where = And(((table.lang == lang),
(table.type == ttype),
@@ -518,14 +514,14 @@ class Translation(ModelSQL, ModelView):
'fuzzy': False,
})
else:
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
cls.write([translation], {
'src': getattr(record, field_name),
'value': value,
'fuzzy': False,
})
if to_create:
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
cls.create(to_create)
return
@@ -568,7 +564,7 @@ class Translation(ModelSQL, ModelView):
'fuzzy': False,
})
else:
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
cls.write([translation], {
'value': value,
'src': getattr(record, field_name),
@@ -581,22 +577,20 @@ class Translation(ModelSQL, ModelView):
'fuzzy': True,
})
if to_create:
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
cls.create(to_create)
@classmethod
def delete_ids(cls, model, ttype, ids):
"Delete translation for each id"
- cursor = Transaction().cursor
translations = []
- for i in range(0, len(ids), cursor.IN_MAX):
- sub_ids = ids[i:i + cursor.IN_MAX]
+ for sub_ids in grouped_slice(ids):
translations += cls.search([
('type', '=', ttype),
('name', 'like', model + ',%'),
- ('res_id', 'in', sub_ids),
+ ('res_id', 'in', list(sub_ids)),
])
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
cls.delete(translations)
@classmethod
@@ -643,9 +637,8 @@ class Translation(ModelSQL, ModelView):
cursor = Transaction().cursor
table = cls.__table__()
if len(args) > cursor.IN_MAX:
- for i in range(0, len(args), cursor.IN_MAX):
- sub_args = args[i:i + cursor.IN_MAX]
- res.update(cls.get_sources(sub_args))
+ for sub_args in grouped_slice(args):
+ res.update(cls.get_sources(list(sub_args)))
return res
for name, ttype, lang, source in args:
name = unicode(name)
@@ -672,11 +665,11 @@ class Translation(ModelSQL, ModelView):
clause.append(where)
if clause:
in_max = cursor.IN_MAX / 7
- for i in range(0, len(clause), in_max):
+ for sub_clause in grouped_slice(clause, in_max):
cursor.execute(*table.select(
table.lang, table.type, table.name, table.src,
table.value,
- where=Or(clause[i:i + in_max])))
+ where=Or(list(sub_clause))))
for lang, ttype, name, source, value in cursor.fetchall():
if (name, ttype, lang, source) not in args:
source = None
@@ -815,8 +808,7 @@ class Translation(ModelSQL, ModelView):
res_id = model_data.db_id
else:
res_id = -1
- with Transaction().set_user(0), \
- Transaction().set_context(module=res_id_module):
+ with Transaction().set_context(module=res_id_module):
domain = [
('name', '=', new_translation.name),
('res_id', '=', res_id),
@@ -889,8 +881,7 @@ class Translation(ModelSQL, ModelView):
or old_translation.fuzzy !=
translation.fuzzy):
to_write.append(old_translation)
- with Transaction().set_user(0), \
- Transaction().set_context(module=module):
+ with Transaction().set_context(module=module):
if to_write and not noupdate:
cls.write(to_write, {
'value': translation.value,
@@ -899,8 +890,7 @@ class Translation(ModelSQL, ModelView):
translations |= set(cls.browse(ids))
if to_create:
- with Transaction().set_user(0), \
- Transaction().set_context(module=module):
+ with Transaction().set_context(module=module):
translations |= set(cls.create(to_create))
if translations:
@@ -1493,8 +1483,7 @@ class TranslationUpdate(Wizard):
'module': row['module'],
})
if to_create:
- with Transaction().set_user(0):
- Translation.create(to_create)
+ Translation.create(to_create)
columns = [translation.name.as_('name'),
translation.res_id.as_('res_id'), translation.type.as_('type'),
translation.module.as_('module')]
@@ -1514,8 +1503,7 @@ class TranslationUpdate(Wizard):
'module': row['module'],
})
if to_create:
- with Transaction().set_user(0):
- Translation.create(to_create)
+ Translation.create(to_create)
columns = [translation.name.as_('name'),
translation.res_id.as_('res_id'), translation.type.as_('type'),
translation.src.as_('src'), translation.module.as_('module')]
diff --git a/trytond/ir/translation.xml b/trytond/ir/translation.xml
index d6de503..e93b30b 100644
--- a/trytond/ir/translation.xml
+++ b/trytond/ir/translation.xml
@@ -127,8 +127,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="name">digits_validation_record</field>
<field name="lang">en_US</field>
<field name="type">error</field>
- <field name="src">The number of digits "%(digits)s" of field "%(field)s" on "%(value)s" exceeds it's limit.</field>
- <field name="value">The number of digits "%(digits)s" of field "%(field)s" on "%(value)s" exceeds it's limit.</field>
+ <field name="src">The number of digits "%(digits)s" of field "%(field)s" on "%(value)s" exceeds its limit.</field>
+ <field name="value">The number of digits "%(digits)s" of field "%(field)s" on "%(value)s" exceeds its limit.</field>
<field name="module">ir</field>
<field name="fuzzy" eval="False"/>
</record>
diff --git a/trytond/ir/trigger.py b/trytond/ir/trigger.py
index 56dbddd..9b88d2f 100644
--- a/trytond/ir/trigger.py
+++ b/trytond/ir/trigger.py
@@ -7,7 +7,7 @@ from sql.aggregate import Count, Max
from ..model import ModelView, ModelSQL, fields
from ..pyson import Eval
-from ..tools import safe_eval
+from ..tools import safe_eval, grouped_slice
from .. import backend
from ..tools import reduce_ids
from ..transaction import Transaction
@@ -172,15 +172,14 @@ class Trigger(ModelSQL, ModelView):
Model = pool.get(trigger.model.model)
ActionModel = pool.get(trigger.action_model.model)
cursor = Transaction().cursor
- in_max = cursor.IN_MAX
trigger_log = TriggerLog.__table__()
ids = map(int, records)
# Filter on limit_number
if trigger.limit_number:
new_ids = []
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids):
+ sub_ids = list(sub_ids)
red_sql = reduce_ids(trigger_log.record_id, sub_ids)
cursor.execute(*trigger_log.select(
trigger_log.record_id, Count(Literal(1)),
@@ -198,8 +197,8 @@ class Trigger(ModelSQL, ModelView):
# Filter on minimum_delay
if trigger.minimum_delay:
new_ids = []
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids):
+ sub_ids = list(sub_ids)
red_sql = reduce_ids(trigger_log.record_id, sub_ids)
cursor.execute(*trigger_log.select(
trigger_log.record_id, Max(trigger_log.create_date),
diff --git a/trytond/ir/ui/icons/tryton-tree.svg b/trytond/ir/ui/icons/tryton-tree.svg
index 0f9b8a2..3e26f16 100644
--- a/trytond/ir/ui/icons/tryton-tree.svg
+++ b/trytond/ir/ui/icons/tryton-tree.svg
@@ -14,7 +14,7 @@
height="48px"
id="svg4198"
sodipodi:version="0.32"
- inkscape:version="0.48.0 r9654"
+ inkscape:version="0.48.4 r9939"
sodipodi:docname="tryton-tree.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.1">
@@ -207,15 +207,15 @@
borderopacity="1.0000000"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="16.5"
- inkscape:cx="24"
- inkscape:cy="24"
+ inkscape:zoom="6.51"
+ inkscape:cx="22.261972"
+ inkscape:cy="12.80998"
inkscape:current-layer="layer1"
- showgrid="false"
+ showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
- inkscape:window-width="1278"
- inkscape:window-height="1007"
+ inkscape:window-width="1364"
+ inkscape:window-height="751"
inkscape:window-x="0"
inkscape:window-y="15"
inkscape:showpageshadow="false"
@@ -244,11 +244,6 @@
</dc:subject>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
- <dc:contributor>
- <cc:Agent>
- <dc:title>Cédric Krier</dc:title>
- </cc:Agent>
- </dc:contributor>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/">
@@ -297,43 +292,43 @@
rx="0.56650788"
ry="0.56650823" />
<rect
- style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect4248"
- width="26"
+ width="30"
height="2"
- x="-34"
+ x="-39"
y="10"
- transform="scale(-1,1)" />
+ transform="scale(-1.000000,1.000000)" />
<rect
y="16"
- x="-31"
+ x="-39"
height="2"
- width="23"
+ width="25.5"
id="rect4250"
style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
transform="scale(-1,1)" />
<rect
style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect4252"
- width="21"
+ width="19"
height="2"
- x="-29"
+ x="-39"
y="22"
transform="scale(-1,1)" />
<rect
y="28"
- x="-34"
+ x="-39"
height="2"
- width="26"
+ width="12.5"
id="rect4254"
style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
transform="scale(-1,1)" />
<rect
style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect4256"
- width="17"
+ width="6"
height="2"
- x="-25"
+ x="-38.5"
y="34"
transform="scale(-1,1)" />
<rect
diff --git a/trytond/ir/ui/menu.py b/trytond/ir/ui/menu.py
index 2d3b45e..94af4d8 100644
--- a/trytond/ir/ui/menu.py
+++ b/trytond/ir/ui/menu.py
@@ -1,5 +1,7 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
+from itertools import groupby
+
from trytond.model import ModelView, ModelSQL, fields
from trytond.transaction import Transaction
from trytond.pool import Pool
@@ -89,6 +91,8 @@ class UIMenu(ModelSQL, ModelView):
('ir.action.wizard', 'ir.action.wizard'),
('ir.action.url', 'ir.action.url'),
]), 'get_action', setter='set_action')
+ action_keywords = fields.One2Many('ir.action.keyword', 'model',
+ 'Action Keywords')
active = fields.Boolean('Active')
favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite')
@@ -190,25 +194,27 @@ class UIMenu(ModelSQL, ModelView):
@classmethod
def get_action(cls, menus, name):
pool = Pool()
- ActionKeyword = pool.get('ir.action.keyword')
actions = dict((m.id, None) for m in menus)
with Transaction().set_context(active_test=False):
- action_keywords = ActionKeyword.search([
- ('keyword', '=', 'tree_open'),
- ('model', 'in', [str(m) for m in menus]),
- ])
- for action_keyword in action_keywords:
- model = action_keyword.model
- Action = pool.get(action_keyword.action.type)
+ menus = cls.browse(menus)
+ action_keywords = sum((list(m.action_keywords) for m in menus), [])
+ key = lambda k: k.action.type
+ action_keywords.sort(key=key)
+ for type, action_keywords in groupby(action_keywords, key=key):
+ action_keywords = list(action_keywords)
+ for action_keyword in action_keywords:
+ model = action_keyword.model
+ actions[model.id] = '%s,-1' % type
+
+ Action = pool.get(type)
+ action2keyword = {k.action.id: k for k in action_keywords}
with Transaction().set_context(active_test=False):
factions = Action.search([
- ('action', '=', action_keyword.action.id),
- ], limit=1)
- if factions:
- action, = factions
- else:
- action = '%s,0' % action_keyword.action.type
- actions[model.id] = str(action)
+ ('action', 'in', action2keyword.keys()),
+ ])
+ for action in factions:
+ model = action2keyword[action.id].model
+ actions[model.id] = str(action)
return actions
@classmethod
diff --git a/trytond/ir/ui/tree.rnc b/trytond/ir/ui/tree.rnc
index 3500bc8..925331e 100644
--- a/trytond/ir/ui/tree.rnc
+++ b/trytond/ir/ui/tree.rnc
@@ -12,6 +12,8 @@ attlist.tree &= attribute sequence { text }?
attlist.tree &= attribute colors { text }?
attlist.tree &=
[ a:defaultValue = "0" ] attribute keyword_open { "0" | "1" }?
+attlist.tree &=
+ [ a:defaultValue = "0" ] attribute tree_state { "0" | "1" }?
field = element field { attlist.field, (prefix | suffix)* }
attlist.field &= attribute name { text }
attlist.field &= attribute readonly { "0" | "1" }?
@@ -39,6 +41,7 @@ attlist.field &=
| "reference"
| "one2one"
| "binary"
+ | "image"
}?
attlist.field &=
[ a:defaultValue = "0" ] attribute tree_invisible { "0" | "1" }?
@@ -47,6 +50,7 @@ attlist.field &=
attlist.field &= attribute icon { text }?
attlist.field &= attribute sum { text }?
attlist.field &= attribute width { text }?
+attlist.field &= attribute height { text }?
attlist.field &=
[ a:defaultValue = "left_to_right" ] attribute orientation {
"left_to_right"
diff --git a/trytond/ir/ui/tree.rng b/trytond/ir/ui/tree.rng
index 85ecc64..0508c44 100644
--- a/trytond/ir/ui/tree.rng
+++ b/trytond/ir/ui/tree.rng
@@ -51,6 +51,16 @@
</attribute>
</optional>
</define>
+ <define name="attlist.tree" combine="interleave">
+ <optional>
+ <attribute name="tree_state" a:defaultValue="0">
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ </define>
<define name="field">
<element name="field">
<ref name="attlist.field"/>
@@ -101,6 +111,7 @@
<value>reference</value>
<value>one2one</value>
<value>binary</value>
+ <value>image</value>
</choice>
</attribute>
</optional>
@@ -142,6 +153,11 @@
</define>
<define name="attlist.field" combine="interleave">
<optional>
+ <attribute name="height"/>
+ </optional>
+ </define>
+ <define name="attlist.field" combine="interleave">
+ <optional>
<attribute name="orientation" a:defaultValue="left_to_right">
<choice>
<value>left_to_right</value>
diff --git a/trytond/ir/ui/view.py b/trytond/ir/ui/view.py
index 5e16398..2c3fb90 100644
--- a/trytond/ir/ui/view.py
+++ b/trytond/ir/ui/view.py
@@ -3,6 +3,11 @@
import os
import sys
import logging
+try:
+ import simplejson as json
+except ImportError:
+ import json
+
from lxml import etree
from trytond.model import ModelView, ModelSQL, fields
from trytond import backend
@@ -305,8 +310,8 @@ class ViewTreeState(ModelSQL, ModelView):
def __setup__(cls):
super(ViewTreeState, cls).__setup__()
cls.__rpc__.update({
- 'set': RPC(readonly=False),
- 'get': RPC(),
+ 'set': RPC(readonly=False, check_access=False),
+ 'get': RPC(check_access=False),
})
@classmethod
@@ -335,40 +340,40 @@ class ViewTreeState(ModelSQL, ModelView):
@classmethod
def set(cls, model, domain, child_name, nodes, selected_nodes):
current_user = Transaction().user
- with Transaction().set_user(0):
- records = cls.search([
- ('user', '=', current_user),
- ('model', '=', model),
- ('domain', '=', domain),
- ('child_name', '=', child_name),
- ])
- cls.delete(records)
- cls.create([{
- 'user': current_user,
- 'model': model,
- 'domain': domain,
- 'child_name': child_name,
- 'nodes': nodes,
- 'selected_nodes': selected_nodes,
- }])
+ records = cls.search([
+ ('user', '=', current_user),
+ ('model', '=', model),
+ ('domain', '=', domain),
+ ('child_name', '=', child_name),
+ ])
+ cls.delete(records)
+ cls.create([{
+ 'user': current_user,
+ 'model': model,
+ 'domain': domain,
+ 'child_name': child_name,
+ 'nodes': nodes,
+ 'selected_nodes': selected_nodes,
+ }])
@classmethod
def get(cls, model, domain, child_name):
+ # Normalize the json domain
+ domain = json.dumps(json.loads(domain))
current_user = Transaction().user
- with Transaction().set_user(0):
- try:
- expanded_info, = cls.search([
- ('user', '=', current_user),
- ('model', '=', model),
- ('domain', '=', domain),
- ('child_name', '=', child_name),
- ],
- limit=1)
- except ValueError:
- return (cls.default_nodes(), cls.default_selected_nodes())
- state = cls(expanded_info)
- return (state.nodes or cls.default_nodes(),
- state.selected_nodes or cls.default_selected_nodes())
+ try:
+ expanded_info, = cls.search([
+ ('user', '=', current_user),
+ ('model', '=', model),
+ ('domain', '=', domain),
+ ('child_name', '=', child_name),
+ ],
+ limit=1)
+ except ValueError:
+ return (cls.default_nodes(), cls.default_selected_nodes())
+ state = cls(expanded_info)
+ return (state.nodes or cls.default_nodes(),
+ state.selected_nodes or cls.default_selected_nodes())
class ViewSearch(ModelSQL, ModelView):
diff --git a/trytond/ir/view/action_act_window_view_form.xml b/trytond/ir/view/action_act_window_view_form.xml
index d282a67..689eac0 100644
--- a/trytond/ir/view/action_act_window_view_form.xml
+++ b/trytond/ir/view/action_act_window_view_form.xml
@@ -1,11 +1,13 @@
<?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. -->
-<form string="View" col="2">
+<form string="View">
<label name="act_window"/>
- <field name="act_window"/>
+ <field name="act_window" colspan="3"/>
<label name="sequence"/>
<field name="sequence"/>
+ <label name="active"/>
+ <field name="active"/>
<label name="view"/>
<field name="view"/>
</form>
diff --git a/trytond/ir/view/action_act_window_view_list.xml b/trytond/ir/view/action_act_window_view_list.xml
index 5868cf7..4f8c136 100644
--- a/trytond/ir/view/action_act_window_view_list.xml
+++ b/trytond/ir/view/action_act_window_view_list.xml
@@ -5,4 +5,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="act_window"/>
<field name="sequence"/>
<field name="view"/>
+ <field name="active" tree_invisible="1"/>
</tree>
diff --git a/trytond/ir/view/model_data_form.xml b/trytond/ir/view/model_data_form.xml
new file mode 100644
index 0000000..9ba13fa
--- /dev/null
+++ b/trytond/ir/view/model_data_form.xml
@@ -0,0 +1,27 @@
+<?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. -->
+<form string="Model Data">
+ <label name="module"/>
+ <field name="module"/>
+ <label name="model"/>
+ <field name="model"/>
+ <label name="fs_id"/>
+ <field name="fs_id"/>
+ <label name="db_id"/>
+ <field name="db_id"/>
+ <label name="noupdate"/>
+ <field name="noupdate"/>
+ <newline/>
+ <label name="out_of_sync"/>
+ <field name="out_of_sync"/>
+ <button string="Sync" name="sync" colspan="2"/>
+ <group id="values" colspan="2" yexpand="1" yfill="1" col="1">
+ <separator name="values"/>
+ <field name="values"/>
+ </group>
+ <group id="fs_values" colspan="2" yexpand="1" yfill="1" col="1">
+ <separator name="fs_values"/>
+ <field name="fs_values"/>
+ </group>
+</form>
diff --git a/trytond/ir/view/model_data_list.xml b/trytond/ir/view/model_data_list.xml
new file mode 100644
index 0000000..ef361cc
--- /dev/null
+++ b/trytond/ir/view/model_data_list.xml
@@ -0,0 +1,13 @@
+<?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="Model Data">
+ <field name="module"/>
+ <field name="model"/>
+ <field name="fs_id"/>
+ <field name="db_id"/>
+ <field name="noupdate"/>
+ <field name="out_of_sync"/>
+ <button string="Sync" name="sync"/>
+</tree>
+
diff --git a/trytond/ir/view/ui_menu_tree.xml b/trytond/ir/view/ui_menu_tree.xml
index 36562f9..995c3cd 100644
--- a/trytond/ir/view/ui_menu_tree.xml
+++ b/trytond/ir/view/ui_menu_tree.xml
@@ -1,7 +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="Menu" keyword_open="1">
+<tree string="Menu" keyword_open="1" tree_state="1">
<field name="name" icon="icon" expand="1"/>
<field name="favorite" tree_invisible="1"/>
</tree>
diff --git a/trytond/model/__init__.py b/trytond/model/__init__.py
index b63e22c..dae657d 100644
--- a/trytond/model/__init__.py
+++ b/trytond/model/__init__.py
@@ -7,6 +7,8 @@ from .modelsingleton import ModelSingleton
from .modelsql import ModelSQL
from .workflow import Workflow
from .dictschema import DictSchemaMixin
+from .match import MatchMixin
+from .union import UnionMixin
__all__ = ['Model', 'ModelView', 'ModelStorage', 'ModelSingleton', 'ModelSQL',
- 'Workflow', 'DictSchemaMixin']
+ 'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin']
diff --git a/trytond/model/dictschema.py b/trytond/model/dictschema.py
index d6f1b33..34363a8 100644
--- a/trytond/model/dictschema.py
+++ b/trytond/model/dictschema.py
@@ -1,5 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the toplevel of this
# repository contains the full copyright notices and license terms.
+from collections import OrderedDict
try:
import simplejson as json
except ImportError:
@@ -33,6 +34,10 @@ class DictSchemaMixin(object):
'invisible': Eval('type_') != 'selection',
}, translate=True, depends=['type_'],
help='A couple of key and label separated by ":" per line')
+ selection_sorted = fields.Boolean('Selection Sorted', states={
+ 'invisible': Eval('type_') != 'selection',
+ }, depends=['type_'],
+ help='If the selection must be sorted on label')
selection_json = fields.Function(fields.Char('Selection JSON',
states={
'invisible': Eval('type_') != 'selection',
@@ -50,6 +55,10 @@ class DictSchemaMixin(object):
def default_digits():
return 2
+ @staticmethod
+ def default_selection_sorted():
+ return True
+
def get_selection_json(self, name):
db_selection = self.selection or ''
selection = [[w.strip() for w in v.split(':', 1)]
@@ -71,9 +80,11 @@ class DictSchemaMixin(object):
if record.type_ == 'selection':
with Transaction().set_context(language=Config.get_language()):
english_key = cls(record.id)
- selection = dict(json.loads(english_key.selection_json))
+ selection = OrderedDict(json.loads(
+ english_key.selection_json))
selection.update(dict(json.loads(record.selection_json)))
new_key['selection'] = selection.items()
+ new_key['sorted'] = record.selection_sorted
elif record.type_ in ('float', 'numeric'):
new_key['digits'] = (16, record.digits)
keys.append(new_key)
diff --git a/trytond/model/fields/binary.py b/trytond/model/fields/binary.py
index 94b278e..42faa79 100644
--- a/trytond/model/fields/binary.py
+++ b/trytond/model/fields/binary.py
@@ -4,7 +4,7 @@ from sql import Query, Expression
from .field import Field, SQLType
from ...transaction import Transaction
-from ...config import CONFIG
+from ... import backend
class Binary(Field):
@@ -59,14 +59,14 @@ class Binary(Field):
def sql_format(value):
if isinstance(value, (Query, Expression)):
return value
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'postgresql' and value is not None:
import psycopg2
return psycopg2.Binary(value)
return value
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'postgresql':
return SQLType('BYTEA', 'BYTEA')
elif db_type == 'mysql':
diff --git a/trytond/model/fields/char.py b/trytond/model/fields/char.py
index 1168cac..6254077 100644
--- a/trytond/model/fields/char.py
+++ b/trytond/model/fields/char.py
@@ -4,7 +4,7 @@ import warnings
from sql import Query, Expression
-from ...config import CONFIG
+from ... import backend
from .field import Field, FieldTranslate, size_validate, SQLType
@@ -59,7 +59,7 @@ class Char(FieldTranslate):
return value
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if self.size and db_type != 'sqlite':
return SQLType('VARCHAR', 'VARCHAR(%s)' % self.size)
elif db_type == 'mysql':
diff --git a/trytond/model/fields/date.py b/trytond/model/fields/date.py
index f769e9d..428f6c4 100644
--- a/trytond/model/fields/date.py
+++ b/trytond/model/fields/date.py
@@ -3,7 +3,7 @@
import datetime
from sql import Query, Expression
-from ...config import CONFIG
+from ... import backend
from .field import Field, SQLType
@@ -71,7 +71,7 @@ class DateTime(Field):
return value.replace(microsecond=0)
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'sqlite':
return SQLType('TIMESTAMP', 'TIMESTAMP')
elif db_type == 'mysql':
@@ -106,7 +106,7 @@ class Timestamp(Field):
return value
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'sqlite':
return SQLType('TIMESTAMP', 'TIMESTAMP')
elif db_type == 'mysql':
diff --git a/trytond/model/fields/dict.py b/trytond/model/fields/dict.py
index a775916..ef63d52 100644
--- a/trytond/model/fields/dict.py
+++ b/trytond/model/fields/dict.py
@@ -7,7 +7,7 @@ except ImportError:
from sql import Query, Expression
from .field import Field, SQLType
-from ...protocols.jsonrpc import object_hook, JSONEncoder
+from ...protocols.jsonrpc import JSONDecoder, JSONEncoder
class Dict(Field):
@@ -28,7 +28,7 @@ class Dict(Field):
for value in values or []:
if value[name]:
dicts[value['id']] = json.loads(value[name],
- object_hook=object_hook)
+ object_hook=JSONDecoder())
return dicts
@staticmethod
diff --git a/trytond/model/fields/field.py b/trytond/model/fields/field.py
index 1404b80..3eb95f2 100644
--- a/trytond/model/fields/field.py
+++ b/trytond/model/fields/field.py
@@ -81,6 +81,9 @@ def depends(*fields, **kwargs):
@wraps(func)
def wrapper(self, *args, **kwargs):
for field in fields:
+ field = field.split('.')[0]
+ if field.startswith('_parent_'):
+ field = field[8:] # Strip '_parent_'
if not hasattr(self, field):
setattr(self, field, None)
return func(self, *args, **kwargs)
@@ -248,6 +251,9 @@ class Field(object):
table, _ = tables[None]
name, operator, value = domain
assert name == self.name
+ method = getattr(Model, 'domain_%s' % name, None)
+ if method:
+ return method(domain, tables)
Operator = SQL_OPERATORS[operator]
column = self.sql_column(table)
expression = Operator(column, self._domain_value(operator, value))
diff --git a/trytond/model/fields/float.py b/trytond/model/fields/float.py
index 1041f0a..42a8db5 100644
--- a/trytond/model/fields/float.py
+++ b/trytond/model/fields/float.py
@@ -2,7 +2,7 @@
#this repository contains the full copyright notices and license terms.
from sql import Query, Expression
-from ...config import CONFIG
+from ... import backend
from .field import Field, SQLType
from ...pyson import PYSON
@@ -59,7 +59,7 @@ class Float(Field):
return float(value)
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'postgresql':
return SQLType('FLOAT8', 'FLOAT8')
elif db_type == 'mysql':
diff --git a/trytond/model/fields/function.py b/trytond/model/fields/function.py
index d5489b7..8a72955 100644
--- a/trytond/model/fields/function.py
+++ b/trytond/model/fields/function.py
@@ -4,6 +4,7 @@
import inspect
import copy
from trytond.model.fields.field import Field
+from trytond.transaction import Transaction
class Function(Field):
@@ -37,14 +38,11 @@ class Function(Field):
def __copy__(self):
return Function(copy.copy(self._field), self.getter,
- setter=self.setter, searcher=self.searcher)
+ setter=self.setter, searcher=self.searcher, loading=self.loading)
def __deepcopy__(self, memo):
- return Function(copy.deepcopy(self._field, memo),
- copy.deepcopy(self.getter, memo),
- setter=copy.deepcopy(self.setter, memo),
- searcher=copy.deepcopy(self.searcher, memo),
- loading=copy.deepcopy(self.loading, memo))
+ return Function(copy.deepcopy(self._field, memo), self.getter,
+ setter=self.setter, searcher=self.searcher, loading=self.loading)
def __getattr__(self, name):
return getattr(self._field, name)
@@ -71,36 +69,38 @@ class Function(Field):
If the function has ``names`` in the function definition then
it will call it with a list of name.
'''
- method = getattr(Model, self.getter)
+ with Transaction().set_context(_check_access=False):
+ method = getattr(Model, self.getter)
- def call(name):
- records = Model.browse(ids)
- if not hasattr(method, 'im_self') or method.im_self:
- return method(records, name)
+ def call(name):
+ records = Model.browse(ids)
+ if not hasattr(method, 'im_self') or method.im_self:
+ return method(records, name)
+ else:
+ return dict((r.id, method(r, name)) for r in records)
+ if isinstance(name, list):
+ names = name
+ # Test is the function works with a list of names
+ if 'names' in inspect.getargspec(method)[0]:
+ return call(names)
+ return dict((name, call(name)) for name in names)
else:
- return dict((r.id, method(r, name)) for r in records)
- if isinstance(name, list):
- names = name
- # Test is the function works with a list of names
- if 'names' in inspect.getargspec(method)[0]:
- return call(names)
- return dict((name, call(name)) for name in names)
- else:
- # Test is the function works with a list of names
- if 'names' in inspect.getargspec(method)[0]:
- name = [name]
- return call(name)
+ # Test is the function works with a list of names
+ if 'names' in inspect.getargspec(method)[0]:
+ name = [name]
+ return call(name)
def set(self, Model, name, ids, value, *args):
'''
Call the setter.
'''
- if self.setter:
- # TODO change setter API to use sequence of records, value
- setter = getattr(Model, self.setter)
- args = iter((ids, value) + args)
- for ids, value in zip(args, args):
- setter(Model.browse(ids), name, value)
+ with Transaction().set_context(_check_access=False):
+ if self.setter:
+ # TODO change setter API to use sequence of records, value
+ setter = getattr(Model, self.setter)
+ args = iter((ids, value) + args)
+ for ids, value in zip(args, args):
+ setter(Model.browse(ids), name, value)
def __set__(self, inst, value):
self._field.__set__(inst, value)
diff --git a/trytond/model/fields/integer.py b/trytond/model/fields/integer.py
index 2ba0694..64f51bf 100644
--- a/trytond/model/fields/integer.py
+++ b/trytond/model/fields/integer.py
@@ -2,7 +2,7 @@
#this repository contains the full copyright notices and license terms.
from sql import Query, Expression
-from ...config import CONFIG
+from ... import backend
from .field import Field, SQLType
@@ -13,7 +13,7 @@ class Integer(Field):
_type = 'integer'
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'postgresql':
return SQLType('INT4', 'INT4')
elif db_type == 'mysql':
@@ -22,7 +22,7 @@ class Integer(Field):
return SQLType('INTEGER', 'INTEGER')
def sql_format(self, value):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if (db_type == 'sqlite'
and value is not None
and not isinstance(value, (Query, Expression))):
@@ -37,7 +37,7 @@ class BigInteger(Integer):
_type = 'biginteger'
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'postgresql':
return SQLType('INT8', 'INT8')
return super(BigInteger, self).sql_type()
diff --git a/trytond/model/fields/many2many.py b/trytond/model/fields/many2many.py
index f037eaa..46f4da5 100644
--- a/trytond/model/fields/many2many.py
+++ b/trytond/model/fields/many2many.py
@@ -5,8 +5,8 @@ from sql import Cast, Literal
from sql.functions import Substring, Position
from .field import Field, size_validate
-from ...transaction import Transaction
from ...pool import Pool
+from ...tools import grouped_slice
class Many2Many(Field):
@@ -58,6 +58,10 @@ class Many2Many(Field):
size = property(_get_size, _set_size)
+ @property
+ def add_remove(self):
+ return self.domain
+
def get(self, ids, model, name, values=None):
'''
Return target records ordered.
@@ -79,13 +83,12 @@ class Many2Many(Field):
origin_field = Relation._fields[self.origin]
relations = []
- for i in range(0, len(ids), Transaction().cursor.IN_MAX):
- sub_ids = ids[i:i + Transaction().cursor.IN_MAX]
+ for sub_ids in grouped_slice(ids):
if origin_field._type == 'reference':
references = ['%s,%s' % (model.__name__, x) for x in sub_ids]
clause = [(self.origin, 'in', references)]
else:
- clause = [(self.origin, 'in', sub_ids)]
+ clause = [(self.origin, 'in', list(sub_ids))]
clause += [(self.target + '.id', '!=', None)]
relations.append(Relation.search(clause, order=order))
relations = list(chain(*relations))
@@ -150,12 +153,10 @@ class Many2Many(Field):
if not target_ids:
return
existing_ids = set()
- in_max = Transaction().cursor.IN_MAX
- for i in range(0, len(target_ids), in_max):
- sub_ids = target_ids[i:i + in_max]
+ for sub_ids in grouped_slice(target_ids):
relations = Relation.search([
search_clause(ids),
- (self.target, 'in', sub_ids),
+ (self.target, 'in', list(sub_ids)),
])
for relation in relations:
existing_ids.add(getattr(relation, self.target).id)
@@ -172,12 +173,10 @@ class Many2Many(Field):
target_ids = map(int, target_ids)
if not target_ids:
return
- in_max = Transaction().cursor.IN_MAX
- for i in range(0, len(target_ids), in_max):
- sub_ids = target_ids[i:i + in_max]
+ for sub_ids in grouped_slice(target_ids):
relation_to_delete.extend(Relation.search([
search_clause(ids),
- (self.target, 'in', sub_ids),
+ (self.target, 'in', list(sub_ids)),
]))
def copy(ids, copy_ids, default=None):
diff --git a/trytond/model/fields/many2one.py b/trytond/model/fields/many2one.py
index ef109d0..e6aa9ba 100644
--- a/trytond/model/fields/many2one.py
+++ b/trytond/model/fields/many2one.py
@@ -6,7 +6,7 @@ from sql.operators import Or
from .field import Field, SQLType
from ...pool import Pool
-from ...config import CONFIG
+from ... import backend
from ...tools import reduce_ids
from ...transaction import Transaction
@@ -86,7 +86,7 @@ class Many2One(Field):
return int(value)
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'postgresql':
return SQLType('INT4', 'INT4')
elif db_type == 'mysql':
diff --git a/trytond/model/fields/numeric.py b/trytond/model/fields/numeric.py
index 2a8dda3..3580bb7 100644
--- a/trytond/model/fields/numeric.py
+++ b/trytond/model/fields/numeric.py
@@ -3,7 +3,7 @@
from decimal import Decimal
from sql import Query, Expression, Cast, Literal, Select, CombiningQuery
-from ...config import CONFIG
+from ... import backend
from .field import SQLType
from .float import Float
@@ -26,14 +26,14 @@ class Numeric(Float):
return value
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'mysql':
return SQLType('DECIMAL', 'DECIMAL(65, 30)')
return SQLType('NUMERIC', 'NUMERIC')
def sql_column(self, table):
column = super(Numeric, self).sql_column(table)
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'sqlite':
# Must be casted as Decimal is stored as bytes
column = Cast(column, self.sql_type().base)
@@ -41,7 +41,7 @@ class Numeric(Float):
def _domain_value(self, operator, value):
value = super(Numeric, self)._domain_value(operator, value)
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'sqlite':
if isinstance(value, (Select, CombiningQuery)):
return value
diff --git a/trytond/model/fields/one2many.py b/trytond/model/fields/one2many.py
index f9c4f2a..5387ab6 100644
--- a/trytond/model/fields/one2many.py
+++ b/trytond/model/fields/one2many.py
@@ -5,8 +5,8 @@ from sql import Cast, Literal
from sql.functions import Substring, Position
from .field import Field, size_validate
-from ...transaction import Transaction
from ...pool import Pool
+from ...tools import grouped_slice
def add_remove_validate(value):
@@ -87,13 +87,12 @@ class One2Many(Field):
res[i] = []
targets = []
- for i in range(0, len(ids), Transaction().cursor.IN_MAX):
- sub_ids = ids[i:i + Transaction().cursor.IN_MAX]
+ for sub_ids in grouped_slice(ids):
if field._type == 'reference':
references = ['%s,%s' % (model.__name__, x) for x in sub_ids]
clause = [(self.field, 'in', references)]
else:
- clause = [(self.field, 'in', sub_ids)]
+ clause = [(self.field, 'in', list(sub_ids))]
targets.append(Relation.search(clause, order=self.order))
targets = list(chain(*targets))
@@ -162,12 +161,10 @@ class One2Many(Field):
target_ids = map(int, target_ids)
if not target_ids:
return
- in_max = Transaction().cursor.IN_MAX
- for i in range(0, len(target_ids), in_max):
- sub_ids = target_ids[i:i + in_max]
+ for sub_ids in grouped_slice(target_ids):
targets = Target.search([
search_clause(ids),
- ('id', 'in', sub_ids),
+ ('id', 'in', list(sub_ids)),
])
to_write.extend((targets, {
self.field: None,
diff --git a/trytond/model/fields/property.py b/trytond/model/fields/property.py
index 13737dd..94a77a4 100644
--- a/trytond/model/fields/property.py
+++ b/trytond/model/fields/property.py
@@ -42,25 +42,27 @@ class Property(Function):
:param values:
:return: a dictionary with ids as key and values as value
'''
- pool = Pool()
- Property = pool.get('ir.property')
- return Property.get(name, model.__name__, ids)
+ with Transaction().set_context(_check_access=False):
+ pool = Pool()
+ Property = pool.get('ir.property')
+ return Property.get(name, model.__name__, ids)
def set(self, Model, name, ids, value, *args):
'''
Set the property.
'''
- pool = Pool()
- Property = pool.get('ir.property')
- args = iter((ids, value) + args)
- for ids, value in zip(args, args):
- if value is not None:
- prop_value = '%s,%s' % (getattr(self, 'model_name', ''),
- str(value))
- else:
- prop_value = None
- # TODO change set API to use sequence of records, value
- Property.set(name, Model.__name__, ids, prop_value)
+ with Transaction().set_context(_check_access=False):
+ pool = Pool()
+ Property = pool.get('ir.property')
+ args = iter((ids, value) + args)
+ for ids, value in zip(args, args):
+ if value is not None:
+ prop_value = '%s,%s' % (getattr(self, 'model_name', ''),
+ str(value))
+ else:
+ prop_value = None
+ # TODO change set API to use sequence of records, value
+ Property.set(name, Model.__name__, ids, prop_value)
def convert_domain(self, domain, tables, Model):
pool = Pool()
diff --git a/trytond/model/fields/reference.py b/trytond/model/fields/reference.py
index 1e3b378..e4a4079 100644
--- a/trytond/model/fields/reference.py
+++ b/trytond/model/fields/reference.py
@@ -8,7 +8,7 @@ from .field import Field, SQLType
from .char import Char
from ...transaction import Transaction
from ...pool import Pool
-from ...config import CONFIG
+from ... import backend
class Reference(Field):
@@ -68,7 +68,7 @@ class Reference(Field):
# Check if reference ids still exist
with Transaction().set_context(active_test=False), \
- Transaction().set_user(0):
+ Transaction().set_context(_check_access=False):
for ref_model, (ref_ids, ids) in ref_to_check.iteritems():
try:
pool.get(ref_model)
@@ -89,11 +89,14 @@ class Reference(Field):
from ..model import Model
if not isinstance(value, (Model, NoneType)):
if isinstance(value, basestring):
- target, id_ = value.split(',')
+ target, value = value.split(',')
else:
- target, id_ = value
+ target, value = value
Target = Pool().get(target)
- value = Target(id_)
+ if isinstance(value, dict):
+ value = Target(**value)
+ else:
+ value = Target(value)
super(Reference, self).__set__(inst, value)
@staticmethod
@@ -106,7 +109,7 @@ class Reference(Field):
return Char.sql_format(value)
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'mysql':
return SQLType('CHAR', 'VARCHAR(255)')
return SQLType('VARCHAR', 'VARCHAR')
diff --git a/trytond/model/fields/selection.py b/trytond/model/fields/selection.py
index 230e153..1f75d27 100644
--- a/trytond/model/fields/selection.py
+++ b/trytond/model/fields/selection.py
@@ -4,7 +4,7 @@ import warnings
from sql.conditionals import Case
-from ...config import CONFIG
+from ... import backend
from .field import Field, SQLType
@@ -44,7 +44,7 @@ class Selection(Field):
__init__.__doc__ += Field.__init__.__doc__
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'mysql':
return SQLType('CHAR', 'VARCHAR(255)')
return SQLType('VARCHAR', 'VARCHAR')
@@ -63,3 +63,29 @@ class Selection(Field):
for key, value in selections:
whens.append((column == key, value))
return [Case(*whens, else_=column)]
+
+ def translated(self, name=None):
+ "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 TranslatedSelection(name)
+
+
+class TranslatedSelection(object):
+ 'A descriptor for translated value of Selection field'
+
+ def __init__(self, name):
+ self.name = name
+
+ def __get__(self, inst, cls):
+ if inst is None:
+ return self
+ selection = dict(cls.fields_get([self.name])[self.name]['selection'])
+ value = getattr(inst, self.name)
+ # None and '' are equivalent
+ if value is None or value == '':
+ if value not in selection:
+ value = {None: '', '': None}[value]
+ return selection[value]
diff --git a/trytond/model/fields/sha.py b/trytond/model/fields/sha.py
index 0209337..222aca8 100644
--- a/trytond/model/fields/sha.py
+++ b/trytond/model/fields/sha.py
@@ -3,7 +3,7 @@
import hashlib
from sql import Query, Expression
-from ...config import CONFIG
+from ... import backend
from .field import SQLType
from .char import Char
@@ -22,7 +22,7 @@ class Sha(Char):
return super(Sha, self).sql_format(value)
def sql_type(self):
- db_type = CONFIG['db_type']
+ db_type = backend.name()
if db_type == 'mysql':
return SQLType('CHAR', 'VARCHAR(40)')
return SQLType('VARCHAR', 'VARCHAR(40)')
diff --git a/trytond/model/match.py b/trytond/model/match.py
new file mode 100644
index 0000000..cb82f19
--- /dev/null
+++ b/trytond/model/match.py
@@ -0,0 +1,19 @@
+# This file is part of Tryton. The COPYRIGHT file at the toplevel of this
+# repository contains the full copyright notices and license terms.
+
+
+class MatchMixin(object):
+
+ def match(self, pattern):
+ '''Match on pattern
+ pattern is a dictionary with model field as key
+ and matching value as value'''
+ for field, pattern_value in pattern.iteritems():
+ value = getattr(self, field)
+ if value is None:
+ continue
+ if self._fields[field]._type == 'many2one':
+ value = value.id
+ if value != pattern_value:
+ return False
+ return True
diff --git a/trytond/model/model.py b/trytond/model/model.py
index 8458697..13e8554 100644
--- a/trytond/model/model.py
+++ b/trytond/model/model.py
@@ -4,6 +4,7 @@
import copy
import collections
import warnings
+from functools import total_ordering
from trytond.model import fields
from trytond.error import WarningErrorMixin
@@ -16,6 +17,7 @@ from trytond.rpc import RPC
__all__ = ['Model']
+ at total_ordering
class Model(WarningErrorMixin, URLMixin, PoolBase):
"""
Define a model in Tryton.
@@ -30,6 +32,7 @@ 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),
}
@@ -47,7 +50,14 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
if not isinstance(getattr(cls, attr), fields.Field):
continue
field_name = attr
- field = copy.deepcopy(getattr(cls, field_name))
+ field = getattr(cls, field_name)
+ # Copy the original field definition to prevent side-effect with
+ # the mutable attributes
+ for parent_cls in cls.__mro__:
+ parent_field = getattr(parent_cls, field_name, None)
+ if isinstance(parent_field, fields.Field):
+ field = parent_field
+ field = copy.deepcopy(field)
setattr(cls, field_name, field)
for attribute in ('on_change', 'on_change_with', 'autocomplete',
@@ -60,8 +70,14 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
continue
else:
function_name = '%s_%s' % (attribute, field_name)
- function = getattr(cls, function_name, None)
- if function:
+ 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)
@@ -157,12 +173,10 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
Translation.register_error_messages(cls, module_name)
@classmethod
- def default_get(cls, fields_names, with_rec_name=True,
- with_on_change=True):
+ def default_get(cls, fields_names, with_rec_name=True):
'''
Return a dict with the default values for each field in fields_names.
If with_rec_name is True, rec_name will be added.
- If with_on_change is True, on_change will be added.
'''
pool = Pool()
Property = pool.get('ir.property')
@@ -186,8 +200,6 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
value[field_name + '.rec_name'] = Target(
value[field_name]).rec_name
- if with_on_change:
- value = cls._default_on_change(value)
if not with_rec_name:
for field in value.keys():
if field.endswith('.rec_name'):
@@ -195,29 +207,6 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
return value
@classmethod
- def _default_on_change(cls, value):
- """
- Call on_change function for the default value
- and return new default value
- """
- pool = Pool()
- res = value.copy()
- val = {}
- for field in value.keys():
- if field in cls._fields:
- if cls._fields[field].on_change:
- inst = cls()
- for fname in cls._fields[field].on_change:
- setattr(inst, fname, value.get(fname))
- val.update(getattr(inst, 'on_change_' + field)())
- if cls._fields[field]._type in ('one2many',):
- Target = pool.get(cls._fields[field].model_name)
- for val2 in res[field]:
- val2.update(Target._default_on_change(val2))
- res.update(val)
- return res
-
- @classmethod
def fields_get(cls, fields_names=None):
"""
Return the definition of each field on the model.
@@ -365,12 +354,15 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
cls._fields[field].field)
if res[field]['type'] == 'many2one':
target = cls._fields[field].get_target()
+ relation_fields = []
for target_name, target_field in target._fields.iteritems():
if (target_field._type == 'one2many'
and target_field.model_name == cls.__name__
and target_field.field == field):
- res[field]['relation_field'] = target_name
- break
+ relation_fields.append(target_name)
+ # Set relation_field only if there is no ambiguity
+ if len(relation_fields) == 1:
+ res[field]['relation_field'], = relation_fields
if res[field]['type'] in ('datetime', 'time'):
res[field]['format'] = copy.copy(cls._fields[field].format)
if res[field]['type'] == 'selection':
@@ -399,6 +391,14 @@ 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:
@@ -480,11 +480,6 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
return NotImplemented
return self.id < other.id
- # TODO: replace by total_ordering when 2.6 will be dropped
- __gt__ = lambda self, other: not (self < other or self == other)
- __le__ = lambda self, other: self < other or self == other
- __ge__ = lambda self, other: not self < other
-
def __ne__(self, other):
if not isinstance(other, Model):
return NotImplemented
diff --git a/trytond/model/modelsingleton.py b/trytond/model/modelsingleton.py
index f9ff034..e1347f6 100644
--- a/trytond/model/modelsingleton.py
+++ b/trytond/model/modelsingleton.py
@@ -83,13 +83,12 @@ class ModelSingleton(ModelStorage):
return res
@classmethod
- def default_get(cls, fields_names, with_rec_name=True,
- with_on_change=True):
+ def default_get(cls, fields_names, with_rec_name=True):
if '_timestamp' in fields_names:
fields_names = list(fields_names)
fields_names.remove('_timestamp')
default = super(ModelSingleton, cls).default_get(fields_names,
- with_rec_name=with_rec_name, with_on_change=with_on_change)
+ with_rec_name=with_rec_name)
singleton = cls.get_singleton()
if singleton:
if with_rec_name:
diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py
index 58b7dc7..4f3cf43 100644
--- a/trytond/model/modelsql.py
+++ b/trytond/model/modelsql.py
@@ -3,7 +3,7 @@
import re
import datetime
from functools import reduce
-from itertools import islice, izip, chain
+from itertools import islice, izip, chain, ifilter
from sql import Table, Column, Literal, Desc, Asc, Expression, Flavor
from sql.functions import Now, Extract
@@ -14,7 +14,7 @@ from sql.aggregate import Count, Max
from trytond.model import ModelStorage, ModelView
from trytond.model import fields
from trytond import backend
-from trytond.tools import reduce_ids
+from trytond.tools import reduce_ids, grouped_slice
from trytond.const import OPERATORS, RECORD_CACHE_SIZE
from trytond.transaction import Transaction
from trytond.pool import Pool
@@ -139,8 +139,7 @@ class ModelSQL(ModelStorage):
if isinstance(field, fields.Many2One) \
and field.model_name == cls.__name__ \
and field.left and field.right:
- with Transaction().set_user(0):
- cls._rebuild_tree(field_name, None, 0)
+ cls._rebuild_tree(field_name, None, 0)
for ident, constraint, _ in cls._sql_constraints:
table.add_constraint(ident, constraint)
@@ -189,10 +188,6 @@ class ModelSQL(ModelStorage):
@staticmethod
def table_query():
- '''
- Return None if the model is a real table in the database
- or return a tuple with the SQL query and the arguments.
- '''
return None
@classmethod
@@ -246,9 +241,7 @@ class ModelSQL(ModelStorage):
table = cls.__table_history__()
user = User.__table__()
revisions = []
- in_max = cursor.IN_MAX
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids):
where = reduce_ids(table.id, sub_ids)
cursor.execute(*table.join(user, 'LEFT',
Coalesce(table.write_uid, table.create_uid) == user.id)
@@ -272,7 +265,6 @@ class ModelSQL(ModelStorage):
def __insert_history(cls, ids, deleted=False):
transaction = Transaction()
cursor = transaction.cursor
- in_max = cursor.IN_MAX
if not cls._history:
return
user = transaction.user
@@ -293,8 +285,7 @@ class ModelSQL(ModelStorage):
continue
columns.append(Column(table, fname))
hcolumns.append(Column(history, fname))
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids):
if not deleted:
where = reduce_ids(table.id, sub_ids)
cursor.execute(*history.insert(hcolumns,
@@ -310,14 +301,13 @@ class ModelSQL(ModelStorage):
return
transaction = Transaction()
cursor = transaction.cursor
- in_max = cursor.IN_MAX
table = cls.__table__()
history = cls.__table_history__()
columns = []
hcolumns = []
- for fname, field in sorted(cls._fields.iteritems()):
- if hasattr(field, 'set'):
- continue
+ fnames = sorted(n for n, f in cls._fields.iteritems()
+ if not hasattr(f, 'set'))
+ for fname in fnames:
columns.append(Column(table, fname))
if fname == 'write_uid':
hcolumns.append(Literal(transaction.user))
@@ -326,16 +316,20 @@ class ModelSQL(ModelStorage):
else:
hcolumns.append(Column(history, fname))
+ def is_deleted(values):
+ return all(not v for n, v in zip(fnames, values)
+ if n not in ['id', 'write_uid', 'write_date'])
+
to_delete = []
to_update = []
for id_ in ids:
column_datetime = Coalesce(history.write_date, history.create_date)
hwhere = (column_datetime <= datetime) & (history.id == id_)
- horder = column_datetime.desc
+ horder = (column_datetime.desc, Column(history, '__id').desc)
cursor.execute(*history.select(*hcolumns,
where=hwhere, order_by=horder, limit=1))
values = cursor.fetchone()
- if not values:
+ if not values or is_deleted(values):
to_delete.append(id_)
else:
to_update.append(id_)
@@ -351,8 +345,7 @@ class ModelSQL(ModelStorage):
cursor.execute(*table.insert(columns, [values]))
if to_delete:
- for i in range(0, len(to_delete), in_max):
- sub_ids = to_delete[i:i + in_max]
+ for sub_ids in grouped_slice(to_delete):
where = reduce_ids(table.id, sub_ids)
cursor.execute(*table.delete(where=where))
cls.__insert_history(to_delete, True)
@@ -363,12 +356,10 @@ class ModelSQL(ModelStorage):
def __check_timestamp(cls, ids):
transaction = Transaction()
cursor = transaction.cursor
- in_max = cursor.IN_MAX
table = cls.__table__()
if not transaction.timestamp:
return
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids):
where = Or()
for id_ in sub_ids:
try:
@@ -394,7 +385,6 @@ class ModelSQL(ModelStorage):
cursor = transaction.cursor
pool = Pool()
Translation = pool.get('ir.translation')
- in_max = cursor.IN_MAX
super(ModelSQL, cls).create(vlist)
@@ -422,8 +412,7 @@ class ModelSQL(ModelStorage):
default.append(f)
if default:
- defaults = cls.default_get(default, with_rec_name=False,
- with_on_change=False)
+ defaults = cls.default_get(default, with_rec_name=False)
values.update(cls._clean_defaults(defaults))
insert_columns = [table.create_uid, table.create_date]
@@ -455,15 +444,15 @@ class ModelSQL(ModelStorage):
new_ids.append(id_new)
except DatabaseIntegrityError, exception:
with Transaction().new_cursor(), \
- Transaction().set_user(0):
+ Transaction().set_context(_check_access=False):
cls.__raise_integrity_error(exception, values)
raise
domain = pool.get('ir.rule').domain_get(cls.__name__,
mode='create')
if domain:
- for i in range(0, len(new_ids), in_max):
- sub_ids = new_ids[i:i + in_max]
+ for sub_ids in grouped_slice(new_ids):
+ sub_ids = list(sub_ids)
red_sql = reduce_ids(table.id, sub_ids)
cursor.execute(*table.select(table.id,
@@ -499,8 +488,8 @@ class ModelSQL(ModelStorage):
cls.__insert_history(new_ids)
records = cls.browse(new_ids)
- for i in range(0, len(records), RECORD_CACHE_SIZE):
- cls._validate(records[i:i + RECORD_CACHE_SIZE])
+ for sub_records in grouped_slice(records, RECORD_CACHE_SIZE):
+ cls._validate(sub_records)
field_names = cls._fields.keys()
cls._update_mptt(field_names, [new_ids] * len(field_names))
@@ -575,8 +564,8 @@ class ModelSQL(ModelStorage):
if 'id' not in fields_names:
columns.append(table.id.as_('id'))
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids, in_max):
+ sub_ids = list(sub_ids)
red_sql = reduce_ids(table.id, sub_ids)
where = red_sql
if history_clause:
@@ -745,7 +734,6 @@ class ModelSQL(ModelStorage):
pool = Pool()
Translation = pool.get('ir.translation')
Config = pool.get('ir.configuration')
- in_max = cursor.IN_MAX
assert not len(args) % 2
all_records = sum(((records, values) + args)[0:None:2], [])
@@ -787,8 +775,8 @@ class ModelSQL(ModelStorage):
update_values.append(field.sql_format(value))
domain = pool.get('ir.rule').domain_get(cls.__name__, mode='write')
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids):
+ sub_ids = list(sub_ids)
red_sql = reduce_ids(table.id, sub_ids)
where = red_sql
if domain:
@@ -811,7 +799,7 @@ class ModelSQL(ModelStorage):
where=red_sql))
except DatabaseIntegrityError, exception:
with Transaction().new_cursor(), \
- Transaction().set_user(0):
+ Transaction().set_context(_check_access=False):
cls.__raise_integrity_error(exception, values,
values.keys())
raise
@@ -826,18 +814,17 @@ class ModelSQL(ModelStorage):
if hasattr(field, 'set'):
fields_to_set.setdefault(fname, []).extend((ids, value))
- field_names = cls._fields.keys()
+ field_names = values.keys()
cls._update_mptt(field_names, [ids] * len(field_names), values)
- all_field_names |= set(values.keys())
+ all_field_names |= set(field_names)
for fname, fargs in fields_to_set.iteritems():
field = cls._fields[fname]
field.set(cls, fname, *fargs)
cls.__insert_history(all_ids)
- for i in range(0, len(all_records), RECORD_CACHE_SIZE):
- cls._validate(all_records[i:i + RECORD_CACHE_SIZE],
- field_names=all_field_names)
+ for sub_records in grouped_slice(all_records, RECORD_CACHE_SIZE):
+ cls._validate(sub_records, field_names=all_field_names)
cls.trigger_write(trigger_eligibles)
@classmethod
@@ -848,7 +835,6 @@ class ModelSQL(ModelStorage):
pool = Pool()
Translation = pool.get('ir.translation')
ids = map(int, records)
- in_max = cursor.IN_MAX
if not ids:
return
@@ -871,8 +857,7 @@ class ModelSQL(ModelStorage):
and field.model_name == cls.__name__
and field.left and field.right):
tree_ids[fname] = []
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids):
where = reduce_ids(Column(table, fname), sub_ids)
cursor.execute(*table.select(table.id, where=where))
tree_ids[fname] += [x[0] for x in cursor.fetchall()]
@@ -903,8 +888,8 @@ class ModelSQL(ModelStorage):
domain = pool.get('ir.rule').domain_get(cls.__name__, mode='delete')
if domain:
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids):
+ sub_ids = list(sub_ids)
red_sql = reduce_ids(table.id, sub_ids)
cursor.execute(*table.select(table.id,
where=red_sql & table.id.in_(domain)))
@@ -916,9 +901,9 @@ class ModelSQL(ModelStorage):
cls.trigger_delete(records)
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
- sub_records = records[i:i + in_max]
+ for sub_ids, sub_records in izip(
+ grouped_slice(ids), grouped_slice(records)):
+ sub_ids = list(sub_ids)
red_sql = reduce_ids(table.id, sub_ids)
transaction.delete_records.setdefault(cls.__name__,
@@ -953,7 +938,7 @@ class ModelSQL(ModelStorage):
Model.delete(models)
for Model, field_name in foreign_keys_tocheck:
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
if Model.search([
(field_name, 'in', sub_ids),
], order=[]):
@@ -961,7 +946,7 @@ class ModelSQL(ModelStorage):
cls.raise_user_error('foreign_model_exist',
error_args=error_args)
- super(ModelSQL, cls).delete(sub_records)
+ super(ModelSQL, cls).delete(list(sub_records))
try:
cursor.execute(*table.delete(where=red_sql))
@@ -983,7 +968,6 @@ class ModelSQL(ModelStorage):
Rule = pool.get('ir.rule')
transaction = Transaction()
cursor = transaction.cursor
- in_max = cursor.IN_MAX
# Get domain clauses
tables, expression = cls.search_domain(domain)
@@ -1034,6 +1018,7 @@ class ModelSQL(ModelStorage):
columns.append(Coalesce(
main_table.write_date,
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()
@@ -1053,11 +1038,50 @@ class ModelSQL(ModelStorage):
cursor.execute(*select)
rows = cursor.dictfetchmany(cursor.IN_MAX)
- cache = cursor.get_cache(transaction.context)
+ cache = cursor.get_cache()
if cls.__name__ not in cache:
cache[cls.__name__] = LRUDict(RECORD_CACHE_SIZE)
delete_records = transaction.delete_records.setdefault(cls.__name__,
set())
+
+ def filter_history(rows):
+ if not (cls._history and transaction.context.get('_datetime')):
+ return rows
+
+ def history_key(row):
+ return row['_datetime'], row['__id']
+
+ ids_history = {}
+ for row in rows:
+ key = history_key(row)
+ if row['id'] in ids_history:
+ if key < ids_history[row['id']]:
+ continue
+ ids_history[row['id']] = key
+
+ to_delete = set()
+ history = cls.__table_history__()
+ for sub_ids in grouped_slice([r['id'] for r in rows]):
+ 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
+ <= transaction.context['_datetime'])))
+ for deleted_id, delete_date in cursor.fetchall():
+ history_date, _ = ids_history[deleted_id]
+ if isinstance(history_date, basestring):
+ strptime = datetime.datetime.strptime
+ format_ = '%Y-%m-%d %H:%M:%S.%f'
+ history_date = strptime(history_date, format_)
+ if history_date <= delete_date:
+ to_delete.add(deleted_id)
+
+ return ifilter(lambda r: history_key(r) == ids_history[r['id']]
+ and r['id'] not in to_delete, rows)
+
+ rows = list(filter_history(rows))
keys = None
for data in islice(rows, 0, cache.size_limit):
if data['id'] in delete_records:
@@ -1065,7 +1089,7 @@ class ModelSQL(ModelStorage):
if keys is None:
keys = data.keys()
for k in keys[:]:
- if k in ('_timestamp', '_datetime'):
+ if k in ('_timestamp', '_datetime', '__id'):
keys.remove(k)
continue
field = cls._fields[k]
@@ -1086,34 +1110,7 @@ class ModelSQL(ModelStorage):
cursor.execute(*table.select(*columns,
where=expression, order_by=order_by,
limit=limit, offset=offset))
- rows = cursor.dictfetchall()
-
- if cls._history and transaction.context.get('_datetime'):
- ids = []
- ids_date = {}
- for data in rows:
- if data['id'] in ids_date:
- if data['_datetime'] <= ids_date[data['id']]:
- continue
- if data['id'] in ids:
- ids.remove(data['id'])
- ids.append(data['id'])
- ids_date[data['id']] = data['_datetime']
- to_delete = set()
- history = cls.__table_history__()
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
- 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
- <= transaction.context['_datetime'])))
- for deleted_id, delete_date in cursor.fetchall():
- if ids_date[deleted_id] < delete_date:
- to_delete.add(deleted_id)
- return cls.browse(filter(lambda x: x not in to_delete, ids))
+ rows = filter_history(cursor.dictfetchall())
return cls.browse([x['id'] for x in rows])
@@ -1184,8 +1181,7 @@ class ModelSQL(ModelStorage):
cls._update_tree(id_, field_name,
field.left, field.right)
else:
- with Transaction().set_user(0):
- cls._rebuild_tree(field_name, None, 0)
+ cls._rebuild_tree(field_name, None, 0)
@classmethod
def _rebuild_tree(cls, parent, parent_id, left):
@@ -1291,8 +1287,7 @@ class ModelSQL(ModelStorage):
sql_clause = '(id != ' + param + ' AND ' + sql_clause + ')'
in_max = cursor.IN_MAX / (len(columns) + 1)
- for i in range(0, len(ids), in_max):
- sub_ids = ids[i:i + in_max]
+ for sub_ids in grouped_slice(ids, in_max):
red_sql = reduce_ids(table.id, sub_ids)
cursor.execute('SELECT id,' + sql + ' '
@@ -1312,8 +1307,7 @@ class ModelSQL(ModelStorage):
match = _RE_CHECK.match(sql)
if match:
sql = match.group(1)
- for i in range(0, len(ids), cursor.IN_MAX):
- sub_ids = ids[i:i + cursor.IN_MAX]
+ for sub_ids in grouped_slice(ids):
red_sql = reduce_ids(table.id, sub_ids)
cursor.execute('SELECT id '
'FROM "' + cls._table + '" '
diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py
index 6698c98..232b038 100644
--- a/trytond/model/modelstorage.py
+++ b/trytond/model/modelstorage.py
@@ -14,16 +14,17 @@ from decimal import Decimal
from itertools import islice, ifilter, chain, izip
from functools import reduce
from operator import itemgetter
+from collections import defaultdict
from trytond.model import Model
from trytond.model import fields
-from trytond.tools import safe_eval, reduce_domain, memoize
+from trytond.tools import reduce_domain, memoize
from trytond.pyson import PYSONEncoder, PYSONDecoder, PYSON
from trytond.const import OPERATORS, RECORD_CACHE_SIZE, BROWSE_FIELD_TRESHOLD
from trytond.transaction import Transaction
from trytond.pool import Pool
-from trytond.cache import LRUDict
-from trytond.config import CONFIG
+from trytond.cache import LRUDict, freeze
+from trytond import backend
from trytond.rpc import RPC
from .modelview import ModelView
@@ -451,6 +452,9 @@ class ModelStorage(Model):
if not isinstance(value, ModelStorage):
break
field_name = fields_tree[i]
+ descriptor = None
+ if '.' in field_name:
+ field_name, descriptor = field_name.split('.')
eModel = pool.get(value.__name__)
field = eModel._fields[field_name]
if field.states and 'invisible' in field.states:
@@ -466,7 +470,10 @@ class ModelStorage(Model):
if invisible:
value = ''
break
- value = getattr(value, field_name)
+ if descriptor:
+ value = getattr(field, descriptor)().__get__(value, eModel)
+ else:
+ value = getattr(value, field_name)
if isinstance(value, (list, tuple)):
first = True
child_fields_names = [(x[:i + 1] == fields_tree[:i + 1] and
@@ -746,7 +753,7 @@ class ModelStorage(Model):
# Allow root user to update/delete
if Transaction().user == 0:
return True
- with Transaction().set_user(0):
+ with Transaction().set_context(_check_access=False):
models_data = ModelData.search([
('model', '=', cls.__name__),
('db_id', 'in', map(int, records)),
@@ -758,10 +765,7 @@ class ModelStorage(Model):
for model_data in models_data:
if not model_data.values:
continue
- xml_values = safe_eval(model_data.values, {
- 'Decimal': Decimal,
- 'datetime': datetime,
- })
+ xml_values = ModelData.load_values(model_data.values)
for key, val in values.iteritems():
if key in xml_values and val != xml_values[key]:
return False
@@ -839,7 +843,7 @@ class ModelStorage(Model):
def _validate(cls, records, field_names=None):
pool = Pool()
# Ensure that records are readable
- with Transaction().set_user(0, set_context=True):
+ with Transaction().set_context(_check_access=False):
records = cls.browse(records)
def call(name):
@@ -893,6 +897,7 @@ class ModelStorage(Model):
Relation = field.get_target()
else:
Relation = cls
+ domains = defaultdict(list)
if is_pyson(field.domain):
pyson_domain = PYSONEncoder().encode(field.domain)
for record in records:
@@ -902,12 +907,13 @@ class ModelStorage(Model):
env['time'] = time
env['context'] = Transaction().context
env['active_id'] = record.id
- domain = PYSONDecoder(env).decode(pyson_domain)
- validate_relation_domain(
- field, [record], Relation, domain)
+ domain = freeze(PYSONDecoder(env).decode(pyson_domain))
+ domains[domain].append(record)
else:
- validate_relation_domain(
- field, records, Relation, field.domain)
+ domains[freeze(field.domain)].extend(records)
+
+ for domain, sub_records in domains.iteritems():
+ validate_relation_domain(field, sub_records, Relation, domain)
def validate_relation_domain(field, records, Relation, domain):
if field._type in ('many2one', 'one2many', 'many2many', 'one2one'):
@@ -1003,7 +1009,7 @@ class ModelStorage(Model):
def raise_user_error(value):
error_args = cls._get_error_args(field_name)
error_args['digits'] = digits[1]
- error_args['value'] = value
+ error_args['value'] = repr(value)
cls.raise_user_error('digits_validation_record',
error_args=error_args)
if value is None:
@@ -1012,7 +1018,7 @@ class ModelStorage(Model):
if (value.quantize(Decimal(str(10.0 ** -digits[1])))
!= value):
raise_user_error(value)
- elif CONFIG.options['db_type'] != 'mysql':
+ elif backend.name() != 'mysql':
if not (round(value, digits[1]) == float(value)):
raise_user_error(value)
# validate digits
@@ -1138,7 +1144,7 @@ class ModelStorage(Model):
else:
self._ids = [id]
- self._cursor_cache = self._cursor.get_cache(self._context)
+ self._cursor_cache = self._cursor.get_cache()
if _local_cache is not None:
self._local_cache = _local_cache
@@ -1223,6 +1229,14 @@ class ModelStorage(Model):
datetime_field = self._fields[field.datetime_field]
ffields[field.datetime_field] = datetime_field
+ # add depends of field with context
+ for field in ffields.values():
+ if field.context:
+ for context_field_name in field.depends:
+ context_field = self._fields.get(context_field_name)
+ if context_field not in ffields:
+ ffields[context_field_name] = context_field
+
def filter_(id_):
return (name not in self._cache.get(id_, {})
and name not in self._local_cache.get(id_, {}))
@@ -1258,14 +1272,18 @@ class ModelStorage(Model):
except KeyError:
return value
ctx = {}
+ if field.context:
+ pyson_context = PYSONEncoder().encode(field.context)
+ ctx.update(PYSONDecoder(data).decode(pyson_context))
datetime_ = None
if getattr(field, 'datetime_field', None):
datetime_ = data.get(field.datetime_field)
ctx = {'_datetime': datetime_}
with Transaction().set_context(**ctx):
- local_cache = model2cache.setdefault((Model, datetime_),
+ key = (Model, freeze(ctx))
+ local_cache = model2cache.setdefault(key,
LRUDict(RECORD_CACHE_SIZE))
- ids = model2ids.setdefault((Model, datetime_), [])
+ ids = model2ids.setdefault(key, [])
if field._type in ('many2one', 'one2one', 'reference'):
ids.append(value)
return Model(value, _ids=ids, _local_cache=local_cache)
@@ -1301,13 +1319,14 @@ class ModelStorage(Model):
if data['id'] == self.id and fname == name:
value = fvalue
if (field._type not in ('many2one', 'one2one', 'one2many',
- 'many2many', 'reference')
+ 'many2many', 'reference', 'binary')
and not isinstance(field, fields.Function)):
continue
if data['id'] not in self._local_cache:
self._local_cache[data['id']] = {}
self._local_cache[data['id']][fname] = fvalue
if (field._type not in ('many2one', 'reference')
+ or field.context
or getattr(field, 'datetime_field', None)
or isinstance(field, fields.Function)):
del data[fname]
diff --git a/trytond/model/modelview.py b/trytond/model/modelview.py
index ae853e2..643bcba 100644
--- a/trytond/model/modelview.py
+++ b/trytond/model/modelview.py
@@ -253,7 +253,7 @@ class ModelView(Model):
- relate: a list of available relations
"""
Action = Pool().get('ir.action.keyword')
- key = (cls.__name__, repr(Transaction().context))
+ key = cls.__name__
result = cls._view_toolbar_get_cache.get(key)
if result:
return result
@@ -426,7 +426,9 @@ class ModelView(Model):
# convert attributes into pyson
encoder = PYSONEncoder()
for attr in ('states', 'domain', 'spell', 'colors'):
- if element.get(attr):
+ 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)))
@@ -476,17 +478,22 @@ class ModelView(Model):
@wraps(func)
def wrapper(cls, *args, **kwargs):
pool = Pool()
+ ModelAccess = pool.get('ir.model.access')
Button = pool.get('ir.model.button')
User = pool.get('res.user')
- if Transaction().user != 0:
+ if ((Transaction().user != 0)
+ and Transaction().context.get('_check_access')):
+ ModelAccess.check(cls.__name__, 'read')
+ ModelAccess.check(cls.__name__, 'write')
groups = set(User.get_groups())
button_groups = Button.get_groups(cls.__name__,
func.__name__)
if button_groups and not groups & button_groups:
raise UserError('Calling button %s on %s is not allowed!'
% (func.__name__, cls.__name__))
- return func(cls, *args, **kwargs)
+ with Transaction().set_context(_check_access=False):
+ return func(cls, *args, **kwargs)
return wrapper
@staticmethod
diff --git a/trytond/model/union.py b/trytond/model/union.py
new file mode 100644
index 0000000..b65f1ad
--- /dev/null
+++ b/trytond/model/union.py
@@ -0,0 +1,67 @@
+# This file is part of Tryton. The COPYRIGHT file at the toplevel of this
+# repository contains the full copyright notices and license terms.
+from sql import Union, Column, Literal, Cast
+
+from trytond.model import fields
+from trytond.pool import Pool
+
+
+class UnionMixin:
+ 'Mixin to combine models'
+
+ @staticmethod
+ def union_models():
+ return []
+
+ @classmethod
+ def union_shard(cls, column, model):
+ models = cls.union_models()
+ length = len(models)
+ i = models.index(model)
+ return ((column * length) + i)
+
+ @classmethod
+ def union_unshard(cls, record_id):
+ pool = Pool()
+ models = cls.union_models()
+ length = len(models)
+ record_id, i = divmod(record_id, length)
+ Model = pool.get(models[i])
+ return Model(record_id)
+
+ @classmethod
+ def union_column(cls, name, field, table, Model):
+ column = Literal(None)
+ union_field = Model._fields.get(name)
+ if union_field:
+ column = Column(table, union_field.name)
+ if (isinstance(field, fields.Many2One)
+ and field.model_name == cls.__name__):
+ target_model = union_field.model_name
+ if target_model in cls.union_models():
+ column = cls.union_shard(column, target_model)
+ else:
+ column = Literal(None)
+ return column
+
+ @classmethod
+ def union_columns(cls, model):
+ pool = Pool()
+ Model = pool.get(model)
+ table = Model.__table__()
+ columns = [cls.union_shard(table.id, model).as_('id')]
+ for name in sorted(cls._fields.keys()):
+ field = cls._fields[name]
+ if name == 'id' or hasattr(field, 'set'):
+ continue
+ column = cls.union_column(name, field, table, Model)
+ columns.append(Cast(column, field.sql_type().base).as_(name))
+ return table, columns
+
+ @classmethod
+ def table_query(cls):
+ queries = []
+ for model in cls.union_models():
+ table, columns = cls.union_columns(model)
+ queries.append(table.select(*columns))
+ return Union(*queries)
diff --git a/trytond/modules/__init__.py b/trytond/modules/__init__.py
index e7ee406..7c233b5 100644
--- a/trytond/modules/__init__.py
+++ b/trytond/modules/__init__.py
@@ -14,7 +14,7 @@ from sql import Table
from sql.functions import Now
import trytond.tools as tools
-from trytond.config import CONFIG
+from trytond.config import config
from trytond.transaction import Transaction
from trytond.cache import Cache
import trytond.convert as convert
@@ -135,11 +135,11 @@ class Node(Singleton):
def get_module_info(name):
"Return the content of the tryton.cfg"
- config = ConfigParser.ConfigParser()
+ module_config = ConfigParser.ConfigParser()
with tools.file_open(os.path.join(name, 'tryton.cfg')) as fp:
- config.readfp(fp)
+ module_config.readfp(fp)
directory = os.path.dirname(fp.name)
- info = dict(config.items('tryton'))
+ info = dict(module_config.items('tryton'))
info['directory'] = directory
for key in ('depends', 'extras_depend', 'xml'):
if key in info:
@@ -152,11 +152,7 @@ def create_graph(module_list):
packages = []
for module in module_list:
- try:
- info = get_module_info(module)
- except IOError:
- if module != 'all':
- raise Exception('Module %s not found' % module)
+ info = get_module_info(module)
packages.append((module, info.get('depends', []),
info.get('extras_depend', []), info))
@@ -193,18 +189,17 @@ def create_graph(module_list):
return graph, packages, later
-def is_module_to_install(module):
- for kind in ('init', 'update'):
- if 'all' in CONFIG[kind] and module != 'tests':
- return True
- elif module in CONFIG[kind]:
- return True
+def is_module_to_install(module, update):
+ if module in update:
+ return True
return False
-def load_module_graph(graph, pool, lang=None):
+def load_module_graph(graph, pool, update=None, lang=None):
if lang is None:
- lang = [CONFIG['language']]
+ lang = [config.get('database', 'language')]
+ if update is None:
+ update = []
modules_todo = []
models_to_update_history = set()
logger = logging.getLogger('modules')
@@ -222,7 +217,7 @@ def load_module_graph(graph, pool, lang=None):
logger.info(module)
classes = pool.setup(module)
package_state = module2state.get(module, 'uninstalled')
- if (is_module_to_install(module)
+ if (is_module_to_install(module, update)
or package_state in ('to install', 'to upgrade')):
if package_state not in ('to install', 'to upgrade'):
if package_state == 'installed':
@@ -362,7 +357,7 @@ def register_classes():
MODULES.append(module)
-def load_modules(database_name, pool, update=False, lang=None):
+def load_modules(database_name, pool, update=None, lang=None):
res = True
def _load_modules():
@@ -372,29 +367,20 @@ def load_modules(database_name, pool, update=False, lang=None):
# Migration from 2.2: workflow module removed
cursor.execute(*ir_module.delete(
where=(ir_module.name == 'workflow')))
- if 'all' in CONFIG['init']:
- cursor.execute(*ir_module.select(ir_module.name,
- where=(ir_module.name != 'tests')))
- else:
- cursor.execute(*ir_module.select(ir_module.name,
- where=ir_module.state.in_(('installed', 'to install',
- 'to upgrade', 'to remove'))))
+ cursor.execute(*ir_module.select(ir_module.name,
+ where=ir_module.state.in_(('installed', 'to install',
+ 'to upgrade', 'to remove'))))
else:
cursor.execute(*ir_module.select(ir_module.name,
where=ir_module.state.in_(('installed', 'to upgrade',
'to remove'))))
module_list = [name for (name,) in cursor.fetchall()]
if update:
- for module in CONFIG['init'].keys():
- if CONFIG['init'][module]:
- module_list.append(module)
- for module in CONFIG['update'].keys():
- if CONFIG['update'][module]:
- module_list.append(module)
+ module_list += update
graph = create_graph(module_list)[0]
try:
- load_module_graph(graph, pool, lang)
+ load_module_graph(graph, pool, update, lang)
except Exception:
cursor.rollback()
raise
@@ -423,6 +409,7 @@ def load_modules(database_name, pool, update=False, lang=None):
Module = pool.get('ir.module.module')
Module.update_list()
cursor.commit()
+ Cache.resets(database_name)
if not Transaction().cursor:
with Transaction().start(database_name, 0):
@@ -433,5 +420,4 @@ def load_modules(database_name, pool, update=False, lang=None):
Transaction().reset_context():
_load_modules()
- Cache.resets(database_name)
return res
diff --git a/trytond/monitor.py b/trytond/monitor.py
index b067ca0..128a0bf 100644
--- a/trytond/monitor.py
+++ b/trytond/monitor.py
@@ -32,14 +32,17 @@ def _modified(path):
return False
-def monitor():
+def monitor(files):
'''
- Monitor module files for change
+ Monitor files and module files for change
:return: True if at least one file has changed
'''
global _MODULES
modified = False
+ for file_ in files:
+ if _modified(file_):
+ modified = True
directories = set()
for module in sys.modules.keys():
if not module.startswith('trytond.'):
diff --git a/trytond/pool.py b/trytond/pool.py
index f57f3dc..65c6f49 100644
--- a/trytond/pool.py
+++ b/trytond/pool.py
@@ -128,7 +128,7 @@ class Pool(object):
'''
return self._locks[self.database_name]
- def init(self, update=False, lang=None):
+ def init(self, update=None, lang=None):
'''
Init pool
Set update to proceed to update
diff --git a/trytond/protocols/dispatcher.py b/trytond/protocols/dispatcher.py
index 2d1915d..2ba1d10 100644
--- a/trytond/protocols/dispatcher.py
+++ b/trytond/protocols/dispatcher.py
@@ -1,18 +1,17 @@
# -*- 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.
-import traceback
import logging
import time
import sys
import pydoc
-from sql import Table, Flavor
+from sql import Table
from trytond.pool import Pool
from trytond import security
from trytond import backend
-from trytond.config import CONFIG
+from trytond.config import config
from trytond.version import VERSION
from trytond.transaction import Transaction
from trytond.cache import Cache
@@ -20,10 +19,11 @@ from trytond.exceptions import UserError, UserWarning, NotLogged, \
ConcurrencyException
from trytond.rpc import RPC
+logger = logging.getLogger(__name__)
ir_configuration = Table('ir_configuration')
ir_lang = Table('ir_lang')
-ir_module = Table('ir_module')
+ir_module = Table('ir_module_module')
res_user = Table('res_user')
@@ -40,16 +40,15 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
except Exception:
return False
res = security.login(database_name, user, session)
- Cache.clean(database_name)
- logger = logging.getLogger('dispatcher')
+ with Transaction().start(database_name, 0):
+ 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))
- Cache.resets(database_name)
return res or False
elif method == 'logout':
name = security.logout(database_name, user, session)
- logger = logging.getLogger('dispatcher')
logger.info(('logout \'%s\' from %s:%d '
'using %s on database \'%s\'')
% (name, host, port, protocol, database_name))
@@ -64,6 +63,7 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
('de_DE', 'Deutsch'),
('en_US', 'English'),
('es_AR', 'Español (Argentina)'),
+ ('es_EC', 'Español (Ecuador)'),
('es_ES', 'Español (España)'),
('es_CO', 'Español (Colombia)'),
('fr_FR', 'Français'),
@@ -81,7 +81,7 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
except Exception:
return False
elif method == 'list':
- if CONFIG['prevent_dblist']:
+ if not config.get('database', 'list'):
raise Exception('AccessDenied')
with Transaction().start(None, 0, close=True) as transaction:
return transaction.database.list(transaction.cursor)
@@ -121,7 +121,7 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
obj = pool.get(object_name, type=object_type)
return pydoc.getdoc(getattr(obj, method))
- for count in range(int(CONFIG['retry']), -1, -1):
+ for count in range(config.getint('database', 'retry'), -1, -1):
try:
user = security.check(database_name, user, session)
except DatabaseOperationalError:
@@ -130,7 +130,6 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
raise
break
- Cache.clean(database_name)
database_list = Pool.database_list()
pool = Pool(database_name)
if not database_name in database_list:
@@ -147,9 +146,13 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
raise UserError('Calling method %s on %s %s is not allowed!'
% (method, object_type, object_name))
- for count in range(int(CONFIG['retry']), -1, -1):
+ exception_message = ('Exception calling %s.%s.%s from %s@%s:%d/%s' %
+ (object_type, object_name, method, user, host, port, database_name))
+
+ for count in range(config.getint('database', 'retry'), -1, -1):
with Transaction().start(database_name, user,
readonly=rpc.readonly) as transaction:
+ Cache.clean(database_name)
try:
c_args, c_kwargs, transaction.context, transaction.timestamp \
= rpc.convert(obj, *args, **kwargs)
@@ -166,23 +169,20 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
for i in inst]
if not rpc.readonly:
transaction.cursor.commit()
- except DatabaseOperationalError, exception:
+ except DatabaseOperationalError:
transaction.cursor.rollback()
if count and not rpc.readonly:
continue
raise
- except Exception, exception:
- if CONFIG['verbose'] and not isinstance(exception, (
- NotLogged, ConcurrencyException, UserError,
- UserWarning)):
- tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
- logger = logging.getLogger('dispatcher')
- logger.error('Exception calling method %s on '
- '%s %s from %s@%s:%d/%s:\n'
- % (method, object_type, object_name, user, host, port,
- database_name) + tb_s)
+ except (NotLogged, ConcurrencyException, UserError, UserWarning):
+ logger.debug(exception_message, exc_info=sys.exc_info())
transaction.cursor.rollback()
raise
+ except Exception:
+ logger.error(exception_message, exc_info=sys.exc_info())
+ transaction.cursor.rollback()
+ raise
+ Cache.resets(database_name)
with Transaction().start(database_name, 0) as transaction:
pool = Pool(database_name)
Session = pool.get('ir.session')
@@ -193,7 +193,6 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
transaction.cursor.rollback()
else:
transaction.cursor.commit()
- Cache.resets(database_name)
return result
@@ -210,7 +209,6 @@ def create(database_name, password, lang, admin_password):
Database = backend.get('Database')
security.check_super(password)
res = False
- logger = logging.getLogger('database')
try:
with Transaction().start(None, 0, close=True, autocommit=True) \
@@ -225,7 +223,7 @@ def create(database_name, password, lang, admin_password):
transaction.cursor.commit()
pool = Pool(database_name)
- pool.init(update=True, lang=[lang])
+ pool.init(update=['res', 'ir'], lang=[lang])
with Transaction().start(database_name, 0) as transaction:
User = pool.get('res.user')
Lang = pool.get('ir.lang')
@@ -246,9 +244,8 @@ def create(database_name, password, lang, admin_password):
transaction.cursor.commit()
res = True
except Exception:
- logger.error('CREATE DB: %s failed' % (database_name,))
- tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
- logger.error('Exception in call: \n' + tb_s)
+ logger.error('CREATE DB: %s failed' % database_name,
+ exc_info=sys.exc_info())
raise
else:
logger.info('CREATE DB: %s' % (database_name,))
@@ -261,7 +258,6 @@ def drop(database_name, password):
Database(database_name).close()
# Sleep to let connections close
time.sleep(1)
- logger = logging.getLogger('database')
with Transaction().start(None, 0, close=True, autocommit=True) \
as transaction:
@@ -270,9 +266,8 @@ def drop(database_name, password):
Database.drop(cursor, database_name)
cursor.commit()
except Exception:
- logger.error('DROP DB: %s failed' % (database_name,))
- tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
- logger.error('Exception in call: \n' + tb_s)
+ logger.error('DROP DB: %s failed' % database_name,
+ exc_info=sys.exc_info())
raise
else:
logger.info('DROP DB: %s' % (database_name))
@@ -286,7 +281,6 @@ def dump(database_name, password):
Database(database_name).close()
# Sleep to let connections close
time.sleep(1)
- logger = logging.getLogger('database')
data = Database.dump(database_name)
logger.info('DUMP DB: %s' % (database_name))
@@ -295,7 +289,6 @@ def dump(database_name, password):
def restore(database_name, password, data, update=False):
Database = backend.get('Database')
- logger = logging.getLogger('database')
security.check_super(password)
try:
database = Database().connect()
@@ -307,14 +300,14 @@ def restore(database_name, password, data, update=False):
Database.restore(database_name, data)
logger.info('RESTORE DB: %s' % (database_name))
if update:
- cursor = Database(database_name).connect().cursor()
- cursor.execute(*ir_lang.select(ir_lang.code,
- where=ir_lang.translatable))
- lang = [x[0] for x in cursor.fetchall()]
- cursor.execute(*ir_module.update([ir_module.state], ['to upgrade'],
- where=(ir_module.state == 'installed')))
- cursor.commit()
- cursor.close()
+ with Transaction().start(database_name, 0) as transaction:
+ cursor = transaction.cursor
+ cursor.execute(*ir_lang.select(ir_lang.code,
+ where=ir_lang.translatable))
+ lang = [x[0] for x in cursor.fetchall()]
+ cursor.execute(*ir_module.select(ir_module.name,
+ where=(ir_module.state == 'installed')))
+ update = [x[0] for x in cursor.fetchall()]
Pool(database_name).init(update=update, lang=lang)
logger.info('Update/Init succeed!')
return True
diff --git a/trytond/protocols/jsonrpc.py b/trytond/protocols/jsonrpc.py
index 27ce605..088d7da 100644
--- a/trytond/protocols/jsonrpc.py
+++ b/trytond/protocols/jsonrpc.py
@@ -2,7 +2,7 @@
#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
+from trytond.config import config
from trytond.protocols.common import daemon, RegisterHandlerMixin
from trytond.exceptions import UserError, UserWarning, NotLogged, \
ConcurrencyException
@@ -33,63 +33,88 @@ except ImportError:
from StringIO import StringIO
-def object_hook(dct):
- if '__class__' in dct:
- if dct['__class__'] == 'datetime':
- return datetime.datetime(dct['year'], dct['month'], dct['day'],
- dct['hour'], dct['minute'], dct['second'], dct['microsecond'])
- elif dct['__class__'] == 'date':
- return datetime.date(dct['year'], dct['month'], dct['day'])
- elif dct['__class__'] == 'time':
- return datetime.time(dct['hour'], dct['minute'], dct['second'],
- dct['microsecond'])
- elif dct['__class__'] == 'buffer':
- return buffer(base64.decodestring(dct['base64']))
- elif dct['__class__'] == 'Decimal':
- return Decimal(dct['decimal'])
- return dct
+class JSONDecoder(object):
+
+ decoders = {}
+
+ @classmethod
+ def register(cls, klass, decoder):
+ assert klass not in cls.decoders
+ cls.decoders[klass] = decoder
+
+ def __call__(self, dct):
+ if dct.get('__class__') in self.decoders:
+ return self.decoders[dct['__class__']](dct)
+ return dct
+
+JSONDecoder.register('datetime',
+ lambda dct: datetime.datetime(dct['year'], dct['month'], dct['day'],
+ dct['hour'], dct['minute'], dct['second'], dct['microsecond']))
+JSONDecoder.register('date',
+ lambda dct: datetime.date(dct['year'], dct['month'], dct['day']))
+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('Decimal', lambda dct: Decimal(dct['decimal']))
class JSONEncoder(json.JSONEncoder):
+ serializers = {}
+
def __init__(self, *args, **kwargs):
super(JSONEncoder, self).__init__(*args, **kwargs)
# Force to use our custom decimal with simplejson
self.use_decimal = False
+ @classmethod
+ def register(cls, klass, encoder):
+ assert klass not in cls.serializers
+ cls.serializers[klass] = encoder
+
def default(self, obj):
- if isinstance(obj, datetime.date):
- if isinstance(obj, datetime.datetime):
- return {'__class__': 'datetime',
- 'year': obj.year,
- 'month': obj.month,
- 'day': obj.day,
- 'hour': obj.hour,
- 'minute': obj.minute,
- 'second': obj.second,
- 'microsecond': obj.microsecond,
- }
- return {'__class__': 'date',
- 'year': obj.year,
- 'month': obj.month,
- 'day': obj.day,
- }
- elif isinstance(obj, datetime.time):
- return {'__class__': 'time',
- 'hour': obj.hour,
- 'minute': obj.minute,
- 'second': obj.second,
- 'microsecond': obj.microsecond,
- }
- elif isinstance(obj, buffer):
- return {'__class__': 'buffer',
- 'base64': base64.encodestring(obj),
- }
- elif isinstance(obj, Decimal):
- return {'__class__': 'Decimal',
- 'decimal': str(obj),
- }
- return super(JSONEncoder, self).default(obj)
+ marshaller = self.serializers.get(type(obj),
+ super(JSONEncoder, self).default)
+ return marshaller(obj)
+
+JSONEncoder.register(datetime.datetime,
+ lambda o: {
+ '__class__': 'datetime',
+ 'year': o.year,
+ 'month': o.month,
+ 'day': o.day,
+ 'hour': o.hour,
+ 'minute': o.minute,
+ 'second': o.second,
+ 'microsecond': o.microsecond,
+ })
+JSONEncoder.register(datetime.date,
+ lambda o: {
+ '__class__': 'date',
+ 'year': o.year,
+ 'month': o.month,
+ 'day': o.day,
+ })
+JSONEncoder.register(datetime.time,
+ lambda o: {
+ '__class__': 'time',
+ 'hour': o.hour,
+ 'minute': o.minute,
+ 'second': o.second,
+ 'microsecond': o.microsecond,
+ })
+JSONEncoder.register(buffer,
+ lambda o: {
+ '__class__': 'buffer',
+ 'base64': base64.encodestring(o),
+ })
+JSONEncoder.register(Decimal,
+ lambda o: {
+ '__class__': 'Decimal',
+ 'decimal': str(o),
+ })
class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
@@ -111,7 +136,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
existing method through subclassing is the prefered means
of changing method dispatch behavior.
"""
- rawreq = json.loads(data, object_hook=object_hook)
+ rawreq = json.loads(data, object_hook=JSONDecoder())
req_id = rawreq.get('id', 0)
method = rawreq['method']
@@ -132,10 +157,6 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
for path in sys.path:
tb_s = tb_s.replace(path, '')
- if CONFIG['debug_mode']:
- import pdb
- traceb = sys.exc_info()[2]
- pdb.post_mortem(traceb)
# report exception back to server
response['error'] = (str(sys.exc_value), tb_s)
@@ -204,7 +225,7 @@ class SimpleJSONRPCRequestHandler(RegisterHandlerMixin,
path = posixpath.normpath(urllib.unquote(path))
words = path.split('/')
words = filter(None, words)
- path = CONFIG['jsondata_path']
+ path = config.get('jsonrpc', 'data')
for word in words:
drive, word = os.path.splitdrive(word)
head, word = os.path.split(word)
@@ -222,7 +243,8 @@ class SimpleJSONRPCRequestHandler(RegisterHandlerMixin,
def send_tryton_url(self, path):
self.send_response(300)
- hostname = CONFIG['hostname'] or unicode(socket.getfqdn(), 'utf8')
+ hostname = (config.get('jsonrpc', 'hostname')
+ or unicode(socket.getfqdn(), 'utf8'))
hostname = '.'.join(encodings.idna.ToASCII(part) for part in
hostname.split('.'))
values = {
diff --git a/trytond/protocols/sslsocket.py b/trytond/protocols/sslsocket.py
index c3dcbdb..9c18a26 100644
--- a/trytond/protocols/sslsocket.py
+++ b/trytond/protocols/sslsocket.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.config import CONFIG
+from trytond.config import config
def SSLSocket(socket):
@@ -8,6 +8,6 @@ def SSLSocket(socket):
import ssl
return ssl.wrap_socket(socket,
server_side=True,
- certfile=CONFIG['certificate'],
- keyfile=CONFIG['privatekey'],
+ certfile=config.get('ssl', 'certificate'),
+ keyfile=config.get('ssl', 'privatekey'),
ssl_version=ssl.PROTOCOL_SSLv23)
diff --git a/trytond/protocols/webdav.py b/trytond/protocols/webdav.py
index 98b12ff..f820280 100644
--- a/trytond/protocols/webdav.py
+++ b/trytond/protocols/webdav.py
@@ -7,7 +7,6 @@ import urlparse
import time
import urllib
import sys
-import traceback
import logging
from threading import local
import xml.dom.minidom
@@ -21,7 +20,6 @@ from pywebdav.lib.davcmd import copyone, copytree, moveone, movetree, \
delone, deltree
from trytond.protocols.sslsocket import SSLSocket
from trytond.protocols.common import daemon
-from trytond.config import CONFIG
from trytond.security import login
from trytond.version import PACKAGE, VERSION, WEBSITE
from trytond.tools.misc import LocalDict
@@ -36,6 +34,8 @@ domimpl = xml.dom.minidom.getDOMImplementation()
DAV_VERSION_1['version'] += ',access-control'
DAV_VERSION_2['version'] += ',access-control'
+logger = logging.getLogger(__name__)
+
# Local int for multi-thread
class LocalInt(local):
@@ -131,12 +131,12 @@ class TrytonDAVInterface(iface.dav_interface):
self.verbose = False
def _log_exception(self, exception):
- if CONFIG['verbose'] and not isinstance(exception, (
- NotLogged, ConcurrencyException, UserError, UserWarning,
- DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden)):
- tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
- logger = logging.getLogger('webdav')
- logger.error('Exception:\n' + tb_s)
+ if isinstance(exception, (NotLogged, ConcurrencyException, UserError,
+ UserWarning, DAV_Error, DAV_NotFound, DAV_Secret,
+ DAV_Forbidden)):
+ logger.debug('Exception', exc_info=sys.exc_info())
+ else:
+ logger.error('Exception', exc_info=sys.exc_info())
@staticmethod
def get_dburi(uri):
@@ -581,7 +581,9 @@ class WebDAVAuthRequestHandler(WebDAVServer.DAVRequestHandler):
if not user:
return None
- Transaction().start(dbname, user)
+ Transaction().start(dbname, user, {
+ '_check_access': True,
+ })
Cache.clean(dbname)
return user
diff --git a/trytond/protocols/xmlrpc.py b/trytond/protocols/xmlrpc.py
index a5bbe2c..7b48c53 100644
--- a/trytond/protocols/xmlrpc.py
+++ b/trytond/protocols/xmlrpc.py
@@ -2,7 +2,6 @@
#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
from trytond.protocols.common import daemon, RegisterHandlerMixin
from trytond.exceptions import UserError, UserWarning, NotLogged, \
ConcurrencyException
@@ -10,16 +9,18 @@ from trytond import security
import SimpleXMLRPCServer
import SocketServer
import xmlrpclib
-import traceback
import socket
import sys
import base64
import datetime
from types import DictType
+import logging
# convert decimal to float before marshalling:
from decimal import Decimal
+logger = logging.getLogger(__name__)
+
def dump_decimal(self, value, write):
value = {'__class__': 'Decimal',
@@ -74,6 +75,28 @@ def dump_struct(self, value, write, escape=xmlrpclib.escape):
xmlrpclib.Marshaller.dispatch[DictType] = dump_struct
+class XMLRPCDecoder(object):
+
+ decoders = {}
+
+ @classmethod
+ def register(cls, klass, decoder):
+ assert klass not in cls.decoders
+ cls.decoders[klass] = decoder
+
+ def __call__(self, dct):
+ if dct.get('__class__') in self.decoders:
+ return self.decoders[dct['__class__']](dct)
+ return dct
+
+XMLRPCDecoder.register('date',
+ lambda dct: datetime.date(dct['year'], dct['month'], dct['day']))
+XMLRPCDecoder.register('time',
+ lambda dct: datetime.time(dct['hour'], dct['minute'], dct['second'],
+ dct['microsecond']))
+XMLRPCDecoder.register('Decimal', lambda dct: Decimal(dct['decimal']))
+
+
def end_struct(self, data):
mark = self._marks.pop()
# map structs to Python dictionaries
@@ -81,14 +104,7 @@ def end_struct(self, data):
items = self._stack[mark:]
for i in range(0, len(items), 2):
dct[xmlrpclib._stringify(items[i])] = items[i + 1]
- if '__class__' in dct:
- if dct['__class__'] == 'date':
- dct = datetime.date(dct['year'], dct['month'], dct['day'])
- elif dct['__class__'] == 'time':
- dct = datetime.time(dct['hour'], dct['minute'], dct['second'],
- dct['microsecond'])
- elif dct['__class__'] == 'Decimal':
- dct = Decimal(dct['decimal'])
+ dct = XMLRPCDecoder()(dct)
self._stack[mark:] = [dct]
self._value = 0
@@ -103,6 +119,14 @@ def _end_dateTime(self, data):
xmlrpclib.Unmarshaller.dispatch["dateTime.iso8601"] = _end_dateTime
+def _end_base64(self, data):
+ value = xmlrpclib.Binary()
+ value.decode(data)
+ self.append(buffer(value.data))
+ self._value = 0
+xmlrpclib.Unmarshaller.dispatch['base64'] = _end_base64
+
+
class GenericXMLRPCRequestHandler:
def _dispatch(self, method, params):
@@ -110,6 +134,7 @@ class GenericXMLRPCRequestHandler:
database_name = self.path[1:]
user = self.tryton['user']
session = self.tryton['session']
+ exception_message = 'Exception calling %s%s' % (method, params)
try:
try:
method_list = method.split('.')
@@ -127,21 +152,15 @@ class GenericXMLRPCRequestHandler:
return dispatch(host, port, 'XML-RPC', database_name, user,
session, object_type, object_name, method, *params)
except (NotLogged, ConcurrencyException), exception:
- raise xmlrpclib.Fault(exception.code,
- '\n'.join(exception.args))
+ logger.debug(exception_message, exc_info=sys.exc_info())
+ raise xmlrpclib.Fault(exception.code, str(exception))
except (UserError, UserWarning), exception:
+ logger.debug(exception_message, exc_info=sys.exc_info())
error, description = exception.args
- raise xmlrpclib.Fault(exception.code,
- '\n'.join((error,) + description))
- except Exception:
- tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
- for path in sys.path:
- tb_s = tb_s.replace(path, '')
- if CONFIG['debug_mode']:
- import pdb
- traceb = sys.exc_info()[2]
- pdb.post_mortem(traceb)
- raise xmlrpclib.Fault(255, str(sys.exc_value) + '\n' + tb_s)
+ raise xmlrpclib.Fault(exception.code, str(exception))
+ except Exception, exception:
+ logger.error(exception_message, exc_info=sys.exc_info())
+ raise xmlrpclib.Fault(255, str(exception))
finally:
security.logout(database_name, user, session)
diff --git a/trytond/pyson.py b/trytond/pyson.py
index 6771f20..bab351c 100644
--- a/trytond/pyson.py
+++ b/trytond/pyson.py
@@ -28,25 +28,25 @@ class PYSON(object):
return Not(self)
def __and__(self, other):
+ if (isinstance(other, PYSON)
+ and other.types() != set([bool])):
+ other = Bool(other)
if (isinstance(self, And)
and not isinstance(self, Or)):
self._statements.append(other)
return self
- if (isinstance(other, PYSON)
- and other.types() != set([bool])):
- other = Bool(other)
if self.types() != set([bool]):
return And(Bool(self), other)
else:
return And(self, other)
def __or__(self, other):
- if isinstance(self, Or):
- self._statements.append(other)
- return self
if (isinstance(other, PYSON)
and other.types() != set([bool])):
other = Bool(other)
+ if isinstance(self, Or):
+ self._statements.append(other)
+ return self
if self.types() != set([bool]):
return Or(Bool(self), other)
else:
diff --git a/trytond/report/report.py b/trytond/report/report.py
index 42a6d5d..b0a3ef1 100644
--- a/trytond/report/report.py
+++ b/trytond/report/report.py
@@ -21,11 +21,12 @@ except ImportError:
Manifest, MANIFEST = None, None
from genshi.filters import Translator
import lxml.etree
-from trytond.config import CONFIG
+from trytond.config import config
from trytond.pool import Pool, PoolBase
from trytond.transaction import Transaction
from trytond.url import URLMixin
from trytond.rpc import RPC
+from trytond.exceptions import UserError
MIMETYPES = {
'odt': 'application/vnd.oasis.opendocument.text',
@@ -102,6 +103,20 @@ class Report(URLMixin, PoolBase):
}
@classmethod
+ def check_access(cls):
+ pool = Pool()
+ ActionReport = pool.get('ir.action.report')
+ User = pool.get('res.user')
+
+ if Transaction().user == 0:
+ return
+
+ groups = set(User.get_groups())
+ report_groups = ActionReport.get_groups(cls.__name__)
+ if report_groups and not groups & report_groups:
+ raise UserError('Calling report %s is not allowed!' % cls.__name__)
+
+ @classmethod
def execute(cls, ids, data):
'''
Execute the report on record ids.
@@ -120,6 +135,7 @@ class Report(URLMixin, PoolBase):
])
if not action_reports:
raise Exception('Error', 'Report (%s) not find!' % cls.__name__)
+ cls.check_access()
action_report = action_reports[0]
records = None
model = action_report.model or data.get('model')
@@ -295,7 +311,7 @@ class Report(URLMixin, PoolBase):
oext = FORMAT2EXT.get(output_format, output_format)
with os.fdopen(fd, 'wb+') as fp:
fp.write(data)
- cmd = ['unoconv', '--connection=%s' % CONFIG['unoconv'],
+ cmd = ['unoconv', '--connection=%s' % config.get('report', 'unoconv'),
'-f', oext, '--stdout', path]
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
diff --git a/trytond/res/group.py b/trytond/res/group.py
index 800b13d..e24f631 100644
--- a/trytond/res/group.py
+++ b/trytond/res/group.py
@@ -3,8 +3,8 @@
"Group"
from itertools import chain
from ..model import ModelView, ModelSQL, fields
-from ..transaction import Transaction
from ..pool import Pool, PoolMeta
+from ..tools import grouped_slice
__all__ = [
'Group', 'Group2',
@@ -19,8 +19,7 @@ class MenuMany2Many(fields.Many2Many):
values=values)
menu_ids = list(set(chain(*res.values())))
test_ids = []
- for i in range(0, len(menu_ids), Transaction().cursor.IN_MAX):
- sub_ids = menu_ids[i:i + Transaction().cursor.IN_MAX]
+ for sub_ids in grouped_slice(menu_ids):
test_ids.append(map(int, Menu.search([
('id', 'in', sub_ids),
])))
diff --git a/trytond/res/ir.xml b/trytond/res/ir.xml
index 73c8d96..041e090 100644
--- a/trytond/res/ir.xml
+++ b/trytond/res/ir.xml
@@ -265,14 +265,6 @@ 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="True"/>
- <field name="perm_delete" eval="True"/>
- </record>
<record model="ir.model.access" id="access_ir_cron">
<field name="model" search="[('model', '=', 'ir.cron')]"/>
<field name="perm_read" eval="True"/>
@@ -746,6 +738,16 @@ this repository contains the full copyright notices and license terms. -->
<field name="group" ref="group_admin"/>
</record>
+ <record model="ir.model.button" id="model_data_sync_button">
+ <field name="name">sync</field>
+ <field name="model" search="[('model', '=', 'ir.model.data')]"/>
+ </record>
+ <record model="ir.model.button-res.group"
+ id="model_data_sync_button_group_admin">
+ <field name="button" ref="model_data_sync_button"/>
+ <field name="group" ref="group_admin"/>
+ </record>
+
<record model="ir.ui.view" id="sequence_type_view_form">
<field name="model">ir.sequence.type</field>
<field name="inherit" ref="ir.sequence_type_view_form"/>
diff --git a/trytond/res/locale/ca_ES.po b/trytond/res/locale/ca_ES.po
index 71decc4..25cdfb9 100644
--- a/trytond/res/locale/ca_ES.po
+++ b/trytond/res/locale/ca_ES.po
@@ -350,7 +350,7 @@ msgstr "Direcció idioma"
msgctxt "field:res.user,login:"
msgid "Login"
-msgstr "Nom d'usuari"
+msgstr "Nom usuari"
msgctxt "field:res.user,menu:"
msgid "Menu Action"
@@ -486,7 +486,7 @@ msgstr "ID"
msgctxt "field:res.user.login.attempt,login:"
msgid "Login"
-msgstr "Nom d'usuari"
+msgstr "Nom usuari"
msgctxt "field:res.user.login.attempt,rec_name:"
msgid "Name"
diff --git a/trytond/res/locale/de_DE.po b/trytond/res/locale/de_DE.po
index 7b1e739..40fed63 100644
--- a/trytond/res/locale/de_DE.po
+++ b/trytond/res/locale/de_DE.po
@@ -739,4 +739,4 @@ msgstr "Hinzufügen"
msgctxt "wizard_button:res.user.config,user,end:"
msgid "End"
-msgstr "Ende"
+msgstr "Fertig"
diff --git a/trytond/res/locale/es_AR.po b/trytond/res/locale/es_AR.po
index f889836..71897ae 100644
--- a/trytond/res/locale/es_AR.po
+++ b/trytond/res/locale/es_AR.po
@@ -102,7 +102,7 @@ msgstr "Usuario creación"
msgctxt "field:ir.model.field-res.group,field:"
msgid "Model Field"
-msgstr "Model de Campo"
+msgstr "Campo del modelo"
msgctxt "field:ir.model.field-res.group,group:"
msgid "Group"
@@ -562,11 +562,11 @@ msgstr "Acción - Grupo"
msgctxt "model:ir.cron,name:cron_trigger_time"
msgid "Run On Time Triggers"
-msgstr "Ejecutar Disparadores Al Tiempo"
+msgstr "Ejecutar disparadores por tiempo"
msgctxt "model:ir.model.button-res.group,name:"
msgid "Model Button - Group"
-msgstr "Modelo de Botón - Grupo"
+msgstr "Modelo Botón - Grupo"
msgctxt "model:ir.model.field-res.group,name:"
msgid "Model Field Group Rel"
@@ -574,11 +574,11 @@ msgstr "Relación entre grupo y campo del modelo"
msgctxt "model:ir.rule.group-res.group,name:"
msgid "Rule Group - Group"
-msgstr "Regla de grupo - grupo"
+msgstr "Regla de grupo - Grupo"
msgctxt "model:ir.rule.group-res.user,name:"
msgid "Rule Group - User"
-msgstr "Regla de grupo - usuario"
+msgstr "Regla de grupo - Usuario"
msgctxt "model:ir.sequence.type-res.group,name:"
msgid "Sequence Type - Group"
@@ -618,7 +618,7 @@ msgstr "Administrador"
msgctxt "model:res.user,name:user_trigger"
msgid "Cron Trigger"
-msgstr "Disparador del Programador de tareas"
+msgstr "Programador de disparadores"
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 "Inicio de Configuración de Usuario"
+msgstr "Configuración inicial de Usuario"
msgctxt "model:res.user.login.attempt,name:"
msgid "Login Attempt"
diff --git a/trytond/res/locale/es_CO.po b/trytond/res/locale/es_CO.po
index b351bbe..4f741c0 100644
--- a/trytond/res/locale/es_CO.po
+++ b/trytond/res/locale/es_CO.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 "Contraseña incorrecta!"
msgctxt "error:res.user:"
msgid "You can not have two users with the same login!"
@@ -394,7 +394,7 @@ msgstr "Barra de Estado"
msgctxt "field:res.user,warnings:"
msgid "Warnings"
-msgstr "Avisos"
+msgstr "Advertencias"
msgctxt "field:res.user,write_date:"
msgid "Write Date"
@@ -478,7 +478,7 @@ 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"
@@ -618,11 +618,11 @@ 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"
-msgstr "res.user-ir.action"
+msgstr "Usuario - Acción"
msgctxt "model:res.user-res.group,name:"
msgid "User - Group"
@@ -638,7 +638,7 @@ msgstr "Intento de Login"
msgctxt "model:res.user.warning,name:"
msgid "User Warning"
-msgstr "Aviso a Usuario"
+msgstr "Advertencia a Usuario"
msgctxt "view:ir.module.module:"
msgid "Cancel Installation"
diff --git a/trytond/res/locale/es_CO.po b/trytond/res/locale/es_EC.po
similarity index 95%
copy from trytond/res/locale/es_CO.po
copy to trytond/res/locale/es_EC.po
index b351bbe..bdae57a 100644
--- a/trytond/res/locale/es_CO.po
+++ b/trytond/res/locale/es_EC.po
@@ -4,7 +4,7 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "error:res.group:"
msgid "The name of the group must be unique!"
-msgstr "El nombre del grupo debe ser único!"
+msgstr "¡El nombre del grupo debe ser único!"
msgctxt "error:res.user:"
msgid "Wrong password!"
@@ -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 ""
@@ -102,7 +102,7 @@ msgstr "Creado por Usuario"
msgctxt "field:ir.model.field-res.group,field:"
msgid "Model Field"
-msgstr "Modelo de Campo"
+msgstr "Campo del Modelo"
msgctxt "field:ir.model.field-res.group,group:"
msgid "Group"
@@ -146,7 +146,7 @@ msgstr "Nombre"
msgctxt "field:ir.rule.group-res.group,rule_group:"
msgid "Rule Group"
-msgstr "Regla de Grupo"
+msgstr "Grupo de Reglas"
msgctxt "field:ir.rule.group-res.group,write_date:"
msgid "Write Date"
@@ -174,7 +174,7 @@ msgstr "Nombre"
msgctxt "field:ir.rule.group-res.user,rule_group:"
msgid "Rule Group"
-msgstr "Regla de Grupo"
+msgstr "Grupo de Reglas"
msgctxt "field:ir.rule.group-res.user,user:"
msgid "User"
@@ -366,7 +366,7 @@ msgstr "Contraseña"
msgctxt "field:res.user,password_hash:"
msgid "Password Hash"
-msgstr "Contraseña Hash"
+msgstr "Hash de Contraseña"
msgctxt "field:res.user,pyson_menu:"
msgid "PySON Menu"
@@ -394,7 +394,7 @@ msgstr "Barra de Estado"
msgctxt "field:res.user,warnings:"
msgid "Warnings"
-msgstr "Avisos"
+msgstr "Advertencias"
msgctxt "field:res.user,write_date:"
msgid "Write Date"
@@ -538,11 +538,11 @@ 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 secuencias en este tipo"
+msgstr "Grupos autorizados para editar las secuencias de este tipo"
msgctxt "help:res.user,actions:"
msgid "Actions that will be run at login"
-msgstr "Acciones que se ejecutan al iniciar sesión"
+msgstr "Acciones que se ejecutarán al iniciar sesión"
msgctxt "model:ir.action,name:act_group_form"
msgid "Groups"
@@ -566,7 +566,7 @@ msgstr "Ejecutar Disparadores a Tiempo"
msgctxt "model:ir.model.button-res.group,name:"
msgid "Model Button - Group"
-msgstr "Modelo de Botón - Grupo"
+msgstr "Botón de Modelo - Grupo"
msgctxt "model:ir.model.field-res.group,name:"
msgid "Model Field Group Rel"
@@ -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 "Regla de Grupo - Grupo"
+msgstr "Grupo de Reglas - Grupo"
msgctxt "model:ir.rule.group-res.user,name:"
msgid "Rule Group - User"
-msgstr "Regla de Grupo - Usuario"
+msgstr "Grupo de Reglas - Usuario"
msgctxt "model:ir.sequence.type-res.group,name:"
msgid "Sequence Type - Group"
-msgstr "Secuencia de Tipo - Grupo"
+msgstr "Tipo de Secuencia - Grupo"
msgctxt "model:ir.ui.menu,name:menu_group_form"
msgid "Groups"
@@ -622,7 +622,7 @@ msgstr "Disparador del Programador de tareas"
msgctxt "model:res.user-ir.action,name:"
msgid "User - Action"
-msgstr "res.user-ir.action"
+msgstr "Usuario - Acción"
msgctxt "model:res.user-res.group,name:"
msgid "User - Group"
@@ -630,15 +630,15 @@ msgstr "Usuario - Grupo"
msgctxt "model:res.user.config.start,name:"
msgid "User Config Init"
-msgstr "Inicio de Configuración de Usuario"
+msgstr "Configuración Inicial de Usuario"
msgctxt "model:res.user.login.attempt,name:"
msgid "Login Attempt"
-msgstr "Intento de Login"
+msgstr "Intento de Inicio de Sesión"
msgctxt "model:res.user.warning,name:"
msgid "User Warning"
-msgstr "Aviso a Usuario"
+msgstr "Advertencia al Usuario"
msgctxt "view:ir.module.module:"
msgid "Cancel Installation"
@@ -654,15 +654,15 @@ msgstr "Cancelar Actualización"
msgctxt "view:ir.module.module:"
msgid "Mark for Installation"
-msgstr "Instalar"
+msgstr "Marcar para Instalar"
msgctxt "view:ir.module.module:"
msgid "Mark for Uninstallation (beta)"
-msgstr "Desinstalar (beta)"
+msgstr "Marcar para Desinstalar (beta)"
msgctxt "view:ir.module.module:"
msgid "Mark for Upgrade"
-msgstr "Actualizar"
+msgstr "Marcar para Actualizar"
msgctxt "view:res.group:"
msgid "Access Permissions"
@@ -686,7 +686,7 @@ msgstr "Agregar Usuarios"
msgctxt "view:res.user.config.start:"
msgid "Be careful that the login must be unique!"
-msgstr "Asegúrese de que los nombres de usuario sean únicos!"
+msgstr "¡Asegúrese de que el nombre de usuario sea único!"
msgctxt "view:res.user.config.start:"
msgid "You can now add some users into the system."
diff --git a/trytond/res/locale/es_ES.po b/trytond/res/locale/es_ES.po
index 7ed0da6..18997e6 100644
--- a/trytond/res/locale/es_ES.po
+++ b/trytond/res/locale/es_ES.po
@@ -350,7 +350,7 @@ msgstr "Dirección del idioma"
msgctxt "field:res.user,login:"
msgid "Login"
-msgstr "Nombre de usuario"
+msgstr "Nombre usuario"
msgctxt "field:res.user,menu:"
msgid "Menu Action"
@@ -486,7 +486,7 @@ msgstr "ID"
msgctxt "field:res.user.login.attempt,login:"
msgid "Login"
-msgstr "Nombre de usuario"
+msgstr "Nombre usuario"
msgctxt "field:res.user.login.attempt,rec_name:"
msgid "Name"
diff --git a/trytond/res/locale/fr_FR.po b/trytond/res/locale/fr_FR.po
index 758ba94..7ed7195 100644
--- a/trytond/res/locale/fr_FR.po
+++ b/trytond/res/locale/fr_FR.po
@@ -4,15 +4,15 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "error:res.group:"
msgid "The name of the group must be unique!"
-msgstr "Le nom du groupe doit être unique !"
+msgstr "Le nom du groupe doit être unique !"
msgctxt "error:res.user:"
msgid "Wrong password!"
-msgstr "Mauvais mot de passe !"
+msgstr "Mauvais mot de passe !"
msgctxt "error:res.user:"
msgid "You can not have two users with the same login!"
-msgstr "Vous ne pouvez pas créer deux utilisateur avec le même identifiant !"
+msgstr "Vous ne pouvez pas créer deux utilisateur avec le même identifiant !"
msgctxt "error:res.user:"
msgid ""
@@ -330,7 +330,7 @@ msgstr "Créé par"
msgctxt "field:res.user,email:"
msgid "Email"
-msgstr "E-mail"
+msgstr "Email"
msgctxt "field:res.user,groups:"
msgid "Groups"
@@ -562,7 +562,7 @@ msgstr "Action - Groupe"
msgctxt "model:ir.cron,name:cron_trigger_time"
msgid "Run On Time Triggers"
-msgstr "Lance les déclencheurs \"À temps\""
+msgstr "Lance les déclencheurs « À temps »"
msgctxt "model:ir.model.button-res.group,name:"
msgid "Model Button - Group"
diff --git a/trytond/res/locale/sl_SI.po b/trytond/res/locale/sl_SI.po
index e481897..8b96eb7 100644
--- a/trytond/res/locale/sl_SI.po
+++ b/trytond/res/locale/sl_SI.po
@@ -21,7 +21,7 @@ msgid ""
"created by the system (updates, module installation, ...)"
msgstr ""
"Skrbnika ni možno odstraniti, ker se interno uporablja za vire, \n"
-"ki jih ustvarja sistem (posodobitve, nameščanje modulov, ...)"
+"ki jih izdeluje sistem (posodobitve, nameščanje modulov, ...)"
msgctxt "field:ir.action-res.group,action:"
msgid "Action"
@@ -29,11 +29,11 @@ msgstr "Ukrep"
msgctxt "field:ir.action-res.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.action-res.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.action-res.group,group:"
msgid "Group"
@@ -65,11 +65,11 @@ msgstr "Gumb"
msgctxt "field:ir.model.button-res.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.model.button-res.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.model.button-res.group,group:"
msgid "Group"
@@ -93,11 +93,11 @@ msgstr "Zapisal"
msgctxt "field:ir.model.field-res.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.model.field-res.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.model.field-res.group,field:"
msgid "Model Field"
@@ -125,11 +125,11 @@ msgstr "Zapisal"
msgctxt "field:ir.rule.group-res.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.rule.group-res.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.rule.group-res.group,group:"
msgid "Group"
@@ -157,11 +157,11 @@ msgstr "Zapisal"
msgctxt "field:ir.rule.group-res.user,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.rule.group-res.user,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.rule.group-res.user,id:"
msgid "ID"
@@ -201,11 +201,11 @@ msgstr "Uporabniške skupine"
msgctxt "field:ir.sequence.type-res.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.sequence.type-res.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.sequence.type-res.group,group:"
msgid "User Groups"
@@ -233,11 +233,11 @@ msgstr "Zapisal"
msgctxt "field:ir.ui.menu-res.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:ir.ui.menu-res.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:ir.ui.menu-res.group,group:"
msgid "Group"
@@ -265,11 +265,11 @@ msgstr "Zapisal"
msgctxt "field:res.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:res.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:res.group,field_access:"
msgid "Access Field"
@@ -321,11 +321,11 @@ msgstr "Aktivno"
msgctxt "field:res.user,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:res.user,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:res.user,email:"
msgid "Email"
@@ -409,11 +409,11 @@ msgstr "Ukrep"
msgctxt "field:res.user-ir.action,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:res.user-ir.action,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:res.user-ir.action,id:"
msgid "ID"
@@ -437,11 +437,11 @@ msgstr "Zapisal"
msgctxt "field:res.user-res.group,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:res.user-res.group,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:res.user-res.group,group:"
msgid "Group"
@@ -473,11 +473,11 @@ msgstr "ID"
msgctxt "field:res.user.login.attempt,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:res.user.login.attempt,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:res.user.login.attempt,id:"
msgid "ID"
@@ -505,11 +505,11 @@ msgstr "Vedno"
msgctxt "field:res.user.warning,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:res.user.warning,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:res.user.warning,id:"
msgid "ID"
diff --git a/trytond/res/user.py b/trytond/res/user.py
index 78bea12..19e1ba8 100644
--- a/trytond/res/user.py
+++ b/trytond/res/user.py
@@ -23,12 +23,12 @@ except ImportError:
from ..model import ModelView, ModelSQL, fields
from ..wizard import Wizard, StateView, Button, StateTransition
-from ..tools import safe_eval
+from ..tools import grouped_slice
from .. import backend
from ..transaction import Transaction
from ..cache import Cache
from ..pool import Pool
-from ..config import CONFIG
+from ..config import config
from ..pyson import PYSONEncoder
from ..rpc import RPC
@@ -77,9 +77,9 @@ class User(ModelSQL, ModelView):
def __setup__(cls):
super(User, cls).__setup__()
cls.__rpc__.update({
- 'get_preferences': RPC(),
- 'set_preferences': RPC(readonly=False),
- 'get_preferences_fields_view': RPC(),
+ 'get_preferences': RPC(check_access=False),
+ 'set_preferences': RPC(readonly=False, check_access=False),
+ 'get_preferences_fields_view': RPC(check_access=False),
})
cls._sql_constraints += [
('login_key', 'UNIQUE (login)',
@@ -198,17 +198,14 @@ class User(ModelSQL, ModelView):
@staticmethod
def get_sessions(users, name):
Session = Pool().get('ir.session')
- cursor = Transaction().cursor
now = datetime.datetime.now()
- timeout = datetime.timedelta(seconds=int(CONFIG['session_timeout']))
+ timeout = datetime.timedelta(
+ seconds=config.getint('session', 'timeout'))
result = dict((u.id, 0) for u in users)
- for i in range(0, len(users), cursor.IN_MAX):
- sub_ids = [u.id for u in users[i:i + cursor.IN_MAX]]
-
- with Transaction().set_user(0):
- sessions = Session.search([
- ('create_uid', 'in', sub_ids),
- ], order=[('create_uid', 'ASC')])
+ 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
@@ -359,8 +356,7 @@ class User(ModelSQL, ModelView):
if preferences is not None:
return preferences.copy()
user = Transaction().user
- with Transaction().set_user(0, set_context=True):
- user = cls(user)
+ user = cls(user)
preferences = cls._get_preferences(user, context_only=context_only)
cls._get_preferences_cache.set(key, preferences)
return preferences.copy()
@@ -375,8 +371,7 @@ class User(ModelSQL, ModelView):
values_clean = values.copy()
fields = cls._preferences_fields + cls._context_fields
user_id = Transaction().user
- with Transaction().set_user(0):
- user = cls(user_id)
+ user = cls(user_id)
for field in values:
if field not in fields or field == 'groups':
del values_clean[field]
@@ -391,8 +386,7 @@ class User(ModelSQL, ModelView):
values_clean['language'] = langs[0].id
else:
del values_clean['language']
- with Transaction().set_user(0):
- cls.write([user], values_clean)
+ cls.write([user], values_clean)
@classmethod
def get_preferences_fields_view(cls):
@@ -558,7 +552,7 @@ class LoginAttempt(ModelSQL):
@staticmethod
def delay():
return (datetime.datetime.now()
- - datetime.timedelta(seconds=int(CONFIG['session_timeout'])))
+ - datetime.timedelta(seconds=config.getint('session', 'timeout')))
@classmethod
def add(cls, login):
diff --git a/trytond/rpc.py b/trytond/rpc.py
index 1e3e2a9..4fc1f7a 100644
--- a/trytond/rpc.py
+++ b/trytond/rpc.py
@@ -10,16 +10,19 @@ class RPC(object):
readonly: The transaction mode
instantiate: The position or the slice of the arguments to be instanciated
result: The function to transform the result
+ check_access: If access right must be checked
'''
- __slots__ = ('readonly', 'instantiate', 'result')
+ __slots__ = ('readonly', 'instantiate', 'result', 'check_access')
- def __init__(self, readonly=True, instantiate=None, result=None):
+ def __init__(self, readonly=True, instantiate=None, result=None,
+ check_access=True):
self.readonly = readonly
self.instantiate = instantiate
if result is None:
result = lambda r: r
self.result = result
+ self.check_access = check_access
def convert(self, obj, *args, **kwargs):
args = list(args)
@@ -32,6 +35,8 @@ class RPC(object):
if '_timestamp' in context:
timestamp = context['_timestamp']
del context['_timestamp']
+ if self.check_access:
+ context['_check_access'] = True
if self.instantiate is not None:
def instance(data):
diff --git a/trytond/security.py b/trytond/security.py
index 7d3e85b..4e92b00 100644
--- a/trytond/security.py
+++ b/trytond/security.py
@@ -1,7 +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.
+import os
+try:
+ import crypt
+except ImportError:
+ pass
+
from trytond.pool import Pool
-from trytond.config import CONFIG
+from trytond.config import config
from trytond.transaction import Transaction
from trytond.exceptions import NotLogged
@@ -48,10 +54,10 @@ def logout(dbname, user, session):
def check_super(passwd):
- if passwd == CONFIG['admin_passwd']:
+ cryptedpasswd = config.get('session', 'super_pwd')
+ if cryptedpasswd and crypt.crypt(passwd, cryptedpasswd) == cryptedpasswd:
return True
- else:
- raise Exception('AccessDenied')
+ raise Exception('AccessDenied')
def check(dbname, user, session):
diff --git a/trytond/server.py b/trytond/server.py
index 720b224..a5df713 100644
--- a/trytond/server.py
+++ b/trytond/server.py
@@ -4,6 +4,7 @@
%prog [options]
"""
import logging
+import logging.config
import logging.handlers
import sys
import os
@@ -12,7 +13,7 @@ import time
from getpass import getpass
import threading
-from trytond.config import CONFIG
+from trytond.config import config, parse_listen
from trytond import backend
from trytond.pool import Pool
from trytond.monitor import monitor
@@ -22,59 +23,37 @@ from .transaction import Transaction
class TrytonServer(object):
def __init__(self, options):
- format = '[%(asctime)s] %(levelname)s:%(name)s:%(message)s'
- datefmt = '%a %b %d %H:%M:%S %Y'
- logging.basicConfig(level=logging.INFO, format=format,
+
+ config.update_etc(options.configfile)
+
+ if options.logconf:
+ logging.config.fileConfig(options.logconf)
+ 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)
- CONFIG.update_etc(options['configfile'])
- CONFIG.update_cmdline(options)
-
- if CONFIG['logfile']:
- logf = CONFIG['logfile']
- # test if the directories exist, else create them
- try:
- diff = 0
- if os.path.isfile(logf):
- diff = int(time.time()) - int(os.stat(logf)[-1])
- handler = logging.handlers.TimedRotatingFileHandler(
- logf, 'D', 1, 30)
- handler.rolloverAt -= diff
- except Exception, exception:
- sys.stderr.write(
- "ERROR: couldn't create the logfile directory:"
- + str(exception))
- else:
- formatter = logging.Formatter(format, datefmt)
- # tell the handler to use this format
- handler.setFormatter(formatter)
-
- # add the handler to the root logger
- logging.getLogger().addHandler(handler)
- logging.getLogger().setLevel(logging.INFO)
- elif os.name != 'nt':
- reverse = '\x1b[7m'
- reset = '\x1b[0m'
- # reverse color for error and critical messages
- for level in logging.ERROR, logging.CRITICAL:
- msg = reverse + logging.getLevelName(level) + reset
- logging.addLevelName(level, msg)
-
- self.logger = logging.getLogger("server")
-
- if CONFIG.configfile:
+ self.logger = logging.getLogger(__name__)
+
+ if options.configfile:
self.logger.info('using %s as configuration file'
- % CONFIG.configfile)
+ % options.configfile)
else:
self.logger.info('using default configuration')
self.logger.info('initialising distributed objects services')
self.xmlrpcd = []
self.jsonrpcd = []
self.webdavd = []
+ self.options = options
+
+ if time.tzname != ('UTC', 'UTC'):
+ self.logger.error('timezeone is not set to UTC')
def run(self):
"Run the server and never return"
- update = bool(CONFIG['init'] or CONFIG['update'])
init = {}
signal.signal(signal.SIGINT, lambda *a: self.stop())
@@ -84,22 +63,18 @@ class TrytonServer(object):
if hasattr(signal, 'SIGUSR1'):
signal.signal(signal.SIGUSR1, lambda *a: self.restart())
- if CONFIG['pidfile']:
- with open(CONFIG['pidfile'], 'w') as fd_pid:
+ if self.options.pidfile:
+ with open(self.options.pidfile, 'w') as fd_pid:
fd_pid.write("%d" % (os.getpid()))
- if not CONFIG["db_name"] \
- and bool(CONFIG['init'] or CONFIG['update']):
- raise Exception('Missing database option!')
-
- if not update:
+ if not self.options.update:
self.start_servers()
- for db_name in CONFIG["db_name"]:
+ for db_name in self.options.database_names:
init[db_name] = False
with Transaction().start(db_name, 0) as transaction:
cursor = transaction.cursor
- if CONFIG['init']:
+ if self.options.update:
if not cursor.test():
self.logger.info("init db")
backend.get('Database').init(cursor)
@@ -108,8 +83,8 @@ class TrytonServer(object):
elif not cursor.test():
raise Exception("'%s' is not a Tryton database!" % db_name)
- for db_name in CONFIG["db_name"]:
- if update:
+ for db_name in self.options.database_names:
+ if self.options.update:
with Transaction().start(db_name, 0) as transaction:
cursor = transaction.cursor
if not cursor.test():
@@ -120,12 +95,9 @@ class TrytonServer(object):
lang = [x[0] for x in cursor.fetchall()]
else:
lang = None
- Pool(db_name).init(update=update, lang=lang)
-
- for kind in ('init', 'update'):
- CONFIG[kind] = {}
+ Pool(db_name).init(update=self.options.update, lang=lang)
- for db_name in CONFIG['db_name']:
+ for db_name in self.options.database_names:
if init[db_name]:
# try to read password from environment variable
# TRYTONPASSFILE, empty TRYTONPASSFILE ignored
@@ -161,14 +133,14 @@ class TrytonServer(object):
})
transaction.cursor.commit()
- if update:
+ if self.options.update:
self.logger.info('Update/Init succeed!')
logging.shutdown()
sys.exit(0)
threads = {}
while True:
- if CONFIG['cron']:
+ if self.options.cron:
for dbname in Pool.database_list():
thread = threads.get(dbname)
if thread and thread.is_alive():
@@ -187,42 +159,41 @@ class TrytonServer(object):
args=(dbname,), kwargs={})
thread.start()
threads[dbname] = thread
- if CONFIG['auto_reload']:
+ if self.options.dev:
for _ in range(60):
- if monitor():
+ if monitor([self.options.configfile]
+ if self.options.configfile else []):
self.restart()
time.sleep(1)
else:
time.sleep(60)
def start_servers(self):
+ ssl = config.get('ssl', 'privatekey')
# Launch Server
- if CONFIG['jsonrpc']:
+ if config.get('jsonrpc', 'listen'):
from trytond.protocols.jsonrpc import JSONRPCDaemon
- for hostname, port in CONFIG['jsonrpc']:
- self.jsonrpcd.append(JSONRPCDaemon(hostname, port,
- CONFIG['ssl_jsonrpc']))
+ 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" %
- (CONFIG['ssl_jsonrpc'] and ' SSL' or '', hostname or '*',
- port))
+ (ssl and ' SSL' or '', hostname or '*', port))
- if CONFIG['xmlrpc']:
+ if config.get('xmlrpc', 'listen'):
from trytond.protocols.xmlrpc import XMLRPCDaemon
- for hostname, port in CONFIG['xmlrpc']:
- self.xmlrpcd.append(XMLRPCDaemon(hostname, port,
- CONFIG['ssl_xmlrpc']))
+ 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" %
- (CONFIG['ssl_xmlrpc'] and ' SSL' or '', hostname or '*',
- port))
+ (ssl and ' SSL' or '', hostname or '*', port))
- if CONFIG['webdav']:
+ if config.get('webdav', 'listen'):
from trytond.protocols.webdav import WebDAVServerThread
- for hostname, port in CONFIG['webdav']:
- self.webdavd.append(WebDAVServerThread(hostname, port,
- CONFIG['ssl_webdav']))
+ 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" %
- (CONFIG['ssl_webdav'] and ' SSL' or '', hostname or '*',
- port))
+ (ssl and ' SSL' or '', hostname or '*', port))
for servers in (self.xmlrpcd, self.jsonrpcd, self.webdavd):
for server in servers:
@@ -234,8 +205,8 @@ class TrytonServer(object):
server.stop()
server.join()
if exit:
- if CONFIG['pidfile']:
- os.unlink(CONFIG['pidfile'])
+ if self.options.pidfile:
+ os.unlink(self.options.pidfile)
logging.getLogger('server').info('stopped')
logging.shutdown()
sys.exit(0)
diff --git a/trytond/tests/__init__.py b/trytond/tests/__init__.py
index 8da0e0a..6e456ca 100644
--- a/trytond/tests/__init__.py
+++ b/trytond/tests/__init__.py
@@ -13,6 +13,7 @@ from .wizard import *
from .workflow import *
from .copy_ import *
from history import *
+from .field_context import *
def register():
@@ -97,6 +98,15 @@ def register():
URLObject,
ModelSQLRequiredField,
ModelSQLTimestamp,
+ Model4Union1,
+ Model4Union2,
+ Model4Union3,
+ Model4Union4,
+ Union,
+ UnionUnion,
+ Model4UnionTree1,
+ Model4UnionTree2,
+ UnionTree,
MPTT,
ImportDataBoolean,
ImportDataInteger,
@@ -141,6 +151,9 @@ def register():
Many2OneTarget,
Many2OneDomainValidation,
TestHistory,
+ TestHistoryLine,
+ FieldContextChild,
+ FieldContextParent,
module='tests', type_='model')
Pool.register(
TestWizard,
diff --git a/trytond/tests/field_context.py b/trytond/tests/field_context.py
new file mode 100644
index 0000000..e575b7d
--- /dev/null
+++ b/trytond/tests/field_context.py
@@ -0,0 +1,25 @@
+# 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
+from trytond.pyson import Eval
+
+__all__ = [
+ 'FieldContextParent',
+ 'FieldContextChild',
+ ]
+
+
+class FieldContextParent(ModelSQL):
+ 'Field Context Parent'
+ __name__ = 'test.field_context.parent'
+ name = fields.Char('Name')
+ child = fields.Many2One('test.field_context.child', 'Child',
+ context={
+ 'name': Eval('name'),
+ })
+
+
+class FieldContextChild(ModelSQL):
+ 'Field Context Child'
+ __name__ = 'test.field_context.child'
diff --git a/trytond/tests/history.py b/trytond/tests/history.py
index b23d8ec..ed1b24b 100644
--- a/trytond/tests/history.py
+++ b/trytond/tests/history.py
@@ -2,7 +2,7 @@
#this repository contains the full copyright notices and license terms.
from trytond.model import ModelSQL, fields
-__all__ = ['TestHistory']
+__all__ = ['TestHistory', 'TestHistoryLine']
class TestHistory(ModelSQL):
@@ -10,3 +10,12 @@ class TestHistory(ModelSQL):
__name__ = 'test.history'
_history = True
value = fields.Integer('Value')
+ lines = fields.One2Many('test.history.line', 'history', 'Lines')
+
+
+class TestHistoryLine(ModelSQL):
+ 'Test History Line'
+ __name__ = 'test.history.line'
+ _history = True
+ history = fields.Many2One('test.history', 'History')
+ name = fields.Char('Name')
diff --git a/trytond/tests/model.py b/trytond/tests/model.py
index 80ed3d4..1815389 100644
--- a/trytond/tests/model.py
+++ b/trytond/tests/model.py
@@ -1,9 +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.
-from trytond.model import ModelSingleton, ModelSQL, fields
+from trytond.model import ModelSingleton, ModelSQL, UnionMixin, fields
__all__ = [
'Singleton', 'URLObject', 'ModelSQLRequiredField', 'ModelSQLTimestamp',
+ 'Model4Union1', 'Model4Union2', 'Model4Union3', 'Model4Union4',
+ 'Union', 'UnionUnion',
+ 'Model4UnionTree1', 'Model4UnionTree2', 'UnionTree',
]
@@ -34,3 +37,74 @@ class ModelSQLRequiredField(ModelSQL):
class ModelSQLTimestamp(ModelSQL):
'Model to test timestamp'
__name__ = 'test.modelsql.timestamp'
+
+
+class Model4Union1(ModelSQL):
+ 'Model for union 1'
+ __name__ = 'test.model.union1'
+ name = fields.Char('Name')
+ optional = fields.Char('Optional')
+
+
+class Model4Union2(ModelSQL):
+ 'Model for union 2'
+ __name__ = 'test.model.union2'
+ name = fields.Char('Name')
+
+
+class Model4Union3(ModelSQL):
+ 'Model for union 3'
+ __name__ = 'test.model.union3'
+ name = fields.Char('Name')
+
+
+class Model4Union4(ModelSQL):
+ 'Model for union 4'
+ __name__ = 'test.model.union4'
+ name = fields.Char('Name')
+
+
+class Union(UnionMixin, ModelSQL):
+ 'Union'
+ __name__ = 'test.union'
+ name = fields.Char('Name')
+ optional = fields.Char('Optional')
+
+ @staticmethod
+ def union_models():
+ return ['test.model.union%s' % i for i in range(1, 4)]
+
+
+class UnionUnion(UnionMixin, ModelSQL):
+ 'Union of union'
+ __name__ = 'test.union.union'
+ name = fields.Char('Name')
+
+ @staticmethod
+ def union_models():
+ return ['test.union', 'test.model.union4']
+
+
+class Model4UnionTree1(ModelSQL):
+ 'Model for union tree 1'
+ __name__ = 'test.model.union.tree1'
+ name = fields.Char('Name')
+
+
+class Model4UnionTree2(ModelSQL):
+ 'Model for union tree 2'
+ __name__ = 'test.model.union.tree2'
+ name = fields.Char('Name')
+ parent = fields.Many2One('test.model.union.tree1', 'Parent')
+
+
+class UnionTree(UnionMixin, ModelSQL):
+ 'Union tree'
+ __name__ = 'test.union.tree'
+ name = fields.Char('Name')
+ parent = fields.Many2One('test.union.tree', 'Parent')
+ childs = fields.One2Many('test.union.tree', 'parent', 'Childs')
+
+ @staticmethod
+ def union_models():
+ return ['test.model.union.tree1', 'test.model.union.tree2']
diff --git a/trytond/tests/run-tests.py b/trytond/tests/run-tests.py
index f576c34..f6dd242 100755
--- a/trytond/tests/run-tests.py
+++ b/trytond/tests/run-tests.py
@@ -8,7 +8,8 @@ import time
import unittest
import sys
-from trytond.config import CONFIG
+from trytond.config import config
+from trytond import backend
if __name__ != '__main__':
raise ImportError('%s can not be imported' % __name__)
@@ -22,18 +23,17 @@ parser.add_argument("-m", "--modules", action="store_true", dest="modules",
parser.add_argument("-v", action="count", default=0, dest="verbosity",
help="Increase verbosity")
parser.add_argument('tests', metavar='test', nargs='*')
+parser.epilog = ('The database name can be specified in the DB_NAME '
+ 'environment variable.')
opt = parser.parse_args()
-CONFIG['db_type'] = 'sqlite'
-CONFIG.update_etc(opt.config)
-if not CONFIG['admin_passwd']:
- CONFIG['admin_passwd'] = 'admin'
+config.update_etc(opt.config)
-if CONFIG['db_type'] == 'sqlite':
+if backend.name() == 'sqlite':
database_name = ':memory:'
else:
database_name = 'test_' + str(int(time.time()))
-os.environ['DB_NAME'] = database_name
+os.environ.setdefault('DB_NAME', database_name)
from trytond.tests.test_tryton import all_suite, modules_suite
if not opt.modules:
diff --git a/trytond/tests/test.py b/trytond/tests/test.py
index f6cbaf7..5f812f2 100644
--- a/trytond/tests/test.py
+++ b/trytond/tests/test.py
@@ -604,6 +604,7 @@ class Selection(ModelSQL):
select = fields.Selection([
('', ''), ('arabic', 'Arabic'), ('hexa', 'Hexadecimal')],
'Selection')
+ select_string = select.translated('select')
dyn_select = fields.Selection('get_selection',
'Instance Dynamic Selection')
dyn_select_static = fields.Selection('static_selection',
diff --git a/trytond/tests/test_access.py b/trytond/tests/test_access.py
index 6322ac4..3251a77 100644
--- a/trytond/tests/test_access.py
+++ b/trytond/tests/test_access.py
@@ -5,6 +5,10 @@ import unittest
from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
install_module
from trytond.transaction import Transaction
+from trytond.exceptions import UserError
+
+CONTEXT = CONTEXT.copy()
+CONTEXT['_check_access'] = True
class ModelAccessTestCase(unittest.TestCase):
@@ -42,7 +46,7 @@ class ModelAccessTestCase(unittest.TestCase):
self.model_access.write([model_access_wo_group], {
'perm_read': False,
})
- self.assertRaises(Exception, self.test_access.read, [test.id])
+ self.assertRaises(UserError, self.test_access.read, [test.id])
# Two access rules with one group allowed
group, = self.group.search([('users', '=', USER)])
@@ -70,11 +74,11 @@ class ModelAccessTestCase(unittest.TestCase):
self.model_access.write([model_access_wo_group], {
'perm_read': False,
})
- self.assertRaises(Exception, self.test_access.read, [test.id])
+ self.assertRaises(UserError, self.test_access.read, [test.id])
# One access disallowed for one group
self.model_access.delete([model_access_wo_group])
- self.assertRaises(Exception, self.test_access.read, [test.id])
+ self.assertRaises(UserError, self.test_access.read, [test.id])
# One access allowed for one group
self.model_access.write([model_access_w_group], {
@@ -123,7 +127,7 @@ class ModelAccessTestCase(unittest.TestCase):
self.model_access.write([model_access_wo_group], {
'perm_write': False,
})
- self.assertRaises(Exception, self.test_access.write, [test], {})
+ self.assertRaises(UserError, self.test_access.write, [test], {})
# Two access rules with one group allowed
group, = self.group.search([('users', '=', USER)])
@@ -150,11 +154,11 @@ class ModelAccessTestCase(unittest.TestCase):
self.model_access.write([model_access_wo_group], {
'perm_write': False,
})
- self.assertRaises(Exception, self.test_access.write, [test], {})
+ self.assertRaises(UserError, self.test_access.write, [test], {})
# One access disallowed for one group
self.model_access.delete([model_access_wo_group])
- self.assertRaises(Exception, self.test_access.write, [test], {})
+ self.assertRaises(UserError, self.test_access.write, [test], {})
# One access allowed for one group
self.model_access.write([model_access_w_group], {
@@ -201,7 +205,7 @@ class ModelAccessTestCase(unittest.TestCase):
self.model_access.write([model_access_wo_group], {
'perm_create': False,
})
- self.assertRaises(Exception, self.test_access.create, {})
+ self.assertRaises(UserError, self.test_access.create, {})
# Two access rules with one group allowed
group, = self.group.search([('users', '=', USER)])
@@ -229,11 +233,11 @@ class ModelAccessTestCase(unittest.TestCase):
self.model_access.write([model_access_wo_group], {
'perm_create': False,
})
- self.assertRaises(Exception, self.test_access.create, [{}])
+ self.assertRaises(UserError, self.test_access.create, [{}])
# One access disallowed for one group
self.model_access.delete([model_access_wo_group])
- self.assertRaises(Exception, self.test_access.create, [{}])
+ self.assertRaises(UserError, self.test_access.create, [{}])
# One access allowed for one group
self.model_access.write([model_access_w_group], {
@@ -282,7 +286,7 @@ class ModelAccessTestCase(unittest.TestCase):
self.model_access.write([model_access_wo_group], {
'perm_delete': False,
})
- self.assertRaises(Exception, self.test_access.delete,
+ self.assertRaises(UserError, self.test_access.delete,
[tests.pop()])
# Two access rules with one group allowed
@@ -311,12 +315,12 @@ class ModelAccessTestCase(unittest.TestCase):
self.model_access.write([model_access_wo_group], {
'perm_delete': False,
})
- self.assertRaises(Exception, self.test_access.delete,
+ self.assertRaises(UserError, self.test_access.delete,
[tests.pop()])
# One access disallowed for one group
self.model_access.delete([model_access_wo_group])
- self.assertRaises(Exception, self.test_access.delete,
+ self.assertRaises(UserError, self.test_access.delete,
[tests.pop()])
# One access allowed for one group
@@ -400,11 +404,11 @@ class ModelFieldAccessTestCase(unittest.TestCase):
'perm_read': False,
})
- self.assertRaises(Exception, self.test_access.read, [test.id],
+ self.assertRaises(UserError, self.test_access.read, [test.id],
['field1'])
self.test_access.read([test.id], ['field2'])
- self.assertRaises(Exception, self.test_access.read, [test.id])
- self.assertRaises(Exception, getattr, test, 'field1')
+ self.assertRaises(UserError, self.test_access.read, [test.id])
+ self.assertRaises(UserError, getattr, test, 'field1')
test.field2
transaction.cursor.cache.clear()
test = self.test_access(test.id)
@@ -453,22 +457,22 @@ class ModelFieldAccessTestCase(unittest.TestCase):
self.field_access.write([field_access_wo_group], {
'perm_read': False,
})
- self.assertRaises(Exception, self.test_access.read, [test.id],
+ self.assertRaises(UserError, self.test_access.read, [test.id],
['field1'])
self.test_access.read([test.id], ['field2'])
- self.assertRaises(Exception, self.test_access.read, [test.id])
- self.assertRaises(Exception, getattr, test, 'field1')
+ self.assertRaises(UserError, self.test_access.read, [test.id])
+ self.assertRaises(UserError, getattr, test, 'field1')
test.field2
transaction.cursor.cache.clear()
test = self.test_access(test.id)
# One access disallowed for one group
self.field_access.delete([field_access_wo_group])
- self.assertRaises(Exception, self.test_access.read, [test.id],
+ self.assertRaises(UserError, self.test_access.read, [test.id],
['field1'])
self.test_access.read([test.id], ['field2'])
- self.assertRaises(Exception, self.test_access.read, [test.id])
- self.assertRaises(Exception, getattr, test, 'field1')
+ self.assertRaises(UserError, self.test_access.read, [test.id])
+ self.assertRaises(UserError, getattr, test, 'field1')
test.field2
transaction.cursor.cache.clear()
test = self.test_access(test.id)
@@ -537,11 +541,11 @@ class ModelFieldAccessTestCase(unittest.TestCase):
'perm_read': False,
})
self.test_access.read([test.id], ['field1'])
- self.assertRaises(Exception, self.test_access.read, [test.id],
+ self.assertRaises(UserError, self.test_access.read, [test.id],
['field2'])
- self.assertRaises(Exception, self.test_access.read, [test.id])
+ self.assertRaises(UserError, self.test_access.read, [test.id])
test.field1
- self.assertRaises(Exception, getattr, test, 'field2')
+ self.assertRaises(UserError, getattr, test, 'field2')
transaction.cursor.cache.clear()
test = self.test_access(test.id)
@@ -549,13 +553,13 @@ class ModelFieldAccessTestCase(unittest.TestCase):
self.field_access.write([field_access1], {
'perm_read': False,
})
- self.assertRaises(Exception, self.test_access.read, [test.id],
+ self.assertRaises(UserError, self.test_access.read, [test.id],
['field1'])
- self.assertRaises(Exception, self.test_access.read, [test.id],
+ self.assertRaises(UserError, self.test_access.read, [test.id],
['field2'])
- self.assertRaises(Exception, self.test_access.read, [test.id])
- self.assertRaises(Exception, getattr, test, 'field1')
- self.assertRaises(Exception, getattr, test, 'field2')
+ self.assertRaises(UserError, self.test_access.read, [test.id])
+ self.assertRaises(UserError, getattr, test, 'field1')
+ self.assertRaises(UserError, getattr, test, 'field2')
transaction.cursor.cache.clear()
test = self.test_access(test.id)
@@ -607,10 +611,10 @@ class ModelFieldAccessTestCase(unittest.TestCase):
})
self.test_access.write([test], {})
- self.assertRaises(Exception, self.test_access.write, [test],
+ self.assertRaises(UserError, self.test_access.write, [test],
{'field1': 'ham'})
self.test_access.write([test], {'field2': 'spam'})
- self.assertRaises(Exception, self.test_access.write, [test], {
+ self.assertRaises(UserError, self.test_access.write, [test], {
'field1': 'ham',
'field2': 'spam',
})
@@ -660,10 +664,10 @@ class ModelFieldAccessTestCase(unittest.TestCase):
'perm_write': False,
})
self.test_access.write([test], {})
- self.assertRaises(Exception, self.test_access.write, [test],
+ self.assertRaises(UserError, self.test_access.write, [test],
{'field1': 'ham'})
self.test_access.write([test], {'field2': 'spam'})
- self.assertRaises(Exception, self.test_access.write, [test], {
+ self.assertRaises(UserError, self.test_access.write, [test], {
'field1': 'ham',
'field2': 'spam',
})
@@ -671,10 +675,10 @@ class ModelFieldAccessTestCase(unittest.TestCase):
# One access disallowed for one group
self.field_access.delete([field_access_wo_group])
self.test_access.write([test], {})
- self.assertRaises(Exception, self.test_access.write, [test],
+ self.assertRaises(UserError, self.test_access.write, [test],
{'field1': 'ham'})
self.test_access.write([test], {'field2': 'ham'})
- self.assertRaises(Exception, self.test_access.write, [test], {
+ self.assertRaises(UserError, self.test_access.write, [test], {
'field1': 'ham',
'field2': 'spam',
})
@@ -744,9 +748,9 @@ class ModelFieldAccessTestCase(unittest.TestCase):
})
self.test_access.write([test], {})
self.test_access.write([test], {'field1': 'ham'})
- self.assertRaises(Exception, self.test_access.write, [test], {
+ self.assertRaises(UserError, self.test_access.write, [test], {
'field2': 'spam'})
- self.assertRaises(Exception, self.test_access.write, [test], {
+ self.assertRaises(UserError, self.test_access.write, [test], {
'field1': 'ham',
'field2': 'spam',
})
@@ -756,11 +760,11 @@ class ModelFieldAccessTestCase(unittest.TestCase):
'perm_write': False,
})
self.test_access.write([test], {})
- self.assertRaises(Exception, self.test_access.write, [test], {
+ self.assertRaises(UserError, self.test_access.write, [test], {
'field1': 'ham'})
- self.assertRaises(Exception, self.test_access.write, [test], {
+ self.assertRaises(UserError, self.test_access.write, [test], {
'field2': 'spam'})
- self.assertRaises(Exception, self.test_access.write, [test], {
+ self.assertRaises(UserError, self.test_access.write, [test], {
'field1': 'ham',
'field2': 'spam',
})
diff --git a/trytond/tests/test_exportdata.py b/trytond/tests/test_exportdata.py
index 989d2d2..417b52b 100644
--- a/trytond/tests/test_exportdata.py
+++ b/trytond/tests/test_exportdata.py
@@ -216,8 +216,9 @@ class ExportDataTestCase(unittest.TestCase):
'selection': 'select1',
}])
self.assertEqual(
- self.export_data.export_data([export1], ['selection']),
- [['select1']])
+ self.export_data.export_data([export1], ['selection',
+ 'selection.translated']),
+ [['select1', 'Select 1']])
export2, = self.export_data.create([{
'selection': None,
diff --git a/trytond/tests/test_field_context.py b/trytond/tests/test_field_context.py
new file mode 100644
index 0000000..99a9cbc
--- /dev/null
+++ b/trytond/tests/test_field_context.py
@@ -0,0 +1,37 @@
+# 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
+from trytond.transaction import Transaction
+
+
+class FieldContextTestCase(unittest.TestCase):
+ "Test context on field"
+
+ def setUp(self):
+ install_module('tests')
+
+ def test_context(self):
+ Parent = POOL.get('test.field_context.parent')
+ Child = POOL.get('test.field_context.child')
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ child = Child()
+ child.save()
+ parent = Parent(name='foo', child=child)
+ parent.save()
+ self.assertEqual(parent.child._context['name'], 'foo')
+
+ parent.name = 'bar'
+ parent.save()
+ self.assertEqual(parent.child._context['name'], 'bar')
+
+
+def suite():
+ func = unittest.TestLoader().loadTestsFromTestCase
+ suite = unittest.TestSuite()
+ for testcase in (FieldContextTestCase,):
+ suite.addTests(func(testcase))
+ return suite
diff --git a/trytond/tests/test_fields.py b/trytond/tests/test_fields.py
index 8d260cc..2c436f6 100644
--- a/trytond/tests/test_fields.py
+++ b/trytond/tests/test_fields.py
@@ -567,7 +567,7 @@ class FieldsTestCase(unittest.TestCase):
'float': 'test',
})
- self.assertRaises(Exception, self.float_required.create, [{}])
+ self.assertRaises(UserError, self.float_required.create, [{}])
transaction.cursor.rollback()
float5, = self.float_required.create([{
@@ -581,17 +581,17 @@ class FieldsTestCase(unittest.TestCase):
}])
self.assert_(float6)
- self.assertRaises(Exception, self.float_digits.create, [{
+ self.assertRaises(UserError, self.float_digits.create, [{
'digits': 1,
'float': 1.11,
}])
- self.assertRaises(Exception, self.float_digits.write,
+ self.assertRaises(UserError, self.float_digits.write,
[float6], {
'float': 1.11,
})
- self.assertRaises(Exception, self.float_digits.write,
+ self.assertRaises(UserError, self.float_digits.write,
[float6], {
'digits': 0,
})
@@ -603,6 +603,42 @@ class FieldsTestCase(unittest.TestCase):
transaction.cursor.rollback()
+ def test0031float(self):
+ 'Test float search with None'
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ float_none, float0, float1 = self.float.create([{
+ 'float': None,
+ }, {
+ 'float': 0,
+ }, {
+ 'float': 1,
+ }])
+ self.assertEqual([float_none], self.float.search([
+ ('float', '=', None),
+ ]))
+ self.assertEqual([float0], self.float.search([
+ ('float', '=', 0),
+ ]))
+ self.assertEqual([float1], self.float.search([
+ ('float', '>', 0),
+ ]))
+
+ self.assertEqual([float0, float1], self.float.search([
+ ('float', '!=', None),
+ ]))
+ self.assertEqual([float1], self.float.search([
+ ('float', '!=', 0),
+ ]))
+ self.assertEqual([float0], self.float.search([
+ ('float', '<', 1),
+ ]))
+
+ self.assertEqual([float_none, float1], self.float.search([
+ 'OR',
+ ('float', '>', 0),
+ ('float', '=', None),
+ ]))
+
def test0040numeric(self):
'Test Numeric'
with Transaction().start(DB_NAME, USER,
@@ -770,7 +806,7 @@ class FieldsTestCase(unittest.TestCase):
'numeric': 'test',
})
- self.assertRaises(Exception, self.numeric_required.create, [{}])
+ self.assertRaises(UserError, self.numeric_required.create, [{}])
transaction.cursor.rollback()
numeric5, = self.numeric_required.create([{
@@ -784,22 +820,22 @@ class FieldsTestCase(unittest.TestCase):
}])
self.assert_(numeric6)
- self.assertRaises(Exception, self.numeric_digits.create, [{
+ self.assertRaises(UserError, self.numeric_digits.create, [{
'digits': 1,
'numeric': Decimal('1.11'),
}])
- self.assertRaises(Exception, self.numeric_digits.write,
+ self.assertRaises(UserError, self.numeric_digits.write,
[numeric6], {
'numeric': Decimal('1.11'),
})
- self.assertRaises(Exception, self.numeric_digits.write,
+ self.assertRaises(UserError, self.numeric_digits.write,
[numeric6], {
'numeric': Decimal('0.10000000000000001'),
})
- self.assertRaises(Exception, self.numeric_digits.write,
+ self.assertRaises(UserError, self.numeric_digits.write,
[numeric6], {
'digits': 0,
})
@@ -825,6 +861,42 @@ class FieldsTestCase(unittest.TestCase):
])
self.assertEqual(numerics, [numeric1])
+ def test0042numeric(self):
+ 'Test numeric search with None'
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ numeric_none, numeric0, numeric1 = self.numeric.create([{
+ 'numeric': None,
+ }, {
+ 'numeric': 0,
+ }, {
+ 'numeric': 1,
+ }])
+ self.assertEqual([numeric_none], self.numeric.search([
+ ('numeric', '=', None),
+ ]))
+ self.assertEqual([numeric0], self.numeric.search([
+ ('numeric', '=', 0),
+ ]))
+ self.assertEqual([numeric1], self.numeric.search([
+ ('numeric', '>', 0),
+ ]))
+
+ self.assertEqual([numeric0, numeric1], self.numeric.search([
+ ('numeric', '!=', None),
+ ]))
+ self.assertEqual([numeric1], self.numeric.search([
+ ('numeric', '!=', 0),
+ ]))
+ self.assertEqual([numeric0], self.numeric.search([
+ ('numeric', '<', 1),
+ ]))
+
+ self.assertEqual([numeric_none, numeric1], self.numeric.search([
+ 'OR',
+ ('numeric', '>', 0),
+ ('numeric', '=', None),
+ ]))
+
def test0050char(self):
'Test Char'
with Transaction().start(DB_NAME, USER,
@@ -1025,10 +1097,10 @@ class FieldsTestCase(unittest.TestCase):
})
self.assertEqual(char2.char, 'Test')
- self.assertRaises(Exception, self.char_required.create, [{}])
+ self.assertRaises(UserError, self.char_required.create, [{}])
transaction.cursor.rollback()
- self.assertRaises(Exception, self.char_required.create, [{
+ self.assertRaises(UserError, self.char_required.create, [{
'char': '',
}])
transaction.cursor.rollback()
@@ -1275,7 +1347,7 @@ class FieldsTestCase(unittest.TestCase):
})
self.assertEqual(text2.text, 'Test')
- self.assertRaises(Exception, self.text_required.create, [{}])
+ self.assertRaises(UserError, self.text_required.create, [{}])
transaction.cursor.rollback()
text5, = self.text_required.create([{
@@ -1288,11 +1360,11 @@ class FieldsTestCase(unittest.TestCase):
}])
self.assert_(text6)
- self.assertRaises(Exception, self.text_size.create, [{
+ self.assertRaises(UserError, self.text_size.create, [{
'text': 'foobar',
}])
- self.assertRaises(Exception, self.text_size.write, [text6], {
+ self.assertRaises(UserError, self.text_size.write, [text6], {
'text': 'foobar',
})
@@ -1546,7 +1618,7 @@ class FieldsTestCase(unittest.TestCase):
self.assert_(date5)
self.assertEqual(date5.date, datetime.date(2009, 1, 1))
- self.assertRaises(Exception, self.date_required.create, [{}])
+ self.assertRaises(UserError, self.date_required.create, [{}])
transaction.cursor.rollback()
date6, = self.date_required.create([{
@@ -1798,7 +1870,7 @@ class FieldsTestCase(unittest.TestCase):
self.assertEqual(datetime5.datetime,
datetime.datetime(2009, 1, 1, 12, 0, 0))
- self.assertRaises(Exception, self.datetime_required.create, [{}])
+ self.assertRaises(UserError, self.datetime_required.create, [{}])
transaction.cursor.rollback()
datetime6, = self.datetime_required.create([{
@@ -1826,7 +1898,7 @@ class FieldsTestCase(unittest.TestCase):
self.assert_(self.datetime_format.create([{
'datetime': datetime.datetime(2009, 1, 1, 12, 30),
}]))
- self.assertRaises(Exception, self.datetime_format.create, [{
+ self.assertRaises(UserError, self.datetime_format.create, [{
'datetime': datetime.datetime(2009, 1, 1, 12, 30, 25),
}])
@@ -2045,7 +2117,7 @@ class FieldsTestCase(unittest.TestCase):
self.assert_(time5)
self.assertEqual(time5.time, datetime.time(12, 0))
- self.assertRaises(Exception, self.time_required.create, [{}])
+ self.assertRaises(UserError, self.time_required.create, [{}])
transaction.cursor.rollback()
time6, = self.time_required.create([{
@@ -2068,7 +2140,7 @@ class FieldsTestCase(unittest.TestCase):
self.assert_(self.time_format.create([{
'time': datetime.time(12, 30),
}]))
- self.assertRaises(Exception, self.time_format.create, [{
+ self.assertRaises(UserError, self.time_format.create, [{
'time': datetime.time(12, 30, 25),
}])
@@ -2155,18 +2227,18 @@ class FieldsTestCase(unittest.TestCase):
})
self.assertEqual(one2one2.one2one, None)
- self.assertRaises(Exception, self.one2one.create, [{
+ self.assertRaises(UserError, self.one2one.create, [{
'name': 'one2one3',
'one2one': target1.id,
}])
transaction.cursor.rollback()
- self.assertRaises(Exception, self.one2one.write, [one2one2], {
+ self.assertRaises(UserError, self.one2one.write, [one2one2], {
'one2one': target1.id,
})
transaction.cursor.rollback()
- self.assertRaises(Exception, self.one2one_required.create, [{
+ self.assertRaises(UserError, self.one2one_required.create, [{
'name': 'one2one3',
}])
transaction.cursor.rollback()
@@ -2184,7 +2256,7 @@ class FieldsTestCase(unittest.TestCase):
target4, = self.one2one_target.create([{
'name': 'target4',
}])
- self.assertRaises(Exception, self.one2one_domain.create, [{
+ self.assertRaises(UserError, self.one2one_domain.create, [{
'name': 'one2one4',
'one2one': target4.id,
}])
@@ -2367,7 +2439,7 @@ class FieldsTestCase(unittest.TestCase):
transaction.cursor.rollback()
- self.assertRaises(Exception, self.one2many_required.create, [{
+ self.assertRaises(UserError, self.one2many_required.create, [{
'name': 'origin3',
}])
transaction.cursor.rollback()
@@ -2385,14 +2457,14 @@ class FieldsTestCase(unittest.TestCase):
self.one2many_size.create([{
'targets': [('create', [{}])] * 3,
}])
- self.assertRaises(Exception, self.one2many_size.create, [{
+ self.assertRaises(UserError, self.one2many_size.create, [{
'targets': [('create', [{}])] * 4,
}])
self.one2many_size_pyson.create([{
'limit': 4,
'targets': [('create', [{}])] * 4,
}])
- self.assertRaises(Exception, self.one2many_size_pyson.create, [{
+ self.assertRaises(UserError, self.one2many_size_pyson.create, [{
'limit': 2,
'targets': [('create', [{}])] * 4,
}])
@@ -2561,7 +2633,7 @@ class FieldsTestCase(unittest.TestCase):
transaction.cursor.rollback()
- self.assertRaises(Exception, self.many2many_required.create, [{
+ self.assertRaises(UserError, self.many2many_required.create, [{
'name': 'origin3',
}])
transaction.cursor.rollback()
@@ -2697,7 +2769,7 @@ class FieldsTestCase(unittest.TestCase):
}])
self.assert_(reference3)
- self.assertRaises(Exception, self.reference_required.create, [{
+ self.assertRaises(UserError, self.reference_required.create, [{
'name': 'reference4',
}])
transaction.cursor.rollback()
@@ -2988,12 +3060,14 @@ class FieldsTestCase(unittest.TestCase):
selection1, = self.selection.create([{'select': 'arabic'}])
self.assert_(selection1)
self.assertEqual(selection1.select, 'arabic')
+ self.assertEqual(selection1.select_string, 'Arabic')
selection2, = self.selection.create([{'select': None}])
self.assert_(selection2)
self.assertEqual(selection2.select, None)
+ self.assertEqual(selection2.select_string, '')
- self.assertRaises(Exception, self.selection.create,
+ self.assertRaises(UserError, self.selection.create,
[{'select': 'chinese'}])
selection3, = self.selection.create(
@@ -3014,15 +3088,15 @@ class FieldsTestCase(unittest.TestCase):
self.assertEqual(selection5.select, 'hexa')
self.assertEqual(selection5.dyn_select, None)
- self.assertRaises(Exception, self.selection.create,
+ self.assertRaises(UserError, self.selection.create,
[{'select': 'arabic', 'dyn_select': '0x3'}])
- self.assertRaises(Exception, self.selection.create,
+ self.assertRaises(UserError, self.selection.create,
[{'select': 'hexa', 'dyn_select': '3'}])
- self.assertRaises(Exception, self.selection_required.create, [{}])
+ self.assertRaises(UserError, self.selection_required.create, [{}])
transaction.cursor.rollback()
- self.assertRaises(Exception, self.selection_required.create,
+ self.assertRaises(UserError, self.selection_required.create,
[{'select': None}])
transaction.cursor.rollback()
@@ -3048,13 +3122,13 @@ class FieldsTestCase(unittest.TestCase):
dict3, = self.dict_default.create([{}])
self.assert_(dict3.dico == {'a': 1})
- self.assertRaises(Exception, self.dict_required.create, [{}])
+ self.assertRaises(UserError, self.dict_required.create, [{}])
transaction.cursor.rollback()
dict4, = self.dict_required.create([{'dico': dict(a=1)}])
self.assert_(dict4.dico == {'a': 1})
- self.assertRaises(Exception, self.dict_required.create,
+ self.assertRaises(UserError, self.dict_required.create,
[{'dico': {}}])
transaction.cursor.rollback()
@@ -3081,13 +3155,13 @@ class FieldsTestCase(unittest.TestCase):
bin3, = self.binary_default.create([{}])
self.assert_(bin3.binary == buffer('default'))
- self.assertRaises(Exception, self.binary_required.create, [{}])
+ self.assertRaises(UserError, self.binary_required.create, [{}])
transaction.cursor.rollback()
bin4, = self.binary_required.create([{'binary': buffer('baz')}])
self.assert_(bin4.binary == buffer('baz'))
- self.assertRaises(Exception, self.binary_required.create,
+ self.assertRaises(UserError, self.binary_required.create,
[{'binary': buffer('')}])
transaction.cursor.rollback()
diff --git a/trytond/tests/test_history.py b/trytond/tests/test_history.py
index 5b4c064..74c9176 100644
--- a/trytond/tests/test_history.py
+++ b/trytond/tests/test_history.py
@@ -7,7 +7,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.config import CONFIG
+from trytond import backend
class HistoryTestCase(unittest.TestCase):
@@ -16,6 +16,17 @@ class HistoryTestCase(unittest.TestCase):
def setUp(self):
install_module('tests')
+ def tearDown(self):
+ History = POOL.get('test.history')
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ cursor = transaction.cursor
+ table = History.__table__()
+ history_table = History.__table_history__()
+ cursor.execute(*table.delete())
+ cursor.execute(*history_table.delete())
+ cursor.commit()
+
def test0010read(self):
'Test read history'
History = POOL.get('test.history')
@@ -65,7 +76,7 @@ class HistoryTestCase(unittest.TestCase):
with Transaction().set_context(_datetime=datetime.datetime.min):
self.assertRaises(UserError, History.read, [history_id])
- @unittest.skipIf(CONFIG['db_type'] in ('sqlite', 'mysql'),
+ @unittest.skipIf(backend.name() in ('sqlite', 'mysql'),
'now() is not the start of the transaction')
def test0020read_same_timestamp(self):
'Test read history with same timestamp'
@@ -173,6 +184,204 @@ class HistoryTestCase(unittest.TestCase):
History.restore_history([history_id], datetime.datetime.min)
self.assertRaises(UserError, History.read, [history_id])
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ History.delete([History(history_id)])
+
+ transaction.cursor.commit()
+
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ History.restore_history([history_id], datetime.datetime.max)
+ self.assertRaises(UserError, History.read, [history_id])
+
+ @unittest.skipIf(backend.name() in ('sqlite', 'mysql'),
+ 'now() is not the start of the transaction')
+ def test0045restore_history_same_timestamp(self):
+ 'Test restore history with same timestamp'
+ 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
+ first = history.create_date
+ history.value = 2
+ history.save()
+ second = history.create_date
+
+ self.assertEqual(first, second)
+
+ 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([history_id], first)
+ history = History(history_id)
+ self.assertEqual(history.value, 2)
+
+ def test0050ordered_search(self):
+ 'Test ordered search of history models'
+ History = POOL.get('test.history')
+ order = [('value', 'ASC')]
+
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ history = History(value=1)
+ history.save()
+ first_id = history.id
+ first_stamp = history.create_date
+ transaction.cursor.commit()
+
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ history = History(value=2)
+ history.save()
+ second_id = history.id
+ second_stamp = history.create_date
+
+ transaction.cursor.commit()
+
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ first, second = History.search([], order=order)
+
+ self.assertEqual(first.id, first_id)
+ self.assertEqual(second.id, second_id)
+
+ first.value = 3
+ first.save()
+ third_stamp = first.write_date
+ transaction.cursor.commit()
+
+ results = [
+ (first_stamp, [first]),
+ (second_stamp, [first, second]),
+ (third_stamp, [second, first]),
+ (datetime.datetime.now(), [second, first]),
+ (datetime.datetime.max, [second, first]),
+ ]
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ for timestamp, instances in results:
+ with Transaction().set_context(_datetime=timestamp):
+ records = History.search([], order=order)
+ self.assertEqual(records, instances)
+
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ to_delete, _ = History.search([], order=order)
+
+ self.assertEqual(to_delete.id, second.id)
+
+ History.delete([to_delete])
+ transaction.cursor.commit()
+
+ results = [
+ (first_stamp, [first]),
+ (second_stamp, [first, second]),
+ (third_stamp, [second, first]),
+ (datetime.datetime.now(), [first]),
+ (datetime.datetime.max, [first]),
+ ]
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ for timestamp, instances in results:
+ with Transaction().set_context(_datetime=timestamp,
+ from_test=True):
+ records = History.search([], order=order)
+ self.assertEqual(records, instances)
+
+ @unittest.skipIf(backend.name() in ('sqlite', 'mysql'),
+ 'now() is not the start of the transaction')
+ def test0060_ordered_search_same_timestamp(self):
+ 'Test ordered search with same timestamp'
+ History = POOL.get('test.history')
+ order = [('value', 'ASC')]
+
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ history = History(value=1)
+ history.save()
+ first_stamp = history.create_date
+ history.value = 4
+ history.save()
+ second_stamp = history.write_date
+
+ self.assertEqual(first_stamp, second_stamp)
+ transaction.cursor.commit()
+
+ results = [
+ (second_stamp, [history], [4]),
+ (datetime.datetime.now(), [history], [4]),
+ (datetime.datetime.max, [history], [4]),
+ ]
+
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ for timestamp, instances, values in results:
+ with Transaction().set_context(_datetime=timestamp,
+ last_test=True):
+ records = History.search([], order=order)
+ self.assertEqual(records, instances)
+ self.assertEqual([x.value for x in records], values)
+
+ def test0070_browse(self):
+ 'Test browsing history'
+ History = POOL.get('test.history')
+ Line = POOL.get('test.history.line')
+
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ history = History(value=1)
+ history.save()
+ history_id = history.id
+ line_a = Line(name='a', history=history)
+ line_a.save()
+ line_a_id = line_a.id
+ line_b = Line(name='b', history=history)
+ line_b.save()
+ line_b_id = line_b.id
+
+ first_stamp = line_b.create_date
+
+ transaction.cursor.commit()
+
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ history = History(history_id)
+ history.value = 2
+ history.save()
+
+ Line.delete([Line(line_b_id)])
+
+ line_a = Line(line_a_id)
+ line_a.name = 'c'
+ line_a.save()
+
+ second_stamp = line_a.write_date
+
+ transaction.cursor.commit()
+
+ with Transaction().start(DB_NAME, USER,
+ context=CONTEXT) as transaction:
+ history = History(history_id)
+ self.assertEqual(history.value, 2)
+ self.assertEqual([l.name for l in history.lines], ['c'])
+
+ with Transaction().set_context(_datetime=first_stamp):
+ history = History(history_id)
+ self.assertEqual(history.value, 1)
+ self.assertEqual([l.name for l in history.lines], ['a', 'b'])
+
+ with Transaction().set_context(_datetime=second_stamp):
+ history = History(history_id)
+ self.assertEqual(history.value, 2)
+ self.assertEqual([l.name for l in history.lines], ['c'])
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(HistoryTestCase)
diff --git a/trytond/tests/test_modelsql.py b/trytond/tests/test_modelsql.py
index 233ed9a..e9ea26d 100644
--- a/trytond/tests/test_modelsql.py
+++ b/trytond/tests/test_modelsql.py
@@ -5,7 +5,7 @@
import unittest
import time
-from trytond.config import CONFIG
+from trytond import backend
from trytond.exceptions import UserError, ConcurrencyException
from trytond.transaction import Transaction
from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
@@ -20,13 +20,12 @@ class ModelSQLTestCase(unittest.TestCase):
self.modelsql = POOL.get('test.modelsql')
self.modelsql_timestamp = POOL.get('test.modelsql.timestamp')
+ @unittest.skipIf(backend.name() == 'sqlite',
+ 'SQLite not concerned because tryton don\'t set "NOT NULL"'
+ 'constraint: "ALTER TABLE" don\'t support NOT NULL constraint'
+ 'without default value')
def test0010required_field_missing(self):
'Test error message when a required field is missing'
- if CONFIG['db_type'] not in ('postgresql', 'mysql'):
- # SQLite not concerned because tryton don't set "NOT NULL"
- # constraint: 'ALTER TABLE' don't support NOT NULL constraint
- # without default value
- return
fields = {
'desc': '',
'integer': 0,
@@ -56,7 +55,7 @@ class ModelSQLTestCase(unittest.TestCase):
timestamp = self.modelsql_timestamp.read([record.id],
['_timestamp'])[0]['_timestamp']
- if CONFIG['db_type'] in ('sqlite', 'mysql'):
+ if backend.name() in ('sqlite', 'mysql'):
# timestamp precision of sqlite is the second
time.sleep(1)
diff --git a/trytond/tests/test_mptt.py b/trytond/tests/test_mptt.py
index 7631456..6bbd37b 100644
--- a/trytond/tests/test_mptt.py
+++ b/trytond/tests/test_mptt.py
@@ -3,6 +3,7 @@
#this repository contains the full copyright notices and license terms.
import sys
import unittest
+from mock import patch
from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
install_module
from trytond.transaction import Transaction
@@ -163,6 +164,23 @@ class MPTTTestCase(unittest.TestCase):
transaction.cursor.rollback()
+ def test0060_update_only_if_parent_is_modified(self):
+ 'The left and right fields must only be updated if parent is modified'
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ records = self.mptt.search([
+ ('parent', '=', None),
+ ])
+ with patch.object(self.mptt, '_update_tree') as mock:
+ self.mptt.write(records, {'name': 'Parent Records'})
+ self.assertFalse(mock.called)
+
+ first_parent, second_parent = records[:2]
+ self.mptt.write(list(first_parent.childs), {
+ 'parent': second_parent.id,
+ })
+
+ self.assertTrue(mock.called)
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(MPTTTestCase)
diff --git a/trytond/tests/test_protocols.py b/trytond/tests/test_protocols.py
new file mode 100644
index 0000000..ee445ca
--- /dev/null
+++ b/trytond/tests/test_protocols.py
@@ -0,0 +1,76 @@
+#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
+import datetime
+from decimal import Decimal
+
+from trytond.protocols.jsonrpc import JSONEncoder, JSONDecoder
+from trytond.protocols.xmlrpc import xmlrpclib
+
+
+class JSONTestCase(unittest.TestCase):
+ 'Test JSON'
+
+ def dumps_loads(self, value):
+ self.assertEqual(json.loads(
+ json.dumps(value, cls=JSONEncoder),
+ object_hook=JSONDecoder()), value)
+
+ def test_datetime(self):
+ 'Test datetime'
+ self.dumps_loads(datetime.datetime.now())
+
+ def test_date(self):
+ 'Test date'
+ self.dumps_loads(datetime.date.today())
+
+ def test_time(self):
+ 'Test time'
+ self.dumps_loads(datetime.datetime.now().time())
+
+ def test_buffer(self):
+ 'Test buffer'
+ self.dumps_loads(buffer('foo'))
+
+ def test_decimal(self):
+ 'Test Decimal'
+ self.dumps_loads(Decimal('3.141592653589793'))
+
+
+class XMLTestCase(unittest.TestCase):
+ 'Test XML'
+
+ def dumps_loads(self, value):
+ s = xmlrpclib.dumps((value,))
+ result, _ = xmlrpclib.loads(s)
+ result, = result
+ self.assertEqual(value, result)
+
+ def test_decimal(self):
+ 'Test Decimal'
+ self.dumps_loads(Decimal('3.141592653589793'))
+
+ def test_buffer(self):
+ 'Test buffer'
+ self.dumps_loads(buffer('foo'))
+
+ def test_date(self):
+ 'Test date'
+ self.dumps_loads(datetime.date.today())
+
+ def test_time(self):
+ 'Test time'
+ self.dumps_loads(datetime.datetime.now().time())
+
+ def test_none(self):
+ 'Test None'
+ self.dumps_loads(None)
+
+
+def suite():
+ suite_ = unittest.TestSuite()
+ suite_.addTests(unittest.TestLoader().loadTestsFromTestCase(JSONTestCase))
+ suite_.addTests(unittest.TestLoader().loadTestsFromTestCase(XMLTestCase))
+ return suite_
diff --git a/trytond/tests/test_sequence.py b/trytond/tests/test_sequence.py
index 727bb1c..802372d 100644
--- a/trytond/tests/test_sequence.py
+++ b/trytond/tests/test_sequence.py
@@ -6,6 +6,7 @@ import datetime
from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
install_module
from trytond.transaction import Transaction
+from trytond.exceptions import UserError
class SequenceTestCase(unittest.TestCase):
@@ -58,7 +59,7 @@ class SequenceTestCase(unittest.TestCase):
self.assertNotEqual(self.sequence.get_id(sequence), timestamp)
next_timestamp = self.sequence._timestamp(sequence)
- self.assertRaises(Exception, self.sequence.write, [sequence], {
+ self.assertRaises(UserError, self.sequence.write, [sequence], {
'last_timestamp': next_timestamp + 100,
})
@@ -82,7 +83,7 @@ class SequenceTestCase(unittest.TestCase):
self.assertNotEqual(self.sequence.get_id(sequence), timestamp)
next_timestamp = self.sequence._timestamp(sequence)
- self.assertRaises(Exception, self.sequence.write, [sequence], {
+ self.assertRaises(UserError, self.sequence.write, [sequence], {
'last_timestamp': next_timestamp + 100,
})
diff --git a/trytond/tests/test_tools.py b/trytond/tests/test_tools.py
index 0b9523c..3a5c3ad 100644
--- a/trytond/tests/test_tools.py
+++ b/trytond/tests/test_tools.py
@@ -60,7 +60,7 @@ class ToolsTestCase(unittest.TestCase):
def test0060safe_eval_builtin(self):
'Attempt to access a unsafe builtin'
- self.assertRaises(Exception, safe_eval, "open('test.txt', 'w')")
+ self.assertRaises(NameError, safe_eval, "open('test.txt', 'w')")
def test0061safe_eval_getattr(self):
'Attempt to get arround direct attr access'
@@ -68,12 +68,12 @@ class ToolsTestCase(unittest.TestCase):
def test0062safe_eval_func_globals(self):
'Attempt to access global enviroment where fun was defined'
- self.assertRaises(Exception, safe_eval,
+ self.assertRaises(SyntaxError, safe_eval,
"def x(): pass; print x.func_globals")
def test0063safe_eval_lowlevel(self):
"Lowlevel tricks to access 'object'"
- self.assertRaises(Exception, safe_eval,
+ self.assertRaises(ValueError, safe_eval,
"().__class__.mro()[1].__subclasses__()")
def test0070datetime_strftime(self):
diff --git a/trytond/tests/test_trigger.py b/trytond/tests/test_trigger.py
index 23bd815..200aedd 100644
--- a/trytond/tests/test_trigger.py
+++ b/trytond/tests/test_trigger.py
@@ -10,6 +10,7 @@ from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
install_module
from trytond.tests.trigger import TRIGGER_LOGS
from trytond.transaction import Transaction
+from trytond.exceptions import UserError
class TriggerTestCase(unittest.TestCase):
@@ -24,8 +25,7 @@ class TriggerTestCase(unittest.TestCase):
def test0010constraints(self):
'Test constraints'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
model, = self.model.search([
('model', '=', 'test.triggered'),
])
@@ -43,25 +43,27 @@ class TriggerTestCase(unittest.TestCase):
}
self.assert_(self.trigger.create([values]))
- # on_exclusive
- for i in range(1, 4):
- for combination in combinations(
- ['create', 'write', 'delete'], i):
+ # on_exclusive
+ for i in range(1, 4):
+ for combination in combinations(
+ ['create', 'write', 'delete'], i):
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
combination_values = values.copy()
for mode in combination:
combination_values['on_%s' % mode] = True
- self.assertRaises(Exception, self.trigger.create,
+ self.assertRaises(UserError, self.trigger.create,
[combination_values])
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
# check_condition
condition_values = values.copy()
condition_values['condition'] = '='
- self.assertRaises(Exception, self.trigger.create,
+ self.assertRaises(UserError, self.trigger.create,
[condition_values])
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
# Restart the cache on the get_triggers method of ir.trigger
self.trigger._get_triggers_cache.clear()
- transaction.cursor.rollback()
def test0020on_create(self):
'Test on_create'
diff --git a/trytond/tests/test_tryton.py b/trytond/tests/test_tryton.py
index dd0cf7d..94bfab9 100644
--- a/trytond/tests/test_tryton.py
+++ b/trytond/tests/test_tryton.py
@@ -7,15 +7,17 @@ import unittest
import doctest
from lxml import etree
-from trytond.config import CONFIG
from trytond.pool import Pool
from trytond import backend
-from trytond.protocols.dispatcher import create
+from trytond.protocols.dispatcher import create, drop
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', 'doctest_dropdb',
+ 'install_module', 'test_view', 'test_depends',
+ 'doctest_setup', 'doctest_teardown',
'suite', 'all_suite', 'modules_suite']
Pool.start()
@@ -26,6 +28,7 @@ DB_NAME = os.environ['DB_NAME']
DB = backend.get('Database')(DB_NAME)
Pool.test = True
POOL = Pool(DB_NAME)
+security.check_super = lambda *a, **k: True
class ModelViewTestCase(unittest.TestCase):
@@ -38,8 +41,8 @@ class ModelViewTestCase(unittest.TestCase):
def test0000test(self):
'Test test'
- self.assertRaises(Exception, install_module, 'nosuchmodule')
- self.assertRaises(Exception, test_view, 'nosuchmodule')
+ self.assertRaises(UserError, install_module, 'nosuchmodule')
+ self.assertRaises(UserError, test_view, 'nosuchmodule')
def test0010ir(self):
'Test ir'
@@ -71,13 +74,7 @@ def install_module(name):
'''
Install module for the tested database
'''
- Database = backend.get('Database')
- database = Database().connect()
- cursor = database.cursor()
- databases = database.list(cursor)
- cursor.close()
- if DB_NAME not in databases:
- create(DB_NAME, CONFIG['admin_passwd'], 'en_US', USER_PASSWORD)
+ create_db()
with Transaction().start(DB_NAME, USER,
context=CONTEXT) as transaction:
Module = POOL.get('ir.module.module')
@@ -176,16 +173,32 @@ def test_depends():
list(depends - set(model._fields)), mname, fname))
-def doctest_dropdb(test):
- '''Remove SQLite memory database'''
- from trytond.backend.sqlite.database import Database as SQLiteDatabase
- database = SQLiteDatabase().connect()
- cursor = database.cursor(autocommit=True)
- try:
- SQLiteDatabase.drop(cursor, ':memory:')
- cursor.commit()
- finally:
- cursor.close()
+def db_exist():
+ Database = backend.get('Database')
+ database = Database().connect()
+ cursor = database.cursor()
+ databases = database.list(cursor)
+ cursor.close()
+ return DB_NAME in databases
+
+
+def create_db():
+ if not db_exist():
+ create(DB_NAME, None, 'en_US', USER_PASSWORD)
+
+
+def drop_db():
+ if db_exist():
+ drop(DB_NAME, None)
+
+
+def drop_create():
+ if db_exist:
+ drop_db()
+ create_db()
+
+doctest_setup = lambda test: drop_create()
+doctest_teardown = lambda test: drop_db()
def suite():
diff --git a/trytond/tests/test_union.py b/trytond/tests/test_union.py
new file mode 100644
index 0000000..723c38c
--- /dev/null
+++ b/trytond/tests/test_union.py
@@ -0,0 +1,102 @@
+#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
+from trytond.transaction import Transaction
+
+
+class UnionMixinTestCase(unittest.TestCase):
+ 'Test UnionMixin'
+
+ def setUp(self):
+ install_module('tests')
+
+ def test_union(self):
+ 'Test union'
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ Union = POOL.get('test.union')
+ for i in range(1, 4):
+ Model = POOL.get('test.model.union%s' % i)
+ for j in range(3):
+ model = Model(name='%s - %s' % (i, j))
+ if hasattr(Model, 'optional'):
+ model.optional = 'optional'
+ model.save()
+
+ self.assertEqual(len(Union.search([])), 9)
+ record, = Union.search([
+ ('name', '=', '2 - 2'),
+ ])
+ self.assertEqual(record.name, '2 - 2')
+ self.assertEqual(record.optional, None)
+
+ record, = Union.search([
+ ('optional', '=', 'optional'),
+ ], limit=1)
+ self.assertEqual(record.optional, 'optional')
+
+ def test_union_union(self):
+ 'Test union of union'
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ Union = POOL.get('test.union.union')
+ for i in range(1, 5):
+ Model = POOL.get('test.model.union%s' % i)
+ for j in range(3):
+ model = Model(name='%s - %s' % (i, j))
+ model.save()
+
+ self.assertEqual(len(Union.search([])), 12)
+ record, = Union.search([
+ ('name', '=', '2 - 2'),
+ ])
+ self.assertEqual(record.name, '2 - 2')
+ record, = Union.search([
+ ('name', '=', '4 - 1'),
+ ])
+ self.assertEqual(record.name, '4 - 1')
+
+ def test_union_tree(self):
+ 'Test union tree'
+ with Transaction().start(DB_NAME, USER, context=CONTEXT):
+ Union = POOL.get('test.union.tree')
+ Model1 = POOL.get('test.model.union.tree1')
+ Model2 = POOL.get('test.model.union.tree2')
+
+ roots = Model1.create([{
+ 'name': 'Root1',
+ }, {
+ 'name': 'Root2',
+ }, {
+ 'name': 'Root3',
+ }])
+
+ Model2.create([{
+ 'name': 'Not child', # To shift ids
+ }, {
+ 'name': 'Child1',
+ 'parent': roots[1].id,
+ }, {
+ 'name': 'Child2',
+ 'parent': roots[1].id,
+ }, {
+ 'name': 'Child3',
+ 'parent': roots[2].id,
+ }])
+
+ uroots = Union.search([('parent', '=', None)],
+ order=[('name', 'ASC')])
+
+ self.assertEqual(len(uroots), 4)
+ names = [r.name for r in uroots]
+ self.assertEqual(names, ['Not child', 'Root1', 'Root2', 'Root3'])
+ childs = [len(r.childs) for r in uroots]
+ self.assertEqual(childs, [0, 0, 2, 1])
+ child_names = sorted((r.name for r in uroots[2].childs))
+ self.assertEqual(child_names, ['Child1', 'Child2'])
+ self.assertEqual(uroots[3].childs[0].name, 'Child3')
+
+
+def suite():
+ return unittest.TestLoader().loadTestsFromTestCase(UnionMixinTestCase)
diff --git a/trytond/tools/misc.py b/trytond/tools/misc.py
index 80caf52..ebe7c66 100644
--- a/trytond/tools/misc.py
+++ b/trytond/tools/misc.py
@@ -12,10 +12,13 @@ import smtplib
import dis
from decimal import Decimal
from array import array
+from itertools import islice
from sql import Literal
from sql.operators import Or
-from trytond.config import CONFIG
+
from trytond.const import OPERATORS
+from trytond.config import config, parse_uri
+from trytond.transaction import Transaction
def find_in_path(name):
@@ -32,42 +35,7 @@ def find_in_path(name):
return name
-def find_pg_tool(name):
- if CONFIG['pg_path'] and CONFIG['pg_path'] != 'None':
- return os.path.join(CONFIG['pg_path'], name)
- else:
- return find_in_path(name)
-
-
-def exec_pg_command(name, *args):
- prog = find_pg_tool(name)
- if not prog:
- raise Exception('Couldn\'t find %s' % name)
- args2 = (os.path.basename(prog),) + args
- return os.spawnv(os.P_WAIT, prog, args2)
-
-
-def exec_pg_command_pipe(name, *args):
- prog = find_pg_tool(name)
- if not prog:
- raise Exception('Couldn\'t find %s' % name)
- if os.name == "nt":
- cmd = '"' + prog + '" ' + ' '.join(args)
- else:
- cmd = prog + ' ' + ' '.join(args)
-
- # if db_password is set in configuration we should pass
- # an environment variable PGPASSWORD to our subprocess
- # see libpg documentation
- child_env = dict(os.environ)
- if CONFIG['db_password']:
- child_env['PGPASSWORD'] = CONFIG['db_password']
- pipe = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, env=child_env)
- return pipe
-
-
-def exec_command_pipe(name, *args):
+def exec_command_pipe(name, *args, **kwargs):
prog = find_in_path(name)
if not prog:
raise Exception('Couldn\'t find %s' % name)
@@ -75,7 +43,11 @@ def exec_command_pipe(name, *args):
cmd = '"' + prog + '" ' + ' '.join(args)
else:
cmd = prog + ' ' + ' '.join(args)
- return os.popen2(cmd, 'b')
+ child_env = dict(os.environ)
+ if kwargs.get('env'):
+ child_env.update(kwargs['env'])
+ return subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, env=child_env)
def file_open(name, mode="r", subdir='modules'):
@@ -132,17 +104,17 @@ def get_smtp_server():
:return: A SMTP instance. The quit() method must be call when all
the calls to sendmail() have been made.
"""
- if CONFIG['smtp_ssl']:
- smtp_server = smtplib.SMTP_SSL(CONFIG['smtp_server'],
- CONFIG['smtp_port'])
+ uri = parse_uri(config.get('email', 'uri'))
+ if uri.scheme.startswith('smtps'):
+ smtp_server = smtplib.SMTP_SSL(uri.hostname, uri.port)
else:
- smtp_server = smtplib.SMTP(CONFIG['smtp_server'], CONFIG['smtp_port'])
+ smtp_server = smtplib.SMTP(uri.hostname, uri.port)
- if CONFIG['smtp_tls']:
+ if 'tls' in uri.scheme:
smtp_server.starttls()
- if CONFIG['smtp_user'] and CONFIG['smtp_password']:
- smtp_server.login(CONFIG['smtp_user'], CONFIG['smtp_password'])
+ if uri.username and uri.password:
+ smtp_server.login(uri.username, uri.password)
return smtp_server
@@ -328,6 +300,7 @@ def reduce_ids(field, ids):
'''
Return a small SQL expression for the list of ids and the sql column
'''
+ ids = list(ids)
if not ids:
return Literal(False)
assert all(x.is_integer() for x in ids if isinstance(x, float)), \
@@ -442,3 +415,11 @@ def reduce_domain(domain):
else:
result.append(arg)
return result
+
+
+def grouped_slice(records, count=None):
+ 'Grouped slice'
+ if count is None:
+ count = Transaction().cursor.IN_MAX
+ for i in xrange(0, len(records), count):
+ yield islice(records, i, i + count)
diff --git a/trytond/url.py b/trytond/url.py
index ce6dd11..be17878 100644
--- a/trytond/url.py
+++ b/trytond/url.py
@@ -5,12 +5,12 @@ import encodings.idna
import urllib
import socket
-from trytond.config import CONFIG
+from trytond.config import config
from trytond.transaction import Transaction
__all__ = ['URLMixin']
-HOSTNAME = (CONFIG['hostname_jsonrpc']
+HOSTNAME = (config.get('jsonrpc', 'hostname')
or unicode(socket.getfqdn(), 'utf8'))
HOSTNAME = '.'.join(encodings.idna.ToASCII(part) if part else ''
for part in HOSTNAME.split('.'))
diff --git a/trytond/version.py b/trytond/version.py
index 063c087..a68a845 100644
--- a/trytond/version.py
+++ b/trytond/version.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.
PACKAGE = "trytond"
-VERSION = "3.2.3"
+VERSION = "3.4.0"
LICENSE = "GPL-3"
WEBSITE = "http://www.tryton.org/"
diff --git a/trytond/webdav/locale/sl_SI.po b/trytond/webdav/locale/es_EC.po
similarity index 72%
copy from trytond/webdav/locale/sl_SI.po
copy to trytond/webdav/locale/es_EC.po
index c9550c2..cac92a0 100644
--- a/trytond/webdav/locale/sl_SI.po
+++ b/trytond/webdav/locale/es_EC.po
@@ -7,28 +7,28 @@ msgid ""
"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 "
-"ustvariti, ker že obstaja zbirka z istim imenom."
+"No puede crear un adjunto llamado \"%(attachment)s\" en la colección "
+"\"%(collections)s\" porque ya existe una colección con este nombre."
msgctxt "error:webdav.collection:"
msgid "The collection name must be unique inside a collection!"
-msgstr "Ime zbirke mora biti edinstveno znotraj zbirke."
+msgstr "¡El nombre de la colección debe ser único dentro de una colección!"
msgctxt "error:webdav.collection:"
msgid ""
"You can not create a collection named \"%(parent)s\" in collection "
"\"%(child)s\" because there is already a file with that name."
msgstr ""
-"Zbirke z imenom \"%(parent)s\" v zbirki \"%(child)s\" ni možno ustvariti, "
-"ker že obstaja datoteka z istim imenom."
+"No puede crear una colección llamada \"%(parent)s\" en la colección "
+"\"%(child)s\" porque ya existe un archivo con ese nombre."
msgctxt "field:ir.attachment,path:"
msgid "Path"
-msgstr "Pot"
+msgstr "Ruta"
msgctxt "field:ir.attachment,shares:"
msgid "Shares"
-msgstr "Skupne rabe"
+msgstr "Recursos Compartidos"
msgctxt "field:ir.attachment,url:"
msgid "URL"
@@ -36,23 +36,23 @@ msgstr "URL"
msgctxt "field:webdav.collection,childs:"
msgid "Children"
-msgstr "Podzbirke"
+msgstr "Hijos"
msgctxt "field:webdav.collection,complete_name:"
msgid "Complete Name"
-msgstr "Polno ime"
+msgstr "Nombre Completo"
msgctxt "field:webdav.collection,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Fecha de Creación"
msgctxt "field:webdav.collection,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Creado por Usuario"
msgctxt "field:webdav.collection,domain:"
msgid "Domain"
-msgstr "Domena"
+msgstr "Dominio"
msgctxt "field:webdav.collection,id:"
msgid "ID"
@@ -60,39 +60,39 @@ msgstr "ID"
msgctxt "field:webdav.collection,model:"
msgid "Model"
-msgstr "Model"
+msgstr "Modelo"
msgctxt "field:webdav.collection,name:"
msgid "Name"
-msgstr "Naziv"
+msgstr "Nombre"
msgctxt "field:webdav.collection,parent:"
msgid "Parent"
-msgstr "Prednik"
+msgstr "Padre"
msgctxt "field:webdav.collection,rec_name:"
msgid "Name"
-msgstr "Ime"
+msgstr "Nombre"
msgctxt "field:webdav.collection,write_date:"
msgid "Write Date"
-msgstr "Zapisano"
+msgstr "Fecha de Modificación"
msgctxt "field:webdav.collection,write_uid:"
msgid "Write User"
-msgstr "Zapisal"
+msgstr "Modificado por Usuario"
msgctxt "field:webdav.share,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Fecha de Creación"
msgctxt "field:webdav.share,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Creado por Usuario"
msgctxt "field:webdav.share,expiration_date:"
msgid "Expiration Date"
-msgstr "Datum veljavnosti"
+msgstr "Fecha de Expiración"
msgctxt "field:webdav.share,id:"
msgid "ID"
@@ -100,19 +100,19 @@ msgstr "ID"
msgctxt "field:webdav.share,key:"
msgid "Key"
-msgstr "Ključ"
+msgstr "Clave"
msgctxt "field:webdav.share,note:"
msgid "Note"
-msgstr "Opomba"
+msgstr "Nota"
msgctxt "field:webdav.share,path:"
msgid "Path"
-msgstr "Pot"
+msgstr "Ruta"
msgctxt "field:webdav.share,rec_name:"
msgid "Name"
-msgstr "Ime"
+msgstr "Nombre"
msgctxt "field:webdav.share,url:"
msgid "URL"
@@ -120,39 +120,39 @@ msgstr "URL"
msgctxt "field:webdav.share,user:"
msgid "User"
-msgstr "Uporabnik"
+msgstr "Usuario"
msgctxt "field:webdav.share,write_date:"
msgid "Write Date"
-msgstr "Zapisano"
+msgstr "Fecha de Modificación"
msgctxt "field:webdav.share,write_uid:"
msgid "Write User"
-msgstr "Zapisal"
+msgstr "Modificado por Usuario"
msgctxt "model:ir.action,name:act_collection_list"
msgid "Collections"
-msgstr "Zbirke"
+msgstr "Colecciones"
msgctxt "model:ir.action,name:act_collection_tree"
msgid "Collections"
-msgstr "Zbirke"
+msgstr "Colecciones"
msgctxt "model:ir.action,name:act_share_list"
msgid "Shares"
-msgstr "Skupne rabe"
+msgstr "Recursos Compartidos"
msgctxt "model:ir.ui.menu,name:menu_collection_list"
msgid "Collections"
-msgstr "Zbirke"
+msgstr "Colecciones"
msgctxt "model:ir.ui.menu,name:menu_collection_tree"
msgid "Collections"
-msgstr "Zbirke"
+msgstr "Colecciones"
msgctxt "model:ir.ui.menu,name:menu_share_list"
msgid "Shares"
-msgstr "Skupne rabe"
+msgstr "Recursos Compartidos"
msgctxt "model:ir.ui.menu,name:menu_webdav"
msgid "WebDAV"
@@ -160,11 +160,11 @@ msgstr "WebDAV"
msgctxt "model:webdav.collection,name:"
msgid "Collection"
-msgstr "Zbirka"
+msgstr "Colección"
msgctxt "model:webdav.share,name:"
msgid "Share"
-msgstr "Skupna raba"
+msgstr "Recurso Compartido"
msgctxt "view:ir.attachment:"
msgid "WebDAV"
@@ -172,16 +172,16 @@ msgstr "WebDAV"
msgctxt "view:webdav.collection:"
msgid "Collection"
-msgstr "Zbirka"
+msgstr "Colección"
msgctxt "view:webdav.collection:"
msgid "Collections"
-msgstr "Zbirke"
+msgstr "Colecciones"
msgctxt "view:webdav.share:"
msgid "Share"
-msgstr "Skupna raba"
+msgstr "Recurso Compartido"
msgctxt "view:webdav.share:"
msgid "Shares"
-msgstr "Skupne rabe"
+msgstr "Recursos Compartidos"
diff --git a/trytond/webdav/locale/fr_FR.po b/trytond/webdav/locale/fr_FR.po
index c94374f..1d0062d 100644
--- a/trytond/webdav/locale/fr_FR.po
+++ b/trytond/webdav/locale/fr_FR.po
@@ -7,20 +7,20 @@ msgid ""
"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 "
-"collection \"%(collection)s\" car il y a déjà une collection avec ce nom."
+"Vous ne pouvez créer une pièce jointe nommée « %(attachment)s » dans la "
+"collection « %(collection)s » car il y a déjà une collection avec ce nom."
msgctxt "error:webdav.collection:"
msgid "The collection name must be unique inside a collection!"
-msgstr "Le nom de collection doit être unique au sein d'une collection !"
+msgstr "Le nom de collection doit être unique au sein d'une collection !"
msgctxt "error:webdav.collection:"
msgid ""
"You can not create a collection named \"%(parent)s\" in collection "
"\"%(child)s\" because there is already a file with that name."
msgstr ""
-"Vous ne pouvez pas créer une collection nommée \"%(parent)s\" dans la "
-"collection \"%(child)s\" car il y a déjà un fichier avec ce nom."
+"Vous ne pouvez pas créer une collection nommée « %(parent)s » dans la "
+"collection « %(child)s » car il y a déjà un fichier avec ce nom."
msgctxt "field:ir.attachment,path:"
msgid "Path"
diff --git a/trytond/webdav/locale/sl_SI.po b/trytond/webdav/locale/sl_SI.po
index c9550c2..b1f2bb1 100644
--- a/trytond/webdav/locale/sl_SI.po
+++ b/trytond/webdav/locale/sl_SI.po
@@ -8,7 +8,7 @@ msgid ""
"\"%(collection)s\" because there is already a collection with that name."
msgstr ""
"Priloge z imenom \"%(attachment)s\" v zbirki \"%(collection)s\" ni možno "
-"ustvariti, ker že obstaja zbirka z istim imenom."
+"izdelati, ker že obstaja zbirka z istim imenom."
msgctxt "error:webdav.collection:"
msgid "The collection name must be unique inside a collection!"
@@ -19,8 +19,8 @@ msgid ""
"You can not create a collection named \"%(parent)s\" in collection "
"\"%(child)s\" because there is already a file with that name."
msgstr ""
-"Zbirke z imenom \"%(parent)s\" v zbirki \"%(child)s\" ni možno ustvariti, "
-"ker že obstaja datoteka z istim imenom."
+"Zbirke z imenom \"%(parent)s\" v zbirki \"%(child)s\" ni možno izdelati, ker"
+" že obstaja datoteka z istim imenom."
msgctxt "field:ir.attachment,path:"
msgid "Path"
@@ -44,11 +44,11 @@ msgstr "Polno ime"
msgctxt "field:webdav.collection,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:webdav.collection,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:webdav.collection,domain:"
msgid "Domain"
@@ -84,11 +84,11 @@ msgstr "Zapisal"
msgctxt "field:webdav.share,create_date:"
msgid "Create Date"
-msgstr "Ustvarjeno"
+msgstr "Izdelano"
msgctxt "field:webdav.share,create_uid:"
msgid "Create User"
-msgstr "Ustvaril"
+msgstr "Izdelal"
msgctxt "field:webdav.share,expiration_date:"
msgid "Expiration Date"
diff --git a/trytond/webdav/webdav.py b/trytond/webdav/webdav.py
index ef55270..9817f49 100644
--- a/trytond/webdav/webdav.py
+++ b/trytond/webdav/webdav.py
@@ -18,8 +18,9 @@ from trytond.model import ModelView, ModelSQL, fields
from trytond.tools import reduce_ids
from trytond.transaction import Transaction
from trytond.pool import Pool
-from trytond.config import CONFIG
+from trytond.config import config
from trytond.pyson import Eval
+from trytond.tools import grouped_slice
__all__ = [
'Collection', 'Share', 'Attachment',
@@ -27,11 +28,11 @@ __all__ = [
def get_webdav_url():
- if CONFIG['ssl_webdav']:
+ if config.get('ssl', 'privatekey'):
protocol = 'https'
else:
protocol = 'http'
- hostname = (CONFIG['hostname_webdav']
+ hostname = (config.get('webdav', 'hostname')
or unicode(socket.getfqdn(), 'utf8'))
hostname = '.'.join(encodings.idna.ToASCII(part) for part in
hostname.split('.'))
@@ -434,8 +435,7 @@ class Collection(ModelSQL, ModelView):
res = None
cursor = Transaction().cursor
table = Model.__table__()
- for i in range(0, len(ids), cursor.IN_MAX):
- sub_ids = ids[i:i + cursor.IN_MAX]
+ for sub_ids in grouped_slice(ids):
red_sql = reduce_ids(table.id, sub_ids)
cursor.execute(*table.select(table.id,
Extract('EPOCH', table.create_date),
@@ -471,8 +471,7 @@ class Collection(ModelSQL, ModelView):
res = None
cursor = Transaction().cursor
table = Model.__table__()
- for i in range(0, len(ids), cursor.IN_MAX):
- sub_ids = ids[i:i + cursor.IN_MAX]
+ for sub_ids in grouped_slice(ids):
red_sql = reduce_ids(table.id, sub_ids)
cursor.execute(*table.select(table.id,
Extract('EPOCH',
diff --git a/trytond/wizard/wizard.py b/trytond/wizard/wizard.py
index c3c725e..e92c35c 100644
--- a/trytond/wizard/wizard.py
+++ b/trytond/wizard/wizard.py
@@ -13,10 +13,11 @@ from trytond.pool import Pool, PoolBase
from trytond.transaction import Transaction
from trytond.error import WarningErrorMixin
from trytond.url import URLMixin
-from trytond.protocols.jsonrpc import object_hook, JSONEncoder
+from trytond.protocols.jsonrpc import JSONDecoder, JSONEncoder
from trytond.model.fields import states_validate
from trytond.pyson import PYSONEncoder
from trytond.rpc import RPC
+from trytond.exceptions import UserError
class Button(object):
@@ -152,7 +153,7 @@ class Wizard(WarningErrorMixin, URLMixin, PoolBase):
cls.__rpc__ = {
'create': RPC(readonly=False),
'delete': RPC(readonly=False),
- 'execute': RPC(readonly=False),
+ 'execute': RPC(readonly=False, check_access=False),
}
cls._error_messages = {}
@@ -181,9 +182,31 @@ class Wizard(WarningErrorMixin, URLMixin, PoolBase):
Translation.register_error_messages(cls, module_name)
@classmethod
+ def check_access(cls):
+ pool = Pool()
+ ModelAccess = pool.get('ir.model.access')
+ ActionWizard = pool.get('ir.action.wizard')
+ User = pool.get('res.user')
+ context = Transaction().context
+
+ if Transaction().user == 0:
+ return
+
+ model = context.get('active_model')
+ if model:
+ ModelAccess.check(model, 'read')
+ ModelAccess.check(model, 'write')
+ groups = set(User.get_groups())
+ wizard_groups = ActionWizard.get_groups(cls.__name__,
+ action_id=context.get('action_id'))
+ if wizard_groups and not groups & wizard_groups:
+ raise UserError('Calling wizard %s is not allowed!' % cls.__name__)
+
+ @classmethod
def create(cls):
"Create a session"
Session = Pool().get('ir.session.wizard')
+ cls.check_access()
return (Session.create([{}])[0].id, cls.start_state, cls.end_state)
@classmethod
@@ -215,6 +238,7 @@ class Wizard(WarningErrorMixin, URLMixin, PoolBase):
- ``defaults``: a dictionary with default values
- ``buttons``: a list of buttons
'''
+ cls.check_access()
wizard = cls(session_id)
for key, values in data.iteritems():
record = getattr(wizard, key)
@@ -265,7 +289,7 @@ class Wizard(WarningErrorMixin, URLMixin, PoolBase):
self._session_id = session_id
session = Session(session_id)
data = json.loads(session.data.encode('utf-8'),
- object_hook=object_hook)
+ object_hook=JSONDecoder())
for state_name, state in self.states.iteritems():
if isinstance(state, StateView):
Target = pool.get(state.model_name)
--
tryton-server
More information about the tryton-debian-vcs
mailing list