[tryton-debian-vcs] tryton-server branch debian updated. debian/3.8.5-1-17-g367e222
Mathias Behrle
tryton-debian-vcs at alioth.debian.org
Thu Jun 2 16:30:06 UTC 2016
The following commit has been merged in the debian branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/tryton-server.git;a=commitdiff;h=debian/3.8.5-1-17-g367e222
commit 367e222b123c0cfbb43bd58001e4e231b995de07
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Wed Jun 1 21:47:37 2016 +0200
Releasing debian version 4.0.1-1.
Signed-off-by: Mathias Behrle <mathiasb at m9s.biz>
diff --git a/debian/changelog b/debian/changelog
index 39904c7..5b3797c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,25 @@
+tryton-server (4.0.1-1) unstable; urgency=medium
+
+ * Merging upstream version 4.0.0.
+ * Merging upstream version 4.0.1.
+ * Updating the copyright file.
+ * Updating Depends, Recommends and Suggests for 4.0.
+ * Updating and adding new manpages for the new dedicated binaries.
+ * Removing the cron option from the service file for the server.
+ * Adding a dedicated service file for the cron job.
+ * Updating README.Debian for 4.0.
+ * Updating the configuration parameters in trytond.conf.
+ * Removing the cron option from the sysvinit configuration.
+ * Providing a separate sysvinit service for the now dedicated cron job.
+ * Refreshing 01_migrate_obsolete_modules patch.
+ * Renaming 01_migrate_obsolete_modules to
+ 01_migrate_obsolete_modules.patch.
+ * Adding Documentation keys to the service files.
+ * Improving the descriptions in the service files.
+ * Adding the NEWS for 4.0.
+
+ -- Mathias Behrle <mathiasb at m9s.biz> Mon, 30 May 2016 18:17:30 +0200
+
tryton-server (3.8.5-1) unstable; urgency=medium
* Merging upstream version 3.8.5.
commit ee7e12860fd5ab0ff11b3aa08232861f3b737f98
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 18:26:05 2016 +0200
Adding the NEWS for 4.0.
diff --git a/debian/NEWS b/debian/NEWS
index 352575d..b3d7e18 100644
--- a/debian/NEWS
+++ b/debian/NEWS
@@ -1,3 +1,23 @@
+tryton-server (4.0.1-1) unstable; urgency=medium
+
+ This is the new major release 4.0.1. It comes with major changes in the
+ configuration (namely the jsonrpc section was migrated to to a new web
+ section). You will have to adapt manually /etc/tryton/trytond.conf to
+ the changed parameters.
+ The formerly included cron job is now run by the dedicated service
+ tryton-server-cron.
+ The formerly included webdav module was separated into the separate
+ package tryton-server-webdav running its own server instance.
+
+ As for each major release don't forget to backup your database(s) and
+ then run the database update with
+ # trytond-admin --all -d <your_database_name>
+ and restart the server(s) with
+ # service tryton-server restart
+ # service tryton-server-cron restart
+
+ -- Mathias Behrle <mathiasb at m9s.biz> Mon, 30 May 2016 18:17:30 +0200
+
tryton-server (3.8.0-1) unstable; urgency=medium
This is the new major release 3.8.0.
commit 4ee1f16f42b0e6c4dd10f79967bf6d6aba0773d2
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 16:17:02 2016 +0200
Improving the descriptions in the service files.
diff --git a/debian/tryton-server-cron.service b/debian/tryton-server-cron.service
index ab930c2..a1187c2 100644
--- a/debian/tryton-server-cron.service
+++ b/debian/tryton-server-cron.service
@@ -1,5 +1,5 @@
[Unit]
-Description=Tryton Application Platform Server
+Description=Tryton Server Cron
Documentation=man:trytond-cron,file:/usr/share/doc/tryton-server,file:/usr/share/doc/tryton-server-doc,http:doc/tryton.org/
After=remote-fs.target
After=network.target
diff --git a/debian/tryton-server.service b/debian/tryton-server.service
index f079c99..3fb786b 100644
--- a/debian/tryton-server.service
+++ b/debian/tryton-server.service
@@ -1,5 +1,5 @@
[Unit]
-Description=Tryton Application Platform Server
+Description=Tryton Server WSGI App
Documentation=man:trytond,file:/usr/share/doc/tryton-server,file:/usr/share/doc/tryton-server-doc,http:doc/tryton.org/
After=remote-fs.target
After=network.target
commit b925c657049678d16c98b6808e16c197771fdbf6
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 16:15:32 2016 +0200
Adding Documentation keys to the service files.
diff --git a/debian/tryton-server-cron.service b/debian/tryton-server-cron.service
index 211de53..ab930c2 100644
--- a/debian/tryton-server-cron.service
+++ b/debian/tryton-server-cron.service
@@ -1,5 +1,6 @@
[Unit]
Description=Tryton Application Platform Server
+Documentation=man:trytond-cron,file:/usr/share/doc/tryton-server,file:/usr/share/doc/tryton-server-doc,http:doc/tryton.org/
After=remote-fs.target
After=network.target
After=postgresql.service
diff --git a/debian/tryton-server.service b/debian/tryton-server.service
index e9ce0c0..f079c99 100644
--- a/debian/tryton-server.service
+++ b/debian/tryton-server.service
@@ -1,5 +1,6 @@
[Unit]
Description=Tryton Application Platform Server
+Documentation=man:trytond,file:/usr/share/doc/tryton-server,file:/usr/share/doc/tryton-server-doc,http:doc/tryton.org/
After=remote-fs.target
After=network.target
After=postgresql.service
commit 727715eb5fcc4a2105abeb49ebe9857121032ca9
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 15:59:33 2016 +0200
Renaming 01_migrate_obsolete_modules to 01_migrate_obsolete_modules.patch.
diff --git a/debian/patches/01_migrate_obsolete_modules b/debian/patches/01_migrate_obsolete_modules.patch
similarity index 100%
rename from debian/patches/01_migrate_obsolete_modules
rename to debian/patches/01_migrate_obsolete_modules.patch
diff --git a/debian/patches/series b/debian/patches/series
index 8e32e7f..40b8409 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1 @@
-01_migrate_obsolete_modules
+01_migrate_obsolete_modules.patch
commit 8b0fd72b098ef7f315eb2a0997533ce27494aedf
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 15:53:36 2016 +0200
Refreshing 01_migrate_obsolete_modules patch.
diff --git a/debian/patches/01_migrate_obsolete_modules b/debian/patches/01_migrate_obsolete_modules
index 3963664..8c4e131 100644
--- a/debian/patches/01_migrate_obsolete_modules
+++ b/debian/patches/01_migrate_obsolete_modules
@@ -8,17 +8,17 @@ Author: Mathias Behrle <mathiasb at m9s.biz>
Bug: http://bugs.tryton.org/issue4280
Forwarded: https://bugs.tryton.org/issue4280
---- tryton-server.orig/trytond/modules/__init__.py 2015-11-12 16:25:02.451033382 +0100
-+++ tryton-server/trytond/modules/__init__.py 2015-11-12 16:25:02.451033382 +0100
-@@ -378,6 +378,11 @@
- if TableHandler.table_exist(cursor, old_table):
- TableHandler.table_rename(cursor, old_table, new_table)
- if update:
-+ # Migration from 2.2: module workflow removed
-+ # Migration from 3.2: module ldap_connection removed
-+ obsolete_modules = ['workflow', 'ldap_connection']
-+ cursor.execute(*ir_module.delete(
-+ where=(ir_module.name.in_(obsolete_modules))))
- cursor.execute(*ir_module.select(ir_module.name,
- where=ir_module.state.in_(('installed', 'to install',
- 'to upgrade', 'to remove'))))
+--- tryton-server.orig/trytond/modules/__init__.py 2016-05-30 15:41:07.293541967 +0200
++++ tryton-server/trytond/modules/__init__.py 2016-05-30 15:50:17.029121049 +0200
+@@ -383,6 +383,11 @@
+ if TableHandler.table_exist(old_table):
+ TableHandler.table_rename(old_table, new_table)
+ if update:
++ # Migration from 2.2: module workflow removed
++ # Migration from 3.2: module ldap_connection removed
++ obsolete_modules = ['workflow', 'ldap_connection']
++ cursor.execute(*ir_module.delete(
++ where=(ir_module.name.in_(obsolete_modules))))
+ cursor.execute(*ir_module.select(ir_module.name,
+ where=ir_module.state.in_(('installed', 'to install',
+ 'to upgrade', 'to remove'))))
commit 5a6a109638e12b7c922c2f7a6cfcb11652cc207e
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 13:29:35 2016 +0200
Providing a separate sysvinit service for the now dedicated cron job.
diff --git a/debian/rules b/debian/rules
index bc555c6..aab7f26 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,7 +1,6 @@
#!/usr/bin/make -f
export PYBUILD_DESTDIR_python2=debian/tryton-server
-
# Run tests on sqlite memory database
# For the complete test suites refer to http://tests.tryton.org/
export TRYTOND_DATABASE_URI=sqlite://
@@ -19,6 +18,5 @@ override_dh_auto_build:
sphinx-build doc build/html
override_dh_installinit:
- dh_installinit --update-rcd-params='defaults 21'
-
-
+ dh_installinit --restart-after-upgrade --name=tryton-server-cron --update-rcd-params='defaults 21'
+ dh_installinit --restart-after-upgrade --update-rcd-params='defaults 21'
diff --git a/debian/tryton-server.tryton-server-cron.init b/debian/tryton-server.tryton-server-cron.init
new file mode 100644
index 0000000..f5d44ce
--- /dev/null
+++ b/debian/tryton-server.tryton-server-cron.init
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides: tryton-server-cron
+# Required-Start: $syslog $remote_fs
+# Required-Stop: $syslog $remote_fs
+# Should-Start: $network postgresql mysql
+# Should-Stop: $network postgresql mysql
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Application Platform
+# Description: Tryton is an Application Platform serving as a base for
+# a complete ERP software. This service runs the cron job.
+### END INIT INFO
+
+PATH="/sbin:/bin:/usr/sbin:/usr/bin"
+DAEMON="/usr/bin/trytond-cron"
+
+test -x "${DAEMON}" || exit 0
+
+BASENAME="trytond"
+NAME="${BASENAME}-cron"
+DESC="Tryton Application Platform"
+DAEMONUSER="tryton"
+PIDDIR="/var/run/${BASENAME}"
+PIDFILE="${PIDDIR}/${NAME}.pid"
+LOGCONF="/etc/tryton/${BASENAME}_log.conf"
+DEFAULTS="/etc/default/tryton-server"
+CONFIGFILE="/etc/tryton/${BASENAME}.conf"
+DAEMON_OPTS="--config ${CONFIGFILE} --logconf ${LOGCONF}"
+
+# Include tryton-server defaults if available
+if [ -r "${DEFAULTS}" ]
+then
+ . "${DEFAULTS}"
+fi
+
+. /lib/lsb/init-functions
+
+# Make sure trytond is started with configured locale
+if [ -n "${LANG}" ]
+then
+ LANG="${LANG}"
+ export LANG
+fi
+
+set -e
+
+do_start ()
+{
+ if [ ! -d "${PIDDIR}" ]
+ then
+ mkdir -p "${PIDDIR}"
+ chown "${DAEMONUSER}":"${DAEMONUSER}" "${PIDDIR}"
+ fi
+
+ start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
+ --chuid ${DAEMONUSER} --background --make-pidfile \
+ --exec ${DAEMON} -- ${DAEMON_OPTS}
+}
+
+do_stop ()
+{
+ start-stop-daemon --stop --quiet --pidfile ${PIDFILE} --oknodo
+}
+
+case "${1}" in
+ start)
+ log_daemon_msg "Starting ${DESC}" "${NAME}"
+ do_start
+ log_end_msg ${?}
+ ;;
+
+ stop)
+ log_daemon_msg "Stopping ${DESC}" "${NAME}"
+ do_stop
+ log_end_msg ${?}
+ ;;
+
+ restart|force-reload)
+ log_daemon_msg "Restarting ${DESC}" "${NAME}"
+ do_stop
+ sleep 1
+ do_start
+ log_end_msg ${?}
+ ;;
+
+ status)
+ status_of_proc -p "${PIDFILE}" "${DAEMON}" "${NAME}" && \
+ exit 0 || exit ${?}
+ ;;
+
+ *)
+ N="/etc/init.d/${NAME}"
+ echo "Usage: ${N} {start|stop|restart|force-reload|status}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
commit 2222ab7e02fc3979f79a35159d325ae97b36f346
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 13:23:58 2016 +0200
Removing the cron option from the sysvinit configuration.
diff --git a/debian/tryton-server.default b/debian/tryton-server.default
index 8f8cc54..e319955 100644
--- a/debian/tryton-server.default
+++ b/debian/tryton-server.default
@@ -19,4 +19,4 @@ LOGCONF="/etc/tryton/trytond_log.conf"
# Additional options that are passed to the Daemon.
# i.e. to increase the verbosity of the server log add -v
-DAEMON_OPTS="--config ${CONFIGFILE} --logconf ${LOGCONF} --cron"
+DAEMON_OPTS="--config ${CONFIGFILE} --logconf ${LOGCONF}"
diff --git a/debian/tryton-server.init b/debian/tryton-server.init
index 765dbb3..5639c93 100644
--- a/debian/tryton-server.init
+++ b/debian/tryton-server.init
@@ -26,7 +26,7 @@ PIDFILE="${PIDDIR}/${NAME}.pid"
LOGCONF="/etc/tryton/${NAME}_log.conf"
DEFAULTS="/etc/default/tryton-server"
CONFIGFILE="/etc/tryton/${NAME}.conf"
-DAEMON_OPTS="--config ${CONFIGFILE} --logconf ${LOGCONF} --cron"
+DAEMON_OPTS="--config ${CONFIGFILE} --logconf ${LOGCONF}"
# Include tryton-server defaults if available
if [ -r "${DEFAULTS}" ]
commit 52b85c6d697ab3b94e84002f14ac1d6bd6aa37ca
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 13:23:20 2016 +0200
Updating the configuration parameters in trytond.conf.
diff --git a/debian/trytond.conf b/debian/trytond.conf
index 56f8938..a5ae51d 100644
--- a/debian/trytond.conf
+++ b/debian/trytond.conf
@@ -1,4 +1,5 @@
-# /etc/tryton/trytond.conf - Configuration file for Tryton Server (trytond)
+# /etc/tryton/trytond.conf - Configuration file for Tryton Server
+# (trytond, trytond-admin, trytond-cron)
#
# This file contains the most common settings for trytond (Defaults
# are commented).
@@ -7,6 +8,25 @@
# and accordingly
# /usr/share/doc/tryton-server-doc/html/topics/configuration.html
+[web]
+# Settings for the web interface
+
+# The IP/host and port number of the interface
+# (Internal default: localhost:8000)
+#
+# Listen on all interfaces (IPv4)
+#listen = 0.0.0.0:8000
+#
+# Listen on all interfaces (IPv4 and IPv6)
+#listen = [::]:8000
+
+# The hostname for this interface
+#hostname =
+
+# The root path to retrieve data for GET requests
+#root = /var/www/localhost/tryton
+
+
[database]
# Database related settings
@@ -52,41 +72,11 @@ path = /var/lib/tryton
# The path to the certificate
#certificate = /etc/ssl/certs/ssl-cert-snakeoil.pem
-[jsonrpc]
-# Settings for the JSON-RPC network interface
-
-# The IP/host and port number of the interface
-# (Internal default: localhost:8000)
-#
-# Listen on all interfaces (IPv4)
-#listen = 0.0.0.0:8000
-#
-# Listen on all interfaces (IPv4 and IPv6)
-#listen = [::]:8000
-
-# The hostname for this interface
-#hostname =
-
-# The root path to retrieve data for GET requests
-#data = jsondata
-
-[xmlrpc]
-# Settings for the XML-RPC network interface
-
-# The IP/host and port number of the interface
-#listen = localhost:8069
-
-[webdav]
-# Settings for the WebDAV network interface
-
-# The IP/host and port number of the interface
-#listen = localhost:8080
-
[session]
# Session settings
# The time (in seconds) until an inactive session expires
-#timeout = 3600
+#timeout = 600
# The server administration password used by the client for
# the execution of database management tasks. It is encrypted
@@ -117,20 +107,70 @@ path = /var/lib/tryton
# Unoconv parameters for connection to the unoconv service.
#unoconv = pipe,name=trytond;urp;StarOffice.ComponentContext
+
+# Special Settings
+
+[cache]
+# Various cache size settings.
+
+# The number of different models kept in the cache per transaction.
+#model = 200
+
+# The number of loaded records kept in the cache of the list.
+# It can be changed locally using the _record_cache_size key in Transaction.context.
+#record = 2000
+
+# The number of fields to load with eager Field.loading.
+#field = 100
+
+[table]
+# This section allows to override the default generated table names. The main purpose
+# is to bypass name length limitations of a database backend.
+# Examples:
+#account.invoice.line = acc_inv_line
+#account.invoice.tax = acc_inv_tax
+
+
# Module settings
#
# Some modules are reading configuration parameters from this
# configuration file. These settings only apply when those modules
# are installed.
#
-#[ldap_authentication]
-# The URI to connect to the LDAP server.
+[ldap_authentication]
+# The LDAP URL to connect to the server following RFC-2255.
#uri = ldap://host:port/dn?attributes?scope?filter?extensions
# A basic default URL could look like
#uri = ldap://localhost:389/
#
-#[product]
+# The LDAP password used to bind if needed.
+#bind_pass =
+#
+# If the LDAP server is an Active Directory (Boolean).
+#active_directory = False
+#
+# The UID attribute for authentication.
+# (Internal default: uid)
+#uid = uid
+# If the user shall be created in the database in case it does not exist.
+# (Boolean)
+#create_user = False
+#
+[product]
# The number of decimals with which the unit prices are stored
# in the database. The default value is 4.
# Warning: This setting can not be lowered once a database is created.
#price_decimal = 4
+#
+[webdav]
+# Settings for the webdav interface
+# The IP/host and port number of the interface
+# (Internal default: localhost:8080)
+listen = localhost:8080
+# Listen on all interfaces (IPv4)
+#listen = 0.0.0.0:8080
+# Listen on all interfaces (IPv4 and IPv6)
+#listen = [::]:8080
+#
+# The hostname for this interface
+#hostname =
commit 5db0191e781f936d968762c83c5dbf958cef23a2
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 13:07:10 2016 +0200
Updating README.Debian for 4.0.
diff --git a/debian/tryton-server.README.Debian b/debian/tryton-server.README.Debian
index 7077d00..71a9092 100644
--- a/debian/tryton-server.README.Debian
+++ b/debian/tryton-server.README.Debian
@@ -40,14 +40,21 @@ achieve this (you need to execute all commands as root):
Preparing the Tryton server
---------------------------
- * Setting up the Tryton server (trytond):
+ * Setting up the Tryton server:
+
+ Note: The server comes with three binaries:
+ - trytond (runs the server(s))
+ - trytond-admin (used for the adiminstration tasks of the server, e.g. the
+ database administration)
+ - trytond-cron (runs the cron part of the server, should only be started
+ once per configuration)
Adjust /etc/tryton/trytond.conf to reflect the setup of your system by using
the database user and password from step 1 for the database URI.
* If the Tryton server shall listen on some external interface (i.e. shall be
- available for clients connecting from other machines), change the jsonrpc
- protocol to listen accordingly.
+ available for clients connecting from other machines), change the listen
+ parameter in the web section accordingly.
* If the Tryton server is listening on external interfaces, it is highly
recommended to enable SSL for the connection.
@@ -62,6 +69,7 @@ Preparing the Tryton server
* Restarting trytond:
# service tryton-server restart
+ # service tryton-server-cron restart
Note: The fingerprint of connected servers is stored in the clients
known_hosts file. When a server is changed for its SSL usage, the client
@@ -78,6 +86,8 @@ Creating the database
Note: The following steps can also be performed easily from the Tryton
Client and are not mandatory to be done on the command line.
+From the command line:
+
* Creating the database:
# su - postgres -c "createdb --encoding=UNICODE --owner=tryton tryton"
@@ -87,7 +97,7 @@ Client and are not mandatory to be done on the command line.
* Initializing the database:
- # /usr/bin/trytond -c /etc/tryton/trytond.conf -u res -d tryton
+ # /usr/bin/trytond-admin -c /etc/tryton/trytond.conf -u res -d tryton
Note: Use the database name you chose in the previous step (here as default: tryton).
You will be asked for the admin password for this database.
@@ -101,7 +111,7 @@ Upgrade
version string) you have to update your database(s).
After the categorically recommended backup do:
- # /usr/bin/trytond -c /etc/tryton/trytond.conf --all -d tryton
+ # /usr/bin/trytond-admin -c /etc/tryton/trytond.conf --all -d tryton
Remember to replace tryton with the name of your database.
@@ -111,14 +121,8 @@ Notes
Now, you're finished with the system setup. Please be aware of the following things:
- * trytond has one default account for server administration:
- - User: admin; password: the one you have configured in trytond.conf
- as super_pwd. This user is the one used for database management tasks
- from the client.
-
- Note: Each Tryton database will have its own admin with login password
- stored in the database itself (not to be confound with the admin of the
- Tryton Server).
+ * Each Tryton database will have its own admin with login password
+ stored in the database itself.
* trytond must have read access to its configuration file, otherwise it will
start with internal defaults. The postinst script will (re)set ownership to
@@ -127,16 +131,16 @@ Now, you're finished with the system setup. Please be aware of the following thi
means of dpkg-statoverride.
* trytond listens by default on port 8000 (jsonrpc). If you need to change
- this, edit /etc/tryton/trytond.conf in the section [jsonrpc].
+ this, edit /etc/tryton/trytond.conf in the section [web].
* trytond in its upstream configuration listens by default to the localhost
interface. If you want to change this default to listen on all interfaces,
- edit /etc/tryton/trytond.conf in the section [jsonrpc].
+ edit /etc/tryton/trytond.conf in the section [web].
* Installation of modules into the database can be done from the
Administration Panel of the client. Under Modules you can select from the
- modules packages (trytond-modules*) you have installed on your system.
+ modules packages (trytond-modules*) that you have installed on your system.
* Only the same major version of Tryton client and Tryton server can connect.
- -- Mathias Behrle <mathiasb at m9s.biz> Tue, 22 Oct 2014 16:45:00 +0200
+ -- Mathias Behrle <mathiasb at m9s.biz> Tue, 30 May 2016 16:45:00 +0200
commit b0ef553d07badb12989532d0d2bfebe90935fa06
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 12:07:53 2016 +0200
Adding a dedicated service file for the cron job.
diff --git a/debian/tryton-server-cron.service b/debian/tryton-server-cron.service
new file mode 100644
index 0000000..211de53
--- /dev/null
+++ b/debian/tryton-server-cron.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Tryton Application Platform Server
+After=remote-fs.target
+After=network.target
+After=postgresql.service
+After=mysql.service
+ConditionPathExists=/etc/tryton/trytond.conf
+
+[Service]
+User=tryton
+Group=tryton
+ExecStart=/usr/bin/trytond-cron --config /etc/tryton/trytond.conf --logconf /etc/tryton/trytond_log.conf
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
commit 41d7f6f249a3354bdc8b7188864567927193f0f4
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 12:07:02 2016 +0200
Removing the cron option from the service file for the server.
diff --git a/debian/tryton-server.service b/debian/tryton-server.service
index d803fd7..e9ce0c0 100644
--- a/debian/tryton-server.service
+++ b/debian/tryton-server.service
@@ -9,7 +9,7 @@ ConditionPathExists=/etc/tryton/trytond.conf
[Service]
User=tryton
Group=tryton
-ExecStart=/usr/bin/trytond --config /etc/tryton/trytond.conf --logconf /etc/tryton/trytond_log.conf --cron
+ExecStart=/usr/bin/trytond --config /etc/tryton/trytond.conf --logconf /etc/tryton/trytond_log.conf
Restart=on-failure
[Install]
commit 3b146fd7207321cd391ee2832a50f8c594981808
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon May 30 11:59:36 2016 +0200
Updating and adding new manpages for the new dedicated binaries.
diff --git a/debian/manpages/trytond.1 b/debian/manpages/trytond-admin.1
similarity index 74%
copy from debian/manpages/trytond.1
copy to debian/manpages/trytond-admin.1
index cc52e6b..8daf3a8 100644
--- a/debian/manpages/trytond.1
+++ b/debian/manpages/trytond-admin.1
@@ -1,17 +1,14 @@
-.TH TRYTOND 1 "2015\-11\-12" "3.8" "Tryton Application Platform"
-
+.TH TRYTOND-ADMIN 1 "2016\-05\-30" "4.0" "Tryton Application Platform"
.SH NAME
-trytond \- Tryton Application Platform (Server)
-
+trytond-admin \- Tryton Application Platform (Database Adminstration)
.SH SYNOPSIS
-\fBtrytond\fR
-
+\fBtrytond-admin\fR
.SH DESCRIPTION
Tryton is a high-level general purpose application platform written in Python and using PostgreSQL as database engine. It is the core base of an ERP.
-
+.sp
+This script is used for the administration tasks of the server.
.SH OPTIONS
-Usage: trytond [options]
-
+Usage: trytond-admin [options]
.PP
.PD 0
.TP 20
@@ -38,32 +35,34 @@ update a module
.TP
.B \--all
update all installed modules
-
+.br
The first time a database is initialized the admin password is read from
a file defined by the TRYTONPASSFILE environment variable or interactively asked from the user.
-The config file can be specified in the TRYTOND_CONFIG environment variable. The
-database URI can be specified in the TRYTOND_DATABASE_URI environment variable.
-
+.br
+The config file can be specified in the TRYTOND_CONFIG environment variable.
+.br
+The database URI can be specified in the TRYTOND_DATABASE_URI environment variable.
.TP
.B \--pidfile FILE
file where the server pid will be stored
.TP
.B \--logconf FILE
logging configuration file (ConfigParser format)
-.TP
-.B \--cron
-enable internal cron
-
+.sp
.SH FILES
-\fB/etc/trytond.conf\fR
-
+\fB/etc/tryton/trytond.conf\fR,
+\fB/etc/tryton/trytond_log.conf\fR
+.sp
.SH SEE ALSO
+\fItrytond\fR(1),
+\fItrytond-cron\fR(1),
\fItryton\fR(1)
-
+.sp
.SH HOMEPAGE
More information about the Tryton server and the Tryton project can be found at <\fIhttp://www.tryton.org/\fR>.
-
+.sp
.SH AUTHOR
Tryton server was written by the Tryton project <\fIhttp://www.tryton.org/\fR>.
.PP
This manual page was written by Daniel Baumann <\fIdaniel at debian.org\fR> and Mathias Behrle <\fImathiasb at m9s.biz\fR>.
+
diff --git a/debian/manpages/trytond.1 b/debian/manpages/trytond-cron.1
similarity index 63%
copy from debian/manpages/trytond.1
copy to debian/manpages/trytond-cron.1
index cc52e6b..dccd53a 100644
--- a/debian/manpages/trytond.1
+++ b/debian/manpages/trytond-cron.1
@@ -1,17 +1,14 @@
-.TH TRYTOND 1 "2015\-11\-12" "3.8" "Tryton Application Platform"
-
+.TH TRYTOND-CRON 1 "2016\-05\-30" "4.0" "Tryton Application Platform"
.SH NAME
-trytond \- Tryton Application Platform (Server)
-
+trytond-cron \- Tryton Application Platform (Cron)
.SH SYNOPSIS
-\fBtrytond\fR
-
+\fBtrytond-cron\fR
.SH DESCRIPTION
Tryton is a high-level general purpose application platform written in Python and using PostgreSQL as database engine. It is the core base of an ERP.
-
+.sp
+This script runs the dedicated cron part of the server.
.SH OPTIONS
Usage: trytond [options]
-
.PP
.PD 0
.TP 20
@@ -33,37 +30,26 @@ enable verbose mode
.B \-d DATABASE [DATABASE ...], --database DATABASE [DATABASE ...]
specify the database name
.TP
-.B \-u MODULE [MODULE ...], --update MODULE [MODULE ...]
-update a module
-.TP
-.B \--all
-update all installed modules
-
-The first time a database is initialized the admin password is read from
-a file defined by the TRYTONPASSFILE environment variable or interactively asked from the user.
-The config file can be specified in the TRYTOND_CONFIG environment variable. The
-database URI can be specified in the TRYTOND_DATABASE_URI environment variable.
-
-.TP
.B \--pidfile FILE
file where the server pid will be stored
.TP
.B \--logconf FILE
logging configuration file (ConfigParser format)
-.TP
-.B \--cron
-enable internal cron
-
+.sp
.SH FILES
-\fB/etc/trytond.conf\fR
-
+\fB/etc/tryton/trytond.conf\fR,
+\fB/etc/tryton/trytond_log.conf\fR
+.sp
.SH SEE ALSO
+\fItrytond\fR(1),
+\fItrytond-admin\fR(1),
\fItryton\fR(1)
-
+.sp
.SH HOMEPAGE
More information about the Tryton server and the Tryton project can be found at <\fIhttp://www.tryton.org/\fR>.
-
+.sp
.SH AUTHOR
Tryton server was written by the Tryton project <\fIhttp://www.tryton.org/\fR>.
.PP
This manual page was written by Daniel Baumann <\fIdaniel at debian.org\fR> and Mathias Behrle <\fImathiasb at m9s.biz\fR>.
+
diff --git a/debian/manpages/trytond.1 b/debian/manpages/trytond.1
index cc52e6b..299fbbd 100644
--- a/debian/manpages/trytond.1
+++ b/debian/manpages/trytond.1
@@ -1,17 +1,14 @@
-.TH TRYTOND 1 "2015\-11\-12" "3.8" "Tryton Application Platform"
-
+.TH TRYTOND 1 "2016\-05\-30" "4.0" "Tryton Application Platform"
.SH NAME
trytond \- Tryton Application Platform (Server)
-
.SH SYNOPSIS
\fBtrytond\fR
-
.SH DESCRIPTION
Tryton is a high-level general purpose application platform written in Python and using PostgreSQL as database engine. It is the core base of an ERP.
-
+.sp
+This script runs the the server (WSGI App).
.SH OPTIONS
Usage: trytond [options]
-
.PP
.PD 0
.TP 20
@@ -33,36 +30,24 @@ enable verbose mode
.B \-d DATABASE [DATABASE ...], --database DATABASE [DATABASE ...]
specify the database name
.TP
-.B \-u MODULE [MODULE ...], --update MODULE [MODULE ...]
-update a module
-.TP
-.B \--all
-update all installed modules
-
-The first time a database is initialized the admin password is read from
-a file defined by the TRYTONPASSFILE environment variable or interactively asked from the user.
-The config file can be specified in the TRYTOND_CONFIG environment variable. The
-database URI can be specified in the TRYTOND_DATABASE_URI environment variable.
-
-.TP
.B \--pidfile FILE
file where the server pid will be stored
.TP
.B \--logconf FILE
logging configuration file (ConfigParser format)
-.TP
-.B \--cron
-enable internal cron
-
+.sp
.SH FILES
-\fB/etc/trytond.conf\fR
-
+\fB/etc/tryton/trytond.conf\fR,
+\fB/etc/tryton/trytond_log.conf\fR
+.sp
.SH SEE ALSO
+\fItrytond-admin\fR(1),
+\fItrytond-cron\fR(1),
\fItryton\fR(1)
-
+.sp
.SH HOMEPAGE
More information about the Tryton server and the Tryton project can be found at <\fIhttp://www.tryton.org/\fR>.
-
+.sp
.SH AUTHOR
Tryton server was written by the Tryton project <\fIhttp://www.tryton.org/\fR>.
.PP
diff --git a/debian/tryton-server.links b/debian/tryton-server.links
index 9ee9859..909de8c 100644
--- a/debian/tryton-server.links
+++ b/debian/tryton-server.links
@@ -1 +1,3 @@
/usr/share/man/man1/trytond.1.gz /usr/share/man/man1/tryton-server.1.gz
+/usr/share/man/man1/trytond-admin.1.gz /usr/share/man/man1/tryton-server-admin.1.gz
+/usr/share/man/man1/trytond-cron.1.gz /usr/share/man/man1/tryton-server-cron.1.gz
commit cc57de0e082b8c953a08062a5bcf33d9331c7988
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Sat May 28 14:39:04 2016 +0200
Updating Depends, Recommends and Suggests for 4.0.
diff --git a/debian/control b/debian/control
index 35fb51f..ca982db 100644
--- a/debian/control
+++ b/debian/control
@@ -17,6 +17,8 @@ Build-Depends:
python-setuptools,
python-sphinx (>= 1.0.7+dfsg),
python-sql,
+ python-werkzeug,
+ python-wrapt,
Standards-Version: 3.9.7
Homepage: http://www.tryton.org/
Vcs-Browser: https://anonscm.debian.org/cgit/tryton/tryton-server.git
@@ -34,20 +36,21 @@ Depends:
python-polib,
python-relatorio,
python-sql,
+ python-werkzeug,
+ python-wrapt,
${misc:Depends},
${python:Depends},
Recommends:
postgresql,
- postgresql-client,
python-bcrypt,
python-levenshtein,
python-psycopg2,
python-pydot,
python-simplejson,
- python-webdav,
ssl-cert,
unoconv,
Suggests:
+ postgresql-client,
python-sphinx,
tryton-client | tryton-neso,
tryton-modules-all,
commit 2d10d20a194a8d2b43fa002d070cf88a4092afed
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Fri May 27 19:01:03 2016 +0200
Updating the copyright file.
diff --git a/debian/copyright b/debian/copyright
index c861e24..eba2cb1 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -6,6 +6,7 @@ Copyright: 2004-2008 Tiny SPRL
2007-2013 Bertrand Chenal
2008-2016 B2CK SPRL
2011 Openlabs Technologies & Consulting (P) Ltd
+ 2011-2016 Nicolas Évrard
License: GPL-3+
Files: doc/*
@@ -21,7 +22,7 @@ License: public-domain
Files: debian/*
Copyright: 2009-2012 Daniel Baumann <daniel at debian.org>
- 2010-2015 Mathias Behrle <mathiasb at m9s.biz>
+ 2010-2016 Mathias Behrle <mathiasb at m9s.biz>
License: GPL-3+
License: GPL-3+
commit 51db7cd3c914d9caab5bd7d2c85e7903857d86a6
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Fri May 27 18:35:36 2016 +0200
Merging upstream version 4.0.1.
diff --git a/CHANGELOG b/CHANGELOG
index 93356a4..1dfc1ed 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+Version 4.0.1 - 2016-05-11
+* Bug fixes (see mercurial logs for details)
+
Version 4.0.0 - 2016-05-02
* Bug fixes (see mercurial logs for details)
* Add sendmail module to send transactional email
diff --git a/PKG-INFO b/PKG-INFO
index ec59371..1356989 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: trytond
-Version: 4.0.0
+Version: 4.0.1
Summary: Tryton server
Home-page: http://www.tryton.org/
Author: Tryton
diff --git a/doc/topics/views/index.rst b/doc/topics/views/index.rst
index 2d8e0f1..cd278c9 100644
--- a/doc/topics/views/index.rst
+++ b/doc/topics/views/index.rst
@@ -227,8 +227,6 @@ Display a field of the object with the value of the current record.
* ``readonly``: Boolean to set the field readonly.
- * ``required``: Boolean to set the field required.
-
* ``mode``: Only for One2Many fields: it is a comma separated list, that
specifies the order of the view used to display the relation. (Example:
``tree,form``)
@@ -514,8 +512,6 @@ field
* ``readonly``: Boolean to set the field readonly.
- * ``required``: Boolean to set the field required.
-
* ``widget``: The widget that must be used instead of the default one.
* ``tree_invisible``: Boolean to display or not the column.
diff --git a/trytond.egg-info/PKG-INFO b/trytond.egg-info/PKG-INFO
index ec59371..1356989 100644
--- a/trytond.egg-info/PKG-INFO
+++ b/trytond.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: trytond
-Version: 4.0.0
+Version: 4.0.1
Summary: Tryton server
Home-page: http://www.tryton.org/
Author: Tryton
diff --git a/trytond/__init__.py b/trytond/__init__.py
index 44be727..54f35f5 100644
--- a/trytond/__init__.py
+++ b/trytond/__init__.py
@@ -5,7 +5,7 @@ import time
import logging
from email import charset
-__version__ = "4.0.0"
+__version__ = "4.0.1"
logger = logging.getLogger(__name__)
os.environ['TZ'] = 'UTC'
diff --git a/trytond/ir/locale/ca_ES.po b/trytond/ir/locale/ca_ES.po
index 97f96f3..33933b5 100644
--- a/trytond/ir/locale/ca_ES.po
+++ b/trytond/ir/locale/ca_ES.po
@@ -71,7 +71,6 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
"La definició del correu electrònic sobre l'informe \"%s\", no és correcta."
-#, fuzzy
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource."
msgstr "El nom dels adjunts ha de ser únic per recurs."
@@ -420,7 +419,7 @@ msgstr "Context"
msgctxt "field:ir.action.act_window,context_model:"
msgid "Context Model"
-msgstr ""
+msgstr "Model del context"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
@@ -1674,98 +1673,82 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Mòduls a actualitzar"
-#, fuzzy
msgctxt "field:ir.note,create_date:"
msgid "Create Date"
msgstr "Data creació"
-#, fuzzy
msgctxt "field:ir.note,create_uid:"
msgid "Create User"
msgstr "Usuari creació"
-#, fuzzy
msgctxt "field:ir.note,id:"
msgid "ID"
msgstr "ID"
-#, fuzzy
msgctxt "field:ir.note,last_modification:"
msgid "Last Modification"
msgstr "Última modificació"
-#, fuzzy
msgctxt "field:ir.note,last_user:"
msgid "Last User"
msgstr "Últim usuari"
msgctxt "field:ir.note,message:"
msgid "Message"
-msgstr ""
+msgstr "Missatge"
msgctxt "field:ir.note,message_wrapped:"
msgid "Message"
-msgstr ""
+msgstr "Missatge"
-#, fuzzy
msgctxt "field:ir.note,rec_name:"
msgid "Name"
msgstr "Nom"
-#, fuzzy
msgctxt "field:ir.note,resource:"
msgid "Resource"
msgstr "Recurs"
msgctxt "field:ir.note,unread:"
msgid "Unread"
-msgstr ""
+msgstr "No llegida"
-#, fuzzy
msgctxt "field:ir.note,write_date:"
msgid "Write Date"
msgstr "Data modificació"
-#, fuzzy
msgctxt "field:ir.note,write_uid:"
msgid "Write User"
msgstr "Usuari modificació"
-#, fuzzy
msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
msgstr "Data creació"
-#, fuzzy
msgctxt "field:ir.note.read,create_uid:"
msgid "Create User"
msgstr "Usuari creació"
-#, fuzzy
msgctxt "field:ir.note.read,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:ir.note.read,note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
-#, fuzzy
msgctxt "field:ir.note.read,rec_name:"
msgid "Name"
msgstr "Nom"
-#, fuzzy
msgctxt "field:ir.note.read,user:"
msgid "User"
msgstr "Usuari"
-#, fuzzy
msgctxt "field:ir.note.read,write_date:"
msgid "Write Date"
msgstr "Data modificació"
-#, fuzzy
msgctxt "field:ir.note.read,write_uid:"
msgid "Write User"
msgstr "Usuari modificació"
@@ -2930,7 +2913,7 @@ msgstr "Realitza instal·lacions/actualitzacions pendents"
msgctxt "model:ir.action,name:act_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notes"
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
@@ -3010,7 +2993,7 @@ msgstr "Gràfica"
msgctxt "model:ir.action,name:report_model_workflow_graph"
msgid "Workflow Graph"
-msgstr ""
+msgstr "Graf dels estats"
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
@@ -3216,11 +3199,11 @@ msgstr "Inici instal·lació/actualització mòdul"
msgctxt "model:ir.note,name:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "model:ir.note.read,name:"
msgid "Note Read"
-msgstr ""
+msgstr "Nota llegida"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3404,7 +3387,7 @@ msgstr "Mòduls"
msgctxt "model:ir.ui.menu,name:menu_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notes"
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
@@ -3658,7 +3641,6 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Model"
-#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Report"
msgstr "Informe"
@@ -3805,7 +3787,7 @@ msgstr "Acció de disparador"
msgctxt "view:ir.cron:"
msgid "Run Once"
-msgstr ""
+msgstr "Executa una vegada"
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
@@ -3963,24 +3945,22 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Mòduls"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "Date"
msgstr "Data"
msgctxt "view:ir.note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "view:ir.note:"
msgid "Notes"
-msgstr ""
+msgstr "Notes"
msgctxt "view:ir.note:"
msgid "Time"
-msgstr ""
+msgstr "Hora"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "User"
msgstr "Usuari"
diff --git a/trytond/ir/locale/de_DE.po b/trytond/ir/locale/de_DE.po
index e3eeac9..62fc2b9 100644
--- a/trytond/ir/locale/de_DE.po
+++ b/trytond/ir/locale/de_DE.po
@@ -70,7 +70,6 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Ungültige E-Mailadresse in Bericht \"%s\"."
-#, fuzzy
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource."
msgstr ""
@@ -428,7 +427,7 @@ msgstr "Kontext"
msgctxt "field:ir.action.act_window,context_model:"
msgid "Context Model"
-msgstr ""
+msgstr "Kontextmodell"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
@@ -1072,7 +1071,7 @@ msgstr "Name"
msgctxt "field:ir.cron,repeat_missed:"
msgid "Repeat Missed"
-msgstr "Ausgelassene wiederholen"
+msgstr "Ausgelassene Aktionen nachholen"
msgctxt "field:ir.cron,request_user:"
msgid "Request User"
@@ -1682,98 +1681,82 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Zu aktualisierende Module"
-#, fuzzy
msgctxt "field:ir.note,create_date:"
msgid "Create Date"
msgstr "Erstellungsdatum"
-#, fuzzy
msgctxt "field:ir.note,create_uid:"
msgid "Create User"
msgstr "Erstellt durch"
-#, fuzzy
msgctxt "field:ir.note,id:"
msgid "ID"
msgstr "ID"
-#, fuzzy
msgctxt "field:ir.note,last_modification:"
msgid "Last Modification"
msgstr "Letzte Änderung"
-#, fuzzy
msgctxt "field:ir.note,last_user:"
msgid "Last User"
msgstr "Letzter Benutzer"
msgctxt "field:ir.note,message:"
msgid "Message"
-msgstr ""
+msgstr "Nachricht"
msgctxt "field:ir.note,message_wrapped:"
msgid "Message"
-msgstr ""
+msgstr "Nachricht"
-#, fuzzy
msgctxt "field:ir.note,rec_name:"
msgid "Name"
msgstr "Name"
-#, fuzzy
msgctxt "field:ir.note,resource:"
msgid "Resource"
msgstr "Ressource"
msgctxt "field:ir.note,unread:"
msgid "Unread"
-msgstr ""
+msgstr "Ungelesen"
-#, fuzzy
msgctxt "field:ir.note,write_date:"
msgid "Write Date"
msgstr "Zuletzt geändert"
-#, fuzzy
msgctxt "field:ir.note,write_uid:"
msgid "Write User"
msgstr "Letzte Änderung durch"
-#, fuzzy
msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
msgstr "Erstellungsdatum"
-#, fuzzy
msgctxt "field:ir.note.read,create_uid:"
msgid "Create User"
msgstr "Erstellt durch"
-#, fuzzy
msgctxt "field:ir.note.read,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:ir.note.read,note:"
msgid "Note"
-msgstr ""
+msgstr "Notiz"
-#, fuzzy
msgctxt "field:ir.note.read,rec_name:"
msgid "Name"
msgstr "Name"
-#, fuzzy
msgctxt "field:ir.note.read,user:"
msgid "User"
msgstr "Benutzer"
-#, fuzzy
msgctxt "field:ir.note.read,write_date:"
msgid "Write Date"
msgstr "Zuletzt geändert"
-#, fuzzy
msgctxt "field:ir.note.read,write_uid:"
msgid "Write User"
msgstr "Letzte Änderung durch"
@@ -2941,7 +2924,7 @@ msgstr "Vorgemerkte Installationen / Aktualisierungen durchführen"
msgctxt "model:ir.action,name:act_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notizen"
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
@@ -3013,15 +2996,15 @@ msgstr "Spaltenbreiten Sicht"
msgctxt "model:ir.action,name:print_model_graph"
msgid "Graph"
-msgstr "Diagramm"
+msgstr "Graph"
msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
-msgstr "Diagramm"
+msgstr "Graph"
msgctxt "model:ir.action,name:report_model_workflow_graph"
msgid "Workflow Graph"
-msgstr ""
+msgstr "Workflow Graph"
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
@@ -3227,11 +3210,11 @@ msgstr "Modulinstallation Aktualisierung Start"
msgctxt "model:ir.note,name:"
msgid "Note"
-msgstr ""
+msgstr "Notiz"
msgctxt "model:ir.note.read,name:"
msgid "Note Read"
-msgstr ""
+msgstr "Notiz gelesen"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3415,7 +3398,7 @@ msgstr "Module"
msgctxt "model:ir.ui.menu,name:menu_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notizen"
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
@@ -3669,7 +3652,6 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modell"
-#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Report"
msgstr "Bericht"
@@ -3688,7 +3670,7 @@ msgstr "Wizardknopf"
msgctxt "selection:ir.ui.menu,action:"
msgid ""
-msgstr ""
+msgstr " "
msgctxt "selection:ir.ui.menu,action:"
msgid "ir.action.act_window"
@@ -3708,7 +3690,7 @@ msgstr "ir.action.wizard"
msgctxt "selection:ir.ui.view,type:"
msgid ""
-msgstr ""
+msgstr " "
msgctxt "selection:ir.ui.view,type:"
msgid "Board"
@@ -3816,7 +3798,7 @@ msgstr "Auszuführende Aktion"
msgctxt "view:ir.cron:"
msgid "Run Once"
-msgstr ""
+msgstr "Einmalig durchführen"
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
@@ -3972,24 +3954,22 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Module"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "Date"
msgstr "Datum"
msgctxt "view:ir.note:"
msgid "Note"
-msgstr ""
+msgstr "Notiz"
msgctxt "view:ir.note:"
msgid "Notes"
-msgstr ""
+msgstr "Notizen"
msgctxt "view:ir.note:"
msgid "Time"
-msgstr ""
+msgstr "Zeit"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "User"
msgstr "Benutzer"
diff --git a/trytond/ir/locale/es_AR.po b/trytond/ir/locale/es_AR.po
index a123811..44a20ce 100644
--- a/trytond/ir/locale/es_AR.po
+++ b/trytond/ir/locale/es_AR.po
@@ -70,10 +70,9 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
"La definición de correo electrónico en el informe «%s» no es correcta."
-#, fuzzy
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource."
-msgstr "¡Los nombres de los archivos adjuntos deben ser únicos por recurso!"
+msgstr "Los nombres de los archivos adjuntos deben ser únicos por recurso."
msgctxt "error:ir.cron:"
msgid "Scheduled action failed"
@@ -425,7 +424,7 @@ msgstr "Valor del contexto"
msgctxt "field:ir.action.act_window,context_model:"
msgid "Context Model"
-msgstr ""
+msgstr "Modelo del contexto"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
@@ -1679,98 +1678,82 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Módulos a actualizar"
-#, fuzzy
msgctxt "field:ir.note,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
-#, fuzzy
msgctxt "field:ir.note,create_uid:"
msgid "Create User"
msgstr "Usuario creación"
-#, fuzzy
msgctxt "field:ir.note,id:"
msgid "ID"
msgstr "ID"
-#, fuzzy
msgctxt "field:ir.note,last_modification:"
msgid "Last Modification"
msgstr "Última Modificación"
-#, fuzzy
msgctxt "field:ir.note,last_user:"
msgid "Last User"
msgstr "Último Usuario"
msgctxt "field:ir.note,message:"
msgid "Message"
-msgstr ""
+msgstr "Mensaje"
msgctxt "field:ir.note,message_wrapped:"
msgid "Message"
-msgstr ""
+msgstr "Mensaje"
-#, fuzzy
msgctxt "field:ir.note,rec_name:"
msgid "Name"
msgstr "Nombre"
-#, fuzzy
msgctxt "field:ir.note,resource:"
msgid "Resource"
msgstr "Recurso"
msgctxt "field:ir.note,unread:"
msgid "Unread"
-msgstr ""
+msgstr "Sin leer"
-#, fuzzy
msgctxt "field:ir.note,write_date:"
msgid "Write Date"
msgstr "Fecha modificación"
-#, fuzzy
msgctxt "field:ir.note,write_uid:"
msgid "Write User"
msgstr "Usuario modificación"
-#, fuzzy
msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
-#, fuzzy
msgctxt "field:ir.note.read,create_uid:"
msgid "Create User"
msgstr "Usuario creación"
-#, fuzzy
msgctxt "field:ir.note.read,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:ir.note.read,note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
-#, fuzzy
msgctxt "field:ir.note.read,rec_name:"
msgid "Name"
msgstr "Nombre"
-#, fuzzy
msgctxt "field:ir.note.read,user:"
msgid "User"
msgstr "Usuario"
-#, fuzzy
msgctxt "field:ir.note.read,write_date:"
msgid "Write Date"
msgstr "Fecha modificación"
-#, fuzzy
msgctxt "field:ir.note.read,write_uid:"
msgid "Write User"
msgstr "Usuario modificación"
@@ -2937,7 +2920,7 @@ msgstr "Realizar instalaciones/actualizaciones pendientes"
msgctxt "model:ir.action,name:act_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
@@ -3017,7 +3000,7 @@ msgstr "Gráfico"
msgctxt "model:ir.action,name:report_model_workflow_graph"
msgid "Workflow Graph"
-msgstr ""
+msgstr "Gráfico de estados"
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
@@ -3223,11 +3206,11 @@ msgstr "Iniciar la instalación o actualizaciones de módulos"
msgctxt "model:ir.note,name:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "model:ir.note.read,name:"
msgid "Note Read"
-msgstr ""
+msgstr "Nota leída"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3411,7 +3394,7 @@ msgstr "Módulos"
msgctxt "model:ir.ui.menu,name:menu_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
@@ -3665,7 +3648,6 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
-#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Report"
msgstr "Informe"
@@ -3812,7 +3794,7 @@ msgstr "Acción a disparar"
msgctxt "view:ir.cron:"
msgid "Run Once"
-msgstr ""
+msgstr "Ejecutar una vez"
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
@@ -3970,24 +3952,22 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Módulos"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "Date"
msgstr "Fecha"
msgctxt "view:ir.note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "view:ir.note:"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "view:ir.note:"
msgid "Time"
-msgstr ""
+msgstr "Hora"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "User"
msgstr "Usuario"
diff --git a/trytond/ir/locale/es_CO.po b/trytond/ir/locale/es_CO.po
index c6e6b48..5df5118 100644
--- a/trytond/ir/locale/es_CO.po
+++ b/trytond/ir/locale/es_CO.po
@@ -70,10 +70,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "El correo electronico definido en el informe \"%s\" es inválido."
-#, fuzzy
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource."
-msgstr "¡Los nombres de los archivos adjuntos deben ser únicos por recurso!"
+msgstr "Los nombres de los archivos adjuntos deben ser únicos por recurso."
msgctxt "error:ir.cron:"
msgid "Scheduled action failed"
@@ -251,14 +250,13 @@ msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
msgstr "\"Al Tiempo\" y otros son mutuamente excluyentes!"
-#, fuzzy
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid PYSON expression on trigger "
"\"%(trigger)s\"."
msgstr ""
-"Condición \"%(condition)s\" no es una expressión python válida en el "
-"disparador \"%s(trigger)s\"."
+"La condición \"%(condition)s\" no es una expresión PYSON válida en el "
+"disparador \"%(trigger)s\"."
msgctxt "error:ir.ui.menu:"
msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
@@ -424,7 +422,7 @@ msgstr "Valor del Contexto"
msgctxt "field:ir.action.act_window,context_model:"
msgid "Context Model"
-msgstr ""
+msgstr "Modelo Contexto"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
@@ -1678,98 +1676,82 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Módulos a actualizar"
-#, fuzzy
msgctxt "field:ir.note,create_date:"
msgid "Create Date"
msgstr "Fecha de Creación"
-#, fuzzy
msgctxt "field:ir.note,create_uid:"
msgid "Create User"
msgstr "Creado por Usuario"
-#, fuzzy
msgctxt "field:ir.note,id:"
msgid "ID"
msgstr "ID"
-#, fuzzy
msgctxt "field:ir.note,last_modification:"
msgid "Last Modification"
msgstr "Última Modificación"
-#, fuzzy
msgctxt "field:ir.note,last_user:"
msgid "Last User"
msgstr "Último Usuario"
msgctxt "field:ir.note,message:"
msgid "Message"
-msgstr ""
+msgstr "Mensaje"
msgctxt "field:ir.note,message_wrapped:"
msgid "Message"
-msgstr ""
+msgstr "Mensaje"
-#, fuzzy
msgctxt "field:ir.note,rec_name:"
msgid "Name"
msgstr "Nombre"
-#, fuzzy
msgctxt "field:ir.note,resource:"
msgid "Resource"
msgstr "Recurso"
msgctxt "field:ir.note,unread:"
msgid "Unread"
-msgstr ""
+msgstr "No leído"
-#, fuzzy
msgctxt "field:ir.note,write_date:"
msgid "Write Date"
msgstr "Fecha de Modificación"
-#, fuzzy
msgctxt "field:ir.note,write_uid:"
msgid "Write User"
msgstr "Modificado por Usuario"
-#, fuzzy
msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
msgstr "Fecha de Creación"
-#, fuzzy
msgctxt "field:ir.note.read,create_uid:"
msgid "Create User"
msgstr "Creado por Usuario"
-#, fuzzy
msgctxt "field:ir.note.read,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:ir.note.read,note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
-#, fuzzy
msgctxt "field:ir.note.read,rec_name:"
msgid "Name"
msgstr "Nombre"
-#, fuzzy
msgctxt "field:ir.note.read,user:"
msgid "User"
msgstr "Usuario"
-#, fuzzy
msgctxt "field:ir.note.read,write_date:"
msgid "Write Date"
msgstr "Fecha de Modificación"
-#, fuzzy
msgctxt "field:ir.note.read,write_uid:"
msgid "Write User"
msgstr "Modificado por Usuario"
@@ -2296,7 +2278,7 @@ msgstr "Número Límite"
msgctxt "field:ir.trigger,minimum_time_delay:"
msgid "Minimum Delay"
-msgstr ""
+msgstr "Retraso Mínimo"
msgctxt "field:ir.trigger,model:"
msgid "Model"
@@ -2810,13 +2792,12 @@ 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"
-#, fuzzy
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
-"Una instrucción Python evaluada con el registro representado por \"self\"\n"
+"Una instrucción PYSON evaluada con el registro representado por \"self\"\n"
"Se dispara la acción si es verdadera."
msgctxt "help:ir.trigger,limit_number:"
@@ -2937,7 +2918,7 @@ msgstr "Realizar Instalaciones/Actualizaciones Pendientes"
msgctxt "model:ir.action,name:act_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
@@ -3017,7 +2998,7 @@ msgstr "Gráfico"
msgctxt "model:ir.action,name:report_model_workflow_graph"
msgid "Workflow Graph"
-msgstr ""
+msgstr "Gráfico de Flujo de Trabajo"
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
@@ -3127,20 +3108,19 @@ msgstr "Español (Colombia)"
msgctxt "model:ir.lang,name:lang_es_MX"
msgid "Spanish (Mexico)"
-msgstr ""
+msgstr "Español (México)"
msgctxt "model:ir.lang,name:lang_fr"
msgid "French"
msgstr "Francés"
-#, fuzzy
msgctxt "model:ir.lang,name:lang_hu_HU"
msgid "Hungarian"
-msgstr "Búlgaro"
+msgstr "Hungaro"
msgctxt "model:ir.lang,name:lang_it_IT"
msgid "Italian"
-msgstr ""
+msgstr "Italiano"
msgctxt "model:ir.lang,name:lang_lt"
msgid "Lithuanian"
@@ -3152,7 +3132,7 @@ msgstr "Holandés"
msgctxt "model:ir.lang,name:lang_pt_BR"
msgid "Portuguese (Brazil)"
-msgstr ""
+msgstr "Portugues"
msgctxt "model:ir.lang,name:lang_ru"
msgid "Russian"
@@ -3225,11 +3205,11 @@ msgstr "Inicio de Asistente de Instalación del Módulo"
msgctxt "model:ir.note,name:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "model:ir.note.read,name:"
msgid "Note Read"
-msgstr ""
+msgstr "Leer Nota"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3413,7 +3393,7 @@ msgstr "Módulos"
msgctxt "model:ir.ui.menu,name:menu_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
@@ -3667,7 +3647,6 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
-#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Report"
msgstr "Reporte"
@@ -3686,7 +3665,7 @@ msgstr "Botón de Asistente"
msgctxt "selection:ir.ui.menu,action:"
msgid ""
-msgstr ""
+msgstr " "
msgctxt "selection:ir.ui.menu,action:"
msgid "ir.action.act_window"
@@ -3706,7 +3685,7 @@ msgstr "ir.action.wizard"
msgctxt "selection:ir.ui.view,type:"
msgid ""
-msgstr ""
+msgstr "Hora de Última Modificación"
msgctxt "selection:ir.ui.view,type:"
msgid "Board"
@@ -3804,10 +3783,9 @@ msgctxt "view:ir.attachment:"
msgid "Attachments"
msgstr "Adjuntos"
-#, fuzzy
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
-msgstr "Última Modificación"
+msgstr "Hora de Última Modificación"
msgctxt "view:ir.cron:"
msgid "Action to trigger"
@@ -3815,7 +3793,7 @@ msgstr "Acción a disparar"
msgctxt "view:ir.cron:"
msgid "Run Once"
-msgstr ""
+msgstr "Ejecutar Una Vez"
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
@@ -3897,14 +3875,13 @@ msgctxt "view:ir.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
msgstr "Bienvenido al asistente de configuración del módulo!"
-#, fuzzy
msgctxt "view:ir.module.config_wizard.first:"
msgid ""
"You will be able to configure your installation depending on the modules you"
" have installed."
msgstr ""
-"Usted será capaz de configurar la instalación en función de los módulos que "
-"ha instalado."
+"Usted podrá configurar la instalación dependiendo de los módulos que haya "
+"instalado."
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
@@ -3974,24 +3951,22 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Módulos"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "Date"
msgstr "Fecha"
msgctxt "view:ir.note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "view:ir.note:"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "view:ir.note:"
msgid "Time"
-msgstr ""
+msgstr "Hora"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "User"
msgstr "Usuario"
@@ -4143,10 +4118,9 @@ msgctxt "view:ir.ui.menu:"
msgid "Menu"
msgstr "Menú"
-#, fuzzy
msgctxt "view:ir.ui.view:"
msgid "Show"
-msgstr "_Mostrar"
+msgstr "Mostrar"
msgctxt "view:ir.ui.view:"
msgid "View"
diff --git a/trytond/ir/locale/es_EC.po b/trytond/ir/locale/es_EC.po
index 58c50ed..236ab7e 100644
--- a/trytond/ir/locale/es_EC.po
+++ b/trytond/ir/locale/es_EC.po
@@ -70,7 +70,6 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "La definición de correo electrónico sobre el informe \"%s\" no es válida."
-#, fuzzy
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource."
msgstr "¡Los nombres de los archivos adjuntos deben ser únicos por recurso!"
@@ -423,7 +422,7 @@ msgstr "Valor del contexto"
msgctxt "field:ir.action.act_window,context_model:"
msgid "Context Model"
-msgstr ""
+msgstr "Modelo del contexto"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
@@ -1677,98 +1676,82 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Módulos a actualizar"
-#, fuzzy
msgctxt "field:ir.note,create_date:"
msgid "Create Date"
msgstr "Fecha de creación"
-#, fuzzy
msgctxt "field:ir.note,create_uid:"
msgid "Create User"
msgstr "Creado por usuario"
-#, fuzzy
msgctxt "field:ir.note,id:"
msgid "ID"
msgstr "ID"
-#, fuzzy
msgctxt "field:ir.note,last_modification:"
msgid "Last Modification"
msgstr "Última modificación"
-#, fuzzy
msgctxt "field:ir.note,last_user:"
msgid "Last User"
msgstr "Último usuario"
msgctxt "field:ir.note,message:"
msgid "Message"
-msgstr ""
+msgstr "Mensaje"
msgctxt "field:ir.note,message_wrapped:"
msgid "Message"
-msgstr ""
+msgstr "Mensaje"
-#, fuzzy
msgctxt "field:ir.note,rec_name:"
msgid "Name"
msgstr "Nombre"
-#, fuzzy
msgctxt "field:ir.note,resource:"
msgid "Resource"
msgstr "Recurso"
msgctxt "field:ir.note,unread:"
msgid "Unread"
-msgstr ""
+msgstr "No leída"
-#, fuzzy
msgctxt "field:ir.note,write_date:"
msgid "Write Date"
msgstr "Fecha de modificación"
-#, fuzzy
msgctxt "field:ir.note,write_uid:"
msgid "Write User"
msgstr "Modificado por usuario"
-#, fuzzy
msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
msgstr "Fecha de creación"
-#, fuzzy
msgctxt "field:ir.note.read,create_uid:"
msgid "Create User"
msgstr "Creado por usuario"
-#, fuzzy
msgctxt "field:ir.note.read,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:ir.note.read,note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
-#, fuzzy
msgctxt "field:ir.note.read,rec_name:"
msgid "Name"
msgstr "Nombre"
-#, fuzzy
msgctxt "field:ir.note.read,user:"
msgid "User"
msgstr "Usuario"
-#, fuzzy
msgctxt "field:ir.note.read,write_date:"
msgid "Write Date"
msgstr "Fecha de modificación"
-#, fuzzy
msgctxt "field:ir.note.read,write_uid:"
msgid "Write User"
msgstr "Modificado por usuario"
@@ -2935,7 +2918,7 @@ msgstr "Realizar instalaciones/actualizaciones pendientes"
msgctxt "model:ir.action,name:act_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
@@ -3015,7 +2998,7 @@ msgstr "Gráfico"
msgctxt "model:ir.action,name:report_model_workflow_graph"
msgid "Workflow Graph"
-msgstr ""
+msgstr "Gráfico de estados"
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
@@ -3221,11 +3204,11 @@ msgstr "Iniciar la instalación o actualización de módulos"
msgctxt "model:ir.note,name:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "model:ir.note.read,name:"
msgid "Note Read"
-msgstr ""
+msgstr "Nota leída"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3409,7 +3392,7 @@ msgstr "Módulos"
msgctxt "model:ir.ui.menu,name:menu_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
@@ -3663,7 +3646,6 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
-#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Report"
msgstr "Informe"
@@ -3810,7 +3792,7 @@ msgstr "Acción a disparar"
msgctxt "view:ir.cron:"
msgid "Run Once"
-msgstr ""
+msgstr "Ejecutar una vez"
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
@@ -3968,24 +3950,22 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Módulos"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "Date"
msgstr "Fecha"
msgctxt "view:ir.note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "view:ir.note:"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "view:ir.note:"
msgid "Time"
-msgstr ""
+msgstr "Hora"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "User"
msgstr "Usuario"
diff --git a/trytond/ir/locale/es_ES.po b/trytond/ir/locale/es_ES.po
index b07e341..50fcb7d 100644
--- a/trytond/ir/locale/es_ES.po
+++ b/trytond/ir/locale/es_ES.po
@@ -71,7 +71,6 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
"La definición de correo electrónico sobre el informe \"%s\" no es correcta."
-#, fuzzy
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource."
msgstr "El nombre de los adjuntos debe ser único por registro."
@@ -422,7 +421,7 @@ msgstr "Valor del contexto"
msgctxt "field:ir.action.act_window,context_model:"
msgid "Context Model"
-msgstr ""
+msgstr "Modelo del contexto"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
@@ -1676,98 +1675,82 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Módulos a actualizar"
-#, fuzzy
msgctxt "field:ir.note,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
-#, fuzzy
msgctxt "field:ir.note,create_uid:"
msgid "Create User"
msgstr "Usuario creación"
-#, fuzzy
msgctxt "field:ir.note,id:"
msgid "ID"
msgstr "ID"
-#, fuzzy
msgctxt "field:ir.note,last_modification:"
msgid "Last Modification"
msgstr "Última modificación"
-#, fuzzy
msgctxt "field:ir.note,last_user:"
msgid "Last User"
msgstr "Último usuario"
msgctxt "field:ir.note,message:"
msgid "Message"
-msgstr ""
+msgstr "Mensaje"
msgctxt "field:ir.note,message_wrapped:"
msgid "Message"
-msgstr ""
+msgstr "Mensaje"
-#, fuzzy
msgctxt "field:ir.note,rec_name:"
msgid "Name"
msgstr "Nombre"
-#, fuzzy
msgctxt "field:ir.note,resource:"
msgid "Resource"
msgstr "Recurso"
msgctxt "field:ir.note,unread:"
msgid "Unread"
-msgstr ""
+msgstr "No leída"
-#, fuzzy
msgctxt "field:ir.note,write_date:"
msgid "Write Date"
msgstr "Fecha modificación"
-#, fuzzy
msgctxt "field:ir.note,write_uid:"
msgid "Write User"
msgstr "Usuario modificación"
-#, fuzzy
msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
-#, fuzzy
msgctxt "field:ir.note.read,create_uid:"
msgid "Create User"
msgstr "Usuario creación"
-#, fuzzy
msgctxt "field:ir.note.read,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:ir.note.read,note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
-#, fuzzy
msgctxt "field:ir.note.read,rec_name:"
msgid "Name"
msgstr "Nombre"
-#, fuzzy
msgctxt "field:ir.note.read,user:"
msgid "User"
msgstr "Usuario"
-#, fuzzy
msgctxt "field:ir.note.read,write_date:"
msgid "Write Date"
msgstr "Fecha modificación"
-#, fuzzy
msgctxt "field:ir.note.read,write_uid:"
msgid "Write User"
msgstr "Usuario modificación"
@@ -2934,7 +2917,7 @@ msgstr "Realizar instalaciones/actualizaciones pendientes"
msgctxt "model:ir.action,name:act_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
@@ -3014,7 +2997,7 @@ msgstr "Gráfico"
msgctxt "model:ir.action,name:report_model_workflow_graph"
msgid "Workflow Graph"
-msgstr ""
+msgstr "Grafo de los estados"
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
@@ -3220,11 +3203,11 @@ msgstr "Iniciar la instalación o actualización de módulos"
msgctxt "model:ir.note,name:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "model:ir.note.read,name:"
msgid "Note Read"
-msgstr ""
+msgstr "Nota leída"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3408,7 +3391,7 @@ msgstr "Módulos"
msgctxt "model:ir.ui.menu,name:menu_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
@@ -3592,7 +3575,7 @@ msgstr "Para actualizar"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr "Realizado"
+msgstr "Finalizado"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
@@ -3662,7 +3645,6 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
-#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Report"
msgstr "Informe"
@@ -3809,7 +3791,7 @@ msgstr "Acción a disparar"
msgctxt "view:ir.cron:"
msgid "Run Once"
-msgstr ""
+msgstr "Ejecutar una vez"
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
@@ -3966,24 +3948,22 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Módulos"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "Date"
msgstr "Fecha"
msgctxt "view:ir.note:"
msgid "Note"
-msgstr ""
+msgstr "Nota"
msgctxt "view:ir.note:"
msgid "Notes"
-msgstr ""
+msgstr "Notas"
msgctxt "view:ir.note:"
msgid "Time"
-msgstr ""
+msgstr "Hora"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "User"
msgstr "Usuario"
diff --git a/trytond/ir/locale/fr_FR.po b/trytond/ir/locale/fr_FR.po
index 04b6abf..5850e13 100644
--- a/trytond/ir/locale/fr_FR.po
+++ b/trytond/ir/locale/fr_FR.po
@@ -72,11 +72,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Définition de mail incorrecte sur le rapport « %s »."
-#, fuzzy
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 !"
+msgstr "Les noms des pièces jointes doivent être unique par ressource."
msgctxt "error:ir.cron:"
msgid "Scheduled action failed"
@@ -437,7 +435,7 @@ msgstr "Valeur du contexte"
msgctxt "field:ir.action.act_window,context_model:"
msgid "Context Model"
-msgstr ""
+msgstr "Modèle de context"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
@@ -1691,98 +1689,82 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Modules à mettre à jour"
-#, fuzzy
msgctxt "field:ir.note,create_date:"
msgid "Create Date"
msgstr "Date de création"
-#, fuzzy
msgctxt "field:ir.note,create_uid:"
msgid "Create User"
msgstr "Créé par"
-#, fuzzy
msgctxt "field:ir.note,id:"
msgid "ID"
msgstr "ID"
-#, fuzzy
msgctxt "field:ir.note,last_modification:"
msgid "Last Modification"
msgstr "Dernière modification"
-#, fuzzy
msgctxt "field:ir.note,last_user:"
msgid "Last User"
msgstr "Dernier utilisateur"
msgctxt "field:ir.note,message:"
msgid "Message"
-msgstr ""
+msgstr "Message"
msgctxt "field:ir.note,message_wrapped:"
msgid "Message"
-msgstr ""
+msgstr "Message"
-#, fuzzy
msgctxt "field:ir.note,rec_name:"
msgid "Name"
msgstr "Nom"
-#, fuzzy
msgctxt "field:ir.note,resource:"
msgid "Resource"
msgstr "Ressource"
msgctxt "field:ir.note,unread:"
msgid "Unread"
-msgstr ""
+msgstr "Non-lu"
-#, fuzzy
msgctxt "field:ir.note,write_date:"
msgid "Write Date"
msgstr "Date de mise à jour"
-#, fuzzy
msgctxt "field:ir.note,write_uid:"
msgid "Write User"
msgstr "Mis à jour par"
-#, fuzzy
msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
msgstr "Date de création"
-#, fuzzy
msgctxt "field:ir.note.read,create_uid:"
msgid "Create User"
msgstr "Créé par"
-#, fuzzy
msgctxt "field:ir.note.read,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:ir.note.read,note:"
msgid "Note"
-msgstr ""
+msgstr "Note"
-#, fuzzy
msgctxt "field:ir.note.read,rec_name:"
msgid "Name"
msgstr "Nom"
-#, fuzzy
msgctxt "field:ir.note.read,user:"
msgid "User"
msgstr "Utilisateur"
-#, fuzzy
msgctxt "field:ir.note.read,write_date:"
msgid "Write Date"
msgstr "Date de mise à jour"
-#, fuzzy
msgctxt "field:ir.note.read,write_uid:"
msgid "Write User"
msgstr "Mis à jour par"
@@ -2949,7 +2931,7 @@ msgstr "Lancer les installations/mise à jours en attente"
msgctxt "model:ir.action,name:act_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notes"
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
@@ -3029,7 +3011,7 @@ msgstr "Graphique"
msgctxt "model:ir.action,name:report_model_workflow_graph"
msgid "Workflow Graph"
-msgstr ""
+msgstr "Graphique de flux de travail"
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
@@ -3235,11 +3217,11 @@ msgstr "Installation Mise à jour de module - Début"
msgctxt "model:ir.note,name:"
msgid "Note"
-msgstr ""
+msgstr "Note"
msgctxt "model:ir.note.read,name:"
msgid "Note Read"
-msgstr ""
+msgstr "Note lue"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3423,7 +3405,7 @@ msgstr "Modules"
msgctxt "model:ir.ui.menu,name:menu_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Notes"
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
@@ -3677,7 +3659,6 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modèle"
-#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Report"
msgstr "Rapport"
@@ -3824,7 +3805,7 @@ msgstr "Action à déclencher"
msgctxt "view:ir.cron:"
msgid "Run Once"
-msgstr ""
+msgstr "Lancer une fois"
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
@@ -3982,24 +3963,22 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Modules"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "Date"
msgstr "Date"
msgctxt "view:ir.note:"
msgid "Note"
-msgstr ""
+msgstr "Note"
msgctxt "view:ir.note:"
msgid "Notes"
-msgstr ""
+msgstr "Notes"
msgctxt "view:ir.note:"
msgid "Time"
-msgstr ""
+msgstr "Heure"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "User"
msgstr "Utilisateur"
diff --git a/trytond/ir/locale/sl_SI.po b/trytond/ir/locale/sl_SI.po
index 0abc790..b0c6f87 100644
--- a/trytond/ir/locale/sl_SI.po
+++ b/trytond/ir/locale/sl_SI.po
@@ -70,7 +70,6 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Neveljavna definicija epošte na izpisu \"%s\"."
-#, fuzzy
msgctxt "error:ir.attachment:"
msgid "The names of attachments must be unique by resource."
msgstr "Imena priponk morajo biti unikatna med resursi."
@@ -415,7 +414,7 @@ msgstr "Vrednost konteksta"
msgctxt "field:ir.action.act_window,context_model:"
msgid "Context Model"
-msgstr ""
+msgstr "Kontekstni model"
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
@@ -1443,7 +1442,7 @@ msgstr "Ime"
msgctxt "field:ir.model.field,relation:"
msgid "Model Relation"
-msgstr "Model veze"
+msgstr "Vezni model"
msgctxt "field:ir.model.field,ttype:"
msgid "Field Type"
@@ -1669,98 +1668,82 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Moduli za posodobitev"
-#, fuzzy
msgctxt "field:ir.note,create_date:"
msgid "Create Date"
msgstr "Izdelano"
-#, fuzzy
msgctxt "field:ir.note,create_uid:"
msgid "Create User"
msgstr "Izdelal"
-#, fuzzy
msgctxt "field:ir.note,id:"
msgid "ID"
msgstr "ID"
-#, fuzzy
msgctxt "field:ir.note,last_modification:"
msgid "Last Modification"
msgstr "Zadnja sprememba"
-#, fuzzy
msgctxt "field:ir.note,last_user:"
msgid "Last User"
msgstr "Zadnji uporabnik"
msgctxt "field:ir.note,message:"
msgid "Message"
-msgstr ""
+msgstr "Sporočilo"
msgctxt "field:ir.note,message_wrapped:"
msgid "Message"
-msgstr ""
+msgstr "Sporočilo"
-#, fuzzy
msgctxt "field:ir.note,rec_name:"
msgid "Name"
-msgstr "Naziv"
+msgstr "Ime"
-#, fuzzy
msgctxt "field:ir.note,resource:"
msgid "Resource"
msgstr "Vir"
msgctxt "field:ir.note,unread:"
msgid "Unread"
-msgstr ""
+msgstr "Neprebrano"
-#, fuzzy
msgctxt "field:ir.note,write_date:"
msgid "Write Date"
msgstr "Zapisano"
-#, fuzzy
msgctxt "field:ir.note,write_uid:"
msgid "Write User"
msgstr "Zapisal"
-#, fuzzy
msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
msgstr "Izdelano"
-#, fuzzy
msgctxt "field:ir.note.read,create_uid:"
msgid "Create User"
msgstr "Izdelal"
-#, fuzzy
msgctxt "field:ir.note.read,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:ir.note.read,note:"
msgid "Note"
-msgstr ""
+msgstr "Zabeležka"
-#, fuzzy
msgctxt "field:ir.note.read,rec_name:"
msgid "Name"
-msgstr "Naziv"
+msgstr "Ime"
-#, fuzzy
msgctxt "field:ir.note.read,user:"
msgid "User"
msgstr "Uporabnik"
-#, fuzzy
msgctxt "field:ir.note.read,write_date:"
msgid "Write Date"
msgstr "Zapisano"
-#, fuzzy
msgctxt "field:ir.note.read,write_uid:"
msgid "Write User"
msgstr "Zapisal"
@@ -2925,7 +2908,7 @@ msgstr "Namesti/nadgradi"
msgctxt "model:ir.action,name:act_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Zabeležke"
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
@@ -3005,7 +2988,7 @@ msgstr "Diagram"
msgctxt "model:ir.action,name:report_model_workflow_graph"
msgid "Workflow Graph"
-msgstr ""
+msgstr "Grafa delovnega toka"
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
@@ -3211,11 +3194,11 @@ msgstr "Začetek nadgradnje modula"
msgctxt "model:ir.note,name:"
msgid "Note"
-msgstr ""
+msgstr "Zabeležka"
msgctxt "model:ir.note.read,name:"
msgid "Note Read"
-msgstr ""
+msgstr "Prebrana zabeležka"
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3399,7 +3382,7 @@ msgstr "Moduli"
msgctxt "model:ir.ui.menu,name:menu_note_form"
msgid "Notes"
-msgstr ""
+msgstr "Zabeležke"
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
@@ -3506,10 +3489,6 @@ msgid "Action form"
msgstr "Ukrep za obrazec"
msgctxt "selection:ir.action.keyword,keyword:"
-msgid "Action tree"
-msgstr "Ukrep za drevesni prikaz"
-
-msgctxt "selection:ir.action.keyword,keyword:"
msgid "Form relate"
msgstr "Relacija"
@@ -3569,7 +3548,6 @@ msgctxt "selection:ir.module,state:"
msgid "Not Installed"
msgstr "Ni nameščeno"
-#, fuzzy
msgctxt "selection:ir.module,state:"
msgid "To be installed"
msgstr "Za namestiti"
@@ -3654,7 +3632,6 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Model"
-#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Report"
msgstr "Poročilo"
@@ -3801,7 +3778,7 @@ msgstr "Sprožitev ukrepa"
msgctxt "view:ir.cron:"
msgid "Run Once"
-msgstr ""
+msgstr "Enkratni zagon"
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
@@ -3957,24 +3934,22 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Moduli"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "Date"
msgstr "Datum"
msgctxt "view:ir.note:"
msgid "Note"
-msgstr ""
+msgstr "Zabeležka"
msgctxt "view:ir.note:"
msgid "Notes"
-msgstr ""
+msgstr "Zabeležke"
msgctxt "view:ir.note:"
msgid "Time"
-msgstr ""
+msgstr "Čas"
-#, fuzzy
msgctxt "view:ir.note:"
msgid "User"
msgstr "Uporabnik"
diff --git a/trytond/protocols/jsonrpc.py b/trytond/protocols/jsonrpc.py
index 69298ef..956d291 100644
--- a/trytond/protocols/jsonrpc.py
+++ b/trytond/protocols/jsonrpc.py
@@ -121,7 +121,10 @@ class JSONRequest(Request):
def parsed_data(self):
if self.parsed_content_type in self.environ.get('CONTENT_TYPE', ''):
try:
- return json.loads(self.decoded_data, object_hook=JSONDecoder())
+ return json.loads(
+ self.decoded_data.decode(
+ self.charset, self.encoding_errors),
+ object_hook=JSONDecoder())
except Exception:
raise BadRequest('Unable to read JSON request')
else:
diff --git a/trytond/res/locale/es_CO.po b/trytond/res/locale/es_CO.po
index 3d4aec4..4a87be7 100644
--- a/trytond/res/locale/es_CO.po
+++ b/trytond/res/locale/es_CO.po
@@ -11,6 +11,8 @@ msgid ""
"Users can not be deleted for logging purpose.\n"
"Instead you must inactivate them."
msgstr ""
+"Los usuarios no pueden ser borrados para impedir que se logueen.\n"
+"A cambio debe desactivarlos."
msgctxt "error:res.user:"
msgid "Wrong password!"
diff --git a/trytond/tests/test_protocols.py b/trytond/tests/test_protocols.py
index 70df424..3115846 100644
--- a/trytond/tests/test_protocols.py
+++ b/trytond/tests/test_protocols.py
@@ -6,13 +6,23 @@ import json
import datetime
from decimal import Decimal
-from trytond.protocols.jsonrpc import JSONEncoder, JSONDecoder
-from trytond.protocols.xmlrpc import client
+from trytond.protocols.jsonrpc import JSONEncoder, JSONDecoder, JSONRequest
+from trytond.protocols.xmlrpc import client, XMLRequest
class JSONTestCase(unittest.TestCase):
'Test JSON'
+ def test_json_request(self):
+ req = JSONRequest.from_values(
+ data=b'{"method": "method", "params": ["foo", "bar"]}',
+ content_type='text/json',
+ )
+ self.assertEqual(req.parsed_data,
+ {'method': 'method', 'params': ['foo', 'bar']})
+ self.assertEqual(req.method, 'method')
+ self.assertEqual(req.params, ['foo', 'bar'])
+
def dumps_loads(self, value):
self.assertEqual(json.loads(
json.dumps(value, cls=JSONEncoder),
@@ -43,6 +53,14 @@ class JSONTestCase(unittest.TestCase):
class XMLTestCase(unittest.TestCase):
'Test XML'
+ def test_xml_request(self):
+ req = XMLRequest.from_values(
+ data=b"<?xml version='1.0'?>\n<methodCall>\n<methodName>method</methodName>\n<params>\n<param>\n<value><string>foo</string></value>\n</param>\n<param>\n<value><string>bar</string></value>\n</param>\n</params>\n</methodCall>\n",
+ content_type='text/xml')
+ self.assertEqual(req.parsed_data, (('foo', 'bar'), 'method'))
+ self.assertEqual(req.method, 'method')
+ self.assertEqual(req.params, ('foo', 'bar'))
+
def dumps_loads(self, value):
s = client.dumps((value,))
result, _ = client.loads(s)
commit 41f0ce3d83b61a4fcd8a7e88149824f37f8aeee6
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Fri May 27 18:26:27 2016 +0200
Merging upstream version 4.0.0.
diff --git a/CHANGELOG b/CHANGELOG
index d28a116..93356a4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,22 +1,32 @@
-Version 3.8.5 - 2016-04-06
-* Bug fixes (see mercurial logs for details)
-
-Version 3.8.4 - 2016-03-14
+Version 4.0.0 - 2016-05-02
* Bug fixes (see mercurial logs for details)
+* Add sendmail module to send transactional email
+* Support Two-Phase Commit in Transaction
+* Allow Report to generate text plain, XML, HTML and XHTML
+* Add workflow graph on ir.model
+* Add context model on ir.action.act_window
+* Switch to WSGI API
* Limit the login size in LoginAttempt
-
-Version 3.8.3 - 2016-02-06
-* Bug fixes (see mercurial logs for details)
+* Remove LocalDict from tools
+* Add LRUDictTransaction
+* Follow PEP-0249 for Database, Transaction and Cursor
+* Add Python3 support
+* Make TestCase create and drop its database
+* Add with_transaction decorator for tests
+* Add note on resources
+* Add 'where' operator for xxx2many fields
* Strip and unquote double-quote from Postgresql schema in search_path
+* Move webdav into a separate module
* Don't read historized user when evaluating record rules as it could lead to
past privilege escalation.
-
-Version 3.8.2 - 2016-01-11
-* Bug fixes (see mercurial logs for details)
-
-Version 3.8.1 - 2015-12-16
-* Bug fixes (see mercurial logs for details)
+* Only rebuild mptt tree if left or right values have their default values
+* Allow nested inherited view
+* Add button on cron to run once
* Check all fields when writing a sequence of records, values (CVE-2015-0861)
+* Add view_ids on tree view
+* Add parent_of operator
+* Enforce type of inheriting view
+* Use instance context in translated descriptor of Selection
Version 3.8.0 - 2015-11-02
* Bug fixes (see mercurial logs for details)
diff --git a/COPYRIGHT b/COPYRIGHT
index 41201b2..dea0462 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -3,6 +3,7 @@ Copyright (C) 2007-2016 Cédric Krier.
Copyright (C) 2007-2013 Bertrand Chenal.
Copyright (C) 2008-2016 B2CK SPRL.
Copyright (C) 2011 Openlabs Technologies & Consulting (P) Ltd.
+Copyright (C) 2011-2016 Nicolas Évrard.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/MANIFEST.in b/MANIFEST.in
index 30f129b..04504c8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -12,7 +12,6 @@ include trytond/ir/tryton.cfg
include trytond/ir/*.xml
include trytond/ir/view/*.xml
include trytond/ir/locale/*.po
-include trytond/ir/module/*.xml
include trytond/ir/ui/*.xml
include trytond/ir/ui/*.rng
include trytond/ir/ui/*.rnc
@@ -23,7 +22,3 @@ include trytond/res/view/*.xml
include trytond/res/locale/*.po
include trytond/tests/tryton.cfg
include trytond/tests/*.xml
-include trytond/webdav/tryton.cfg
-include trytond/webdav/*.xml
-include trytond/webdav/view/*.xml
-include trytond/webdav/locale/*.po
diff --git a/PKG-INFO b/PKG-INFO
index eaae680..ec59371 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,12 @@
Metadata-Version: 1.1
Name: trytond
-Version: 3.8.5
+Version: 4.0.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.8/
+Download-URL: http://downloads.tryton.org/4.0/
Description: trytond
=======
@@ -106,6 +106,7 @@ Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Natural Language :: Bulgarian
Classifier: Natural Language :: Catalan
+Classifier: Natural Language :: Chinese (Simplified)
Classifier: Natural Language :: Czech
Classifier: Natural Language :: Dutch
Classifier: Natural Language :: English
@@ -119,6 +120,9 @@ Classifier: Natural Language :: Slovenian
Classifier: Natural Language :: Spanish
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
diff --git a/bin/trytond b/bin/trytond
index 89d7b31..11b38ae 100755
--- a/bin/trytond
+++ b/bin/trytond
@@ -1,80 +1,50 @@
#!/usr/bin/env python
-#This file is part of Tryton. The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
import sys
import os
-import argparse
+import glob
+
+from werkzeug.serving import run_simple
DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
'..', '..', 'trytond')))
if os.path.isdir(DIR):
sys.path.insert(0, os.path.dirname(DIR))
-from trytond import __version__
-from trytond import server
-
-
-def parse_commandline():
- options = {}
-
- parser = argparse.ArgumentParser(prog='trytond')
-
- parser.add_argument('--version', action='version',
- version='%(prog)s ' + __version__)
- parser.add_argument("-c", "--config", dest="configfile", metavar='FILE',
- default=os.environ.get('TRYTOND_CONFIG'), help="specify config file")
- parser.add_argument('--dev', dest='dev', action='store_true',
- help='enable development mode')
- parser.add_argument("-v", "--verbose", action="store_true",
- dest="verbose", help="enable verbose mode")
-
- 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", metavar='FILE',
- help="file where the server pid will be stored")
- 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.')
-
- options = parser.parse_args()
-
- if not options.database_names and options.update:
- parser.error('Missing database option')
- return options
-
-
-if '--profile' in sys.argv:
- import profile
- import pstats
- import tempfile
- sys.argv.remove('--profile')
-
- options = parse_commandline()
- statfile = tempfile.mkstemp(".stat", "trytond-")[1]
- profile.run('server.TrytonServer(options).run()', statfile)
- s = pstats.Stats(statfile)
- s.sort_stats('cumulative').print_stats()
- s.sort_stats('call').print_stats()
- s.sort_stats('time').print_stats()
- s.sort_stats('time')
- s.print_callers()
- s.print_callees()
-
- os.remove(statfile)
-else:
- options = parse_commandline()
- server.TrytonServer(options).run()
+import trytond.commandline as commandline
+from trytond.config import config, split_netloc
+from trytond.application import app
+from trytond.pool import Pool
+from trytond.modules import get_module_list, get_module_info
+
+
+parser = commandline.get_parser_daemon()
+options = parser.parse_args()
+commandline.config_log(options)
+config.update_etc(options.configfile)
+
+with commandline.pidfile(options):
+ for name in options.database_names:
+ Pool(name).init()
+ hostname, port = split_netloc(config.get('web', 'listen'))
+ static_files = {
+ '/': config.get('web', 'root'),
+ }
+ certificate = config.get('ssl', 'certificate')
+ privatekey = config.get('ssl', 'privatekey')
+ if certificate or privatekey:
+ ssl_context = (certificate, privatekey)
+ else:
+ ssl_context = None
+ extra_files = [options.configfile]
+ for module in get_module_list():
+ info = get_module_info(module)
+ path = os.path.join(info['directory'], 'view', '*.xml')
+ extra_files.extend(glob.glob(path))
+ run_simple(hostname, port, app,
+ threaded=True,
+ extra_files=extra_files,
+ static_files=static_files,
+ ssl_context=ssl_context,
+ use_reloader=options.dev)
diff --git a/bin/trytond-admin b/bin/trytond-admin
new file mode 100755
index 0000000..075fe1c
--- /dev/null
+++ b/bin/trytond-admin
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import sys
+import os
+
+DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
+ '..', '..', 'trytond')))
+if os.path.isdir(DIR):
+ sys.path.insert(0, os.path.dirname(DIR))
+
+import trytond.commandline as commandline
+from trytond.config import config
+import trytond.admin as admin
+
+parser = commandline.get_parser_admin()
+options = parser.parse_args()
+config.update_etc(options.configfile)
+commandline.config_log(options)
+
+admin.run(options)
diff --git a/bin/trytond-cron b/bin/trytond-cron
new file mode 100755
index 0000000..e855d77
--- /dev/null
+++ b/bin/trytond-cron
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import sys
+import os
+import time
+
+DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
+ '..', '..', 'trytond')))
+if os.path.isdir(DIR):
+ sys.path.insert(0, os.path.dirname(DIR))
+
+import trytond.commandline as commandline
+from trytond.config import config
+import trytond.cron as cron
+
+parser = commandline.get_parser_daemon()
+options = parser.parse_args()
+config.update_etc(options.configfile)
+commandline.config_log(options)
+
+with commandline.pidfile(options):
+ while True:
+ cron.run(options)
+ time.sleep(60)
diff --git a/doc/conf.py b/doc/conf.py
index efb1eae..520e241 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.8'
+version = '4.0'
# The full version, including alpha/beta/rc tags.
-release = '3.8'
+release = '4.0'
# 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 5ee2f0d..2c72ca5 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -26,7 +26,8 @@ First steps
* **Installation:**
:ref:`Installation <topics-install>` |
:ref:`Configuration <topics-configuration>` |
- :ref:`Setup a database <topics-setup-database>`
+ :ref:`Setup a database <topics-setup-database>` |
+ :ref:`Start the server <topics-start-server>`
The model layer
===============
diff --git a/doc/ref/index.rst b/doc/ref/index.rst
index 30ff4e0..4203034 100644
--- a/doc/ref/index.rst
+++ b/doc/ref/index.rst
@@ -14,3 +14,4 @@ API Reference
tools/singleton
pool
rpc
+ sendmail
diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst
index 032603e..8139c62 100644
--- a/doc/ref/models/fields.rst
+++ b/doc/ref/models/fields.rst
@@ -373,6 +373,11 @@ Binary
A binary field. It will be represented in Python by a ``bytes`` instance.
+.. warning::
+ If the context contains a key composed of the model name and field name
+ separated by a dot and its value is the string `size` then the read value
+ is the size instead of the content.
+
:class:`Binary` has one extra optional argument:
.. attribute:: Binary.filename
@@ -404,8 +409,8 @@ A string field with limited values to choice.
The first element in each tuple is the actual value stored. The second
element is the human-readable name.
- It can also be the name of a class method on the model, that will return an
- appropriate list. The signature of the method is::
+ It can also be the name of a class or instance method on the model, that
+ will return an appropriate list. The signature of the method is::
selection()
@@ -439,7 +444,8 @@ 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.
+ must be used on the same class as the field. It will use the language
+ defined in the context of the instance accessed.
Reference
---------
@@ -484,6 +490,9 @@ A many-to-one relation field.
Tree Traversal`_.
It only works if the :attr:`model_name` is the same then the model.
+.. warning:: The MPTT Tree will be rebuild on database update if one record
+ is found having left or right field value equals to the default or NULL.
+
.. _`Modified Preorder Tree Traversal`: http://en.wikipedia.org/wiki/Tree_traversal
.. attribute:: Many2One.right
diff --git a/doc/ref/pool.rst b/doc/ref/pool.rst
index 0ca5638..88f42bc 100644
--- a/doc/ref/pool.rst
+++ b/doc/ref/pool.rst
@@ -39,10 +39,15 @@ Instance methods:
Return an interator over instances names.
-.. method:: Pool.setup(module)
+.. method:: Pool.fill(module)
- Setup classes for module and return a list of classes for each type in a
- dictionary.
+ Fill the pool with the registered class from the module and return a list
+ of classes for each type in a dictionary.
+
+.. method:: Pool.setup([classes])
+
+ Call all setup methods of the classes provided or for all the registered
+ classes.
========
PoolMeta
diff --git a/doc/ref/sendmail.rst b/doc/ref/sendmail.rst
new file mode 100644
index 0000000..4b77b31
--- /dev/null
+++ b/doc/ref/sendmail.rst
@@ -0,0 +1,44 @@
+.. _ref-sendmail:
+.. module:: trytond.sendmail
+
+========
+Sendmail
+========
+
+.. method:: sendmail_transactional(from_addr, to_addrs, msg[, transaction[, datamanager]])
+
+Send email message only if the current transaction is successfully committed.
+The required arguments are an `RFC 822`_ from-address string, a list of `RFC
+822`_ to-address strings (a bare string will be treated as a list with 1
+address), and an email message.
+The caller may pass a :class:`Transaction` instance to join otherwise the
+current one will be joined. A specific data manager can be specified otherwise
+the default :class:`SMTPDataManager` will be used for sending email.
+
+.. warning::
+
+ An SMTP failure will be only logged without raising any exception.
+
+.. method:: sendmail(from_addr, to_addrs, msg[, server])
+
+Send email message like :meth:`sendmail_transactional` but directly without
+caring about the transaction.
+The caller may pass a server instance from `smtplib`_.
+
+.. method:: get_smtp_server([uri])
+
+Return a SMTP instance from `smtplib`_ using the `uri` or the one defined in
+the `email` section of the :ref:`configuration <topics-configuration>`.
+
+
+.. class:: SMTPDataManager([uri])
+
+A :class:`SMTPDataManager` implements a data manager which send queued email at
+commit. An option optional `uri` can be passed to configure the SMTP connection.
+
+.. method:: SMTPDataManager.put(from_addr, to_addrs, msg)
+
+ Queue the email message to send.
+
+.. _`RFC 822`: https://tools.ietf.org/html/rfc822.html
+.. _`smtplib`: https://docs.python.org/2/library/smtplib.html
diff --git a/doc/ref/transaction.rst b/doc/ref/transaction.rst
index 2ddc292..b212945 100644
--- a/doc/ref/transaction.rst
+++ b/doc/ref/transaction.rst
@@ -7,19 +7,21 @@ Transaction
.. class:: Transaction
-This class is a `singleton`_ that contains thread-local parameters of the
-database transaction.
-
-.. _`singleton`: http://en.wikipedia.org/wiki/Singleton_pattern
+This class represents a Tryton transaction that contains thread-local
+parameters of a database connection. The Transaction instances are
+`context manager`_ that will commit or rollback the database transaction. In
+the event of an exception the transaction is rolled back, otherwise it is
+commited.
+.. attribute:: Transaction.database
-.. attribute:: Transaction.cursor
+ The database.
- The database cursor.
+.. attribute:: Transaction.readonly
-.. attribute:: Transaction.database
+.. attribute:: Transaction.connection
- The database.
+ The database connection as defined by the `PEP-0249`_.
.. attribute:: Transaction.user
@@ -43,14 +45,20 @@ database transaction.
Count the number of modification made in this transaction.
+.. method:: Transaction.cursor()
+
+ Returns a cursor object using the ``Transaction.connection``.
+
.. method:: Transaction.start(database_name, user[, readonly[, context[, close[, autocommit]]]])
Start a new transaction and return a `context manager`_.
.. method:: Transaction.stop()
- Stop a started transaction. This method should not be called directly as it
- will be by the context manager when exiting the `with` statement.
+ Stop a started transaction and pop it from the stack of transactions.
+
+ This method should not be called directly as it will be by the context
+ manager when exiting the `with` statement.
.. method:: Transaction.set_context(context, \**kwargs)
@@ -63,15 +71,28 @@ database transaction.
`set_context` will put the previous user id in the context to simulate the
record rules. The user will be restored when exiting the `with` statement.
-.. method:: Transaction.set_cursor(cursor)
+.. method:: Transaction.set_current_transaction(transaction)
+
+ Add a specific ``transaction`` on the top of the transaction stack. A
+ transaction is commited or rollbacked only when its last reference is
+ popped from the stack.
+
+.. method:: Transaction.new_transaction([autocommit[, readonly]])
+
+ Create a new transaction with the same database, user and context as the
+ original transaction and adds it to the stack of transactions.
- Modify the cursor of the transaction and return a `context manager`_. The
- previous cursor will be restored when exiting the `with` statement.
+.. method:: Transaction.join(datamanager)
-.. method:: Transaction.new_cursor([autocommit[, readonly]])
+ Register in the transaction a data manager conforming to the `Two-Phase
+ Commit protocol`_. More information on how to implement such data manager
+ is available at the `Zope documentation`_.
- Change the cursor of the transaction with a new one on the same database
- and return a `context manager`_. The previous cursor will be restored when
- exiting the `with` statement and the new one will be closed.
+ This method returns the registered datamanager. It could be a different yet
+ equivalent (in term of python equality) datamanager than the one passed to the
+ method.
.. _`context manager`: http://docs.python.org/reference/datamodel.html#context-managers
+.. _`PEP-0249`: https://www.python.org/dev/peps/pep-0249/
+.. _`Two-Phase Commit protocol`: https://en.wikipedia.org/wiki/Two-phase_commit_protocol
+.. _`Zope documentation`: http://zodb.readthedocs.org/en/latest/transactions.html#the-two-phase-commit-protocol-in-practice
diff --git a/doc/topics/access_rights.rst b/doc/topics/access_rights.rst
index 5f625e0..0e69bba 100644
--- a/doc/topics/access_rights.rst
+++ b/doc/topics/access_rights.rst
@@ -26,10 +26,19 @@ Same as for model access but applied on the field. It uses records of
Button
======
-For each button of a model the records of 'ir.model.button` define the list of
+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
+They are defined by records of `ir.rule.group` which contains a list of
+`ir.rule` domain to which the rule applies. The group are selected by groups or
+users. The access is granted for a record:
+
+ - if the user is in at least one group that has the permission activated,
+
+ - or if the user is in no group by there is a default group with the
+ permission,
+
+ - or if there is a global group with the permission.
diff --git a/doc/topics/actions.rst b/doc/topics/actions.rst
index 015a5f1..da68525 100644
--- a/doc/topics/actions.rst
+++ b/doc/topics/actions.rst
@@ -22,12 +22,10 @@ Keyword
Keywords define where to display the action in the client.
-There are six places:
+There are five places:
* Open tree (`tree_open`)
- * Action tree (`tree_action`)
-
* Print form (`form_print`)
* Action form (`form_action`)
diff --git a/doc/topics/configuration.rst b/doc/topics/configuration.rst
index 4d31d6c..f02029f 100644
--- a/doc/topics/configuration.rst
+++ b/doc/topics/configuration.rst
@@ -29,51 +29,31 @@ possible values.
Some modules could request the usage of other sections for which the guideline
asks them to be named like their module.
-jsonrpc
--------
+web
+---
-Defines the behavior of the JSON-RPC_ network interface.
+Defines the behavior of the web interface.
listen
~~~~~~
-Defines a comma separated list of couples of host (or IP address) and port
-number separated by a colon to listen on.
+Defines the couple of host (or IP address) and port number separated by a colon
+to listen on.
Default `localhost:8000`
hostname
~~~~~~~~
-Defines the hostname for this network interface.
+Defines the hostname.
-data
+root
~~~~
-Defines the root path to retrieve data for `GET` requests.
+Defines the root path served by `GET` requests.
Default: `/var/www/localhost/tryton`
-xmlrpc
-------
-
-Defines the behavior of the XML-RPC_ network interface.
-
-listen
-~~~~~~
-
-Same as for `jsonrpc` except it has no default value.
-
-webdav
-------
-
-Define the behavior of the WebDAV_ network interface.
-
-listen
-~~~~~~
-
-Same as for `jsonrpc` except it has no default value.
-
database
--------
@@ -250,7 +230,6 @@ Default: `pipe,name=trytond;urp;StarOffice.ComponentContext`
.. _JSON-RPC: http://en.wikipedia.org/wiki/JSON-RPC
.. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC
-.. _WebDAV: http://en.wikipedia.org/wiki/WebDAV
.. _RFC-3986: http://tools.ietf.org/html/rfc3986
.. _SMTP-URL: http://tools.ietf.org/html/draft-earhart-url-smtp-00
.. _SSL: http://en.wikipedia.org/wiki/Secure_Sockets_Layer
diff --git a/doc/topics/domain.rst b/doc/topics/domain.rst
index 616d1fd..fdc1597 100644
--- a/doc/topics/domain.rst
+++ b/doc/topics/domain.rst
@@ -227,4 +227,30 @@ pattern, unless otherwise noted::
Is a parent child comparison operator. It is the negation of the
`child_of`_ operator.
+``parent_of``
+-------------
+
+ Is a parent child comparison operator. It is the same as `child_of`_
+ operator but if ``<field name>`` is a parent of ``<operand>``.
+
+``not parent_of``
+-----------------
+
+ Is a parent child comparison operator. it is the negation of this
+ `parent_of`_ operator.
+
+``where``
+---------
+
+ Is a :class:`trytond.model.fields.One2Many` /
+ :class:`trytond.model.fields.Many2Many` domain operator. It returns true
+ for every row of the target model that match the domain specified as
+ ``<operand>``.
+
+``not where``
+-------------
+ Is a :class:`trytond.model.fields.One2Many` /
+ :class:`trytond.model.fields.Many2Many` domain operator. It returns true
+ for every row of the target model that does not match the domain specified
+ as ``<operand>``.
diff --git a/doc/topics/index.rst b/doc/topics/index.rst
index c19ba8a..6560776 100644
--- a/doc/topics/index.rst
+++ b/doc/topics/index.rst
@@ -13,6 +13,7 @@ Introduction to all the key parts of trytond:
configuration
setup_database
logs
+ start_server
models/index
models/fields_default_value
models/fields_on_change
diff --git a/doc/topics/install.rst b/doc/topics/install.rst
index 889e360..4a08e98 100644
--- a/doc/topics/install.rst
+++ b/doc/topics/install.rst
@@ -8,17 +8,18 @@ Prerequisites
=============
* Python 2.7 or later (http://www.python.org/)
+ * Werkzeug (http://werkzeug.pocoo.org/)
+ * wrapt (https://github.com/GrahamDumpleton/wrapt)
* lxml 2.0 or later (http://lxml.de/)
* relatorio 0.2.0 or later (http://code.google.com/p/python-relatorio/)
* Genshi (http://genshi.edgewall.org/)
* python-dateutil (http://labix.org/python-dateutil)
* polib (https://bitbucket.org/izi/polib/wiki/Home)
- * python-sql 0.2 or later (http://code.google.com/p/python-sql/)
- * Optional: psycopg 2 or later (http://www.initd.org/)
+ * python-sql 0.4 or later (http://code.google.com/p/python-sql/)
+ * Optional: psycopg 2.5.0 or later (http://www.initd.org/)
* Optional: psycopg2cffi 2.5.0 or later
(http://github.com/chtd/psycopg2cffi)
* Optional: MySQL-python (http://sourceforge.net/projects/mysql-python/)
- * Optional: pywebdav 0.9.8 or later (http://code.google.com/p/pywebdav/)
* Optional: pydot (http://code.google.com/p/pydot/)
* Optional: unoconv http://dag.wieers.com/home-made/unoconv/)
* Optional: sphinx (http://sphinx.pocoo.org/)
diff --git a/doc/topics/modules/index.rst b/doc/topics/modules/index.rst
index f1694ed..21d0638 100644
--- a/doc/topics/modules/index.rst
+++ b/doc/topics/modules/index.rst
@@ -6,7 +6,7 @@ Modules
The modules of Tryton extend the functionality of the platform. The server
comes by default with only a basic functionality included in these modules:
-``ir``, ``res``, ``webdav``.
+``ir``, ``res``.
Module Structure
================
diff --git a/doc/topics/setup_database.rst b/doc/topics/setup_database.rst
index fe5ed91..977602d 100644
--- a/doc/topics/setup_database.rst
+++ b/doc/topics/setup_database.rst
@@ -20,7 +20,7 @@ Initialize a database
A database can be initialized using this command line::
- trytond -c <config file> -d <database name> --all
+ trytond-admin -c <config file> -d <database name> --all
-At the end of the process, `trytond` will ask to set the password for the
+At the end of the process, `trytond-admin` will ask to set the password for the
`admin` user.
diff --git a/doc/topics/start_server.rst b/doc/topics/start_server.rst
new file mode 100644
index 0000000..71853ba
--- /dev/null
+++ b/doc/topics/start_server.rst
@@ -0,0 +1,41 @@
+.. _topics-start-server:
+
+=======================
+How to start the server
+=======================
+
+Web service
+===========
+
+You can start the default web server bundled in Tryton with this command line::
+
+ trytond -c <config file>
+
+The server will wait for client connections on the interface defined in the
+`web` section of the :ref:`configuration <topics-configuration>`.
+
+WSGI server
+-----------
+
+If you prefer to run Tryton inside your own WSGI server instead of the simple
+server of Werkzeug, you can use the application `trytond.application.app` and
+set the environment variable `TRYTOND_CONFIG` to point to the
+:ref:`configuration <topics-configuration>`.
+
+.. warning:: You must manage to serve the static files from the web root.
+
+Cron service
+============
+
+If you want to run some scheduled actions, you must also run the cron server
+with this command line::
+
+ trytond-cron -c <config file> -d <database>
+
+The server will wake up every minutes and preform the scheduled actions defined
+in the `database`.
+
+Services options
+================
+
+You will find more options for those services by using `--help` arguments.
diff --git a/doc/topics/views/index.rst b/doc/topics/views/index.rst
index eda830c..2d8e0f1 100644
--- a/doc/topics/views/index.rst
+++ b/doc/topics/views/index.rst
@@ -37,6 +37,10 @@ There is three types of views:
* Graph
+ * Board
+
+ * Calendar
+
Form view
=========
@@ -55,7 +59,7 @@ Elements of the view are put on the screen following the rules:
* Elements take one or more columns when they are put in the table. If
there are not enough free columns on the current row, the elements are put
- at the begining of the next row.
+ at the beginning of the next row.
XML description
@@ -267,8 +271,8 @@ image
Display an image.
- * ``name``: the name of the image. It must be the name with the extension
- of an image from ``tryton/share/pixmaps/``.
+ * ``name``: the name of the image. It must be the name of a record of
+ `ir.ui.icon`.
* ``yexpand``: see in common-attributes-yexpand_.
diff --git a/setup.py b/setup.py
index 657be71..c512efd 100644
--- a/setup.py
+++ b/setup.py
@@ -5,11 +5,14 @@
from setuptools import setup, find_packages
import os
import re
+import io
import platform
def read(fname):
- return open(os.path.join(os.path.dirname(__file__), fname)).read()
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
def get_version():
@@ -32,7 +35,7 @@ if minor_version % 2:
if platform.python_implementation() == 'PyPy':
pg_require = ['psycopg2cffi >= 2.5']
else:
- pg_require = ['psycopg2 >= 2.0']
+ pg_require = ['psycopg2 >= 2.5']
setup(name=name,
version=version,
@@ -54,10 +57,9 @@ setup(name=name,
'trytond.ir.module': ['*.xml'],
'trytond.ir.ui': ['*.xml', '*.rng', '*.rnc'],
'trytond.res': ['tryton.cfg', '*.xml', 'view/*.xml', 'locale/*.po'],
- 'trytond.webdav': ['tryton.cfg', '*.xml', 'view/*.xml', 'locale/*.po'],
'trytond.tests': ['tryton.cfg', '*.xml'],
},
- scripts=['bin/trytond'],
+ scripts=['bin/trytond', 'bin/trytond-admin', 'bin/trytond-cron'],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: No Input/Output (Daemon)',
@@ -66,6 +68,7 @@ setup(name=name,
'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: Bulgarian',
'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
'Natural Language :: Czech',
'Natural Language :: Dutch',
'Natural Language :: English',
@@ -79,6 +82,9 @@ setup(name=name,
'Natural Language :: Spanish',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries :: Application Frameworks',
@@ -92,11 +98,12 @@ setup(name=name,
'python-dateutil',
'polib',
'python-sql >= 0.4',
+ 'werkzeug',
+ 'wrapt',
],
extras_require={
'PostgreSQL': pg_require,
'MySQL': ['MySQL-python'],
- 'WebDAV': ['PyWebDAV >= 0.9.8'],
'unoconv': ['unoconv'],
'graphviz': ['pydot'],
'simplejson': ['simplejson'],
@@ -108,4 +115,5 @@ setup(name=name,
test_suite='trytond.tests',
test_loader='trytond.test_loader:Loader',
tests_require=['mock'],
+ use_2to3=True,
)
diff --git a/trytond.egg-info/PKG-INFO b/trytond.egg-info/PKG-INFO
index eaae680..ec59371 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.8.5
+Version: 4.0.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.8/
+Download-URL: http://downloads.tryton.org/4.0/
Description: trytond
=======
@@ -106,6 +106,7 @@ Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Natural Language :: Bulgarian
Classifier: Natural Language :: Catalan
+Classifier: Natural Language :: Chinese (Simplified)
Classifier: Natural Language :: Czech
Classifier: Natural Language :: Dutch
Classifier: Natural Language :: English
@@ -119,6 +120,9 @@ Classifier: Natural Language :: Slovenian
Classifier: Natural Language :: Spanish
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
diff --git a/trytond.egg-info/SOURCES.txt b/trytond.egg-info/SOURCES.txt
index 7df8bed..cfa8af9 100644
--- a/trytond.egg-info/SOURCES.txt
+++ b/trytond.egg-info/SOURCES.txt
@@ -6,6 +6,8 @@ MANIFEST.in
README
setup.py
bin/trytond
+bin/trytond-admin
+bin/trytond-cron
doc/Makefile
doc/conf.py
doc/index.rst
@@ -14,6 +16,7 @@ doc/ref/index.rst
doc/ref/pool.rst
doc/ref/pyson.rst
doc/ref/rpc.rst
+doc/ref/sendmail.rst
doc/ref/transaction.rst
doc/ref/wizard.rst
doc/ref/models/fields.rst
@@ -29,6 +32,7 @@ doc/topics/install.rst
doc/topics/logs.rst
doc/topics/pyson.rst
doc/topics/setup_database.rst
+doc/topics/start_server.rst
doc/topics/translation.rst
doc/topics/wizard.rst
doc/topics/models/fields_default_value.rst
@@ -39,21 +43,25 @@ doc/topics/reports/index.rst
doc/topics/views/extension.rst
doc/topics/views/index.rst
trytond/__init__.py
+trytond/admin.py
+trytond/application.py
trytond/cache.py
+trytond/commandline.py
trytond/config.py
trytond/const.py
trytond/convert.py
+trytond/cron.py
trytond/error.py
trytond/exceptions.py
-trytond/monitor.py
trytond/pool.py
trytond/pyson.py
trytond/rpc.py
trytond/security.py
-trytond/server.py
+trytond/sendmail.py
trytond/test_loader.py
trytond/transaction.py
trytond/url.py
+trytond/wsgi.py
trytond.egg-info/PKG-INFO
trytond.egg-info/SOURCES.txt
trytond.egg-info/dependency_links.txt
@@ -95,8 +103,11 @@ trytond/ir/model.py
trytond/ir/model.xml
trytond/ir/module.py
trytond/ir/module.xml
+trytond/ir/note.py
+trytond/ir/note.xml
trytond/ir/property.py
trytond/ir/property.xml
+trytond/ir/resource.py
trytond/ir/rule.py
trytond/ir/rule.xml
trytond/ir/sequence.py
@@ -121,11 +132,13 @@ trytond/ir/locale/fr_FR.po
trytond/ir/locale/hu_HU.po
trytond/ir/locale/it_IT.po
trytond/ir/locale/ja_JP.po
+trytond/ir/locale/lo_LA.po
trytond/ir/locale/lt_LT.po
trytond/ir/locale/nl_NL.po
trytond/ir/locale/pt_BR.po
trytond/ir/locale/ru_RU.po
trytond/ir/locale/sl_SI.po
+trytond/ir/locale/zh_CN.po
trytond/ir/ui/__init__.py
trytond/ir/ui/board.rnc
trytond/ir/ui/board.rng
@@ -204,6 +217,8 @@ trytond/ir/view/module_form.xml
trytond/ir/view/module_install_upgrade_done_form.xml
trytond/ir/view/module_install_upgrade_start_form.xml
trytond/ir/view/module_list.xml
+trytond/ir/view/note_form.xml
+trytond/ir/view/note_list.xml
trytond/ir/view/property_form.xml
trytond/ir/view/property_list.xml
trytond/ir/view/rule_form.xml
@@ -271,11 +286,9 @@ trytond/model/fields/sha.py
trytond/model/fields/text.py
trytond/modules/__init__.py
trytond/protocols/__init__.py
-trytond/protocols/common.py
trytond/protocols/dispatcher.py
trytond/protocols/jsonrpc.py
-trytond/protocols/sslsocket.py
-trytond/protocols/webdav.py
+trytond/protocols/wrappers.py
trytond/protocols/xmlrpc.py
trytond/report/__init__.py
trytond/report/report.py
@@ -301,11 +314,13 @@ trytond/res/locale/fr_FR.po
trytond/res/locale/hu_HU.po
trytond/res/locale/it_IT.po
trytond/res/locale/ja_JP.po
+trytond/res/locale/lo_LA.po
trytond/res/locale/lt_LT.po
trytond/res/locale/nl_NL.po
trytond/res/locale/pt_BR.po
trytond/res/locale/ru_RU.po
trytond/res/locale/sl_SI.po
+trytond/res/locale/zh_CN.po
trytond/res/view/group_form.xml
trytond/res/view/group_list.xml
trytond/res/view/sequence_type_form.xml
@@ -348,6 +363,7 @@ trytond/tests/test_mptt.py
trytond/tests/test_protocols.py
trytond/tests/test_pyson.py
trytond/tests/test_res.py
+trytond/tests/test_sendmail.py
trytond/tests/test_sequence.py
trytond/tests/test_tools.py
trytond/tests/test_transaction.py
@@ -355,7 +371,6 @@ trytond/tests/test_trigger.py
trytond/tests/test_tryton.py
trytond/tests/test_union.py
trytond/tests/test_user.py
-trytond/tests/test_webdav.py
trytond/tests/test_wizard.py
trytond/tests/test_workflow.py
trytond/tests/trigger.py
@@ -370,33 +385,5 @@ trytond/tools/datetime_strftime.py
trytond/tools/decimal_.py
trytond/tools/misc.py
trytond/tools/singleton.py
-trytond/webdav/__init__.py
-trytond/webdav/tryton.cfg
-trytond/webdav/webdav.py
-trytond/webdav/webdav.xml
-trytond/webdav/locale/bg_BG.po
-trytond/webdav/locale/ca_ES.po
-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/es_MX.po
-trytond/webdav/locale/fr_FR.po
-trytond/webdav/locale/hu_HU.po
-trytond/webdav/locale/it_IT.po
-trytond/webdav/locale/ja_JP.po
-trytond/webdav/locale/lt_LT.po
-trytond/webdav/locale/nl_NL.po
-trytond/webdav/locale/pt_BR.po
-trytond/webdav/locale/ru_RU.po
-trytond/webdav/locale/sl_SI.po
-trytond/webdav/view/attachment_form.xml
-trytond/webdav/view/collection_form.xml
-trytond/webdav/view/collection_list.xml
-trytond/webdav/view/collection_tree.xml
-trytond/webdav/view/share_form.xml
-trytond/webdav/view/share_list.xml
trytond/wizard/__init__.py
trytond/wizard/wizard.py
\ No newline at end of file
diff --git a/trytond.egg-info/requires.txt b/trytond.egg-info/requires.txt
index 12c57b9..421dcfa 100644
--- a/trytond.egg-info/requires.txt
+++ b/trytond.egg-info/requires.txt
@@ -4,6 +4,8 @@ Genshi
python-dateutil
polib
python-sql >= 0.4
+werkzeug
+wrapt
[BCrypt]
bcrypt
@@ -15,10 +17,7 @@ python-Levenshtein
MySQL-python
[PostgreSQL]
-psycopg2 >= 2.0
-
-[WebDAV]
-PyWebDAV >= 0.9.8
+psycopg2 >= 2.5
[cdecimal]
cdecimal
diff --git a/trytond/__init__.py b/trytond/__init__.py
index 43b3472..44be727 100644
--- a/trytond/__init__.py
+++ b/trytond/__init__.py
@@ -2,13 +2,18 @@
# this repository contains the full copyright notices and license terms.
import os
import time
+import logging
from email import charset
-__version__ = "3.8.5"
+__version__ = "4.0.0"
+logger = logging.getLogger(__name__)
os.environ['TZ'] = 'UTC'
if hasattr(time, 'tzset'):
time.tzset()
+if time.tzname[0] != 'UTC':
+ logger.error('Timezone must be set to UTC instead of %s', time.tzname[0])
+
# set email encoding for utf-8 to 'quoted-printable'
charset.add_charset('utf-8', charset.QP, charset.QP)
diff --git a/trytond/admin.py b/trytond/admin.py
new file mode 100644
index 0000000..aa5374f
--- /dev/null
+++ b/trytond/admin.py
@@ -0,0 +1,83 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import sys
+import os
+import logging
+from getpass import getpass
+
+from sql import Table
+
+from trytond.transaction import Transaction
+from trytond import backend
+from trytond.pool import Pool
+
+__all__ = ['run']
+logger = logging.getLogger(__name__)
+
+
+def run(options):
+ Database = backend.get('Database')
+ init = {}
+ for db_name in options.database_names:
+ init[db_name] = False
+ with Transaction().start(db_name, 0):
+ database = Database(db_name)
+ database.connect()
+ if options.update:
+ if not database.test():
+ logger.info("init db")
+ database.init()
+ init[db_name] = True
+ elif not database.test():
+ raise Exception("'%s' is not a Tryton database!" % db_name)
+
+ for db_name in options.database_names:
+ if options.update:
+ with Transaction().start(db_name, 0) as transaction,\
+ transaction.connection.cursor() as cursor:
+ database = Database(db_name)
+ database.connect()
+ if not database.test():
+ raise Exception("'%s' is not a Tryton database!" % db_name)
+ lang = Table('ir_lang')
+ cursor.execute(*lang.select(lang.code,
+ where=lang.translatable == True))
+ lang = [x[0] for x in cursor.fetchall()]
+ else:
+ lang = None
+ Pool(db_name).init(update=options.update, lang=lang)
+
+ for db_name in options.database_names:
+ if init[db_name]:
+ # try to read password from environment variable
+ # TRYTONPASSFILE, empty TRYTONPASSFILE ignored
+ passpath = os.getenv('TRYTONPASSFILE')
+ password = ''
+ if passpath:
+ try:
+ with open(passpath) as passfile:
+ password = passfile.readline()[:-1]
+ except Exception, err:
+ sys.stderr.write('Can not read password '
+ 'from "%s": "%s"\n' % (passpath, err))
+
+ if not password:
+ while True:
+ password = getpass('Admin Password for %s: ' % db_name)
+ password2 = getpass('Admin Password Confirmation: ')
+ if password != password2:
+ sys.stderr.write('Admin Password Confirmation '
+ 'doesn\'t match Admin Password!\n')
+ continue
+ if not password:
+ sys.stderr.write('Admin Password is required!\n')
+ continue
+ break
+
+ with Transaction().start(db_name, 0) as transaction:
+ pool = Pool()
+ User = pool.get('res.user')
+ admin, = User.search([('login', '=', 'admin')])
+ User.write([admin], {
+ 'password': password,
+ })
diff --git a/trytond/application.py b/trytond/application.py
new file mode 100644
index 0000000..ef3b73b
--- /dev/null
+++ b/trytond/application.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+from trytond.pool import Pool
+from trytond.wsgi import app
+
+__all__ = ['app']
+
+Pool.start()
+import trytond.protocols.dispatcher
diff --git a/trytond/backend/__init__.py b/trytond/backend/__init__.py
index 31f1bf1..b82e8bb 100644
--- a/trytond/backend/__init__.py
+++ b/trytond/backend/__init__.py
@@ -24,10 +24,14 @@ def get(prop):
if modname not in sys.modules:
try:
__import__(modname)
- except ImportError:
+ except ImportError, exception:
if not pkg_resources:
- raise
- ep, = pkg_resources.iter_entry_points('trytond.backend', db_type)
+ raise exception
+ try:
+ ep, = pkg_resources.iter_entry_points(
+ 'trytond.backend', db_type)
+ except ValueError:
+ raise exception
mod_path = os.path.join(ep.dist.location,
*ep.module_name.split('.')[:-1])
fp, pathname, description = imp.find_module(db_type, [mod_path])
diff --git a/trytond/backend/database.py b/trytond/backend/database.py
index 06f618f..22567cd 100644
--- a/trytond/backend/database.py
+++ b/trytond/backend/database.py
@@ -1,7 +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.
-from trytond.config import config
-
DatabaseIntegrityError = None
DatabaseOperationalError = None
@@ -11,12 +9,13 @@ class DatabaseInterface(object):
Define generic interface for database connection
'''
flavor = None
+ IN_MAX = 1000
- def __new__(cls, database_name=''):
+ def __new__(cls, name=''):
return object.__new__(cls)
- def __init__(self, database_name=''):
- self.database_name = database_name
+ def __init__(self, name=''):
+ self.name = name
def connect(self):
'''
@@ -26,12 +25,18 @@ class DatabaseInterface(object):
'''
raise NotImplementedError
- def cursor(self, autocommit=False, readonly=False):
+ def get_connection(self, autocommit, readonly=False):
+ '''Retrieve a connection on the database
+
+ :param autocommit: a boolean to activate autocommit
+ :param readonly: a boolean to specify if the transaction is readonly
'''
- Retreive a cursor on the database
+ raise NotImplementedError
- :param autocommit: a boolean to active autocommit
- :return: a Cursor
+ def put_connection(self, connection, close=False):
+ '''Release the connection
+
+ :param close: if close is True the connection is discarded
'''
raise NotImplementedError
@@ -41,21 +46,21 @@ class DatabaseInterface(object):
'''
raise NotImplementedError
- @staticmethod
- def create(cursor, database_name):
+ @classmethod
+ def create(cls, connection, database_name):
'''
Create a database
- :param database_name: the database name
+ :param connection: the connection to the database
+ :param database_name: the new database name
'''
raise NotImplementedError
- @staticmethod
- def drop(cursor, database_name):
+ def drop(self, connection, database_name):
'''
Drop a database
- :param cursor: a cursor on an other database
+ :param connection: the connection to the database
:param database_name: the database name
'''
raise NotImplementedError
@@ -81,8 +86,7 @@ class DatabaseInterface(object):
'''
raise NotImplementedError
- @staticmethod
- def list(cursor):
+ def list(self):
'''
Get the list of database
@@ -90,107 +94,60 @@ class DatabaseInterface(object):
'''
raise NotImplementedError
- @staticmethod
- def init(cursor):
+ def init(self):
'''
Initialize a database
-
- :param cursor: a cursor on the database
- '''
- raise NotImplementedError
-
-
-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):
- from trytond.cache import LRUDict
- from trytond.transaction import Transaction
- user = Transaction().user
- 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(config.getint('cache', 'model')))
-
- def execute(self, sql, params=None):
- '''
- Execute a query
-
- :param sql: a sql query string
- :param params: a tuple or list of parameters
- '''
- raise NotImplementedError
-
- def close(self, close=False):
- '''
- Close the cursor
-
- :param close: boolean to not release cursor in pool
'''
raise NotImplementedError
- def commit(self):
- '''
- Commit the cursor
- '''
- for cache in self.cache.itervalues():
- cache.clear()
-
- def rollback(self):
- '''
- Rollback the cursor
- '''
- for cache in self.cache.itervalues():
- cache.clear()
-
def test(self):
'''
Test if it is a Tryton database.
'''
raise NotImplementedError
- def nextid(self, table):
+ def nextid(self, connection, table):
'''
Return the next sequenced id for a table.
+ :param connection: a connection on the database
:param table: the table name
:return: an integer
'''
- def setnextid(self, table, value):
+ def setnextid(self, connection, table, value):
'''
Set the current sequenced id for a table.
+ :param connection: a connection on the database
:param table: the table name
'''
- def currid(self, table):
+ def currid(self, connection, table):
'''
Return the current sequenced id for a table.
+ :param connection: a connection on the database
:param table: the table name
:return: an integer
'''
- def lastid(self):
+ def update_auto_increment(self, connection, table, value):
'''
- Return the last id inserted.
+ Update auto_increment value of table
- :return: an integer
+ :param connection: a connection on the database
+ :param table: the table name
+ :param value: the auto_increment value
'''
+ pass
- def lock(self, table):
+ @classmethod
+ def lock(cls, connection, table):
'''
Lock the table
+ :param connection: a connection on the database
:param table: the table name
'''
raise NotImplementedError
@@ -203,15 +160,6 @@ class CursorInterface(object):
'''
raise NotImplementedError
- def update_auto_increment(self, table, value):
- '''
- Update auto_increment value of table
-
- :param table: the table name
- :param value: the auto_increment value
- '''
- pass
-
def has_returning(self):
'''
Return True if database implements RETURNING clause in INSERT or UPDATE
@@ -219,26 +167,8 @@ class CursorInterface(object):
:return: a boolean
'''
+ return False
def has_multirow_insert(self):
'Return True if database supports multirow insert'
return False
-
- def __build_dict(self, row):
- return dict((desc[0], row[i])
- for i, desc in enumerate(self.description))
-
- def dictfetchone(self):
- row = self.fetchone()
- if row:
- return self.__build_dict(row)
- else:
- return row
-
- def dictfetchmany(self, size):
- rows = self.fetchmany(size)
- return [self.__build_dict(row) for row in rows]
-
- def dictfetchall(self):
- rows = self.fetchall()
- return [self.__build_dict(row) for row in rows]
diff --git a/trytond/backend/mysql/database.py b/trytond/backend/mysql/database.py
index 0c6d349..0c091f6 100644
--- a/trytond/backend/mysql/database.py
+++ b/trytond/backend/mysql/database.py
@@ -1,6 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-from trytond.backend.database import DatabaseInterface, CursorInterface
+from trytond.backend.database import DatabaseInterface
from trytond.config import config, parse_uri
import MySQLdb
import MySQLdb.cursors
@@ -15,8 +15,7 @@ import urllib
from sql import Flavor, Expression
from sql.functions import Extract, Overlay, CharLength
-__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError',
- 'Cursor']
+__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError']
class MySQLExtract(Extract):
@@ -82,12 +81,12 @@ class Database(DatabaseInterface):
def connect(self):
return self
- def cursor(self, autocommit=False, readonly=False):
+ def get_connection(self, autocommit=False, readonly=False):
conv = MySQLdb.converters.conversions.copy()
conv[float] = lambda value, _: repr(value)
conv[MySQLdb.constants.FIELD_TYPE.TIME] = MySQLdb.times.Time_or_None
args = {
- 'db': self.database_name,
+ 'db': self.name,
'sql_mode': 'traditional,postgresql',
'use_unicode': True,
'charset': 'utf8',
@@ -104,21 +103,26 @@ class Database(DatabaseInterface):
if uri.password:
args['passwd'] = urllib.unquote_plus(uri.password)
conn = MySQLdb.connect(**args)
- cursor = Cursor(conn, self.database_name)
+ cursor = conn.cursor()
cursor.execute('SET time_zone = `UTC`')
- return cursor
+ return conn
+
+ def put_connection(self, connection, close=False):
+ connection.close()
def close(self):
return
@classmethod
- def create(cls, cursor, database_name):
+ def create(cls, connection, database_name):
+ cursor = connection.cursor()
cursor.execute('CREATE DATABASE `' + database_name + '` '
'DEFAULT CHARACTER SET = \'utf8\'')
cls._list_cache = None
@classmethod
- def drop(cls, cursor, database_name):
+ def drop(cls, connection, database_name):
+ cursor = connection.cursor()
cursor.execute('DROP DATABASE `' + database_name + '`')
cls._list_cache = None
@@ -195,13 +199,14 @@ class Database(DatabaseInterface):
Database._list_cache = None
return True
- @staticmethod
- def list(cursor):
+ def list(self):
now = time.time()
timeout = config.getint('session', 'timeout')
res = Database._list_cache
if res and abs(Database._list_cache_timestamp - now) < timeout:
return res
+ conn = self.get_connection()
+ cursor = conn.cursor()
cursor.execute('SHOW DATABASES')
res = []
for db_name, in cursor.fetchall():
@@ -209,27 +214,26 @@ class Database(DatabaseInterface):
database = Database(db_name).connect()
except Exception:
continue
- cursor2 = database.cursor()
- if cursor2.test():
+ if database.test():
res.append(db_name)
- cursor2.close(close=True)
- else:
- cursor2.close()
- database.close()
+ database.close()
+ self.put_connection(conn)
Database._list_cache = res
Database._list_cache_timestamp = now
return res
- @staticmethod
- def init(cursor):
+ def init(self):
from trytond.modules import get_module_info
+
+ connection = self.get_connection()
+ cursor = connection.cursor()
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
with open(sql_file) as fp:
for line in fp.read().split(';'):
if (len(line) > 0) and (not line.isspace()):
cursor.execute(line)
- for module in ('ir', 'res', 'webdav'):
+ for module in ('ir', 'res'):
state = 'uninstalled'
if module in ('ir', 'res'):
state = 'to install'
@@ -246,40 +250,15 @@ class Database(DatabaseInterface):
'VALUES (%s, now(), %s, %s)',
(0, module_id, dependency))
-
-class Cursor(CursorInterface):
-
- def __init__(self, conn, database_name):
- super(Cursor, self).__init__()
- self._conn = conn
- self.database_name = database_name
- self.dbname = self.database_name # XXX to remove
- self.cursor = conn.cursor()
-
- def __getattr__(self, name):
- return getattr(self.cursor, name)
-
- def execute(self, sql, params=None):
- if params:
- return self.cursor.execute(sql, params)
- else:
- return self.cursor.execute(sql)
-
- def close(self, close=False):
- self.cursor.close()
- self.rollback()
-
- def commit(self):
- super(Cursor, self).commit()
- self._conn.commit()
-
- def rollback(self):
- super(Cursor, self).rollback()
- self._conn.rollback()
+ connection.commit()
+ self.put_connection(connection)
def test(self):
- self.cursor.execute("SHOW TABLES")
- for table, in self.cursor.fetchall():
+ is_tryton_database = False
+ connection = self.get_connection()
+ cursor = connection.cursor()
+ cursor.execute("SHOW TABLES")
+ for table, in cursor.fetchall():
if table in (
'ir_model',
'ir_model_field',
@@ -292,14 +271,17 @@ class Cursor(CursorInterface):
'ir_translation',
'ir_lang',
):
- return True
- return False
+ is_tryton_database = True
+ break
+ self.put_connection(connection)
+ return is_tryton_database
- def lastid(self):
- self.cursor.execute('SELECT LAST_INSERT_ID()')
- return self.cursor.fetchone()[0]
+ def lastid(self, cursor):
+ # This call is not thread safe
+ cursor.execute('SELECT LAST_INSERT_ID()')
+ return cursor.fetchone()[0]
- def lock(self, table):
+ def lock(self, connection, table):
# Lock of table doesn't work because MySQL require
# that the session locks all tables that will be accessed
# but 'FLUSH TABLES WITH READ LOCK' creates deadlock
@@ -311,15 +293,7 @@ class Cursor(CursorInterface):
def has_multirow_insert(self):
return True
- def limit_clause(self, select, limit=None, offset=None):
- if offset and limit is None:
- limit = 18446744073709551610 # max bigint
- if limit is not None:
- select += ' LIMIT %d' % limit
- if offset is not None and offset != 0:
- select += ' OFFSET %d' % offset
- return select
-
- def update_auto_increment(self, table, value):
- self.cursor.execute('ALTER TABLE `%s` AUTO_INCREMENT = %%s' % table,
+ def update_auto_increment(self, connection, table, value):
+ cursor = connection.cursor()
+ cursor.execute('ALTER TABLE `%s` AUTO_INCREMENT = %%s' % table,
(value,))
diff --git a/trytond/backend/mysql/table.py b/trytond/backend/mysql/table.py
index 14b604d..a7bd9d2 100644
--- a/trytond/backend/mysql/table.py
+++ b/trytond/backend/mysql/table.py
@@ -1,6 +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.transaction import Transaction
from trytond.backend.table import TableHandlerInterface
import logging
@@ -9,159 +10,173 @@ logger = logging.getLogger(__name__)
class TableHandler(TableHandlerInterface):
- def __init__(self, cursor, model, module_name=None, history=False):
- super(TableHandler, self).__init__(cursor, model,
+ def __init__(self, model, module_name=None, history=False):
+ super(TableHandler, self).__init__(model,
module_name=module_name, history=history)
self._columns = {}
self._constraints = []
self._fkeys = []
self._indexes = []
- self._field2module = {}
self._model = model
+ cursor = Transaction().connection.cursor()
# Create new table if necessary
- if not self.table_exist(self.cursor, self.table_name):
+ if not self.table_exist(self.table_name):
if not self.history:
- self.cursor.execute('CREATE TABLE `%s` ('
+ cursor.execute('CREATE TABLE `%s` ('
'id BIGINT AUTO_INCREMENT NOT NULL, '
'PRIMARY KEY(id)'
') ENGINE=InnoDB;' % self.table_name)
else:
- self.cursor.execute('CREATE TABLE `%s` ('
+ cursor.execute('CREATE TABLE `%s` ('
'__id BIGINT AUTO_INCREMENT NOT NULL, '
'id BIGINT, '
'PRIMARY KEY(__id)'
') ENGINE=InnoDB;' % self.table_name)
- self._update_definitions()
+ self._update_definitions(columns=True)
if 'id' not in self._columns:
if not self.history:
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor.execute('ALTER TABLE `%s` '
'ADD COLUMN id BIGINT AUTO_INCREMENT '
'NOT NULL PRIMARY KEY' % self.table_name)
else:
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor.execute('ALTER TABLE `%s` '
'ADD COLUMN id BIGINT' % self.table_name)
- self._update_definitions()
+ self._update_definitions(columns=True)
if self.history and '__id' not in self._columns:
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor.execute('ALTER TABLE `%s` '
'ADD COLUMN __id BIGINT AUTO_INCREMENT '
'NOT NULL PRIMARY KEY' % self.table_name)
self._update_definitions()
@staticmethod
- def table_exist(cursor, table_name):
+ def table_exist(table_name):
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
cursor.execute("SELECT table_name FROM information_schema.tables "
"WHERE table_schema = %s AND table_name = %s",
- (cursor.database_name, table_name))
+ (transaction.database.name, table_name))
return bool(cursor.rowcount)
@staticmethod
- def table_rename(cursor, old_name, new_name):
+ def table_rename(old_name, new_name):
+ cursor = Transaction().connection.cursor()
# Rename table
- if (TableHandler.table_exist(cursor, old_name)
- and not TableHandler.table_exist(cursor, new_name)):
+ if (TableHandler.table_exist(old_name)
+ and not TableHandler.table_exist(new_name)):
cursor.execute('ALTER TABLE `%s` RENAME TO `%s`'
% (old_name, new_name))
# Rename history table
old_history = old_name + '__history'
new_history = new_name + '__history'
- if (TableHandler.table_exist(cursor, old_history)
- and not TableHandler.table_exist(cursor, new_history)):
+ if (TableHandler.table_exist(old_history)
+ and not TableHandler.table_exist(new_history)):
cursor.execute('ALTER TABLE `%s` RENAME TO `%s`'
% (old_history, new_history))
@staticmethod
- def sequence_exist(cursor, sequence_name):
+ def sequence_exist(sequence_name):
return True
@staticmethod
- def sequence_rename(cursor, old_name, new_name):
+ def sequence_rename(old_name, new_name):
pass
def column_exist(self, column_name):
return column_name in self._columns
def column_rename(self, old_name, new_name, exception=False):
+ cursor = Transaction().connection.cursor()
if (self.column_exist(old_name)
and not self.column_exist(new_name)):
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor.execute('ALTER TABLE `%s` '
'RENAME COLUMN `%s` TO `%s`'
% (self.table_name, old_name, new_name))
- self._update_definitions()
+ self._update_definitions(columns=True)
elif exception and self.column_exist(new_name):
raise Exception('Unable to rename column %s.%s to %s.%s: '
'%s.%s already exist!'
% (self.table_name, old_name, self.table_name, new_name,
self.table_name, new_name))
- def _update_definitions(self):
- # Fetch columns definitions from the table
- self.cursor.execute("SELECT column_name, character_maximum_length, "
- "data_type, is_nullable, column_default "
- "FROM information_schema.columns "
- "WHERE table_schema = %s AND table_name = %s",
- (self.cursor.database_name, self.table_name))
- self._columns = {}
- for line in self.cursor.fetchall():
- column, size, typname, nullable, default = line
- self._columns[column] = {
- 'size': size,
- 'typname': typname,
- 'nullable': nullable == 'YES' and True or False,
- 'default': default,
- }
-
- # fetch constraints for the table
- self.cursor.execute("SELECT constraint_name, constraint_type "
- "FROM information_schema.table_constraints "
- "WHERE table_schema = %s AND table_name = %s",
- (self.cursor.database_name, self.table_name))
- self._constraints = []
- self._fkeys = []
- for line in self.cursor.fetchall():
- conname, contype = line
- if contype not in ('PRIMARY KEY', 'FOREIGN KEY'):
- self._constraints.append(conname)
- elif contype == 'FOREIGN KEY':
- self._fkeys.append(conname)
-
- # Fetch indexes defined for the table
- self.cursor.execute('SHOW INDEXES FROM `%s`' % self.table_name)
- self._indexes = list(set(x[2] for x in self.cursor.fetchall()
- if x[2] != 'PRIMARY'))
-
- # Keep track of which module created each field
- self._field2module = {}
- if self.object_name is not None:
- self.cursor.execute('SELECT f.name, f.module '
- 'FROM ir_model_field f '
- 'JOIN ir_model m on (f.model=m.id) '
- 'WHERE m.model = %s',
- (self.object_name,))
- for line in self.cursor.fetchall():
- self._field2module[line[0]] = line[1]
+ def _update_definitions(self,
+ columns=None, constraints=None, indexes=None):
+ if columns is None and constraints is None and indexes is None:
+ columns = constraints = indexes = True
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
+ if columns:
+ # Fetch columns definitions from the table
+ cursor.execute("SELECT column_name, character_maximum_length, "
+ "data_type, is_nullable, column_default "
+ "FROM information_schema.columns "
+ "WHERE table_schema = %s AND table_name = %s",
+ (transaction.database.name, self.table_name))
+ self._columns = {}
+ for line in cursor.fetchall():
+ column, size, typname, nullable, default = line
+ self._columns[column] = {
+ 'size': size,
+ 'typname': typname,
+ 'nullable': nullable == 'YES' and True or False,
+ 'default': default,
+ }
+
+ if constraints:
+ # fetch constraints for the table
+ cursor.execute("SELECT constraint_name, constraint_type "
+ "FROM information_schema.table_constraints "
+ "WHERE table_schema = %s AND table_name = %s",
+ (transaction.database.name, self.table_name))
+ self._constraints = []
+ self._fkeys = []
+ for line in cursor.fetchall():
+ conname, contype = line
+ if contype not in ('PRIMARY KEY', 'FOREIGN KEY'):
+ self._constraints.append(conname)
+ elif contype == 'FOREIGN KEY':
+ self._fkeys.append(conname)
+
+ if indexes:
+ # Fetch indexes defined for the table
+ cursor.execute('SHOW INDEXES FROM `%s`' % self.table_name)
+ self._indexes = list(set(x[2] for x in cursor.fetchall()
+ if x[2] != 'PRIMARY'))
+
+ @property
+ def _field2module(self):
+ cursor = Transaction().connection.cursor()
+ cursor.execute('SELECT f.name, f.module '
+ 'FROM ir_model_field f '
+ 'JOIN ir_model m on (f.model=m.id) '
+ 'WHERE m.model = %s',
+ (self.object_name,))
+ return dict(cursor)
def alter_size(self, column_name, column_type):
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor = Transaction().connection.cursor()
+ cursor.execute('ALTER TABLE `%s` '
'MODIFY COLUMN `%s` %s'
% (self.table_name, column_name,
self._column_definition(column_name)))
- self._update_definitions()
+ self._update_definitions(columns=True)
def alter_type(self, column_name, column_type):
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor = Transaction().connection.cursor()
+ cursor.execute('ALTER TABLE `%s` '
'MODIFY COLUMN `%s` %s'
% (self.table_name, column_name,
self._column_definition(column_name, typname=column_type)))
- self._update_definitions()
+ self._update_definitions(columns=True)
def db_default(self, column_name, value):
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor = Transaction().connection.cursor()
+ cursor.execute('ALTER TABLE `%s` '
'MODIFY COLUMN `%s` %s'
% (self.table_name, column_name,
self._column_definition(column_name, default=value)))
- self._update_definitions()
+ self._update_definitions(columns=True)
def add_raw_column(self, column_name, column_type, column_format,
default_fun=None, field_size=None, migrate=True, string=''):
@@ -209,23 +224,24 @@ class TableHandler(TableHandlerInterface):
field_size)
return
+ cursor = Transaction().connection.cursor()
column_type = column_type[1]
- self.cursor.execute('ALTER TABLE `%s` ADD COLUMN `%s` %s' %
+ cursor.execute('ALTER TABLE `%s` ADD COLUMN `%s` %s' %
(self.table_name, column_name, column_type))
if column_format:
# check if table is non-empty:
- self.cursor.execute('SELECT 1 FROM `%s` limit 1' % self.table_name)
- if self.cursor.rowcount:
+ cursor.execute('SELECT 1 FROM `%s` limit 1' % self.table_name)
+ if cursor.rowcount:
# Populate column with default values:
default = None
if default_fun is not None:
default = default_fun()
- self.cursor.execute('UPDATE `' + self.table_name + '` '
+ cursor.execute('UPDATE `' + self.table_name + '` '
'SET `' + column_name + '` = %s',
(column_format(default),))
- self._update_definitions()
+ self._update_definitions(columns=True)
def add_fk(self, column_name, reference, on_delete=None):
if on_delete is None:
@@ -233,19 +249,22 @@ class TableHandler(TableHandlerInterface):
conname = '%s_%s_fkey' % (self.table_name, column_name)
if conname in self._fkeys:
self.drop_fk(column_name)
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor = Transaction().connection.cursor()
+ cursor.execute('ALTER TABLE `%s` '
'ADD CONSTRAINT `%s` FOREIGN KEY (`%s`) '
'REFERENCES `%s` (id) ON DELETE %s'
- % (self.table_name, conname, column_name, reference, on_delete))
- self._update_definitions()
+ % (self.table_name, conname, column_name, reference,
+ on_delete))
+ self._update_definitions(constraints=True)
def drop_fk(self, column_name, table=None):
conname = '%s_%s_fkey' % (self.table_name, column_name)
if conname not in self._fkeys:
return
- self.cursor.execute('ALTER TABLE `%s` '
+ cursor = Transaction().connection.cursor()
+ cursor.execute('ALTER TABLE `%s` '
'DROP FOREIGN KEY `%s`' % (self.table_name, conname))
- self._update_definitions()
+ self._update_definitions(constraints=True)
def index_action(self, column_name, action='add', table=None):
if isinstance(column_name, basestring):
@@ -260,123 +279,135 @@ class TableHandler(TableHandlerInterface):
if self._columns[k]['typname'] in ('text', 'blob'):
return
- if action == 'add':
- if index_name in self._indexes:
- return
- self.cursor.execute('CREATE INDEX `' + index_name + '` '
- 'ON `' + self.table_name + '` '
- '( ' + ','.join(['`' + x + '`' for x in column_name]) + ')')
- self._update_definitions()
- elif action == 'remove':
- if len(column_name) == 1:
- if (self._field2module.get(column_name[0], self.module_name)
- != self.module_name):
+ with Transaction().connection.cursor() as cursor:
+ if action == 'add':
+ if index_name in self._indexes:
return
-
- if index_name in self._indexes:
- self.cursor.execute('DROP INDEX `%s` ON `%s`'
- % (index_name, self.table_name))
- self._update_definitions()
- else:
- raise Exception('Index action not supported!')
+ cursor.execute('CREATE INDEX `' + index_name + '` '
+ 'ON `' + self.table_name
+ + '` ( '
+ + ','.join(['`' + x + '`' for x in column_name])
+ + ')')
+ self._update_definitions(indexes=True)
+ elif action == 'remove':
+ if len(column_name) == 1:
+ if (self._field2module.get(column_name[0],
+ self.module_name) != self.module_name):
+ return
+
+ if index_name in self._indexes:
+ cursor.execute('DROP INDEX `%s` ON `%s`'
+ % (index_name, self.table_name))
+ self._update_definitions(indexes=True)
+ else:
+ raise Exception('Index action not supported!')
def not_null_action(self, column_name, action='add'):
if not self.column_exist(column_name):
return
- if action == 'add':
- if not self._columns[column_name]['nullable']:
- return
- self.cursor.execute('SELECT id FROM `%s` '
- 'WHERE `%s` IS NULL'
- % (self.table_name, column_name))
- if not self.cursor.rowcount:
- self.cursor.execute('ALTER TABLE `%s` '
+ with Transaction().connection.cursor() as cursor:
+ if action == 'add':
+ if not self._columns[column_name]['nullable']:
+ return
+ cursor.execute('SELECT id FROM `%s` '
+ 'WHERE `%s` IS NULL'
+ % (self.table_name, column_name))
+ if not cursor.rowcount:
+ cursor.execute('ALTER TABLE `%s` '
+ 'MODIFY COLUMN `%s` %s'
+ % (self.table_name, column_name,
+ self._column_definition(column_name,
+ nullable=False)))
+ self._update_definitions(columns=True)
+ else:
+ logger.warning(
+ 'Unable to set column %s '
+ 'of table %s not null !\n'
+ 'Try to re-run: '
+ 'trytond.py --update=module\n'
+ 'If it doesn\'t work, update records '
+ 'and execute manually:\n'
+ 'ALTER TABLE `%s` MODIFY COLUMN `%s` %s',
+ column_name, self.table_name, self.table_name,
+ column_name, self._column_definition(column_name,
+ nullable=False))
+ elif action == 'remove':
+ if self._columns[column_name]['nullable']:
+ return
+ if (self._field2module.get(column_name, self.module_name)
+ != self.module_name):
+ return
+ cursor.execute('ALTER TABLE `%s` '
'MODIFY COLUMN `%s` %s'
% (self.table_name, column_name,
- self._column_definition(column_name, nullable=False)))
- self._update_definitions()
+ self._column_definition(column_name, nullable=True)))
+ self._update_definitions(columns=True)
else:
- logger.warning(
- 'Unable to set column %s '
- 'of table %s not null !\n'
- 'Try to re-run: '
- 'trytond.py --update=module\n'
- 'If it doesn\'t work, update records '
- 'and execute manually:\n'
- 'ALTER TABLE `%s` MODIFY COLUMN `%s` %s',
- column_name, self.table_name, self.table_name,
- column_name, self._column_definition(column_name,
- nullable=False))
- elif action == 'remove':
- if self._columns[column_name]['nullable']:
- return
- if (self._field2module.get(column_name, self.module_name)
- != self.module_name):
- return
- self.cursor.execute('ALTER TABLE `%s` '
- 'MODIFY COLUMN `%s` %s'
- % (self.table_name, column_name,
- self._column_definition(column_name, nullable=True)))
- self._update_definitions()
- else:
- raise Exception('Not null action not supported!')
+ raise Exception('Not null action not supported!')
def add_constraint(self, ident, constraint, exception=False):
ident = self.table_name + "_" + ident
if ident in self._constraints:
# This constrain already exists
return
- try:
- self.cursor.execute('ALTER TABLE `%s` '
- 'ADD CONSTRAINT `%s` %s'
- % (self.table_name, ident, constraint), constraint.params)
- except Exception:
- if exception:
- raise
- logger.warning(
- 'unable to add \'%s\' constraint on table %s !\n'
- 'If you want to have it, you should update the records '
- 'and execute manually:\n'
- 'ALTER table `%s` ADD CONSTRAINT `%s` %s',
- constraint, self.table_name, self.table_name, ident,
- constraint, exc_info=True)
- self._update_definitions()
+
+ with Transaction().connection.cursor() as cursor:
+ try:
+ cursor.execute('ALTER TABLE `%s` '
+ 'ADD CONSTRAINT `%s` %s'
+ % (self.table_name, ident, constraint), constraint.params)
+ except Exception:
+ if exception:
+ raise
+ logger.warning(
+ 'unable to add \'%s\' constraint on table %s !\n'
+ 'If you want to have it, you should update the records '
+ 'and execute manually:\n'
+ 'ALTER table `%s` ADD CONSTRAINT `%s` %s',
+ constraint, self.table_name, self.table_name, ident,
+ constraint, exc_info=True)
+ self._update_definitions(constraints=True)
def drop_constraint(self, ident, exception=False, table=None):
ident = (table or self.table_name) + "_" + ident
if ident not in self._constraints:
return
- try:
- self.cursor.execute('ALTER TABLE `%s` '
- 'DROP CONSTRAINT `%s`'
- % (self.table_name, ident))
- except Exception:
- if exception:
- raise
- logger.warning(
- 'unable to drop \'%s\' constraint on table %s!',
- ident, self.table_name)
- self._update_definitions()
+
+ with Transaction().connection.cursor() as cursor:
+ try:
+ cursor.execute('ALTER TABLE `%s` '
+ 'DROP CONSTRAINT `%s`'
+ % (self.table_name, ident))
+ except Exception:
+ if exception:
+ raise
+ logger.warning(
+ 'unable to drop \'%s\' constraint on table %s!',
+ ident, self.table_name)
+ self._update_definitions(constraints=True)
def drop_column(self, column_name, exception=False):
if not self.column_exist(column_name):
return
- try:
- self.cursor.execute(
- 'ALTER TABLE `%s` DROP COLUMN `%s`' %
- (self.table_name, column_name))
-
- except Exception:
- if exception:
- raise
- logger.warning(
- 'unable to drop \'%s\' column on table %s!',
- column_name, self.table_name)
- self._update_definitions()
+
+ with Transaction().connection.cursor() as cursor:
+ try:
+ cursor.execute(
+ 'ALTER TABLE `%s` DROP COLUMN `%s`' %
+ (self.table_name, column_name))
+
+ except Exception:
+ if exception:
+ raise
+ logger.warning(
+ 'unable to drop \'%s\' column on table %s!',
+ column_name, self.table_name)
+ self._update_definitions(columns=True)
@staticmethod
- def drop_table(cursor, model, table, cascade=False):
+ def drop_table(model, table, cascade=False):
+ cursor = Transaction().connection.cursor()
cursor.execute('DELETE from ir_model_data where '
'model = %s', model)
diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py
index 3d7cf2d..505973a 100644
--- a/trytond/backend/postgresql/database.py
+++ b/trytond/backend/postgresql/database.py
@@ -12,6 +12,7 @@ try:
compat.register()
except ImportError:
pass
+from psycopg2 import connect
from psycopg2.pool import ThreadedConnectionPool
from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
@@ -26,11 +27,10 @@ from psycopg2 import OperationalError as DatabaseOperationalError
from sql import Flavor
-from trytond.backend.database import DatabaseInterface, CursorInterface
+from trytond.backend.database import DatabaseInterface
from trytond.config import config, parse_uri
-__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError',
- 'Cursor']
+__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError']
logger = logging.getLogger(__name__)
@@ -60,34 +60,40 @@ class Database(DatabaseInterface):
_version_cache = {}
flavor = Flavor(ilike=True)
- def __new__(cls, database_name='template1'):
- if database_name in cls._databases:
- return cls._databases[database_name]
- return DatabaseInterface.__new__(cls, database_name=database_name)
+ def __new__(cls, name='template1'):
+ if name in cls._databases:
+ return cls._databases[name]
+ return DatabaseInterface.__new__(cls, name=name)
- def __init__(self, database_name='template1'):
- super(Database, self).__init__(database_name=database_name)
- self._databases.setdefault(database_name, self)
+ def __init__(self, name='template1'):
+ super(Database, self).__init__(name=name)
+ self._databases.setdefault(name, self)
+ self._search_path = None
+ self._current_user = None
- def connect(self):
- if self._connpool is not None:
- return self
- logger.info('connect to "%s"', self.database_name)
+ @classmethod
+ def dsn(cls, name):
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
+ name = "dbname=%s" % name
user = uri.username and "user=%s" % uri.username or ''
password = ("password=%s" % urllib.unquote_plus(uri.password)
if uri.password else '')
+ return '%s %s %s %s %s' % (host, port, name, user, password)
+
+ def connect(self):
+ if self._connpool is not None:
+ return self
+ logger.info('connect to "%s"', self.name)
minconn = config.getint('database', 'minconn', default=1)
maxconn = config.getint('database', 'maxconn', default=64)
- dsn = '%s %s %s %s %s' % (host, port, name, user, password)
- self._connpool = ThreadedConnectionPool(minconn, maxconn, dsn)
+ self._connpool = ThreadedConnectionPool(
+ minconn, maxconn, self.dsn(self.name))
return self
- def cursor(self, autocommit=False, readonly=False):
+ def get_connection(self, autocommit=False, readonly=False):
if self._connpool is None:
self.connect()
conn = self._connpool.getconn()
@@ -95,10 +101,13 @@ class Database(DatabaseInterface):
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
else:
conn.set_isolation_level(ISOLATION_LEVEL_REPEATABLE_READ)
- cursor = Cursor(self._connpool, conn, self)
if readonly:
+ cursor = conn.cursor()
cursor.execute('SET TRANSACTION READ ONLY')
- return cursor
+ return conn
+
+ def put_connection(self, connection, close=False):
+ self._connpool.putconn(connection, close=close)
def close(self):
if self._connpool is None:
@@ -107,23 +116,26 @@ class Database(DatabaseInterface):
self._connpool = None
@classmethod
- def create(cls, cursor, database_name):
+ def create(cls, connection, database_name):
+ cursor = connection.cursor()
cursor.execute('CREATE DATABASE "' + database_name + '" '
'TEMPLATE template0 ENCODING \'unicode\'')
+ connection.commit()
cls._list_cache = None
- @classmethod
- def drop(cls, cursor, database_name):
+ def drop(self, connection, database_name):
+ cursor = connection.cursor()
cursor.execute('DROP DATABASE "' + database_name + '"')
- cls._list_cache = None
+ Database._list_cache = None
- def get_version(self, cursor):
- if self.database_name not in self._version_cache:
+ def get_version(self, connection):
+ if self.name not in self._version_cache:
+ cursor = connection.cursor()
cursor.execute('SELECT version()')
version, = cursor.fetchone()
- self._version_cache[self.database_name] = tuple(map(int,
+ self._version_cache[self.name] = tuple(map(int,
RE_VERSION.search(version).groups()))
- return self._version_cache[self.database_name]
+ return self._version_cache[self.name]
@staticmethod
def dump(database_name):
@@ -158,10 +170,8 @@ class Database(DatabaseInterface):
from trytond.tools import exec_command_pipe
database = Database().connect()
- cursor = database.cursor(autocommit=True)
- database.create(cursor, database_name)
- cursor.commit()
- cursor.close()
+ connection = database.get_connection(autocommit=True)
+ database.create(connection, database_name)
database.close()
cmd = ['pg_restore', '--no-owner']
@@ -195,8 +205,8 @@ class Database(DatabaseInterface):
raise Exception('Couldn\'t restore database')
database = Database(database_name).connect()
- cursor = database.cursor()
- if not cursor.test():
+ cursor = database.get_connection().cursor()
+ if not database.test():
cursor.close()
database.close()
raise Exception('Couldn\'t restore database!')
@@ -205,43 +215,43 @@ class Database(DatabaseInterface):
Database._list_cache = None
return True
- @staticmethod
- def list(cursor):
+ def list(self):
now = time.time()
timeout = config.getint('session', 'timeout')
res = Database._list_cache
if res and abs(Database._list_cache_timestamp - now) < timeout:
return res
+
+ connection = self.get_connection()
+ cursor = connection.cursor()
cursor.execute('SELECT datname FROM pg_database '
'WHERE datistemplate = false ORDER BY datname')
res = []
- for db_name, in cursor.fetchall():
- db_name = db_name.encode('utf-8')
+ for db_name, in cursor:
try:
- database = Database(db_name).connect()
+ with connect(self.dsn(db_name)) as conn:
+ if self._test(conn):
+ res.append(db_name)
except Exception:
continue
- cursor2 = database.cursor()
- if cursor2.test():
- res.append(db_name)
- cursor2.close(close=True)
- else:
- cursor2.close(close=True)
- database.close()
+ self.put_connection(connection)
+
Database._list_cache = res
Database._list_cache_timestamp = now
return res
- @staticmethod
- def init(cursor):
+ def init(self):
from trytond.modules import get_module_info
+
+ connection = self.get_connection()
+ cursor = connection.cursor()
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
with open(sql_file) as fp:
for line in fp.read().split(';'):
if (len(line) > 0) and (not line.isspace()):
cursor.execute(line)
- for module in ('ir', 'res', 'webdav'):
+ for module in ('ir', 'res'):
state = 'uninstalled'
if module in ('ir', 'res'):
state = 'to install'
@@ -258,115 +268,89 @@ class Database(DatabaseInterface):
'VALUES (%s, now(), %s, %s)',
(0, module_id, dependency))
-
-class Cursor(CursorInterface):
-
- def __init__(self, connpool, conn, database):
- super(Cursor, self).__init__()
- self._connpool = connpool
- self._conn = conn
- self._database = database
- self._current_user = None
- self._search_path = None
- self.cursor = conn.cursor()
- self.commit()
- self.sql_from_log = {}
- self.sql_into_log = {}
- self.count = {
- 'from': 0,
- 'into': 0,
- }
-
- @property
- def database_name(self):
- return self._database.database_name
-
- # TODO to remove
- @property
- def dbname(self):
- return self.database_name
-
- def __getattr__(self, name):
- return getattr(self.cursor, name)
-
- def execute(self, sql, params=None):
- if params:
- return self.cursor.execute(sql, params)
- else:
- return self.cursor.execute(sql)
-
- def close(self, close=False):
- self.cursor.close()
- self.rollback()
- self._connpool.putconn(self._conn, close=close)
-
- def commit(self):
- super(Cursor, self).commit()
- self._conn.commit()
-
- def rollback(self):
- super(Cursor, self).rollback()
- self._conn.rollback()
+ connection.commit()
+ self.put_connection(connection)
def test(self):
- self.cursor.execute('SELECT 1 FROM information_schema.tables '
+ connection = self.get_connection()
+ is_tryton_database = self._test(connection)
+ self.put_connection(connection)
+ return is_tryton_database
+
+ @classmethod
+ def _test(cls, connection):
+ cursor = connection.cursor()
+ cursor.execute('SELECT 1 FROM information_schema.tables '
'WHERE table_name IN %s',
(('ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu',
'res_user', 'res_group', 'ir_module',
- 'ir_module_dependency', 'ir_translation', 'ir_lang'),))
- return len(self.cursor.fetchall()) != 0
+ 'ir_module_dependency', 'ir_translation',
+ 'ir_lang'),))
+ return len(cursor.fetchall()) != 0
- def nextid(self, table):
- self.cursor.execute("SELECT NEXTVAL('" + table + "_id_seq')")
- return self.cursor.fetchone()[0]
+ def nextid(self, connection, table):
+ cursor = connection.cursor()
+ cursor.execute("SELECT NEXTVAL('" + table + "_id_seq')")
+ return cursor.fetchone()[0]
- def setnextid(self, table, value):
- self.cursor.execute("SELECT SETVAL('" + table + "_id_seq', %d)"
- % value)
+ def setnextid(self, connection, table, value):
+ cursor = connection.cursor()
+ cursor.execute("SELECT SETVAL('" + table + "_id_seq', %d)" % value)
- def currid(self, table):
- self.cursor.execute('SELECT last_value FROM "' + table + '_id_seq"')
- return self.cursor.fetchone()[0]
+ def currid(self, connection, table):
+ cursor = connection.cursor()
+ cursor.execute('SELECT last_value FROM "' + table + '_id_seq"')
+ return cursor.fetchone()[0]
- def lock(self, table):
- self.cursor.execute('LOCK "%s" IN EXCLUSIVE MODE NOWAIT' % table)
+ def lock(self, connection, table):
+ cursor = connection.cursor()
+ cursor.execute('LOCK "%s" IN EXCLUSIVE MODE NOWAIT' % table)
def has_constraint(self):
return True
- def limit_clause(self, select, limit=None, offset=None):
- if limit is not None:
- select += ' LIMIT %d' % limit
- if offset is not None and offset != 0:
- select += ' OFFSET %d' % offset
- return select
-
- def has_returning(self):
- # RETURNING clause is available since PostgreSQL 8.2
- return self._database.get_version(self) >= (8, 2)
-
def has_multirow_insert(self):
return True
+ def get_table_schema(self, connection, table_name):
+ cursor = connection.cursor()
+ for schema in self.search_path:
+ cursor.execute('SELECT 1 '
+ 'FROM information_schema.tables '
+ 'WHERE table_name = %s AND table_schema = %s',
+ (table_name, schema))
+ if cursor.rowcount:
+ return schema
+
@property
def current_user(self):
if self._current_user is None:
- self.execute('SELECT current_user')
- self._current_user, = self.fetchone()
+ connection = self.get_connection()
+ try:
+ cursor = connection.cursor()
+ cursor.execute('SELECT current_user')
+ self._current_user = cursor.fetchone()[0]
+ finally:
+ self.put_connection(connection)
return self._current_user
@property
def search_path(self):
if self._search_path is None:
- self.execute('SHOW search_path')
- path, = self.fetchone()
- special_values = {
- 'user': self.current_user,
- }
- self._search_path = [
- unescape_quote(replace_special_values(
- p.strip(), **special_values))
- for p in path.split(',')]
+ connection = self.get_connection()
+ try:
+ cursor = connection.cursor()
+ cursor.execute('SHOW search_path')
+ path, = cursor.fetchone()
+ special_values = {
+ 'user': self.current_user,
+ }
+ self._search_path = [
+ unescape_quote(replace_special_values(
+ p.strip(), **special_values))
+ for p in path.split(',')]
+ finally:
+ self.put_connection(connection)
return self._search_path
register_type(UNICODE)
diff --git a/trytond/backend/postgresql/table.py b/trytond/backend/postgresql/table.py
index 932481c..f5c70ad 100644
--- a/trytond/backend/postgresql/table.py
+++ b/trytond/backend/postgresql/table.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.transaction import Transaction
from trytond.backend.table import TableHandlerInterface
import logging
@@ -11,102 +11,99 @@ logger = logging.getLogger(__name__)
class TableHandler(TableHandlerInterface):
- def __init__(self, cursor, model, module_name=None, history=False):
- super(TableHandler, self).__init__(cursor, model,
+ def __init__(self, model, module_name=None, history=False):
+ super(TableHandler, self).__init__(model,
module_name=module_name, history=history)
self._columns = {}
self._constraints = []
self._fk_deltypes = {}
self._indexes = []
- self._field2module = {}
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
# Create sequence if necessary
- if not self.sequence_exist(self.cursor, self.sequence_name):
- self.cursor.execute('CREATE SEQUENCE "%s"' % self.sequence_name)
+ if not self.sequence_exist(self.sequence_name):
+ cursor.execute('CREATE SEQUENCE "%s"' % self.sequence_name)
# Create new table if necessary
- if not self.table_exist(self.cursor, self.table_name):
- self.cursor.execute('CREATE TABLE "%s" ()' % self.table_name)
- self.table_schema = self.get_table_schema(self.cursor, self.table_name)
+ if not self.table_exist(self.table_name):
+ cursor.execute('CREATE TABLE "%s" ()' % self.table_name)
+ self.table_schema = transaction.database.get_table_schema(
+ transaction.connection, self.table_name)
- self.cursor.execute('SELECT tableowner = current_user FROM pg_tables '
+ cursor.execute('SELECT tableowner = current_user FROM pg_tables '
'WHERE tablename = %s AND schemaname = %s',
(self.table_name, self.table_schema))
- self.is_owner, = self.cursor.fetchone()
+ self.is_owner, = cursor.fetchone()
if model.__doc__ and self.is_owner:
- self.cursor.execute('COMMENT ON TABLE "%s" IS \'%s\'' %
+ cursor.execute('COMMENT ON TABLE "%s" IS \'%s\'' %
(self.table_name, model.__doc__.replace("'", "''")))
- self._update_definitions()
+ self._update_definitions(columns=True)
if 'id' not in self._columns:
if not self.history:
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'ADD COLUMN id INTEGER '
'DEFAULT nextval(\'"%s"\') NOT NULL'
% (self.table_name, self.sequence_name))
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'ADD PRIMARY KEY(id)' % self.table_name)
else:
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'ADD COLUMN id INTEGER' % self.table_name)
- self._update_definitions()
+ self._update_definitions(columns=True)
if self.history and '__id' not in self._columns:
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'ADD COLUMN __id INTEGER '
'DEFAULT nextval(\'"%s"\') NOT NULL' %
(self.table_name, self.sequence_name))
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'ADD PRIMARY KEY(__id)' % self.table_name)
else:
default = "nextval('%s'::regclass)" % self.sequence_name
if self.history:
if self._columns['__id']['default'] != default:
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'ALTER __id SET DEFAULT %s'
% (self.table_name, default))
if self._columns['id']['default'] != default:
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'ALTER id SET DEFAULT %s'
% (self.table_name, default))
self._update_definitions()
- @classmethod
- def get_table_schema(cls, cursor, table_name):
- for schema in cursor.search_path:
- cursor.execute('SELECT 1 '
- 'FROM information_schema.tables '
- 'WHERE table_name = %s AND table_schema = %s',
- (table_name, schema))
- if cursor.rowcount:
- return schema
-
- @classmethod
- def table_exist(cls, cursor, table_name):
- return bool(cls.get_table_schema(cursor, table_name))
+ @staticmethod
+ def table_exist(table_name):
+ transaction = Transaction()
+ return bool(transaction.database.get_table_schema(
+ transaction.connection, table_name))
@staticmethod
- def table_rename(cursor, old_name, new_name):
+ def table_rename(old_name, new_name):
+ cursor = Transaction().connection.cursor()
# Rename table
- if (TableHandler.table_exist(cursor, old_name)
- and not TableHandler.table_exist(cursor, new_name)):
+ if (TableHandler.table_exist(old_name)
+ and not TableHandler.table_exist(new_name)):
cursor.execute('ALTER TABLE "%s" RENAME TO "%s"'
% (old_name, new_name))
# Rename sequence
old_sequence = old_name + '_id_seq'
new_sequence = new_name + '_id_seq'
- TableHandler.sequence_rename(cursor, old_sequence, new_sequence)
+ TableHandler.sequence_rename(old_sequence, new_sequence)
# Rename history table
old_history = old_name + "__history"
new_history = new_name + "__history"
- if (TableHandler.table_exist(cursor, old_history)
- and not TableHandler.table_exist(cursor, new_history)):
+ if (TableHandler.table_exist(old_history)
+ and not TableHandler.table_exist(new_history)):
cursor.execute('ALTER TABLE "%s" RENAME TO "%s"'
% (old_history, new_history))
@classmethod
- def sequence_schema(cls, cursor, sequence_name):
- for schema in cursor.search_path:
+ def sequence_schema(cls, sequence_name):
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
+ for schema in transaction.database.search_path:
cursor.execute('SELECT 1 '
'FROM information_schema.sequences '
'WHERE sequence_name = %s AND sequence_schema = %s',
@@ -115,13 +112,14 @@ class TableHandler(TableHandlerInterface):
return schema
@classmethod
- def sequence_exist(cls, cursor, sequence_name):
- return bool(cls.sequence_schema(cursor, sequence_name))
+ def sequence_exist(cls, sequence_name):
+ return bool(cls.sequence_schema(sequence_name))
@staticmethod
- def sequence_rename(cursor, old_name, new_name):
- if (TableHandler.sequence_exist(cursor, old_name)
- and not TableHandler.sequence_exist(cursor, new_name)):
+ def sequence_rename(old_name, new_name):
+ cursor = Transaction().connection.cursor()
+ if (TableHandler.sequence_exist(old_name)
+ and not TableHandler.sequence_exist(new_name)):
cursor.execute('ALTER TABLE "%s" RENAME TO "%s"'
% (old_name, new_name))
@@ -129,92 +127,101 @@ class TableHandler(TableHandlerInterface):
return column_name in self._columns
def column_rename(self, old_name, new_name, exception=False):
+ cursor = Transaction().connection.cursor()
if (self.column_exist(old_name)
and not self.column_exist(new_name)):
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'RENAME COLUMN "%s" TO "%s"'
% (self.table_name, old_name, new_name))
- self._update_definitions()
+ self._update_definitions(columns=True)
elif exception and self.column_exist(new_name):
raise Exception('Unable to rename column %s.%s to %s.%s: '
'%s.%s already exist!'
% (self.table_name, old_name, self.table_name, new_name,
self.table_name, new_name))
- def _update_definitions(self):
- self._columns = {}
- # Fetch columns definitions from the table
- self.cursor.execute('SELECT '
- 'column_name, udt_name, is_nullable, character_maximum_length, '
- 'column_default '
- 'FROM information_schema.columns '
- 'WHERE table_name = %s AND table_schema = %s',
- (self.table_name, self.table_schema))
- for column, typname, nullable, size, default in self.cursor.fetchall():
- self._columns[column] = {
- 'typname': typname,
- 'notnull': True if nullable == 'NO' else False,
- 'size': size,
- 'default': default,
- }
-
- # fetch constraints for the table
- self.cursor.execute('SELECT constraint_name '
- 'FROM information_schema.table_constraints '
- 'WHERE table_name = %s AND table_schema = %s',
- (self.table_name, self.table_schema))
- self._constraints = [c for c, in self.cursor.fetchall()]
-
- self.cursor.execute('SELECT k.column_name, r.delete_rule '
- 'FROM information_schema.key_column_usage AS k '
- 'JOIN information_schema.referential_constraints AS r '
- 'ON r.constraint_schema = k.constraint_schema '
- 'AND r.constraint_name = k.constraint_name '
- 'WHERE k.table_name = %s AND k.table_schema = %s',
- (self.table_name, self.table_schema))
- self._fk_deltypes = dict(self.cursor.fetchall())
-
- # Fetch indexes defined for the table
- self.cursor.execute("SELECT cl2.relname "
- "FROM pg_index ind "
- "JOIN pg_class cl on (cl.oid = ind.indrelid) "
- "JOIN pg_namespace n ON (cl.relnamespace = n.oid) "
- "JOIN pg_class cl2 on (cl2.oid = ind.indexrelid) "
- "WHERE cl.relname = %s AND n.nspname = %s",
- (self.table_name, self.table_schema))
- self._indexes = [l[0] for l in self.cursor.fetchall()]
-
- # Keep track of which module created each field
- self._field2module = {}
- if self.object_name is not None:
- self.cursor.execute('SELECT f.name, f.module '
- 'FROM ir_model_field f '
- 'JOIN ir_model m on (f.model=m.id) '
- 'WHERE m.model = %s',
- (self.object_name,))
- for line in self.cursor.fetchall():
- self._field2module[line[0]] = line[1]
+ def _update_definitions(self,
+ columns=None, constraints=None, indexes=None):
+ if columns is None and constraints is None and indexes is None:
+ columns = constraints = indexes = True
+ cursor = Transaction().connection.cursor()
+ if columns:
+ self._columns = {}
+ # Fetch columns definitions from the table
+ cursor.execute('SELECT '
+ 'column_name, udt_name, is_nullable, '
+ 'character_maximum_length, '
+ 'column_default '
+ 'FROM information_schema.columns '
+ 'WHERE table_name = %s AND table_schema = %s',
+ (self.table_name, self.table_schema))
+ for column, typname, nullable, size, default in cursor.fetchall():
+ self._columns[column] = {
+ 'typname': typname,
+ 'notnull': True if nullable == 'NO' else False,
+ 'size': size,
+ 'default': default,
+ }
+
+ if constraints:
+ # fetch constraints for the table
+ cursor.execute('SELECT constraint_name '
+ 'FROM information_schema.table_constraints '
+ 'WHERE table_name = %s AND table_schema = %s',
+ (self.table_name, self.table_schema))
+ self._constraints = [c for c, in cursor.fetchall()]
+
+ cursor.execute('SELECT k.column_name, r.delete_rule '
+ 'FROM information_schema.key_column_usage AS k '
+ 'JOIN information_schema.referential_constraints AS r '
+ 'ON r.constraint_schema = k.constraint_schema '
+ 'AND r.constraint_name = k.constraint_name '
+ 'WHERE k.table_name = %s AND k.table_schema = %s',
+ (self.table_name, self.table_schema))
+ self._fk_deltypes = dict(cursor.fetchall())
+
+ if indexes:
+ # Fetch indexes defined for the table
+ cursor.execute("SELECT cl2.relname "
+ "FROM pg_index ind "
+ "JOIN pg_class cl on (cl.oid = ind.indrelid) "
+ "JOIN pg_namespace n ON (cl.relnamespace = n.oid) "
+ "JOIN pg_class cl2 on (cl2.oid = ind.indexrelid) "
+ "WHERE cl.relname = %s AND n.nspname = %s",
+ (self.table_name, self.table_schema))
+ self._indexes = [l[0] for l in cursor.fetchall()]
+
+ @property
+ def _field2module(self):
+ cursor = Transaction().connection.cursor()
+ cursor.execute('SELECT f.name, f.module '
+ 'FROM ir_model_field f '
+ 'JOIN ir_model m on (f.model=m.id) '
+ 'WHERE m.model = %s',
+ (self.object_name,))
+ return dict(cursor)
def alter_size(self, column_name, column_type):
-
- self.cursor.execute("ALTER TABLE \"%s\" "
+ cursor = Transaction().connection.cursor()
+ cursor.execute("ALTER TABLE \"%s\" "
"RENAME COLUMN \"%s\" TO _temp_change_size"
% (self.table_name, column_name))
- self.cursor.execute("ALTER TABLE \"%s\" "
+ cursor.execute("ALTER TABLE \"%s\" "
"ADD COLUMN \"%s\" %s"
% (self.table_name, column_name, column_type))
- self.cursor.execute("UPDATE \"%s\" "
+ cursor.execute("UPDATE \"%s\" "
"SET \"%s\" = _temp_change_size::%s"
% (self.table_name, column_name, column_type))
- self.cursor.execute("ALTER TABLE \"%s\" "
+ cursor.execute("ALTER TABLE \"%s\" "
"DROP COLUMN _temp_change_size"
% (self.table_name,))
- self._update_definitions()
+ self._update_definitions(columns=True)
def alter_type(self, column_name, column_type):
- self.cursor.execute('ALTER TABLE "' + self.table_name + '" '
+ cursor = Transaction().connection.cursor()
+ cursor.execute('ALTER TABLE "' + self.table_name + '" '
'ALTER "' + column_name + '" TYPE ' + column_type)
- self._update_definitions()
+ self._update_definitions(columns=True)
def db_default(self, column_name, value):
if value in [True, False]:
@@ -222,21 +229,24 @@ class TableHandler(TableHandlerInterface):
else:
test = value
if self._columns[column_name]['default'] != test:
- self.cursor.execute('ALTER TABLE "' + self.table_name + '" '
+ cursor = Transaction().connection.cursor()
+ cursor.execute('ALTER TABLE "' + self.table_name + '" '
'ALTER COLUMN "' + column_name + '" SET DEFAULT %s',
(value,))
def add_raw_column(self, column_name, column_type, column_format,
default_fun=None, field_size=None, migrate=True, string=''):
+ cursor = Transaction().connection.cursor()
+
def comment():
if self.is_owner:
- self.cursor.execute('COMMENT ON COLUMN "%s"."%s" IS \'%s\'' %
+ cursor.execute('COMMENT ON COLUMN "%s"."%s" IS \'%s\'' %
(self.table_name, column_name, string.replace("'", "''")))
if self.column_exist(column_name):
if (column_name in ('create_date', 'write_date')
and column_type[1].lower() != 'timestamp(6)'):
# Migrate dates from timestamp(0) to timestamp
- self.cursor.execute('ALTER TABLE "' + self.table_name + '" '
+ cursor.execute('ALTER TABLE "' + self.table_name + '" '
'ALTER COLUMN "' + column_name + '" TYPE timestamp')
comment()
if not migrate:
@@ -261,11 +271,11 @@ class TableHandler(TableHandlerInterface):
and self._columns[column_name]['typname'] == 'varchar'):
# Migrate size
if field_size is None:
- if self._columns[column_name]['size'] > 0:
+ if self._columns[column_name]['size']:
self.alter_size(column_name, base_type)
elif self._columns[column_name]['size'] == field_size:
pass
- elif (self._columns[column_name]['size'] > 0
+ elif (self._columns[column_name]['size']
and self._columns[column_name]['size'] < field_size):
self.alter_size(column_name, column_type[1])
else:
@@ -279,23 +289,23 @@ class TableHandler(TableHandlerInterface):
return
column_type = column_type[1]
- self.cursor.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s'
+ cursor.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s'
% (self.table_name, column_name, column_type))
comment()
if column_format:
# check if table is non-empty:
- self.cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name)
- if self.cursor.rowcount:
+ cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name)
+ if cursor.rowcount:
# Populate column with default values:
default = None
if default_fun is not None:
default = default_fun()
- self.cursor.execute('UPDATE "' + self.table_name + '" '
+ cursor.execute('UPDATE "' + self.table_name + '" '
'SET "' + column_name + '" = %s',
(column_format(default),))
- self._update_definitions()
+ self._update_definitions(columns=True)
def add_fk(self, column_name, reference, on_delete=None):
if on_delete is not None:
@@ -303,25 +313,26 @@ class TableHandler(TableHandlerInterface):
else:
on_delete = 'SET NULL'
+ cursor = Transaction().connection.cursor()
name = self.table_name + '_' + column_name + '_fkey'
- self.cursor.execute('SELECT 1 '
+ cursor.execute('SELECT 1 '
'FROM information_schema.key_column_usage '
'WHERE table_name = %s AND table_schema = %s '
'AND constraint_name = %s',
(self.table_name, self.table_schema, name))
add = False
- if not self.cursor.rowcount:
+ if not cursor.rowcount:
add = True
elif self._fk_deltypes.get(column_name) != on_delete:
self.drop_fk(column_name)
add = True
if add:
- self.cursor.execute('ALTER TABLE "' + self.table_name + '" '
+ cursor.execute('ALTER TABLE "' + self.table_name + '" '
'ADD CONSTRAINT "' + name + '" '
'FOREIGN KEY ("' + column_name + '") '
'REFERENCES "' + reference + '" '
'ON DELETE ' + on_delete)
- self._update_definitions()
+ self._update_definitions(constraints=True)
def drop_fk(self, column_name, table=None):
self.drop_constraint(column_name + '_fkey', table=table)
@@ -336,69 +347,73 @@ class TableHandler(TableHandlerInterface):
else:
test_index_name = index_name
- if action == 'add':
- if test_index_name in self._indexes:
- return
- self.cursor.execute('CREATE INDEX "' + index_name + '" '
- 'ON "' + self.table_name + '" ( '
- + ','.join(['"' + x + '"' for x in column_name]) + ')')
- self._update_definitions()
- elif action == 'remove':
- if len(column_name) == 1:
- if (self._field2module.get(column_name[0], self.module_name)
- != self.module_name):
+ with Transaction().connection.cursor() as cursor:
+ if action == 'add':
+ if test_index_name in self._indexes:
return
-
- if test_index_name in self._indexes:
- self.cursor.execute('DROP INDEX "%s" ' % (index_name,))
- self._update_definitions()
- else:
- raise Exception('Index action not supported!')
+ cursor.execute('CREATE INDEX "' + index_name + '" '
+ 'ON "' + self.table_name + '" ( '
+ + ','.join(['"' + x + '"' for x in column_name]) + ')')
+ self._update_definitions(indexes=True)
+ elif action == 'remove':
+ if len(column_name) == 1:
+ if (self._field2module.get(column_name[0],
+ self.module_name) != self.module_name):
+ return
+
+ if test_index_name in self._indexes:
+ cursor.execute('DROP INDEX "%s" ' % (index_name,))
+ self._update_definitions(indexes=True)
+ else:
+ raise Exception('Index action not supported!')
def not_null_action(self, column_name, action='add'):
if not self.column_exist(column_name):
return
- if action == 'add':
- if self._columns[column_name]['notnull']:
- return
- self.cursor.execute('SELECT id FROM "%s" '
- 'WHERE "%s" IS NULL'
- % (self.table_name, column_name))
- if not self.cursor.rowcount:
- self.cursor.execute('ALTER TABLE "' + self.table_name + '" '
- 'ALTER COLUMN "' + column_name + '" SET NOT NULL')
- self._update_definitions()
+ with Transaction().connection.cursor() as cursor:
+ if action == 'add':
+ if self._columns[column_name]['notnull']:
+ return
+ cursor.execute('SELECT id FROM "%s" '
+ 'WHERE "%s" IS NULL'
+ % (self.table_name, column_name))
+ if not cursor.rowcount:
+ cursor.execute('ALTER TABLE "' + self.table_name + '" '
+ 'ALTER COLUMN "' + column_name + '" SET NOT NULL')
+ self._update_definitions(columns=True)
+ else:
+ logger.warning(
+ 'Unable to set column %s '
+ 'of table %s not null !\n'
+ 'Try to re-run: '
+ 'trytond.py --update=module\n'
+ 'If it doesn\'t work, update records '
+ 'and execute manually:\n'
+ 'ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL',
+ column_name, self.table_name, self.table_name,
+ column_name)
+ elif action == 'remove':
+ if not self._columns[column_name]['notnull']:
+ return
+ if (self._field2module.get(column_name, self.module_name)
+ != self.module_name):
+ return
+ cursor.execute('ALTER TABLE "%s" '
+ 'ALTER COLUMN "%s" DROP NOT NULL'
+ % (self.table_name, column_name))
+ self._update_definitions(columns=True)
else:
- logger.warning(
- 'Unable to set column %s '
- 'of table %s not null !\n'
- 'Try to re-run: '
- 'trytond.py --update=module\n'
- 'If it doesn\'t work, update records '
- 'and execute manually:\n'
- 'ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL',
- column_name, self.table_name, self.table_name, column_name)
- elif action == 'remove':
- if not self._columns[column_name]['notnull']:
- return
- if (self._field2module.get(column_name, self.module_name)
- != self.module_name):
- return
- self.cursor.execute('ALTER TABLE "%s" '
- 'ALTER COLUMN "%s" DROP NOT NULL'
- % (self.table_name, column_name))
- self._update_definitions()
- else:
- raise Exception('Not null action not supported!')
+ raise Exception('Not null action not supported!')
def add_constraint(self, ident, constraint, exception=False):
ident = self.table_name + "_" + ident
if ident in self._constraints:
# This constrain already exist
return
+ cursor = Transaction().connection.cursor()
try:
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'ADD CONSTRAINT "%s" %s'
% (self.table_name, ident, constraint), constraint.params)
except Exception:
@@ -411,14 +426,15 @@ class TableHandler(TableHandlerInterface):
'ALTER table "%s" ADD CONSTRAINT "%s" %s',
constraint, self.table_name, self.table_name, ident,
constraint)
- self._update_definitions()
+ self._update_definitions(constraints=True)
def drop_constraint(self, ident, exception=False, table=None):
ident = (table or self.table_name) + "_" + ident
if ident not in self._constraints:
return
+ cursor = Transaction().connection.cursor()
try:
- self.cursor.execute('ALTER TABLE "%s" '
+ cursor.execute('ALTER TABLE "%s" '
'DROP CONSTRAINT "%s"'
% (self.table_name, ident))
except Exception:
@@ -427,13 +443,14 @@ class TableHandler(TableHandlerInterface):
logger.warning(
'unable to drop \'%s\' constraint on table %s!',
ident, self.table_name)
- self._update_definitions()
+ self._update_definitions(constraints=True)
def drop_column(self, column_name, exception=False):
if not self.column_exist(column_name):
return
+ cursor = Transaction().connection.cursor()
try:
- self.cursor.execute(
+ cursor.execute(
'ALTER TABLE "%s" DROP COLUMN "%s"' %
(self.table_name, column_name))
@@ -443,10 +460,11 @@ class TableHandler(TableHandlerInterface):
logger.warning(
'unable to drop \'%s\' column on table %s!',
column_name, self.table_name, exc_info=True)
- self._update_definitions()
+ self._update_definitions(columns=True)
@staticmethod
- def drop_table(cursor, model, table, cascade=False):
+ def drop_table(model, table, cascade=False):
+ cursor = Transaction().connection.cursor()
cursor.execute('DELETE FROM ir_model_data '
'WHERE model = \'%s\'' % model)
diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py
index 91346b0..ff5a91a 100644
--- a/trytond/backend/sqlite/database.py
+++ b/trytond/backend/sqlite/database.py
@@ -1,6 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-from trytond.backend.database import DatabaseInterface, CursorInterface
+from trytond.backend.database import DatabaseInterface
from trytond.config import config
import os
from decimal import Decimal
@@ -24,8 +24,7 @@ from sql import Flavor, Table
from sql.functions import (Function, Extract, Position, Substring,
Overlay, CharLength, CurrentTimestamp)
-__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError',
- 'Cursor']
+__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError']
class SQLiteExtract(Function):
@@ -152,6 +151,22 @@ def sign(value):
return value
+def greatest(*args):
+ args = filter(lambda a: a is not None, args)
+ if args:
+ return max(args)
+ else:
+ return None
+
+
+def least(*args):
+ args = filter(lambda a: a is not None, args)
+ if args:
+ return min(args)
+ else:
+ return None
+
+
MAPPING = {
Extract: SQLiteExtract,
Position: SQLitePosition,
@@ -162,35 +177,52 @@ MAPPING = {
}
+class SQLiteCursor(sqlite.Cursor):
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ pass
+
+
+class SQLiteConnection(sqlite.Connection):
+
+ def cursor(self):
+ return super(SQLiteConnection, self).cursor(SQLiteCursor)
+
+
class Database(DatabaseInterface):
_local = threading.local()
_conn = None
flavor = Flavor(paramstyle='qmark', function_mapping=MAPPING)
+ IN_MAX = 200
- def __new__(cls, database_name=':memory:'):
- if (database_name == ':memory:'
+ def __new__(cls, name=':memory:'):
+ if (name == ':memory:'
and getattr(cls._local, 'memory_database', None)):
return cls._local.memory_database
- return DatabaseInterface.__new__(cls, database_name=database_name)
+ return DatabaseInterface.__new__(cls, name=name)
- def __init__(self, database_name=':memory:'):
- super(Database, self).__init__(database_name=database_name)
- if database_name == ':memory:':
+ def __init__(self, name=':memory:'):
+ super(Database, self).__init__(name=name)
+ if name == ':memory:':
Database._local.memory_database = self
def connect(self):
- if self.database_name == ':memory:':
+ if self.name == ':memory:':
path = ':memory:'
else:
- db_filename = self.database_name + '.sqlite'
+ db_filename = self.name + '.sqlite'
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:
return self
self._conn = sqlite.connect(path,
- detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES)
+ detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES,
+ factory=SQLiteConnection)
self._conn.create_function('extract', 2, SQLiteExtract.extract)
self._conn.create_function('date_trunc', 2, date_trunc)
self._conn.create_function('split_part', 3, split_part)
@@ -201,29 +233,32 @@ class Database(DatabaseInterface):
self._conn.create_function('replace', 3, replace)
self._conn.create_function('now', 0, now)
self._conn.create_function('sign', 1, sign)
- self._conn.create_function('greatest', -1, max)
- self._conn.create_function('least', -1, min)
+ self._conn.create_function('greatest', -1, greatest)
+ self._conn.create_function('least', -1, least)
self._conn.execute('PRAGMA foreign_keys = ON')
return self
- def cursor(self, autocommit=False, readonly=False):
+ def get_connection(self, autocommit=False, readonly=False):
if self._conn is None:
self.connect()
if autocommit:
self._conn.isolation_level = None
else:
self._conn.isolation_level = 'IMMEDIATE'
- return Cursor(self._conn, self.database_name)
+ return self._conn
+
+ def put_connection(self, connection=None, close=False):
+ pass
def close(self):
- if self.database_name == ':memory:':
+ if self.name == ':memory:':
return
if self._conn is None:
return
self._conn = None
- @staticmethod
- def create(cursor, database_name):
+ @classmethod
+ def create(cls, connection, database_name):
if database_name == ':memory:':
path = ':memory:'
else:
@@ -235,10 +270,9 @@ class Database(DatabaseInterface):
cursor = conn.cursor()
cursor.close()
- @classmethod
- def drop(cls, cursor, database_name):
+ def drop(self, connection, database_name):
if database_name == ':memory:':
- cls._local.memory_database._conn = None
+ self._local.memory_database._conn = None
return
if os.sep in database_name:
return
@@ -270,8 +304,7 @@ class Database(DatabaseInterface):
with open(path, 'wb') as file_p:
file_p.write(data)
- @staticmethod
- def list(cursor):
+ def list(self):
res = []
listdir = [':memory:']
try:
@@ -285,80 +318,48 @@ class Database(DatabaseInterface):
else:
db_name = db_file[:-7]
try:
- database = Database(db_name)
+ database = Database(db_name).connect()
except Exception:
continue
- cursor2 = database.cursor()
- if cursor2.test():
+ if database.test():
res.append(db_name)
- cursor2.close()
+ database.close()
return res
- @staticmethod
- def init(cursor):
+ def init(self):
from trytond.modules import get_module_info
- sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
- with open(sql_file) as fp:
- for line in fp.read().split(';'):
- if (len(line) > 0) and (not line.isspace()):
- cursor.execute(line)
-
- ir_module = Table('ir_module')
- ir_module_dependency = Table('ir_module_dependency')
- for module in ('ir', 'res', 'webdav'):
- state = 'uninstalled'
- if module in ('ir', 'res'):
- state = 'to install'
- info = get_module_info(module)
- insert = ir_module.insert(
- [ir_module.create_uid, ir_module.create_date, ir_module.name,
- ir_module.state],
- [[0, CurrentTimestamp(), module, state]])
- cursor.execute(*insert)
- cursor.execute('SELECT last_insert_rowid()')
- module_id, = cursor.fetchone()
- for dependency in info.get('depends', []):
- insert = ir_module_dependency.insert(
- [ir_module_dependency.create_uid,
- ir_module_dependency.create_date,
- ir_module_dependency.module, ir_module_dependency.name
- ],
- [[0, CurrentTimestamp(), module_id, dependency]])
+ with self._conn as conn:
+ cursor = conn.cursor()
+ sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
+ with open(sql_file) as fp:
+ for line in fp.read().split(';'):
+ if (len(line) > 0) and (not line.isspace()):
+ cursor.execute(line)
+
+ ir_module = Table('ir_module')
+ ir_module_dependency = Table('ir_module_dependency')
+ for module in ('ir', 'res'):
+ state = 'uninstalled'
+ if module in ('ir', 'res'):
+ state = 'to install'
+ info = get_module_info(module)
+ insert = ir_module.insert(
+ [ir_module.create_uid, ir_module.create_date,
+ ir_module.name, ir_module.state],
+ [[0, CurrentTimestamp(), module, state]])
cursor.execute(*insert)
-
-
-class Cursor(CursorInterface):
- IN_MAX = 200
-
- def __init__(self, conn, database_name):
- super(Cursor, self).__init__()
- self._conn = conn
- self.database_name = database_name
- self.dbname = self.database_name # XXX to remove
- self.cursor = conn.cursor()
-
- def __getattr__(self, name):
- if _FIX_ROWCOUNT and name == 'rowcount':
- return -1
- return getattr(self.cursor, name)
-
- def execute(self, sql, params=None):
- if params:
- return self.cursor.execute(sql, params)
- else:
- return self.cursor.execute(sql)
-
- def close(self, close=False):
- self.cursor.close()
- self.rollback()
-
- def commit(self):
- super(Cursor, self).commit()
- self._conn.commit()
-
- def rollback(self):
- super(Cursor, self).rollback()
- self._conn.rollback()
+ cursor.execute('SELECT last_insert_rowid()')
+ module_id, = cursor.fetchone()
+ for dependency in info.get('depends', []):
+ insert = ir_module_dependency.insert(
+ [ir_module_dependency.create_uid,
+ ir_module_dependency.create_date,
+ ir_module_dependency.module,
+ ir_module_dependency.name,
+ ],
+ [[0, CurrentTimestamp(), module_id, dependency]])
+ cursor.execute(*insert)
+ conn.commit()
def test(self):
sqlite_master = Table('sqlite_master')
@@ -376,17 +377,19 @@ class Cursor(CursorInterface):
'ir_translation',
'ir_lang',
])
- try:
- self.cursor.execute(*select)
- except Exception:
- return False
- return len(self.cursor.fetchall()) != 0
+ with self._conn as conn:
+ cursor = conn.cursor()
+ try:
+ cursor.execute(*select)
+ except Exception:
+ return False
+ return len(cursor.fetchall()) != 0
- def lastid(self):
- self.cursor.execute('SELECT last_insert_rowid()')
- return self.cursor.fetchone()[0]
+ def lastid(self, cursor):
+ # This call is not thread safe
+ return cursor.lastrowid
- def lock(self, table):
+ def lock(self, connection, table):
pass
def has_constraint(self):
diff --git a/trytond/backend/sqlite/table.py b/trytond/backend/sqlite/table.py
index 236645e..1609ede 100644
--- a/trytond/backend/sqlite/table.py
+++ b/trytond/backend/sqlite/table.py
@@ -1,9 +1,11 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+from trytond.transaction import Transaction
from trytond.backend.table import TableHandlerInterface
import logging
import re
+import warnings
__all__ = ['TableHandler']
@@ -11,31 +13,32 @@ logger = logging.getLogger(__name__)
class TableHandler(TableHandlerInterface):
- def __init__(self, cursor, model, module_name=None, history=False):
- super(TableHandler, self).__init__(cursor, model,
+ def __init__(self, model, module_name=None, history=False):
+ super(TableHandler, self).__init__(model,
module_name=module_name, history=history)
self._columns = {}
self._constraints = []
self._fk_deltypes = {}
self._indexes = []
- self._field2module = {}
self._model = model
+ cursor = Transaction().connection.cursor()
# Create new table if necessary
- if not self.table_exist(self.cursor, self.table_name):
+ if not self.table_exist(self.table_name):
if not self.history:
- self.cursor.execute('CREATE TABLE "%s" '
+ cursor.execute('CREATE TABLE "%s" '
'(id INTEGER PRIMARY KEY AUTOINCREMENT)'
% self.table_name)
else:
- self.cursor.execute('CREATE TABLE "%s" '
+ cursor.execute('CREATE TABLE "%s" '
'(__id INTEGER PRIMARY KEY AUTOINCREMENT, '
'id INTEGER)' % self.table_name)
self._update_definitions()
@staticmethod
- def table_exist(cursor, table_name):
+ def table_exist(table_name):
+ cursor = Transaction().connection.cursor()
cursor.execute("SELECT sql FROM sqlite_master "
"WHERE type = 'table' AND name = ?",
(table_name,))
@@ -68,38 +71,38 @@ class TableHandler(TableHandlerInterface):
return True
@staticmethod
- def table_rename(cursor, old_name, new_name):
- if (TableHandler.table_exist(cursor, old_name)
- and not TableHandler.table_exist(cursor, new_name)):
+ def table_rename(old_name, new_name):
+ cursor = Transaction().connection.cursor()
+ if (TableHandler.table_exist(old_name)
+ and not TableHandler.table_exist(new_name)):
cursor.execute('ALTER TABLE "%s" RENAME TO "%s"'
% (old_name, new_name))
# Rename history table
old_history = old_name + "__history"
new_history = new_name + "__history"
- if (TableHandler.table_exist(cursor, old_history)
- and not TableHandler.table_exist(cursor, new_history)):
+ if (TableHandler.table_exist(old_history)
+ and not TableHandler.table_exist(new_history)):
cursor.execute('ALTER TABLE "%s" RENAME TO "%s"'
% (old_history, new_history))
@staticmethod
- def sequence_exist(cursor, sequence_name):
+ def sequence_exist(sequence_name):
return True
@staticmethod
- def sequence_rename(cursor, old_name, new_name):
+ def sequence_rename(old_name, new_name):
pass
def column_exist(self, column_name):
return column_name in self._columns
def column_rename(self, old_name, new_name, exception=False):
+ cursor = Transaction().connection.cursor()
if self.column_exist(old_name) and \
not self.column_exist(new_name):
temp_table = '_temp_%s' % self.table_name
- TableHandler.table_rename(self.cursor, self.table_name,
- temp_table)
- new_table = TableHandler(self.cursor, self._model,
- history=self.history)
+ TableHandler.table_rename(self.table_name, temp_table)
+ new_table = TableHandler(self._model, history=self.history)
for column, (notnull, hasdef, size, typname) \
in self._columns.iteritems():
if column == old_name:
@@ -109,12 +112,12 @@ class TableHandler(TableHandlerInterface):
new_columns = new_table._columns.keys()
old_columns = [x if x != old_name else new_name
for x in new_columns]
- self.cursor.execute(('INSERT INTO "%s" (' +
+ cursor.execute(('INSERT INTO "%s" (' +
','.join('"%s"' % x for x in new_columns) +
') SELECT ' +
','.join('"%s"' % x for x in old_columns) + ' ' +
'FROM "%s"') % (self.table_name, temp_table))
- self.cursor.execute('DROP TABLE "%s"' % temp_table)
+ cursor.execute('DROP TABLE "%s"' % temp_table)
self._update_definitions()
elif exception and self.column_exist(new_name):
raise Exception('Unable to rename column %s.%s to %s.%s: '
@@ -122,62 +125,56 @@ class TableHandler(TableHandlerInterface):
% (self.table_name, old_name, self.table_name, new_name,
self.table_name, new_name))
- def _update_definitions(self):
-
+ def _update_definitions(self, columns=None, indexes=None):
+ if columns is None and indexes is None:
+ columns = indexes = True
+ cursor = Transaction().connection.cursor()
# Fetch columns definitions from the table
- self.cursor.execute('PRAGMA table_info("' + self.table_name + '")')
- self._columns = {}
- for _, column, type_, notnull, hasdef, _ in self.cursor.fetchall():
- column = re.sub(r'^\"|\"$', '', column)
- match = re.match(r'(\w+)(\((.*?)\))?', type_)
- if match:
- typname = match.group(1).upper()
- size = match.group(3) and int(match.group(3)) or 0
- else:
- typname = type_.upper()
- size = -1
- self._columns[column] = {
- 'notnull': notnull,
- 'hasdef': hasdef,
- 'size': size,
- 'typname': typname,
- }
+ if columns:
+ cursor.execute('PRAGMA table_info("' + self.table_name + '")')
+ self._columns = {}
+ for _, column, type_, notnull, hasdef, _ in cursor.fetchall():
+ column = re.sub(r'^\"|\"$', '', column)
+ match = re.match(r'(\w+)(\((.*?)\))?', type_)
+ if match:
+ typname = match.group(1).upper()
+ size = match.group(3) and int(match.group(3)) or 0
+ else:
+ typname = type_.upper()
+ size = -1
+ self._columns[column] = {
+ 'notnull': notnull,
+ 'hasdef': hasdef,
+ 'size': size,
+ 'typname': typname,
+ }
# Fetch indexes defined for the table
- try:
- self.cursor.execute('PRAGMA index_list("' + self.table_name + '")')
- except IndexError: # There is sometimes IndexError
- self.cursor.execute('PRAGMA index_list("' + self.table_name + '")')
- self._indexes = [l[1] for l in self.cursor.fetchall()]
-
- # Keep track of which module created each field
- self._field2module = {}
- if self.object_name is not None:
- self.cursor.execute('SELECT f.name, f.module '
- 'FROM ir_model_field f '
- 'JOIN ir_model m on (f.model=m.id) '
- 'WHERE m.model = ?',
- (self.object_name,))
- for line in self.cursor.fetchall():
- self._field2module[line[0]] = line[1]
+ if indexes:
+ try:
+ cursor.execute('PRAGMA index_list("' + self.table_name + '")')
+ except IndexError: # There is sometimes IndexError
+ cursor.execute('PRAGMA index_list("' + self.table_name + '")')
+ self._indexes = [l[1] for l in cursor.fetchall()]
+
+ @property
+ def _field2module(self):
+ cursor = Transaction().connection.cursor()
+ cursor.execute('SELECT f.name, f.module '
+ 'FROM ir_model_field f '
+ 'JOIN ir_model m on (f.model=m.id) '
+ 'WHERE m.model = ?',
+ (self.object_name,))
+ return dict(cursor)
def alter_size(self, column_name, column_type):
- logger.warning(
- 'Unable to alter size of column %s '
- 'of table %s!',
- column_name, self.table_name)
+ warnings.warn('Unable to alter size of column with SQLite backend')
def alter_type(self, column_name, column_type):
- logger.warning(
- 'Unable to alter type of column %s '
- 'of table %s!',
- column_name, self.table_name)
+ warnings.warn('Unable to alter type of column with SQLite backend')
def db_default(self, column_name, value):
- logger.warning(
- 'Unable to set default on column %s '
- 'of table %s!',
- column_name, self.table_name)
+ warnings.warn('Unable to set default on column with SQLite backend')
def add_raw_column(self, column_name, column_type, column_format,
default_fun=None, field_size=None, migrate=True, string=''):
@@ -221,57 +218,55 @@ class TableHandler(TableHandlerInterface):
field_size)
return
+ cursor = Transaction().connection.cursor()
column_type = column_type[1]
default = ''
- self.cursor.execute(('ALTER TABLE "%s" ADD COLUMN "%s" %s' + default) %
+ cursor.execute(('ALTER TABLE "%s" ADD COLUMN "%s" %s' + default) %
(self.table_name, column_name, column_type))
if column_format:
# check if table is non-empty:
- self.cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name)
- if self.cursor.fetchone():
+ cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name)
+ if cursor.fetchone():
# Populate column with default values:
default = None
if default_fun is not None:
default = default_fun()
- self.cursor.execute('UPDATE "' + self.table_name + '" '
+ cursor.execute('UPDATE "' + self.table_name + '" '
'SET "' + column_name + '" = ?',
(column_format(default),))
- self._update_definitions()
+ self._update_definitions(columns=True)
def add_fk(self, column_name, reference, on_delete=None):
- logger.warning(
- 'Unable to add foreign key on table %s!',
- self.table_name)
+ warnings.warn('Unable to add foreign key with SQLite backend')
def drop_fk(self, column_name, table=None):
- logger.warning(
- 'Unable to drop foreign key on table %s!',
- self.table_name)
+ warnings.warn('Unable to drop foreign key with SQLite backend')
def index_action(self, column_name, action='add', table=None):
if isinstance(column_name, basestring):
column_name = [column_name]
index_name = self.table_name + "_" + '_'.join(column_name) + "_index"
+ cursor = Transaction().connection.cursor()
if action == 'add':
if index_name in self._indexes:
return
- self.cursor.execute('CREATE INDEX "' + index_name + '" '
+ cursor.execute('CREATE INDEX "' + index_name + '" '
'ON "' + self.table_name + '" ( ' +
','.join('"' + x + '"' for x in column_name) +
')')
- self._update_definitions()
+ self._update_definitions(indexes=True)
elif action == 'remove':
if len(column_name) == 1:
- if self._field2module.get(column_name[0], self.module_name) \
- != self.module_name:
+ if self._field2module.get(column_name[0],
+ self.module_name) != self.module_name:
return
if index_name in self._indexes:
- self.cursor.execute('DROP INDEX "%s" ' % (index_name,))
- self._update_definitions()
+ cursor.execute('DROP INDEX "%s" ' % (index_name,))
+ self._update_definitions(indexes=True)
else:
raise Exception('Index action not supported!')
@@ -280,35 +275,24 @@ class TableHandler(TableHandlerInterface):
return
if action == 'add':
- logger.warning(
- 'Unable to set not null on column %s '
- 'of table %s!',
- column_name, self.table_name)
+ warnings.warn('Unable to set not null with SQLite backend')
elif action == 'remove':
- logger.warning(
- 'Unable to remove not null on column %s '
- 'of table %s!',
- column_name, self.table_name)
+ warnings.warn('Unable to remove not null with SQLite backend')
else:
raise Exception('Not null action not supported!')
def add_constraint(self, ident, constraint, exception=False):
- logger.warning(
- 'Unable to add constraint on table %s!',
- self.table_name)
+ warnings.warn('Unable to add constraint with SQLite backend')
def drop_constraint(self, ident, exception=False, table=None):
- logger.warning(
- 'Unable to drop constraint on table %s!',
- self.table_name)
+ warnings.warn('Unable to drop constraint with SQLite backend')
def drop_column(self, column_name, exception=False):
- logger.warning(
- 'Unable to drop "%s" column on table %s!',
- column_name, self.table_name)
+ warnings.warn('Unable to drop column with SQLite backend')
@staticmethod
- def drop_table(cursor, model, table, cascade=False):
+ def drop_table(model, table, cascade=False):
+ cursor = Transaction().connection.cursor()
cursor.execute('DELETE from ir_model_data where '
'model = \'%s\'' % model)
diff --git a/trytond/backend/table.py b/trytond/backend/table.py
index afdeb6b..887d415 100644
--- a/trytond/backend/table.py
+++ b/trytond/backend/table.py
@@ -7,15 +7,13 @@ class TableHandlerInterface(object):
Define generic interface to handle database table
'''
- def __init__(self, cursor, model, module_name=None, history=False):
+ def __init__(self, model, module_name=None, history=False):
'''
- :param cursor: the database cursor
:param model: the Model linked to the table
:param module_name: the module name
:param history: a boolean to define if it is a history table
'''
super(TableHandlerInterface, self).__init__()
- self.cursor = cursor
if history:
self.table_name = model._table + '__history'
else:
@@ -29,44 +27,40 @@ class TableHandlerInterface(object):
self.history = history
@staticmethod
- def table_exist(cursor, table_name):
+ def table_exist(table_name):
'''
Table exist
- :param cursor: the database cursor
:param table_name: the table name
:return: a boolean
'''
raise NotImplementedError
@staticmethod
- def table_rename(cursor, old_name, new_name):
+ def table_rename(old_name, new_name):
'''
Rename table
- :param cursor: the database cursor
:param old_name: the old table name
:param new_name: the new table name
'''
raise NotImplementedError
@staticmethod
- def sequence_exist(cursor, sequence_name):
+ def sequence_exist(sequence_name):
'''
Sequence exist
- :param cursor: the database cursor
:param sequence_name: the sequence name
:return: a boolean
'''
raise NotImplementedError
@staticmethod
- def sequence_rename(cursor, old_name, new_name):
+ def sequence_rename(old_name, new_name):
'''
Rename sequence
- :param cursor: the database cursor
:param old_name: the old sequence name
:param new_name: the new sequence name
'''
@@ -206,7 +200,7 @@ class TableHandlerInterface(object):
raise NotImplementedError
@staticmethod
- def drop_table(cursor, model, table, cascade=False):
+ def drop_table(model, table, cascade=False):
'''
Remove a table and clean ir_model_data from the given model.
diff --git a/trytond/cache.py b/trytond/cache.py
index a239987..4a89382 100644
--- a/trytond/cache.py
+++ b/trytond/cache.py
@@ -43,11 +43,10 @@ class Cache(object):
return key
def get(self, key, default=None):
- cursor = Transaction().cursor
+ dbname = Transaction().database.name
key = self._key(key)
with self._lock:
- cache = self._cache.setdefault(cursor.dbname,
- LRUDict(self.size_limit))
+ cache = self._cache.setdefault(dbname, LRUDict(self.size_limit))
try:
result = cache[key] = cache.pop(key)
return result
@@ -55,11 +54,10 @@ class Cache(object):
return default
def set(self, key, value):
- cursor = Transaction().cursor
+ dbname = Transaction().database.name
key = self._key(key)
with self._lock:
- cache = self._cache.setdefault(cursor.dbname,
- LRUDict(self.size_limit))
+ cache = self._cache.setdefault(dbname, LRUDict(self.size_limit))
try:
cache[key] = value
except TypeError:
@@ -67,15 +65,15 @@ class Cache(object):
return value
def clear(self):
- cursor = Transaction().cursor
- Cache.reset(cursor.dbname, self._name)
+ dbname = Transaction().database.name
+ Cache.reset(dbname, self._name)
with self._lock:
- self._cache[cursor.dbname] = LRUDict(self.size_limit)
+ self._cache[dbname] = LRUDict(self.size_limit)
@staticmethod
def clean(dbname):
- with Transaction().new_cursor():
- cursor = Transaction().cursor
+ with Transaction().new_transaction() as transaction,\
+ transaction.connection.cursor() as cursor:
table = Table('ir_cache')
cursor.execute(*table.select(table.timestamp, table.name))
timestamps = {}
@@ -97,25 +95,24 @@ class Cache(object):
@staticmethod
def resets(dbname):
- 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(*table.select(table.name,
+ table = Table('ir_cache')
+ with Transaction().new_transaction() as transaction,\
+ transaction.connection.cursor() as cursor,\
+ Cache._resets_lock:
+ Cache._resets.setdefault(dbname, set())
+ for name in Cache._resets[dbname]:
+ cursor.execute(*table.select(table.name,
+ where=table.name == name))
+ if cursor.fetchone():
+ # It would be better to insert only
+ cursor.execute(*table.update([table.timestamp],
+ [CurrentTimestamp()],
where=table.name == name))
- if cursor.fetchone():
- # It would be better to insert only
- cursor.execute(*table.update([table.timestamp],
- [CurrentTimestamp()],
- where=table.name == name))
- else:
- cursor.execute(*table.insert(
- [table.timestamp, table.name],
- [[CurrentTimestamp(), name]]))
- Cache._resets[dbname].clear()
- cursor.commit()
+ else:
+ cursor.execute(*table.insert(
+ [table.timestamp, table.name],
+ [[CurrentTimestamp(), name]]))
+ Cache._resets[dbname].clear()
@classmethod
def drop(cls, dbname):
@@ -128,6 +125,8 @@ class LRUDict(OrderedDict):
Dictionary with a size limit.
If size limit is reached, it will remove the first added items.
"""
+ __slots__ = ('size_limit',)
+
def __init__(self, size_limit, *args, **kwargs):
assert size_limit > 0
self.size_limit = size_limit
@@ -150,3 +149,24 @@ class LRUDict(OrderedDict):
def _check_size_limit(self):
while len(self) > self.size_limit:
self.popitem(last=False)
+
+
+class LRUDictTransaction(LRUDict):
+ """
+ Dictionary with a size limit. (see LRUDict)
+ It is refreshed when transaction counter is changed.
+ """
+ __slots__ = ('transaction', 'counter')
+
+ def __init__(self, *args, **kwargs):
+ super(LRUDictTransaction, self).__init__(*args, **kwargs)
+ self.transaction = Transaction()
+ self.counter = self.transaction.counter
+
+ def clear(self):
+ super(LRUDictTransaction, self).clear()
+ self.counter = self.transaction.counter
+
+ def refresh(self):
+ if self.counter != self.transaction.counter:
+ self.clear()
diff --git a/bin/trytond b/trytond/commandline.py
old mode 100755
new mode 100644
similarity index 54%
copy from bin/trytond
copy to trytond/commandline.py
index 89d7b31..45bb900
--- a/bin/trytond
+++ b/trytond/commandline.py
@@ -1,47 +1,52 @@
-#!/usr/bin/env python
-#This file is part of Tryton. The COPYRIGHT file at the top level of
-#this repository contains the full copyright notices and license terms.
-import sys
-import os
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
import argparse
-
-DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
- '..', '..', 'trytond')))
-if os.path.isdir(DIR):
- sys.path.insert(0, os.path.dirname(DIR))
+import os
+import logging
+import logging.config
+import logging.handlers
+from contextlib import contextmanager
from trytond import __version__
-from trytond import server
+logger = logging.getLogger(__name__)
-def parse_commandline():
- options = {}
- parser = argparse.ArgumentParser(prog='trytond')
+def get_parser():
+ parser = argparse.ArgumentParser()
parser.add_argument('--version', action='version',
version='%(prog)s ' + __version__)
parser.add_argument("-c", "--config", dest="configfile", metavar='FILE',
default=os.environ.get('TRYTOND_CONFIG'), help="specify config file")
- parser.add_argument('--dev', dest='dev', action='store_true',
- help='enable development mode')
parser.add_argument("-v", "--verbose", action="store_true",
dest="verbose", help="enable verbose mode")
+ parser.add_argument('--dev', dest='dev', action='store_true',
+ help='enable development mode')
parser.add_argument("-d", "--database", dest="database_names", nargs='+',
default=[], metavar='DATABASE', help="specify the database name")
+ parser.add_argument("--logconf", dest="logconf", metavar='FILE',
+ help="logging configuration file (ConfigParser format)")
+
+ return parser
+
+
+def get_parser_daemon():
+ parser = get_parser()
+ parser.add_argument("--pidfile", dest="pidfile", metavar='FILE',
+ help="file where the server pid will be stored")
+ return parser
+
+
+def get_parser_admin():
+ parser = get_parser()
+
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", metavar='FILE',
- help="file where the server pid will be stored")
- 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'
@@ -49,32 +54,35 @@ def parse_commandline():
'environment variable.\n'
'The database URI can be specified in the TRYTOND_DATABASE_URI '
'environment variable.')
+ return parser
+
+
+def config_log(options):
+ if options.logconf:
+ logging.config.fileConfig(options.logconf)
+ logging.getLogger('server').info('using %s as logging '
+ 'configuration file', options.logconf)
+ else:
+ logformat = ('%(process)s %(thread)s [%(asctime)s] '
+ '%(levelname)s %(name)s %(message)s')
+ if options.verbose:
+ if options.dev:
+ level = logging.DEBUG
+ else:
+ level = logging.INFO
+ else:
+ level = logging.ERROR
+ logging.basicConfig(level=level, format=logformat)
+ logging.captureWarnings(True)
+
- options = parser.parse_args()
-
- if not options.database_names and options.update:
- parser.error('Missing database option')
- return options
-
-
-if '--profile' in sys.argv:
- import profile
- import pstats
- import tempfile
- sys.argv.remove('--profile')
-
- options = parse_commandline()
- statfile = tempfile.mkstemp(".stat", "trytond-")[1]
- profile.run('server.TrytonServer(options).run()', statfile)
- s = pstats.Stats(statfile)
- s.sort_stats('cumulative').print_stats()
- s.sort_stats('call').print_stats()
- s.sort_stats('time').print_stats()
- s.sort_stats('time')
- s.print_callers()
- s.print_callees()
-
- os.remove(statfile)
-else:
- options = parse_commandline()
- server.TrytonServer(options).run()
+ at contextmanager
+def pidfile(options):
+ path = options.pidfile
+ if not path:
+ yield
+ else:
+ with open(path, 'w') as fd:
+ fd.write('%d' % os.getpid())
+ yield
+ os.unlink(path)
diff --git a/trytond/config.py b/trytond/config.py
index 46f5d1f..44ceaf9 100644
--- a/trytond/config.py
+++ b/trytond/config.py
@@ -3,9 +3,11 @@
import os
import ConfigParser
import urlparse
+import logging
__all__ = ['config', 'get_hostname', 'get_port', 'split_netloc',
'parse_listen', 'parse_uri']
+logger = logging.getLogger(__name__)
def get_hostname(netloc):
@@ -39,11 +41,9 @@ 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('web')
+ self.set('web', 'listen', 'localhost:8000')
+ self.set('web', 'root', '/var/www/localhost/tryton')
self.add_section('database')
self.set('database', 'uri',
os.environ.get('TRYTOND_DATABASE_URI', 'sqlite://'))
@@ -63,10 +63,12 @@ class TrytonConfigParser(ConfigParser.RawConfigParser):
self.add_section('report')
self.set('report', 'unoconv',
'pipe,name=trytond;urp;StarOffice.ComponentContext')
+ self.update_etc()
def update_etc(self, configfile=os.environ.get('TRYTOND_CONFIG')):
if not configfile:
return
+ logger.info('using %s as configuration file', configfile)
self.read(configfile)
def get(self, section, option, *args, **kwargs):
diff --git a/trytond/const.py b/trytond/const.py
index 23503c0..07a56ec 100644
--- a/trytond/const.py
+++ b/trytond/const.py
@@ -1,8 +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.
OPERATORS = (
+ 'where',
+ 'not where',
'child_of',
'not child_of',
+ 'parent_of',
+ 'not parent_of',
'=',
'!=',
'like',
diff --git a/trytond/convert.py b/trytond/convert.py
index c3000dc..d8f2758 100644
--- a/trytond/convert.py
+++ b/trytond/convert.py
@@ -43,7 +43,7 @@ class MenuitemTagHandler:
self.xml_id = None
def startElement(self, name, attributes):
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
values = {}
@@ -354,7 +354,7 @@ class Fs2bdAccessor:
models = Model.browse(ids)
for model in models:
if model.id in self.browserecord[module][model_name]:
- for cache in Transaction().cursor.cache.values():
+ for cache in Transaction().cache.values():
for cache in (cache, cache.get('_language_cache',
{}).values()):
if (model_name in cache
@@ -460,8 +460,6 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
if name in self.taghandlerlist:
self.taghandler = self.taghandlerlist[name]
- self.taghandler.startElement(name, attributes)
-
elif name == "data":
self.noupdate = bool(int(attributes.get("noupdate", '0')))
self.grouped = bool(int(attributes.get('grouped', 0)))
@@ -482,7 +480,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
else:
logger.info("Tag %s not supported", (name,))
return
- elif not self.skip_data:
+ if self.taghandler and not self.skip_data:
self.taghandler.startElement(name, attributes)
def characters(self, data):
@@ -604,7 +602,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
old_values = self.ModelData.load_values(old_values)
for key in old_values:
- if isinstance(old_values[key], str):
+ if isinstance(old_values[key], bytes):
# Fix for migration to unicode
old_values[key] = old_values[key].decode('utf-8')
@@ -644,7 +642,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
# if the fs value is the same has in the db, whe ignore it
val = values[key]
- if isinstance(values[key], str):
+ if isinstance(values[key], bytes):
# Fix for migration to unicode
val = values[key].decode('utf-8')
if db_field == val:
@@ -783,7 +781,7 @@ def post_import(pool, module, to_delete):
"""
Remove the records that are given in to_delete.
"""
- cursor = Transaction().cursor
+ transaction = Transaction()
mdata_delete = []
ModelData = pool.get("ir.model.data")
@@ -810,9 +808,9 @@ def post_import(pool, module, to_delete):
logger.warning(
'Could not delete id %d of model %s because model no '
'longer exists.', db_id, model)
- cursor.commit()
+ transaction.commit()
except Exception:
- cursor.rollback()
+ transaction.rollback()
logger.error(
'Could not delete id: %d of model %s\n'
'There should be some relation '
@@ -826,7 +824,7 @@ def post_import(pool, module, to_delete):
'active': False,
})
except Exception:
- cursor.rollback()
+ transaction.rollback()
logger.error(
'Could not inactivate id: %d of model %s\n',
db_id, model, exc_info=True)
@@ -834,6 +832,6 @@ def post_import(pool, module, to_delete):
# Clean model_data:
if mdata_delete:
ModelData.delete(mdata_delete)
- cursor.commit()
+ transaction.commit()
return True
diff --git a/trytond/cron.py b/trytond/cron.py
new file mode 100644
index 0000000..f327b04
--- /dev/null
+++ b/trytond/cron.py
@@ -0,0 +1,43 @@
+# 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 threading
+import logging
+
+from trytond.pool import Pool
+from trytond.transaction import Transaction
+
+__all__ = ['run']
+logger = logging.getLogger(__name__)
+
+
+def run(options):
+ database_list = Pool.database_list()
+ for dbname in options.database_names:
+ thread = _threads.get(dbname)
+ if thread and thread.is_alive():
+ logger.info('skip "%s" as previous cron still running', dbname)
+ continue
+ pool = Pool(dbname)
+ if dbname not in database_list:
+ with Transaction().start(dbname, 0, readonly=True):
+ pool.init()
+ if not pool.lock.acquire(0):
+ logger.warning('can not acquire lock on "%s"', dbname)
+ continue
+ try:
+ try:
+ Cron = pool.get('ir.cron')
+ except KeyError:
+ logger.error(
+ 'missing "ir.cron" on "%s"', dbname, exc_info=True)
+ continue
+ finally:
+ pool.lock.release()
+ thread = threading.Thread(
+ target=Cron.run,
+ args=(dbname,), kwargs={})
+ logger.info('start thread for "%s"', dbname)
+ thread.start()
+ _threads[dbname] = thread
+_threads = {}
+Pool.start()
diff --git a/trytond/exceptions.py b/trytond/exceptions.py
index dd086b1..1b05f78 100644
--- a/trytond/exceptions.py
+++ b/trytond/exceptions.py
@@ -2,7 +2,11 @@
# this repository contains the full copyright notices and license terms.
-class UserError(Exception):
+class TrytonException(Exception):
+ pass
+
+
+class UserError(TrytonException):
def __init__(self, message, description=''):
super(UserError, self).__init__('UserError', (message, description))
@@ -11,7 +15,7 @@ class UserError(Exception):
self.code = 1
-class UserWarning(Exception):
+class UserWarning(TrytonException):
def __init__(self, name, message, description=''):
super(UserWarning, self).__init__('UserWarning', (name, message,
@@ -22,14 +26,7 @@ class UserWarning(Exception):
self.code = 2
-class NotLogged(Exception):
-
- def __init__(self):
- super(NotLogged, self).__init__('NotLogged')
- self.code = 3
-
-
-class ConcurrencyException(Exception):
+class ConcurrencyException(TrytonException):
def __init__(self, message):
super(ConcurrencyException, self).__init__('ConcurrencyException',
diff --git a/trytond/ir/__init__.py b/trytond/ir/__init__.py
index ab1e31e..a000482 100644
--- a/trytond/ir/__init__.py
+++ b/trytond/ir/__init__.py
@@ -11,6 +11,7 @@ from .property import *
from .action import *
from .model import *
from .attachment import *
+from .note import *
from .cron import *
from .lang import *
from .export import *
@@ -61,6 +62,8 @@ def register():
ModelData,
PrintModelGraphStart,
Attachment,
+ Note,
+ NoteRead,
Cron,
Lang,
Export,
@@ -95,4 +98,5 @@ def register():
module='ir', type_='wizard')
Pool.register(
ModelGraph,
+ ModelWorkflowGraph,
module='ir', type_='report')
diff --git a/trytond/ir/action.py b/trytond/ir/action.py
index 5ff7e7f..40970b0 100644
--- a/trytond/ir/action.py
+++ b/trytond/ir/action.py
@@ -102,7 +102,6 @@ class ActionKeyword(ModelSQL, ModelView):
__name__ = 'ir.action.keyword'
keyword = fields.Selection([
('tree_open', 'Open tree'),
- ('tree_action', 'Action tree'),
('form_print', 'Print form'),
('form_action', 'Action form'),
('form_relate', 'Form relate'),
@@ -129,7 +128,7 @@ class ActionKeyword(ModelSQL, ModelView):
TableHandler = backend.get('TableHandler')
super(ActionKeyword, cls).__register__(module_name)
- table = TableHandler(Transaction().cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.index_action(['keyword', 'model'], 'add')
def get_groups(self, name):
@@ -311,9 +310,12 @@ class ActionMixin(ModelSQL):
for field in later:
del values[field]
action_values['type'] = cls.default_type()
- cursor = Transaction().cursor
- if cursor.nextid(cls._table):
- cursor.setnextid(cls._table, cursor.currid(Action._table))
+ transaction = Transaction()
+ database = transaction.database
+ cursor = transaction.connection.cursor()
+ if database.nextid(transaction.connection, cls._table):
+ database.setnextid(transaction.connection, cls._table,
+ database.currid(transaction.connection, Action._table))
if 'action' not in values:
action, = Action.create([action_values])
values['action'] = action.id
@@ -323,7 +325,8 @@ class ActionMixin(ModelSQL):
cursor.execute(*ir_action.update(
[ir_action.id], [action.id],
where=ir_action.id == record.id))
- cursor.update_auto_increment(cls._table, action.id)
+ transaction.database.update_auto_increment(
+ transaction.connection, cls._table, action.id)
record = cls(action.id)
new_records.append(record)
cls.write([record], later)
@@ -397,6 +400,10 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
('odp', 'OpenDocument Presentation'),
('ods', 'OpenDocument Spreadsheet'),
('odg', 'OpenDocument Graphics'),
+ ('plain', 'Plain Text'),
+ ('xml', 'XML'),
+ ('html', 'HTML'),
+ ('xhtml', 'XHTML'),
], string='Template Extension', required=True,
translate=False)
extension = fields.Selection([
@@ -481,8 +488,9 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
TableHandler = backend.get('TableHandler')
super(ActionReport, cls).__register__(module_name)
- cursor = Transaction().cursor
- table = TableHandler(cursor, cls, module_name)
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
+ table = TableHandler(cls, module_name)
action_report = cls.__table__()
# Migration from 1.0 report_name_uniq has been removed
@@ -503,7 +511,7 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
cls.write(cls.browse(ids), {'extension': 'odt'})
table.drop_column("output_format")
- TableHandler.dropTable(cursor, 'ir.action.report.outputformat',
+ TableHandler.dropTable('ir.action.report.outputformat',
'ir_action_report_outputformat')
# Migrate from 2.0 remove required on extension
@@ -517,7 +525,7 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
# report_content_custom to remove base64 encoding
if (table.column_exist('report_content_data')
and table.column_exist('report_content_custom')):
- limit = cursor.IN_MAX
+ limit = transaction.database.IN_MAX
cursor.execute(*action_report.select(
Count(action_report.id)))
report_count, = cursor.fetchone()
@@ -590,8 +598,8 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
contents = {}
converter = fields.Binary.cast
default = None
- format_ = Transaction().context.pop('%s.%s'
- % (cls.__name__, name), '')
+ format_ = Transaction().context.get(
+ '%s.%s' % (cls.__name__, name), '')
if format_ == 'size':
converter = len
default = 0
@@ -667,6 +675,7 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView):
context = fields.Char('Context Value')
order = fields.Char('Order Value')
res_model = fields.Char('Model')
+ context_model = fields.Char('Context Model')
act_window_views = fields.One2Many('ir.action.act_window.view',
'act_window', 'Views')
views = fields.Function(fields.Binary('Views'), 'get_views')
@@ -705,12 +714,12 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
TableHandler = backend.get('TableHandler')
act_window = cls.__table__()
super(ActionActWindow, cls).__register__(module_name)
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.0: new search_value format
cursor.execute(*act_window.update(
@@ -903,7 +912,7 @@ class ActionActWindowView(ModelSQL, ModelView):
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
super(ActionActWindowView, cls).__register__(module_name)
- table = TableHandler(Transaction().cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 1.0 remove multi
table.drop_column('multi')
diff --git a/trytond/ir/attachment.py b/trytond/ir/attachment.py
index 28bdf1d..dcf4f59 100644
--- a/trytond/ir/attachment.py
+++ b/trytond/ir/attachment.py
@@ -3,14 +3,13 @@
import os
import hashlib
from sql.operators import Concat
-from sql.conditionals import Coalesce
from ..model import ModelView, ModelSQL, fields, Unique
from ..config import config
from .. import backend
from ..transaction import Transaction
from ..pyson import Eval
-from ..pool import Pool
+from .resource import ResourceMixin
__all__ = [
'Attachment',
@@ -24,7 +23,7 @@ def firstline(description):
return ''
-class Attachment(ModelSQL, ModelView):
+class Attachment(ResourceMixin, ModelSQL, ModelView):
"Attachment"
__name__ = 'ir.attachment'
name = fields.Char('Name', required=True)
@@ -37,8 +36,6 @@ class Attachment(ModelSQL, ModelView):
}, depends=['type']), 'get_data', setter='set_data')
description = fields.Text('Description')
summary = fields.Function(fields.Char('Summary'), 'on_change_with_summary')
- resource = fields.Reference('Resource', selection='models_get',
- select=True)
link = fields.Char('Link', states={
'invisible': Eval('type') != 'link',
}, depends=['type'])
@@ -47,31 +44,25 @@ class Attachment(ModelSQL, ModelView):
data_size = fields.Function(fields.Integer('Data size', states={
'invisible': Eval('type') != 'data',
}, depends=['type']), 'get_data')
- last_modification = fields.Function(fields.DateTime('Last Modification'),
- 'get_last_modification')
- last_user = fields.Function(fields.Char('Last User'),
- 'get_last_user')
@classmethod
def __setup__(cls):
super(Attachment, cls).__setup__()
- cls._order.insert(0, ('last_modification', 'DESC'))
-
table = cls.__table__()
cls._sql_constraints += [
('resource_name',
Unique(table, table.resource, table.name),
- 'The names of attachments must be unique by resource!'),
+ 'The names of attachments must be unique by resource.'),
]
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
super(Attachment, cls).__register__(module_name)
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
attachment = cls.__table__()
# Migration from 1.4 res_model and res_id merged into resource
@@ -91,30 +82,13 @@ class Attachment(ModelSQL, ModelView):
return 'data'
@staticmethod
- def default_resource():
- return Transaction().context.get('resource')
-
- @staticmethod
def default_collision():
return 0
- @staticmethod
- def models_get():
- pool = Pool()
- Model = pool.get('ir.model')
- ModelAccess = pool.get('ir.model.access')
- models = Model.search([])
- access = ModelAccess.get_access([m.model for m in models])
- res = []
- for model in models:
- if access[model.model]['read']:
- res.append([model.model, model.name])
- return res
-
def get_data(self, name):
- db_name = Transaction().cursor.dbname
- format_ = Transaction().context.pop('%s.%s'
- % (self.__name__, name), '')
+ db_name = Transaction().database.name
+ format_ = Transaction().context.get(
+ '%s.%s' % (self.__name__, name), '')
value = None
if name == 'data_size' or format_ == 'size':
value = 0
@@ -142,9 +116,10 @@ class Attachment(ModelSQL, ModelView):
def set_data(cls, attachments, name, value):
if value is None:
return
- cursor = Transaction().cursor
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
table = cls.__table__()
- db_name = cursor.dbname
+ db_name = transaction.database.name
directory = os.path.join(config.get('database', 'path'), db_name)
if not os.path.isdir(directory):
os.makedirs(directory, 0770)
@@ -191,72 +166,3 @@ class Attachment(ModelSQL, ModelView):
@fields.depends('description')
def on_change_with_summary(self, name=None):
return firstline(self.description or '')
-
- def get_last_modification(self, name):
- return (self.write_date if self.write_date else self.create_date
- ).replace(microsecond=0)
-
- @staticmethod
- def order_last_modification(tables):
- table, _ = tables[None]
- return [Coalesce(table.write_date, table.create_date)]
-
- def get_last_user(self, name):
- return (self.write_uid.rec_name if self.write_uid
- else self.create_uid.rec_name)
-
- @classmethod
- def check_access(cls, ids, mode='read'):
- pool = Pool()
- ModelAccess = pool.get('ir.model.access')
- if ((Transaction().user == 0)
- or not Transaction().context.get('_check_access')):
- return
- model_names = set()
- with Transaction().set_context(_check_access=False):
- for attachment in cls.browse(ids):
- if attachment.resource:
- model_names.add(attachment.resource.__name__)
- for model_name in model_names:
- ModelAccess.check(model_name, mode=mode)
-
- @classmethod
- def read(cls, ids, fields_names=None):
- cls.check_access(ids, mode='read')
- return super(Attachment, cls).read(ids, fields_names=fields_names)
-
- @classmethod
- def delete(cls, attachments):
- cls.check_access([a.id for a in attachments], mode='delete')
- super(Attachment, cls).delete(attachments)
-
- @classmethod
- def write(cls, attachments, values, *args):
- all_attachments = []
- actions = iter((attachments, values) + args)
- for records, _ in zip(actions, actions):
- all_attachments += records
- cls.check_access([a.id for a in all_attachments], mode='write')
- super(Attachment, cls).write(attachments, values, *args)
- cls.check_access(all_attachments, mode='write')
-
- @classmethod
- def create(cls, vlist):
- attachments = super(Attachment, cls).create(vlist)
- cls.check_access([a.id for a in attachments], mode='create')
- return attachments
-
- @classmethod
- def view_header_get(cls, value, view_type='form'):
- pool = Pool()
- Model = pool.get('ir.model')
- value = super(Attachment, cls).view_header_get(value,
- view_type=view_type)
- resource = Transaction().context.get('resource')
- if resource:
- model_name, record_id = resource.split(',', 1)
- ir_model, = Model.search([('model', '=', model_name)])
- Resource = pool.get(model_name)
- record = Resource(int(record_id))
- value = '%s - %s - %s' % (ir_model.name, record.rec_name, value)
- return value
diff --git a/trytond/ir/cron.py b/trytond/ir/cron.py
index 470e901..9e384fc 100644
--- a/trytond/ir/cron.py
+++ b/trytond/ir/cron.py
@@ -9,13 +9,13 @@ from email.mime.text import MIMEText
from email.header import Header
from ast import literal_eval
-from ..model import ModelView, ModelSQL, fields
-from ..tools import get_smtp_server
+from ..model import ModelView, ModelSQL, fields, dualmethod
from ..transaction import Transaction
from ..pool import Pool
from .. import backend
from ..config import config
from ..cache import Cache
+from ..sendmail import sendmail
__all__ = [
'Cron',
@@ -70,15 +70,20 @@ class Cron(ModelSQL, ModelView):
'request_body': ("The following action failed to execute "
"properly: \"%s\"\n%s\n Traceback: \n\n%s\n")
})
+ cls._buttons.update({
+ 'run_once': {
+ 'icon': 'tryton-executable',
+ },
+ })
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
cron = cls.__table__()
# Migration from 2.0: rename numbercall, doall and nextcall
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.column_rename('numbercall', 'number_calls')
table.column_rename('doall', 'repeat_missed')
table.column_rename('nextcall', 'next_call')
@@ -126,59 +131,56 @@ class Cron(ModelSQL, ModelView):
'''
return _INTERVALTYPES[cron.interval_type](cron.interval_number)
- @classmethod
- def send_error_message(cls, cron):
- tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
- tb_s = tb_s.decode('utf-8', 'ignore')
- subject = cls.raise_user_error('request_title',
- raise_exception=False)
- body = cls.raise_user_error('request_body',
- (cron.name, cron.__url__, tb_s),
- raise_exception=False)
-
- from_addr = config.get('email', 'from')
- to_addr = cron.request_user.email
-
- msg = MIMEText(body, _charset='utf-8')
- msg['To'] = to_addr
- msg['From'] = from_addr
- msg['Subject'] = Header(subject, 'utf-8')
- if not to_addr:
- logger.error(msg.as_string())
- else:
- try:
- server = get_smtp_server()
- server.sendmail(from_addr, to_addr, msg.as_string())
- server.quit()
- except Exception:
- logger.error('Unable to deliver email:\n %s',
- msg.as_string(), exc_info=True)
-
- @classmethod
- def _callback(cls, cron):
+ def send_error_message(self):
pool = Pool()
Config = pool.get('ir.configuration')
- try:
- args = (cron.args or []) and literal_eval(cron.args)
+
+ if self.request_user.language:
+ language = self.request_user.language.code
+ else:
+ language = Config.get_language()
+
+ with Transaction().set_user(self.user.id), \
+ Transaction().set_context(language=language):
+ tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
+ tb_s = tb_s.decode('utf-8', 'ignore')
+ subject = self.raise_user_error('request_title',
+ raise_exception=False)
+ body = self.raise_user_error('request_body',
+ (self.name, self.__url__, tb_s),
+ raise_exception=False)
+
+ from_addr = config.get('email', 'from')
+ to_addr = self.request_user.email
+
+ msg = MIMEText(body, _charset='utf-8')
+ msg['To'] = to_addr
+ msg['From'] = from_addr
+ msg['Subject'] = Header(subject, 'utf-8')
+ if not to_addr:
+ logger.error(msg.as_string())
+ else:
+ sendmail(from_addr, to_addr, msg)
+
+ @dualmethod
+ @ModelView.button
+ def run_once(cls, crons):
+ pool = Pool()
+ for cron in crons:
+ if cron.args:
+ args = literal_eval(cron.args)
+ else:
+ args = []
Model = pool.get(cron.model)
with Transaction().set_user(cron.user.id):
getattr(Model, cron.function)(*args)
- except Exception:
- Transaction().cursor.rollback()
-
- req_user = cron.request_user
- language = (req_user.language.code if req_user.language
- else Config.get_language())
- with Transaction().set_user(cron.user.id), \
- Transaction().set_context(language=language):
- cls.send_error_message(cron)
@classmethod
def run(cls, db_name):
now = datetime.datetime.now()
with Transaction().start(db_name, 0) as transaction:
Cache.clean(db_name)
- transaction.cursor.lock(cls._table)
+ transaction.database.lock(transaction.connection, cls._table)
crons = cls.search([
('number_calls', '!=', 0),
('next_call', '<=', datetime.datetime.now()),
@@ -191,7 +193,11 @@ class Cron(ModelSQL, ModelView):
first = True
while next_call < now and number_calls != 0:
if first or cron.repeat_missed:
- cls._callback(cron)
+ try:
+ cron.run_once()
+ except Exception:
+ transaction.rollback()
+ cron.send_error_message()
next_call += cls.get_delta(cron)
if number_calls > 0:
number_calls -= 1
@@ -202,8 +208,8 @@ class Cron(ModelSQL, ModelView):
if not number_calls:
cron.active = False
cron.save()
- transaction.cursor.commit()
+ transaction.commit()
except Exception:
- transaction.cursor.rollback()
+ transaction.rollback()
logger.error('Running cron %s', cron.id, exc_info=True)
Cache.resets(db_name)
diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py
index a6f8c24..f429ffe 100644
--- a/trytond/ir/lang.py
+++ b/trytond/ir/lang.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.
+from __future__ import absolute_import
+
+import sys
import datetime
import warnings
+warnings.filterwarnings('ignore', "", ImportWarning)
+from locale import CHAR_MAX
+warnings.resetwarnings()
from ast import literal_eval
from ..model import ModelView, ModelSQL, fields, Check
@@ -10,13 +16,8 @@ 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')
+Transaction.cache_keys.add('translate_name')
__all__ = [
'Lang',
@@ -154,9 +155,11 @@ class Lang(ModelSQL, ModelView):
Check the date format
'''
for lang in langs:
+ date = lang.date
+ if sys.version_info < (3,):
+ date = date.encode('utf-8')
try:
- datetime_strftime(datetime.datetime.now(),
- lang.date.encode('utf-8'))
+ datetime_strftime(datetime.datetime.now(), date)
except Exception:
cls.raise_user_error('invalid_date', {
'format': lang.date,
@@ -415,7 +418,10 @@ class Lang(ModelSQL, ModelView):
TIME_LOCALE[code][f][datetime.timetuple()[i]])
format = format.replace('%p',
TIME_LOCALE[code]['%p'][datetime.timetuple()[3] < 12 and 0
- or 1]).encode('utf-8')
- else:
- format = format.encode('utf-8')
- return datetime_strftime(datetime, format).decode('utf-8')
+ or 1])
+ if sys.version_info < (3,):
+ format.encode('utf-8')
+ result = datetime_strftime(datetime, format)
+ if sys.version_info < (3,):
+ result = result.decode('utf-8')
+ return result
diff --git a/trytond/ir/lang.xml b/trytond/ir/lang.xml
index 9729ff9..f14fb61 100644
--- a/trytond/ir/lang.xml
+++ b/trytond/ir/lang.xml
@@ -104,6 +104,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_lo">
+ <field name="code">lo_LA</field>
+ <field name="name">ລາວ</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_lt">
<field name="code">lt_LT</field>
<field name="name">Lithuanian</field>
@@ -144,6 +152,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_zh_CN">
+ <field name="code">zh_CN</field>
+ <field name="name">中国(简体)</field>
+ <field name="date">%Y-%m-%d</field>
+ <field name="grouping">[3, 0]</field>
+ <field name="decimal_point">.</field>
+ <field name="thousands_sep">,</field>
+ </record>
<record model="ir.ui.view" id="lang_view_tree">
<field name="model">ir.lang</field>
diff --git a/trytond/ir/locale/bg_BG.po b/trytond/ir/locale/bg_BG.po
index d73bda2..11dd4f9 100644
--- a/trytond/ir/locale/bg_BG.po
+++ b/trytond/ir/locale/bg_BG.po
@@ -2,11 +2,14 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
+#, fuzzy
msgctxt "error:access_error:"
msgid ""
"You try to bypass an access rule.\n"
"(Document type: %s)"
msgstr ""
+"Опитвате се да прескочите право за достъп!\n"
+"(Вид документ: %s)"
msgctxt "error:delete_xml_record:"
msgid "You are not allowed to delete this record."
@@ -58,8 +61,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr ""
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "Имената на прикачените файлове трябва да са уникални по ресурс!"
msgctxt "error:ir.cron:"
@@ -145,23 +149,24 @@ msgstr "Модела трябва да е уникален!"
msgctxt "error:ir.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr ""
+msgstr "Зависимостта трябва да е уникална за модул!"
msgctxt "error:ir.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr ""
+msgstr "Липсващи зависимости %s за модул \"%s\""
msgctxt "error:ir.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
msgstr ""
+"От модула който исакте да деинсталирате зависят следните инсталирани модули:"
msgctxt "error:ir.module:"
msgid "The name of the module must be unique!"
-msgstr ""
+msgstr "Името на модула трябва да е уникално!"
msgctxt "error:ir.module:"
msgid "You can not remove a module that is installed or will be installed"
-msgstr ""
+msgstr "Не може да изтривате модул който инсталиран или ще бъде инсталиран"
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
@@ -239,11 +244,14 @@ msgctxt "error:ir.ui.view:"
msgid "Invalid XML for view \"%s\"."
msgstr ""
+#, fuzzy
msgctxt "error:read_error:"
msgid ""
"You try to read records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
+"Опитвате се да прочетете запис който вече не съществува!\n"
+"(Вид документ: %s)"
msgctxt "error:recursion_error:"
msgid ""
@@ -293,11 +301,14 @@ msgctxt "error:too_many_relations_found:"
msgid "Too many relations found: %r in %s"
msgstr "Намерени са твърде много зависимости: %r в %s"
+#, fuzzy
msgctxt "error:write_error:"
msgid ""
"You try to write on records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
+"Опитвате се да прочетете запис който вече не съществува!\n"
+"(Вид документ: %s)"
msgctxt "error:write_xml_record:"
msgid "You are not allowed to modify this record."
@@ -363,9 +374,10 @@ msgctxt "field:ir.action,write_uid:"
msgid "Write User"
msgstr "Променено от"
+#, fuzzy
msgctxt "field:ir.action.act_window,act_window_domains:"
msgid "Domains"
-msgstr ""
+msgstr "Домейн"
msgctxt "field:ir.action.act_window,act_window_views:"
msgid "Views"
@@ -384,6 +396,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Стойност на котекст"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Създадено на"
@@ -396,9 +412,10 @@ msgctxt "field:ir.action.act_window,domain:"
msgid "Domain Value"
msgstr "Стойност на домейн"
+#, fuzzy
msgctxt "field:ir.action.act_window,domains:"
msgid "Domains"
-msgstr ""
+msgstr "Домейн"
#, fuzzy
msgctxt "field:ir.action.act_window,groups:"
@@ -999,9 +1016,10 @@ msgctxt "field:ir.configuration,id:"
msgid "ID"
msgstr "ID"
+#, fuzzy
msgctxt "field:ir.configuration,language:"
msgid "language"
-msgstr ""
+msgstr "Език"
#, fuzzy
msgctxt "field:ir.configuration,rec_name:"
@@ -1538,152 +1556,249 @@ msgstr "Ниво"
msgctxt "field:ir.module,childs:"
msgid "Childs"
-msgstr ""
+msgstr "Деца"
msgctxt "field:ir.module,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Създадено на"
msgctxt "field:ir.module,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Създадено от"
msgctxt "field:ir.module,dependencies:"
msgid "Dependencies"
-msgstr ""
+msgstr "Зависимости"
msgctxt "field:ir.module,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module,name:"
msgid "Name"
-msgstr ""
+msgstr "Име"
msgctxt "field:ir.module,parents:"
msgid "Parents"
-msgstr ""
+msgstr "Родители"
msgctxt "field:ir.module,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Име"
msgctxt "field:ir.module,state:"
msgid "State"
-msgstr ""
+msgstr "Състояние"
msgctxt "field:ir.module,version:"
msgid "Version"
-msgstr ""
+msgstr "Версия"
msgctxt "field:ir.module,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Променено на"
msgctxt "field:ir.module,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Променено от"
msgctxt "field:ir.module.config_wizard.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.first,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,action:"
msgid "Action"
-msgstr ""
+msgstr "Действие"
msgctxt "field:ir.module.config_wizard.item,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Създадено на"
msgctxt "field:ir.module.config_wizard.item,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Създадено от"
msgctxt "field:ir.module.config_wizard.item,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Име"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,sequence:"
msgid "Sequence"
-msgstr ""
+msgstr "Последователност"
msgctxt "field:ir.module.config_wizard.item,state:"
msgid "State"
-msgstr ""
+msgstr "Състояние"
msgctxt "field:ir.module.config_wizard.item,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Променено на"
msgctxt "field:ir.module.config_wizard.item,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Променено от"
msgctxt "field:ir.module.config_wizard.other,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.other,percentage:"
msgid "Percentage"
-msgstr ""
+msgstr "Процент"
msgctxt "field:ir.module.dependency,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Създадено на"
msgctxt "field:ir.module.dependency,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Създадено от"
msgctxt "field:ir.module.dependency,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.dependency,module:"
msgid "Module"
-msgstr ""
+msgstr "Модул"
msgctxt "field:ir.module.dependency,name:"
msgid "Name"
-msgstr ""
+msgstr "Име"
msgctxt "field:ir.module.dependency,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Име"
msgctxt "field:ir.module.dependency,state:"
msgid "State"
-msgstr ""
+msgstr "Състояние"
msgctxt "field:ir.module.dependency,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Променено на"
msgctxt "field:ir.module.dependency,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Променено от"
msgctxt "field:ir.module.install_upgrade.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
+msgstr "Модули за обновяване"
+
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Създадено на"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Създадено от"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Последна промяна"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Последен потребител"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Име"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Ресурс"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
msgstr ""
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Променено на"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Променено от"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Създадено на"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Създадено от"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Име"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Потребител"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Променено на"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Променено от"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Създадено на"
@@ -2327,9 +2442,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Действие"
+#, fuzzy
msgctxt "field:ir.ui.menu,action_keywords:"
msgid "Action Keywords"
-msgstr ""
+msgstr "Бърз клавиш на действие"
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
@@ -2751,11 +2867,14 @@ msgctxt "help:ir.rule.group,rules:"
msgid "The rule is satisfied if at least one test is True"
msgstr "Правилото е удовлетворено ако поне един тест е успешен"
+#, fuzzy
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
+"Python израз изчислен със запис представен със \"self\"\n"
+"Извиква действието ако е истина."
msgctxt "help:ir.trigger,limit_number:"
msgid ""
@@ -2872,6 +2991,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Започване на изчакващите инталации/обновявания"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Свойства"
@@ -2948,13 +3071,18 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Графика"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Действие на активния прозорец"
+#, fuzzy
msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
-msgstr ""
+msgstr "Изглед на действие на активния на прозорец"
msgctxt ""
"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
@@ -3063,9 +3191,10 @@ msgctxt "model:ir.lang,name:lang_fr"
msgid "French"
msgstr "Френски"
+#, fuzzy
msgctxt "model:ir.lang,name:lang_hu_HU"
msgid "Hungarian"
-msgstr ""
+msgstr "Български"
msgctxt "model:ir.lang,name:lang_it_IT"
msgid "Italian"
@@ -3121,34 +3250,43 @@ msgstr "Печат на графика на модел"
msgctxt "model:ir.module,name:"
msgid "Module"
-msgstr ""
+msgstr "Модул"
+#, fuzzy
msgctxt "model:ir.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr ""
+msgstr "Помощник за настройка на модук - други"
msgctxt "model:ir.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr ""
+msgstr "Първи помощник на конфигуриране на модул"
msgctxt "model:ir.module.config_wizard.item,name:"
msgid "Config wizard to run after installing module"
-msgstr ""
+msgstr "Задаване на помощника да се стартира след инсталиране на модула"
msgctxt "model:ir.module.config_wizard.other,name:"
msgid "Module Config Wizard Other"
-msgstr ""
+msgstr "Помощник за настройка на модук - други"
msgctxt "model:ir.module.dependency,name:"
msgid "Module dependency"
-msgstr ""
+msgstr "Зависимости на модула"
msgctxt "model:ir.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr ""
+msgstr "Приключи инсталиране/обновяване на модул"
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
+msgstr "Начало на инсталиране/обновяване на модул"
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
msgstr ""
msgctxt "model:ir.property,name:"
@@ -3203,13 +3341,15 @@ msgctxt "model:ir.translation.export.start,name:"
msgid "Export translation"
msgstr "Извличане на превод - файл"
+#, fuzzy
msgctxt "model:ir.translation.set.start,name:"
msgid "Set Translation"
-msgstr ""
+msgstr "Изчистване на преводи"
+#, fuzzy
msgctxt "model:ir.translation.set.succeed,name:"
msgid "Set Translation"
-msgstr ""
+msgstr "Изчистване на преводи"
msgctxt "model:ir.translation.update.start,name:"
msgid "Update translation"
@@ -3332,6 +3472,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Модули"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Свойства"
@@ -3372,9 +3516,10 @@ msgctxt "model:ir.ui.menu,name:menu_translation_form"
msgid "Translations"
msgstr "Преводи"
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_translation_set"
msgid "Set Translations"
-msgstr ""
+msgstr "Изчистване на преводи"
msgctxt "model:ir.ui.menu,name:menu_translation_update"
msgid "Synchronize Translations"
@@ -3416,17 +3561,19 @@ msgctxt "model:ir.ui.view,name:"
msgid "View"
msgstr "Изглед"
+#, fuzzy
msgctxt "model:ir.ui.view.show.start,name:"
msgid "Show view"
-msgstr ""
+msgstr "Показване на изглед"
msgctxt "model:ir.ui.view_search,name:"
msgid "View Search"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.view_tree_state,name:"
msgid "View Tree State"
-msgstr ""
+msgstr "Ширина на дърво с изгледи"
msgctxt "model:ir.ui.view_tree_width,name:"
msgid "View Tree Width"
@@ -3494,55 +3641,55 @@ msgstr "Отдясно наляво"
msgctxt "selection:ir.module,state:"
msgid "Installed"
-msgstr ""
+msgstr "Инсталиран"
msgctxt "selection:ir.module,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Не е инсталиран"
msgctxt "selection:ir.module,state:"
msgid "To be installed"
-msgstr ""
+msgstr "За инсталиране"
msgctxt "selection:ir.module,state:"
msgid "To be removed"
-msgstr ""
+msgstr "За премахване"
msgctxt "selection:ir.module,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "За обновяване"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr ""
+msgstr "Приключен"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
-msgstr ""
+msgstr "Отваряне"
msgctxt "selection:ir.module.dependency,state:"
msgid "Installed"
-msgstr ""
+msgstr "Инсталиран"
msgctxt "selection:ir.module.dependency,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Не е инсталиран"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be installed"
-msgstr ""
+msgstr "За инсталиране"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be removed"
-msgstr ""
+msgstr "За премахване"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "За обновяване"
msgctxt "selection:ir.module.dependency,state:"
msgid "Unknown"
-msgstr ""
+msgstr "Неизвестен"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
@@ -3584,9 +3731,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Модел"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Справка"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3650,9 +3798,10 @@ msgctxt "view:ir.action.act_window.domain:"
msgid "Domain"
msgstr "Домейн"
+#, fuzzy
msgctxt "view:ir.action.act_window.domain:"
msgid "Domains"
-msgstr ""
+msgstr "Домейн"
#, fuzzy
msgctxt "view:ir.action.act_window.view:"
@@ -3723,15 +3872,20 @@ msgctxt "view:ir.attachment:"
msgid "Attachments"
msgstr "Прикачени файлове"
+#, fuzzy
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
-msgstr ""
+msgstr "Последна промяна"
msgctxt "view:ir.cron:"
msgid "Action to trigger"
msgstr "Действие за стартиране"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Планирано действие"
@@ -3771,9 +3925,10 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Бутони"
+#, fuzzy
msgctxt "view:ir.model.data:"
msgid "Model Data"
-msgstr ""
+msgstr "Модел на данни"
msgctxt "view:ir.model.data:"
msgid "Sync"
@@ -3800,9 +3955,10 @@ msgctxt "view:ir.model:"
msgid "Model Description"
msgstr "Описание на модел"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.done:"
msgid "Module configuration"
-msgstr ""
+msgstr "Конфицурация на модул"
msgctxt "view:ir.module.config_wizard.done:"
msgid "The configuration is done."
@@ -3810,37 +3966,39 @@ msgstr ""
msgctxt "view:ir.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
-msgstr ""
+msgstr "Добре дошли в помощника за конфигуриране на модула!"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.first:"
msgid ""
"You will be able to configure your installation depending on the modules you"
" have installed."
msgstr ""
+"Може да настроите инсталацията си в зависимост от инсталираните модули."
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
-msgstr ""
+msgstr "Елементи на помощника за конфигуриране"
msgctxt "view:ir.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
-msgstr ""
+msgstr "Следваща стъпа на помощника за конфигуриране!"
msgctxt "view:ir.module.dependency:"
msgid "Dependencies"
-msgstr ""
+msgstr "Зависимости"
msgctxt "view:ir.module.dependency:"
msgid "Dependency"
-msgstr ""
+msgstr "Зависимост"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "System Upgrade Done"
-msgstr ""
+msgstr "Обновяване на системата завършено"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "The modules have been upgraded / installed !"
-msgstr ""
+msgstr "Модулите са обновени / инсталирани !"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Note that this operation may take a few minutes."
@@ -3848,44 +4006,66 @@ msgstr ""
msgctxt "view:ir.module.install_upgrade.start:"
msgid "System Upgrade"
-msgstr ""
+msgstr "Обновяване на системата"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Your system will be upgraded."
-msgstr ""
+msgstr "Системата ще бъде обновена."
msgctxt "view:ir.module:"
msgid "Cancel Installation"
-msgstr ""
+msgstr "Отказ от инсталалиране"
msgctxt "view:ir.module:"
msgid "Cancel Uninstallation"
-msgstr ""
+msgstr "Отказ от деинсталиране"
msgctxt "view:ir.module:"
msgid "Cancel Upgrade"
-msgstr ""
+msgstr "Отказ от обновяване"
msgctxt "view:ir.module:"
msgid "Mark for Installation"
-msgstr ""
+msgstr "Отбелязване за инсталиране"
msgctxt "view:ir.module:"
msgid "Mark for Uninstallation (beta)"
-msgstr ""
+msgstr "Отбелязване за деинсталиране (beta)"
msgctxt "view:ir.module:"
msgid "Mark for Upgrade"
-msgstr ""
+msgstr "Отбелязване за обновяване"
msgctxt "view:ir.module:"
msgid "Module"
-msgstr ""
+msgstr "Модул"
msgctxt "view:ir.module:"
msgid "Modules"
+msgstr "Модули"
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Дата"
+
+msgctxt "view:ir.note:"
+msgid "Note"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Потребител"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Свойства"
@@ -3979,21 +4159,24 @@ msgctxt "view:ir.translation.export.start:"
msgid "Export Translation"
msgstr "Извличане на превод"
+#, fuzzy
msgctxt "view:ir.translation.set.start:"
msgid "Set Translations"
-msgstr ""
+msgstr "Изчистване на преводи"
+#, fuzzy
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
-msgstr ""
+msgstr "Синхронизиране на преводите"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
msgstr ""
+#, fuzzy
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
-msgstr ""
+msgstr "Изчистване на преводи"
msgctxt "view:ir.translation.update.start:"
msgid "Synchronize Translations"
@@ -4031,9 +4214,10 @@ msgctxt "view:ir.ui.menu:"
msgid "Menu"
msgstr "Меню"
+#, fuzzy
msgctxt "view:ir.ui.view:"
msgid "Show"
-msgstr ""
+msgstr "Показване"
msgctxt "view:ir.ui.view:"
msgid "View"
@@ -4047,13 +4231,15 @@ msgctxt "view:ir.ui.view_search:"
msgid "View Searches"
msgstr ""
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "View Tree State"
-msgstr ""
+msgstr "Ширина на дърво с изгледи"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "Views Tree State"
-msgstr ""
+msgstr "Ширина на дърво с изгледи"
msgctxt "view:ir.ui.view_tree_width:"
msgid "View Tree Width"
@@ -4071,37 +4257,40 @@ msgctxt "wizard_button:ir.model.print_model_graph,start,print_:"
msgid "Print"
msgstr "Печат"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,done,end:"
msgid "OK"
-msgstr ""
+msgstr "Добре"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,first,action:"
msgid "OK"
-msgstr ""
+msgstr "Добре"
msgctxt "wizard_button:ir.module.config_wizard,first,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Отказ"
msgctxt "wizard_button:ir.module.config_wizard,other,action:"
msgid "Next"
-msgstr ""
+msgstr "Напред"
msgctxt "wizard_button:ir.module.config_wizard,other,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Отказ"
+#, fuzzy
msgctxt "wizard_button:ir.module.install_upgrade,done,config:"
msgid "OK"
-msgstr ""
+msgstr "Добре"
msgctxt "wizard_button:ir.module.install_upgrade,start,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Отказ"
msgctxt "wizard_button:ir.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
-msgstr ""
+msgstr "Започване на обновяване"
msgctxt "wizard_button:ir.translation.clean,start,clean:"
msgid "Clean"
diff --git a/trytond/ir/locale/ca_ES.po b/trytond/ir/locale/ca_ES.po
index 08ae998..97f96f3 100644
--- a/trytond/ir/locale/ca_ES.po
+++ b/trytond/ir/locale/ca_ES.po
@@ -71,8 +71,9 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
"La definició del correu electrònic sobre l'informe \"%s\", no és correcta."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "El nom dels adjunts ha de ser únic per recurs."
msgctxt "error:ir.cron:"
@@ -417,6 +418,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Context"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Data creació"
@@ -1669,6 +1674,102 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Mòduls a actualitzar"
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Data creació"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Usuari creació"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Última modificació"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Últim usuari"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nom"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Recurs"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Data modificació"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Usuari modificació"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Data creació"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Usuari creació"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nom"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Usuari"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Data modificació"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Usuari modificació"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Data creació"
@@ -2827,6 +2928,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Realitza instal·lacions/actualitzacions pendents"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Propietats"
@@ -2903,6 +3008,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Gràfica"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Accions de finestra"
@@ -3105,6 +3214,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr "Inici instal·lació/actualització mòdul"
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr "Propietat"
@@ -3285,6 +3402,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Mòduls"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Propietats"
@@ -3537,9 +3658,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Model"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Informe"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3682,6 +3804,10 @@ msgid "Action to trigger"
msgstr "Acció de disparador"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Acció planificada"
@@ -3837,6 +3963,28 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Mòduls"
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Usuari"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Propietats"
diff --git a/trytond/ir/locale/cs_CZ.po b/trytond/ir/locale/cs_CZ.po
index d71a1fa..d5b3532 100644
--- a/trytond/ir/locale/cs_CZ.po
+++ b/trytond/ir/locale/cs_CZ.po
@@ -59,7 +59,7 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr ""
msgctxt "error:ir.cron:"
@@ -383,6 +383,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr ""
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr ""
@@ -1635,6 +1639,86 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr ""
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr ""
+
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr ""
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr ""
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr ""
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr ""
@@ -2777,6 +2861,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr ""
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr ""
@@ -2853,6 +2941,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr ""
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr ""
@@ -3055,6 +3147,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr ""
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr ""
@@ -3235,6 +3335,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr ""
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr ""
@@ -3488,7 +3592,7 @@ msgid "Model"
msgstr ""
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
+msgid "Report"
msgstr ""
msgctxt "selection:ir.translation,type:"
@@ -3632,6 +3736,10 @@ msgid "Action to trigger"
msgstr ""
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr ""
@@ -3785,6 +3893,26 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr ""
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr ""
diff --git a/trytond/ir/locale/de_DE.po b/trytond/ir/locale/de_DE.po
index ac65d63..e3eeac9 100644
--- a/trytond/ir/locale/de_DE.po
+++ b/trytond/ir/locale/de_DE.po
@@ -4,14 +4,6 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "error:access_error:"
msgid ""
-"You try to bypass an access rule!\n"
-"(Document type: %s)"
-msgstr ""
-"Fehlende Zugriffsberechtigung.\n"
-"(Dokumententyp: %s)"
-
-msgctxt "error:access_error:"
-msgid ""
"You try to bypass an access rule.\n"
"(Document type: %s)"
msgstr ""
@@ -78,8 +70,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Ungültige E-Mailadresse in Bericht \"%s\"."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr ""
"Der Name für einen Anhang kann nur einmal pro Ressource vergeben werden!"
@@ -281,14 +274,6 @@ msgstr "Ungültige XML-Daten für Sicht \"%s\"."
msgctxt "error:read_error:"
msgid ""
-"You try to read records that don't exist anymore!\n"
-"(Document type: %s)"
-msgstr ""
-"Leseversuch von nicht mehr vorhandenen Datensätzen.\n"
-"(Dokumententyp: %s)"
-
-msgctxt "error:read_error:"
-msgid ""
"You try to read records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
@@ -351,14 +336,6 @@ msgstr "Zu viele Beziehungen gefunden: %r in %s"
msgctxt "error:write_error:"
msgid ""
-"You try to write on records that don't exist anymore!\n"
-"(Document type: %s)"
-msgstr ""
-"Schreibversuch auf nicht mehr vorhandene Datensätze.\n"
-"(Dokumententyp: %s)"
-
-msgctxt "error:write_error:"
-msgid ""
"You try to write on records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
@@ -449,6 +426,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Kontext"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Erstellungsdatum"
@@ -1701,6 +1682,102 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Zu aktualisierende Module"
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Erstellungsdatum"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Erstellt durch"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Letzte Änderung"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Letzter Benutzer"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Name"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Ressource"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Zuletzt geändert"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Letzte Änderung durch"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Erstellungsdatum"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Erstellt durch"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Name"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Benutzer"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Zuletzt geändert"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Letzte Änderung durch"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Erstellungsdatum"
@@ -2862,6 +2939,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Vorgemerkte Installationen / Aktualisierungen durchführen"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Eigenschaften"
@@ -2938,6 +3019,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Diagramm"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Aktion aktives Fenster"
@@ -3140,6 +3225,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr "Modulinstallation Aktualisierung Start"
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr "Eigenschaft"
@@ -3320,6 +3413,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Module"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Eigenschaften"
@@ -3572,9 +3669,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modell"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Bericht"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3717,6 +3815,10 @@ msgid "Action to trigger"
msgstr "Auszuführende Aktion"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Geplante Aktion"
@@ -3870,6 +3972,28 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Module"
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Benutzer"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Eigenschaften"
@@ -3899,14 +4023,6 @@ msgctxt "view:ir.rule:"
msgid "Test"
msgstr "Test"
-msgctxt "view:ir.sequence.strict:"
-msgid "Legend (for prefix, suffix)"
-msgstr "Legende (für Präfix, Suffix)"
-
-msgctxt "view:ir.sequence.strict:"
-msgid "Sequences Strict"
-msgstr "Strikte Nummernkreise"
-
msgctxt "view:ir.sequence.type:"
msgid "Sequence Type"
msgstr "Nummernkreistyp"
diff --git a/trytond/ir/locale/es_AR.po b/trytond/ir/locale/es_AR.po
index a3ebf6e..a123811 100644
--- a/trytond/ir/locale/es_AR.po
+++ b/trytond/ir/locale/es_AR.po
@@ -70,8 +70,9 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
"La definición de correo electrónico en el informe «%s» no es correcta."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "¡Los nombres de los archivos adjuntos deben ser únicos por recurso!"
msgctxt "error:ir.cron:"
@@ -422,6 +423,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Valor del contexto"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
@@ -1674,6 +1679,102 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Módulos a actualizar"
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Fecha creación"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Usuario creación"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Última Modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Último Usuario"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Recurso"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Fecha modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Usuario modificación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Fecha creación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Usuario creación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Fecha modificación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Usuario modificación"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
@@ -2834,6 +2935,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Realizar instalaciones/actualizaciones pendientes"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -2910,6 +3015,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Gráfico"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Ventana de acciones"
@@ -3112,6 +3221,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr "Iniciar la instalación o actualizaciones de módulos"
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr "Propiedad"
@@ -3292,6 +3409,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Módulos"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -3544,9 +3665,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Informe"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3689,6 +3811,10 @@ msgid "Action to trigger"
msgstr "Acción a disparar"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Acción programada"
@@ -3844,6 +3970,28 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Módulos"
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Fecha"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Usuario"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Propiedades"
diff --git a/trytond/ir/locale/es_CO.po b/trytond/ir/locale/es_CO.po
index f9154cc..c6e6b48 100644
--- a/trytond/ir/locale/es_CO.po
+++ b/trytond/ir/locale/es_CO.po
@@ -70,8 +70,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "El correo electronico definido en el informe \"%s\" es inválido."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "¡Los nombres de los archivos adjuntos deben ser únicos por recurso!"
msgctxt "error:ir.cron:"
@@ -162,23 +163,25 @@ msgstr "¡El modelo debe ser único!"
msgctxt "error:ir.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr ""
+msgstr "¡Las dependencias por módulo deben ser únicas!"
msgctxt "error:ir.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr ""
+msgstr "Faltan las dependencias %s para el módulo \"%s\""
msgctxt "error:ir.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
msgstr ""
+"Los módulos que está intentando desinstalar dependen de módulos instalados:"
msgctxt "error:ir.module:"
msgid "The name of the module must be unique!"
-msgstr ""
+msgstr "¡El nombre del módulo debe ser único!"
msgctxt "error:ir.module:"
msgid "You can not remove a module that is installed or will be installed"
msgstr ""
+"No puede eliminar un módulo que está instalado o que va a ser instalado"
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
@@ -248,11 +251,14 @@ msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
msgstr "\"Al Tiempo\" y otros son mutuamente excluyentes!"
+#, fuzzy
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid PYSON expression on trigger "
"\"%(trigger)s\"."
msgstr ""
+"Condición \"%(condition)s\" no es una expressión python válida en el "
+"disparador \"%s(trigger)s\"."
msgctxt "error:ir.ui.menu:"
msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
@@ -416,6 +422,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Valor del Contexto"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Fecha de Creación"
@@ -1522,152 +1532,248 @@ msgstr "Nivel"
msgctxt "field:ir.module,childs:"
msgid "Childs"
-msgstr ""
+msgstr "Hijos"
msgctxt "field:ir.module,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Fecha de Creación"
msgctxt "field:ir.module,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Creado por Usuario"
msgctxt "field:ir.module,dependencies:"
msgid "Dependencies"
-msgstr ""
+msgstr "Dependencias"
msgctxt "field:ir.module,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module,name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
msgctxt "field:ir.module,parents:"
msgid "Parents"
-msgstr ""
+msgstr "Padres"
msgctxt "field:ir.module,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
msgctxt "field:ir.module,state:"
msgid "State"
-msgstr ""
+msgstr "Estado"
msgctxt "field:ir.module,version:"
msgid "Version"
-msgstr ""
+msgstr "Versión"
msgctxt "field:ir.module,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Fecha de Modificación"
msgctxt "field:ir.module,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Modificado por Usuario"
msgctxt "field:ir.module.config_wizard.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.first,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,action:"
msgid "Action"
-msgstr ""
+msgstr "Acción"
msgctxt "field:ir.module.config_wizard.item,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Fecha de Creación"
msgctxt "field:ir.module.config_wizard.item,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Creado por Usuario"
msgctxt "field:ir.module.config_wizard.item,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
msgctxt "field:ir.module.config_wizard.item,sequence:"
msgid "Sequence"
-msgstr ""
+msgstr "Secuencia"
msgctxt "field:ir.module.config_wizard.item,state:"
msgid "State"
-msgstr ""
+msgstr "Estado"
msgctxt "field:ir.module.config_wizard.item,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Fecha de Modificación"
msgctxt "field:ir.module.config_wizard.item,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Modificado por Usuario"
msgctxt "field:ir.module.config_wizard.other,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.other,percentage:"
msgid "Percentage"
-msgstr ""
+msgstr "Porcentaje"
msgctxt "field:ir.module.dependency,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Fecha de Creación"
msgctxt "field:ir.module.dependency,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Creado por Usuario"
msgctxt "field:ir.module.dependency,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.dependency,module:"
msgid "Module"
-msgstr ""
+msgstr "Módulo"
msgctxt "field:ir.module.dependency,name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
msgctxt "field:ir.module.dependency,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
msgctxt "field:ir.module.dependency,state:"
msgid "State"
-msgstr ""
+msgstr "Estado"
msgctxt "field:ir.module.dependency,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Fecha de Modificación"
msgctxt "field:ir.module.dependency,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Modificado por Usuario"
msgctxt "field:ir.module.install_upgrade.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
+msgstr "Módulos a actualizar"
+
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Fecha de Creación"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Creado por Usuario"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Última Modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Último Usuario"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Recurso"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
msgstr ""
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Fecha de Modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Modificado por Usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Fecha de Creación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Creado por Usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Fecha de Modificación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Modificado por Usuario"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Fecha de Creación"
@@ -2704,11 +2810,14 @@ 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"
+#, fuzzy
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
+"Una instrucción Python evaluada con el registro representado por \"self\"\n"
+"Se dispara la acción si es verdadera."
msgctxt "help:ir.trigger,limit_number:"
msgid ""
@@ -2826,6 +2935,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Realizar Instalaciones/Actualizaciones Pendientes"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -2902,6 +3015,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Gráfico"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Acción ventana acción"
@@ -3016,9 +3133,10 @@ msgctxt "model:ir.lang,name:lang_fr"
msgid "French"
msgstr "Francés"
+#, fuzzy
msgctxt "model:ir.lang,name:lang_hu_HU"
msgid "Hungarian"
-msgstr ""
+msgstr "Búlgaro"
msgctxt "model:ir.lang,name:lang_it_IT"
msgid "Italian"
@@ -3074,34 +3192,43 @@ msgstr "Imprimir Gráfico del Modelo"
msgctxt "model:ir.module,name:"
msgid "Module"
-msgstr ""
+msgstr "Módulo"
msgctxt "model:ir.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr ""
+msgstr "Configuración del Módulo Terminada"
msgctxt "model:ir.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr ""
+msgstr "Primer Asistente de Configuración de Módulo"
msgctxt "model:ir.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"
msgctxt "model:ir.module.config_wizard.other,name:"
msgid "Module Config Wizard Other"
-msgstr ""
+msgstr "Siguiente Asistente de Configuración de Módulo"
msgctxt "model:ir.module.dependency,name:"
msgid "Module dependency"
-msgstr ""
+msgstr "Dependencias del módulo"
msgctxt "model:ir.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr ""
+msgstr "Actualización del Módulo Terminada"
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
+msgstr "Inicio de Asistente de Instalación del Módulo"
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
msgstr ""
msgctxt "model:ir.property,name:"
@@ -3284,6 +3411,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Módulos"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -3446,55 +3577,55 @@ msgstr "Derecha-a-Izquierda"
msgctxt "selection:ir.module,state:"
msgid "Installed"
-msgstr ""
+msgstr "Instalado"
msgctxt "selection:ir.module,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "No instalado"
msgctxt "selection:ir.module,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Pendiente de ser instalado"
msgctxt "selection:ir.module,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Pendiente de ser eliminado"
msgctxt "selection:ir.module,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Pendiente de ser actualizado"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr ""
+msgstr "Terminado"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
-msgstr ""
+msgstr "Abrir"
msgctxt "selection:ir.module.dependency,state:"
msgid "Installed"
-msgstr ""
+msgstr "Instalado"
msgctxt "selection:ir.module.dependency,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "No instalado"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Pendiente de ser instalado"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Pendiente de ser eliminado"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Pendiente de ser actualizado"
msgctxt "selection:ir.module.dependency,state:"
msgid "Unknown"
-msgstr ""
+msgstr "Desconocido"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
@@ -3536,9 +3667,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Reporte"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3672,15 +3804,20 @@ msgctxt "view:ir.attachment:"
msgid "Attachments"
msgstr "Adjuntos"
+#, fuzzy
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
-msgstr ""
+msgstr "Última Modificación"
msgctxt "view:ir.cron:"
msgid "Action to trigger"
msgstr "Acción a disparar"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Acción Programada"
@@ -3750,90 +3887,115 @@ msgstr "Descripción del modelo"
msgctxt "view:ir.module.config_wizard.done:"
msgid "Module configuration"
-msgstr ""
+msgstr "Configuración del módulo"
msgctxt "view:ir.module.config_wizard.done:"
msgid "The configuration is done."
-msgstr ""
+msgstr "La configuracion ha terminado."
msgctxt "view:ir.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
-msgstr ""
+msgstr "Bienvenido al asistente de configuración del módulo!"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.first:"
msgid ""
"You will be able to configure your installation depending on the modules you"
" have installed."
msgstr ""
+"Usted será capaz de configurar la instalación en función de los módulos que "
+"ha instalado."
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
-msgstr ""
+msgstr "Asistentes de Configuración"
msgctxt "view:ir.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
-msgstr ""
+msgstr "¡Siguiente Paso del Asistente de Configuración!"
msgctxt "view:ir.module.dependency:"
msgid "Dependencies"
-msgstr ""
+msgstr "Dependencias"
msgctxt "view:ir.module.dependency:"
msgid "Dependency"
-msgstr ""
+msgstr "Dependencia"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "System Upgrade Done"
-msgstr ""
+msgstr "Actualización del Sistema Finalizada"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "The modules have been upgraded / installed !"
-msgstr ""
+msgstr "Los módulos se han actualizado / instalado"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Note that this operation may take a few minutes."
-msgstr ""
+msgstr "Esta operación puede demorar varios minutos."
msgctxt "view:ir.module.install_upgrade.start:"
msgid "System Upgrade"
-msgstr ""
+msgstr "Actualización del Sistema"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Your system will be upgraded."
-msgstr ""
+msgstr "Su sistema sera actualizado."
msgctxt "view:ir.module:"
msgid "Cancel Installation"
-msgstr ""
+msgstr "Cancelar Instalación"
msgctxt "view:ir.module:"
msgid "Cancel Uninstallation"
-msgstr ""
+msgstr "Cancelar Desinstalación"
msgctxt "view:ir.module:"
msgid "Cancel Upgrade"
-msgstr ""
+msgstr "Cancelar Actualización"
msgctxt "view:ir.module:"
msgid "Mark for Installation"
-msgstr ""
+msgstr "Marcar para Instalar"
msgctxt "view:ir.module:"
msgid "Mark for Uninstallation (beta)"
-msgstr ""
+msgstr "Marcar para Desinstalar (beta)"
msgctxt "view:ir.module:"
msgid "Mark for Upgrade"
-msgstr ""
+msgstr "Marcar para Actualizar"
msgctxt "view:ir.module:"
msgid "Module"
-msgstr ""
+msgstr "Módulo"
msgctxt "view:ir.module:"
msgid "Modules"
+msgstr "Módulos"
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Fecha"
+
+msgctxt "view:ir.note:"
+msgid "Note"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Usuario"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Propiedades"
@@ -3981,9 +4143,10 @@ msgctxt "view:ir.ui.menu:"
msgid "Menu"
msgstr "Menú"
+#, fuzzy
msgctxt "view:ir.ui.view:"
msgid "Show"
-msgstr ""
+msgstr "_Mostrar"
msgctxt "view:ir.ui.view:"
msgid "View"
@@ -4023,35 +4186,35 @@ msgstr "Imprimir"
msgctxt "wizard_button:ir.module.config_wizard,done,end:"
msgid "OK"
-msgstr ""
+msgstr "Aceptar"
msgctxt "wizard_button:ir.module.config_wizard,first,action:"
msgid "OK"
-msgstr ""
+msgstr "Aceptar"
msgctxt "wizard_button:ir.module.config_wizard,first,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancelar"
msgctxt "wizard_button:ir.module.config_wizard,other,action:"
msgid "Next"
-msgstr ""
+msgstr "Siguiente"
msgctxt "wizard_button:ir.module.config_wizard,other,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancelar"
msgctxt "wizard_button:ir.module.install_upgrade,done,config:"
msgid "OK"
-msgstr ""
+msgstr "Aceptar"
msgctxt "wizard_button:ir.module.install_upgrade,start,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancelar"
msgctxt "wizard_button:ir.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
-msgstr ""
+msgstr "Iniciar Mejora"
msgctxt "wizard_button:ir.translation.clean,start,clean:"
msgid "Clean"
diff --git a/trytond/ir/locale/es_EC.po b/trytond/ir/locale/es_EC.po
index adcc03e..58c50ed 100644
--- a/trytond/ir/locale/es_EC.po
+++ b/trytond/ir/locale/es_EC.po
@@ -70,8 +70,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "La definición de correo electrónico sobre el informe \"%s\" no es válida."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "¡Los nombres de los archivos adjuntos deben ser únicos por recurso!"
msgctxt "error:ir.cron:"
@@ -116,19 +117,19 @@ 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)"
-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 eliminar este documento! (%s)"
+msgstr "No puede eliminar 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!"
@@ -420,6 +421,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Valor del contexto"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Fecha de creación"
@@ -662,7 +667,7 @@ msgstr "Impresión directa"
msgctxt "field:ir.action.report,email:"
msgid "Email"
-msgstr "Correo electrónico"
+msgstr "Email"
msgctxt "field:ir.action.report,extension:"
msgid "Extension"
@@ -698,7 +703,7 @@ msgstr "Nombre"
msgctxt "field:ir.action.report,pyson_email:"
msgid "PySON Email"
-msgstr "Correo electrónico PySON"
+msgstr "Email PySON"
msgctxt "field:ir.action.report,rec_name:"
msgid "Name"
@@ -822,7 +827,7 @@ msgstr "Creado por usuario"
msgctxt "field:ir.action.wizard,email:"
msgid "Email"
-msgstr "Correo electrónico"
+msgstr "Email"
msgctxt "field:ir.action.wizard,groups:"
msgid "Groups"
@@ -1672,6 +1677,102 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Módulos a actualizar"
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Fecha de creación"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Creado por usuario"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Última modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Último usuario"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Recurso"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Fecha de modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Modificado por usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Fecha de creación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Creado por usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Fecha de modificación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Modificado por usuario"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Fecha de creación"
@@ -2694,7 +2795,7 @@ msgstr ""
msgctxt "help:ir.rule.group,default_p:"
msgid "Add this rule to all users by default"
-msgstr "Añadir esta regla a todos los usuarios por defecto"
+msgstr "Agregar esta regla a todos los usuarios por defecto"
msgctxt "help:ir.rule.group,global_p:"
msgid ""
@@ -2832,6 +2933,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Realizar instalaciones/actualizaciones pendientes"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -2908,6 +3013,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Gráfico"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Acción de ventana"
@@ -3110,6 +3219,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr "Iniciar la instalación o actualización de módulos"
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr "Propiedad"
@@ -3290,6 +3407,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Módulos"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -3542,9 +3663,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Informe"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3687,6 +3809,10 @@ msgid "Action to trigger"
msgstr "Acción a disparar"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Acción programada"
@@ -3842,6 +3968,28 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Módulos"
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Fecha"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Usuario"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Propiedades"
diff --git a/trytond/ir/locale/es_ES.po b/trytond/ir/locale/es_ES.po
index 76cd62d..b07e341 100644
--- a/trytond/ir/locale/es_ES.po
+++ b/trytond/ir/locale/es_ES.po
@@ -71,8 +71,9 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
"La definición de correo electrónico sobre el informe \"%s\" no es correcta."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "El nombre de los adjuntos debe ser único por registro."
msgctxt "error:ir.cron:"
@@ -419,6 +420,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Valor del contexto"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
@@ -1671,6 +1676,102 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Módulos a actualizar"
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Fecha creación"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Usuario creación"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Última modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Último usuario"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Recurso"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Fecha modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Usuario modificación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Fecha creación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Usuario creación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Fecha modificación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Usuario modificación"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
@@ -2831,6 +2932,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Realizar instalaciones/actualizaciones pendientes"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -2907,6 +3012,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Gráfico"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Acción de ventana"
@@ -3109,6 +3218,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr "Iniciar la instalación o actualización de módulos"
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr "Propiedades"
@@ -3289,6 +3406,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Módulos"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -3541,9 +3662,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Informe"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3686,6 +3808,10 @@ msgid "Action to trigger"
msgstr "Acción a disparar"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Acción planificada"
@@ -3840,6 +3966,28 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Módulos"
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Fecha"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Usuario"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Propiedades"
diff --git a/trytond/ir/locale/es_MX.po b/trytond/ir/locale/es_MX.po
index 88da459..de9e9b0 100644
--- a/trytond/ir/locale/es_MX.po
+++ b/trytond/ir/locale/es_MX.po
@@ -71,8 +71,9 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
"La definición de correo electrónico sobre el informe \"%s\" no es correcta."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "El nombre de los adjuntos debe ser único por registro."
msgctxt "error:ir.cron:"
@@ -161,25 +162,32 @@ msgctxt "error:ir.model:"
msgid "The model must be unique!"
msgstr "El modelo debe ser único."
+#, fuzzy
msgctxt "error:ir.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr ""
+msgstr "Las dependencias deben ser únicas por módulo."
+#, fuzzy
msgctxt "error:ir.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr ""
+msgstr "Faltan las dependencias %s para el módulo \"%s\"."
+#, fuzzy
msgctxt "error:ir.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
msgstr ""
+"Los módulos que está intentando desinstalar depende de otros módulos "
+"instalados:"
+#, fuzzy
msgctxt "error:ir.module:"
msgid "The name of the module must be unique!"
-msgstr ""
+msgstr "El nombre del módulo debe ser único."
+#, fuzzy
msgctxt "error:ir.module:"
msgid "You can not remove a module that is installed or will be installed"
-msgstr ""
+msgstr "No puede eliminar un módulo que está instalado o será instalado."
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
@@ -245,11 +253,14 @@ msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
msgstr "\"Por tiempo\" y otros son mutuamente excluyentes."
+#, fuzzy
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid PYSON expression on trigger "
"\"%(trigger)s\"."
msgstr ""
+"La condición \"%(condition)s\" no es una expresión Python válida en el "
+"disparador \"P%(trigger)s\"."
msgctxt "error:ir.ui.menu:"
msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
@@ -415,6 +426,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Valor del contexto"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
@@ -1519,154 +1534,287 @@ msgctxt "field:ir.model.print_model_graph.start,level:"
msgid "Level"
msgstr "Nivel"
+#, fuzzy
msgctxt "field:ir.module,childs:"
msgid "Childs"
-msgstr ""
+msgstr "Hijos"
+#, fuzzy
msgctxt "field:ir.module,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Fecha creación"
+#, fuzzy
msgctxt "field:ir.module,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Usuario creación"
+#, fuzzy
msgctxt "field:ir.module,dependencies:"
msgid "Dependencies"
-msgstr ""
+msgstr "Dependencias"
+#, fuzzy
msgctxt "field:ir.module,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module,name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
+#, fuzzy
msgctxt "field:ir.module,parents:"
msgid "Parents"
-msgstr ""
+msgstr "Padres"
+#, fuzzy
msgctxt "field:ir.module,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
+#, fuzzy
msgctxt "field:ir.module,state:"
msgid "State"
-msgstr ""
+msgstr "Estado"
+#, fuzzy
msgctxt "field:ir.module,version:"
msgid "Version"
-msgstr ""
+msgstr "Versión"
+#, fuzzy
msgctxt "field:ir.module,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Fecha modificación"
+#, fuzzy
msgctxt "field:ir.module,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Usuario modificación"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.first,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,action:"
msgid "Action"
-msgstr ""
+msgstr "Acción"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Fecha creación"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Usuario creación"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,sequence:"
msgid "Sequence"
-msgstr ""
+msgstr "Secuencia"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,state:"
msgid "State"
-msgstr ""
+msgstr "Estado"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Fecha modificación"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Usuario modificación"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.other,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.other,percentage:"
msgid "Percentage"
-msgstr ""
+msgstr "Porcentaje"
+#, fuzzy
msgctxt "field:ir.module.dependency,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Fecha creación"
+#, fuzzy
msgctxt "field:ir.module.dependency,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Usuario creación"
+#, fuzzy
msgctxt "field:ir.module.dependency,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module.dependency,module:"
msgid "Module"
-msgstr ""
+msgstr "Módulo"
+#, fuzzy
msgctxt "field:ir.module.dependency,name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
+#, fuzzy
msgctxt "field:ir.module.dependency,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nombre"
+#, fuzzy
msgctxt "field:ir.module.dependency,state:"
msgid "State"
-msgstr ""
+msgstr "Estado"
+#, fuzzy
msgctxt "field:ir.module.dependency,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Fecha modificación"
+#, fuzzy
msgctxt "field:ir.module.dependency,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Usuario modificación"
+#, fuzzy
msgctxt "field:ir.module.install_upgrade.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module.install_upgrade.start,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
+msgstr "Módulos a actualizar"
+
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Fecha creación"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Usuario creación"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Última modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Último usuario"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Recurso"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Fecha modificación"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Usuario modificación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Fecha creación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Usuario creación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
msgstr ""
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nombre"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Usuario"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Fecha modificación"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Usuario modificación"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Fecha creación"
@@ -2703,11 +2851,14 @@ msgctxt "help:ir.rule.group,rules:"
msgid "The rule is satisfied if at least one test is True"
msgstr "La regla es correcta si al menos una condición es cierta."
+#, fuzzy
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
+"Una instrucción de Python evaluada con el registro representado por \"self\".\n"
+"Dispara la acción si es verdadera."
msgctxt "help:ir.trigger,limit_number:"
msgid ""
@@ -2825,6 +2976,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Realizar instalaciones/actualizaciones pendientes"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -2901,6 +3056,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Gráfico"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Acción de ventana"
@@ -3015,9 +3174,10 @@ msgctxt "model:ir.lang,name:lang_fr"
msgid "French"
msgstr "Francés"
+#, fuzzy
msgctxt "model:ir.lang,name:lang_hu_HU"
msgid "Hungarian"
-msgstr ""
+msgstr "Búlgaro"
msgctxt "model:ir.lang,name:lang_it_IT"
msgid "Italian"
@@ -3071,36 +3231,52 @@ msgctxt "model:ir.model.print_model_graph.start,name:"
msgid "Print Model Graph"
msgstr "Imprimir gráfico de modelos"
+#, fuzzy
msgctxt "model:ir.module,name:"
msgid "Module"
-msgstr ""
+msgstr "Módulo"
+#, fuzzy
msgctxt "model:ir.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr ""
+msgstr "Finalización asistente de configuración de módulos"
+#, fuzzy
msgctxt "model:ir.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr ""
+msgstr "Asistente de configuración del módulo - Primero"
+#, fuzzy
msgctxt "model:ir.module.config_wizard.item,name:"
msgid "Config wizard to run after installing module"
-msgstr ""
+msgstr "Asistente de configuración después de instalar un módulo"
+#, fuzzy
msgctxt "model:ir.module.config_wizard.other,name:"
msgid "Module Config Wizard Other"
-msgstr ""
+msgstr "Otro asistente de configuración del módulo"
+#, fuzzy
msgctxt "model:ir.module.dependency,name:"
msgid "Module dependency"
-msgstr ""
+msgstr "Dependencias del módulo"
+#, fuzzy
msgctxt "model:ir.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr ""
+msgstr "Finalización instalación o actualización de módulos"
+#, fuzzy
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
+msgstr "Iniciar la instalación o actualización de módulos"
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
msgstr ""
msgctxt "model:ir.property,name:"
@@ -3283,6 +3459,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Módulos"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Propiedades"
@@ -3443,57 +3623,70 @@ msgctxt "selection:ir.lang,direction:"
msgid "Right-to-left"
msgstr "De derecha a izquierda"
+#, fuzzy
msgctxt "selection:ir.module,state:"
msgid "Installed"
-msgstr ""
+msgstr "Instalado"
+#, fuzzy
msgctxt "selection:ir.module,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "No instalado"
+#, fuzzy
msgctxt "selection:ir.module,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Para instalar"
+#, fuzzy
msgctxt "selection:ir.module,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Para eliminar"
+#, fuzzy
msgctxt "selection:ir.module,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Para actualizar"
+#, fuzzy
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr ""
+msgstr "Realizado"
+#, fuzzy
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
-msgstr ""
+msgstr "Pendiente"
+#, fuzzy
msgctxt "selection:ir.module.dependency,state:"
msgid "Installed"
-msgstr ""
+msgstr "Instalado"
+#, fuzzy
msgctxt "selection:ir.module.dependency,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "No instalado"
+#, fuzzy
msgctxt "selection:ir.module.dependency,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Para instalar"
+#, fuzzy
msgctxt "selection:ir.module.dependency,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Para eliminar"
+#, fuzzy
msgctxt "selection:ir.module.dependency,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Para actualizar"
+#, fuzzy
msgctxt "selection:ir.module.dependency,state:"
msgid "Unknown"
-msgstr ""
+msgstr "Desconocido"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
@@ -3535,9 +3728,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Informes"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3595,21 +3789,25 @@ msgctxt "selection:ir.ui.view,type:"
msgid "Tree"
msgstr "Árbol"
+#, fuzzy
msgctxt "view:ir.action.act_window.domain:"
msgid "Domain"
-msgstr ""
+msgstr "Dominio"
+#, fuzzy
msgctxt "view:ir.action.act_window.domain:"
msgid "Domains"
-msgstr ""
+msgstr "Dominios"
+#, fuzzy
msgctxt "view:ir.action.act_window.view:"
msgid "View"
-msgstr ""
+msgstr "Vista"
+#, fuzzy
msgctxt "view:ir.action.act_window.view:"
msgid "Views"
-msgstr ""
+msgstr "Vistas"
msgctxt "view:ir.action.act_window:"
msgid "General"
@@ -3623,21 +3821,24 @@ msgctxt "view:ir.action.act_window:"
msgid "Open a Window"
msgstr ""
+#, fuzzy
msgctxt "view:ir.action.keyword:"
msgid "Keyword"
-msgstr ""
+msgstr "Acción de teclado"
+#, fuzzy
msgctxt "view:ir.action.keyword:"
msgid "Keywords"
-msgstr ""
+msgstr "Acciones de teclado"
msgctxt "view:ir.action.report:"
msgid "General"
msgstr ""
+#, fuzzy
msgctxt "view:ir.action.report:"
msgid "Report"
-msgstr ""
+msgstr "Informes"
msgctxt "view:ir.action.report:"
msgid "Report xml"
@@ -3647,61 +3848,75 @@ msgctxt "view:ir.action.url:"
msgid "General"
msgstr ""
+#, fuzzy
msgctxt "view:ir.action.url:"
msgid "URL"
-msgstr ""
+msgstr "URLs"
msgctxt "view:ir.action.wizard:"
msgid "General"
msgstr ""
+#, fuzzy
msgctxt "view:ir.action.wizard:"
msgid "Wizard"
-msgstr ""
+msgstr "Asistentes"
+#, fuzzy
msgctxt "view:ir.action:"
msgid "Action"
-msgstr ""
+msgstr "Acción"
msgctxt "view:ir.action:"
msgid "General"
msgstr ""
+#, fuzzy
msgctxt "view:ir.attachment:"
msgid "Attachments"
-msgstr ""
+msgstr "Adjuntos"
+#, fuzzy
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
-msgstr ""
+msgstr "Última modificación"
msgctxt "view:ir.cron:"
msgid "Action to trigger"
msgstr ""
msgctxt "view:ir.cron:"
-msgid "Scheduled Action"
+msgid "Run Once"
msgstr ""
+#, fuzzy
+msgctxt "view:ir.cron:"
+msgid "Scheduled Action"
+msgstr "Acciones planificadas"
+
+#, fuzzy
msgctxt "view:ir.cron:"
msgid "Scheduled Actions"
-msgstr ""
+msgstr "Acciones planificadas"
+#, fuzzy
msgctxt "view:ir.export:"
msgid "Exports"
-msgstr ""
+msgstr "Exportaciones"
msgctxt "view:ir.lang:"
msgid "Date Formatting"
msgstr ""
+#, fuzzy
msgctxt "view:ir.lang:"
msgid "Language"
-msgstr ""
+msgstr "Idioma"
+#, fuzzy
msgctxt "view:ir.lang:"
msgid "Languages"
-msgstr ""
+msgstr "Idiomas"
msgctxt "view:ir.lang:"
msgid "Numbers Formatting"
@@ -3711,45 +3926,54 @@ msgctxt "view:ir.model.access:"
msgid "Access controls"
msgstr ""
+#, fuzzy
msgctxt "view:ir.model.button:"
msgid "Button"
-msgstr ""
+msgstr "Botones"
+#, fuzzy
msgctxt "view:ir.model.button:"
msgid "Buttons"
-msgstr ""
+msgstr "Botones"
+#, fuzzy
msgctxt "view:ir.model.data:"
msgid "Model Data"
-msgstr ""
+msgstr "Datos del modelo"
msgctxt "view:ir.model.data:"
msgid "Sync"
msgstr ""
+#, fuzzy
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
-msgstr ""
+msgstr "Permiso de los campos"
+#, fuzzy
msgctxt "view:ir.model.field:"
msgid "Field"
-msgstr ""
+msgstr "Campo"
+#, fuzzy
msgctxt "view:ir.model.field:"
msgid "Fields"
-msgstr ""
+msgstr "Campos"
+#, fuzzy
msgctxt "view:ir.model.print_model_graph.start:"
msgid "Print Model Graph"
-msgstr ""
+msgstr "Imprimir gráfico de modelos"
+#, fuzzy
msgctxt "view:ir.model:"
msgid "Model Description"
-msgstr ""
+msgstr "Descripción modelo"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.done:"
msgid "Module configuration"
-msgstr ""
+msgstr "Configuración del módulo"
msgctxt "view:ir.module.config_wizard.done:"
msgid "The configuration is done."
@@ -3765,21 +3989,24 @@ msgid ""
" have installed."
msgstr ""
+#, fuzzy
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
-msgstr ""
+msgstr "Asistente de configuración"
msgctxt "view:ir.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
msgstr ""
+#, fuzzy
msgctxt "view:ir.module.dependency:"
msgid "Dependencies"
-msgstr ""
+msgstr "Dependencias"
+#, fuzzy
msgctxt "view:ir.module.dependency:"
msgid "Dependency"
-msgstr ""
+msgstr "Dependencias"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "System Upgrade Done"
@@ -3825,42 +4052,72 @@ msgctxt "view:ir.module:"
msgid "Mark for Upgrade"
msgstr ""
+#, fuzzy
msgctxt "view:ir.module:"
msgid "Module"
-msgstr ""
+msgstr "Módulo"
+#, fuzzy
msgctxt "view:ir.module:"
msgid "Modules"
+msgstr "Módulos"
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Fecha"
+
+msgctxt "view:ir.note:"
+msgid "Note"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Usuario"
+
+#, fuzzy
msgctxt "view:ir.property:"
msgid "Properties"
-msgstr ""
+msgstr "Propiedades"
+#, fuzzy
msgctxt "view:ir.property:"
msgid "Property"
-msgstr ""
+msgstr "Propiedades"
msgctxt "view:ir.rule.group:"
msgid ""
"If there is no test defined, the rule is always satisfied if not global"
msgstr ""
+#, fuzzy
msgctxt "view:ir.rule.group:"
msgid "Record rules"
-msgstr ""
+msgstr "Reglas de registros"
+#, fuzzy
msgctxt "view:ir.rule.group:"
msgid "The rule is satisfied if at least one test is True"
-msgstr ""
+msgstr "La regla es correcta si al menos una condición es cierta."
+#, fuzzy
msgctxt "view:ir.rule:"
msgid "Test"
-msgstr ""
+msgstr "Tests"
+#, fuzzy
msgctxt "view:ir.sequence.type:"
msgid "Sequence Type"
-msgstr ""
+msgstr "Tipos de secuencia"
msgctxt "view:ir.sequence:"
msgid "${day}"
@@ -3874,141 +4131,170 @@ msgctxt "view:ir.sequence:"
msgid "${year}"
msgstr ""
+#, fuzzy
msgctxt "view:ir.sequence:"
msgid "Day:"
-msgstr ""
+msgstr "Días"
+#, fuzzy
msgctxt "view:ir.sequence:"
msgid "Incremental"
-msgstr ""
+msgstr "Incremental"
msgctxt "view:ir.sequence:"
msgid "Legend (Placeholders for prefix, suffix)"
msgstr ""
+#, fuzzy
msgctxt "view:ir.sequence:"
msgid "Month:"
-msgstr ""
+msgstr "Meses"
+#, fuzzy
msgctxt "view:ir.sequence:"
msgid "Sequences"
-msgstr ""
+msgstr "Secuencias"
+#, fuzzy
msgctxt "view:ir.sequence:"
msgid "Timestamp"
-msgstr ""
+msgstr "Fecha-hora"
msgctxt "view:ir.sequence:"
msgid "Year:"
msgstr ""
+#, fuzzy
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations"
-msgstr ""
+msgstr "Limpiar traducciones"
+#, fuzzy
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations?"
-msgstr ""
+msgstr "Limpiar traducciones"
+#, fuzzy
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations"
-msgstr ""
+msgstr "Limpiar traducciones"
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations Succeed!"
msgstr ""
+#, fuzzy
msgctxt "view:ir.translation.export.result:"
msgid "Export Translation"
-msgstr ""
+msgstr "Exportar traducciones"
+#, fuzzy
msgctxt "view:ir.translation.export.start:"
msgid "Export Translation"
-msgstr ""
+msgstr "Exportar traducciones"
+#, fuzzy
msgctxt "view:ir.translation.set.start:"
msgid "Set Translations"
-msgstr ""
+msgstr "Definir traducciones"
+#, fuzzy
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
-msgstr ""
+msgstr "Sincronizar traducciones"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
msgstr ""
+#, fuzzy
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
-msgstr ""
+msgstr "Definir traducciones"
+#, fuzzy
msgctxt "view:ir.translation.update.start:"
msgid "Synchronize Translations"
-msgstr ""
+msgstr "Sincronizar traducciones"
+#, fuzzy
msgctxt "view:ir.translation:"
msgid "Translations"
-msgstr ""
+msgstr "Traducciones"
+#, fuzzy
msgctxt "view:ir.trigger:"
msgid "Trigger"
-msgstr ""
+msgstr "Disparador"
+#, fuzzy
msgctxt "view:ir.trigger:"
msgid "Triggers"
-msgstr ""
+msgstr "Disparadores"
+#, fuzzy
msgctxt "view:ir.ui.icon:"
msgid "Icon"
-msgstr ""
+msgstr "Icono"
+#, fuzzy
msgctxt "view:ir.ui.icon:"
msgid "Icons"
-msgstr ""
+msgstr "Iconos"
+#, fuzzy
msgctxt "view:ir.ui.menu.favorite:"
msgid "Menu Favorite"
-msgstr ""
+msgstr "Menú favorito"
+#, fuzzy
msgctxt "view:ir.ui.menu.favorite:"
msgid "Menu Favorites"
-msgstr ""
+msgstr "Menú favorito"
+#, fuzzy
msgctxt "view:ir.ui.menu:"
msgid "Menu"
-msgstr ""
+msgstr "Menú"
msgctxt "view:ir.ui.view:"
msgid "Show"
msgstr ""
+#, fuzzy
msgctxt "view:ir.ui.view:"
msgid "View"
-msgstr ""
+msgstr "Vista"
+#, fuzzy
msgctxt "view:ir.ui.view_search:"
msgid "View Search"
-msgstr ""
+msgstr "Búsquedas favoritas"
+#, fuzzy
msgctxt "view:ir.ui.view_search:"
msgid "View Searches"
-msgstr ""
+msgstr "Búsquedas favoritas"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "View Tree State"
-msgstr ""
+msgstr "Estado vista árbol"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "Views Tree State"
-msgstr ""
+msgstr "Estado vista árbol"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_width:"
msgid "View Tree Width"
-msgstr ""
+msgstr "Ancho vista de árbol"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_width:"
msgid "Views Tree Width"
-msgstr ""
+msgstr "Ancho vista de árbol"
msgctxt "wizard_button:ir.model.print_model_graph,start,end:"
msgid "Cancel"
@@ -4018,37 +4304,45 @@ msgctxt "wizard_button:ir.model.print_model_graph,start,print_:"
msgid "Print"
msgstr "Imprimir"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,done,end:"
msgid "OK"
-msgstr ""
+msgstr "Aceptar"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,first,action:"
msgid "OK"
-msgstr ""
+msgstr "Aceptar"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,first,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancelar"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,other,action:"
msgid "Next"
-msgstr ""
+msgstr "Siguiente"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,other,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancelar"
+#, fuzzy
msgctxt "wizard_button:ir.module.install_upgrade,done,config:"
msgid "OK"
-msgstr ""
+msgstr "Aceptar"
+#, fuzzy
msgctxt "wizard_button:ir.module.install_upgrade,start,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancelar"
+#, fuzzy
msgctxt "wizard_button:ir.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
-msgstr ""
+msgstr "Iniciar actualización"
msgctxt "wizard_button:ir.translation.clean,start,clean:"
msgid "Clean"
diff --git a/trytond/ir/locale/fr_FR.po b/trytond/ir/locale/fr_FR.po
index f46a678..04b6abf 100644
--- a/trytond/ir/locale/fr_FR.po
+++ b/trytond/ir/locale/fr_FR.po
@@ -72,8 +72,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Définition de mail incorrecte sur le rapport « %s »."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+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 !"
@@ -434,6 +435,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Valeur du contexte"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Date de création"
@@ -1686,6 +1691,102 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Modules à mettre à jour"
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Date de création"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Créé par"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Dernière modification"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Dernier utilisateur"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nom"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Ressource"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Date de mise à jour"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Mis à jour par"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Date de création"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Créé par"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nom"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Utilisateur"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Date de mise à jour"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Mis à jour par"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Date de création"
@@ -2846,6 +2947,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Lancer les installations/mise à jours en attente"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Propriétés"
@@ -2922,6 +3027,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Graphique"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Action ouvrir fenêtre"
@@ -3124,6 +3233,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr "Installation Mise à jour de module - Début"
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr "Propriété"
@@ -3304,6 +3421,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Modules"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Propriétés"
@@ -3556,9 +3677,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modèle"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Rapport"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3701,6 +3823,10 @@ msgid "Action to trigger"
msgstr "Action à déclencher"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Action planifiée"
@@ -3856,6 +3982,28 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Modules"
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Date"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Utilisateur"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Propriétés"
diff --git a/trytond/ir/locale/hu_HU.po b/trytond/ir/locale/hu_HU.po
index e5e0389..9e1e786 100644
--- a/trytond/ir/locale/hu_HU.po
+++ b/trytond/ir/locale/hu_HU.po
@@ -70,8 +70,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Érvénytelen e-mail cím a jelentésben \"%s\"."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "A nevet egy mellékletnek csak egyszer adható meg!"
msgctxt "error:ir.cron:"
@@ -164,23 +165,23 @@ msgstr "A minta csak egyszer adható."
msgctxt "error:ir.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr ""
+msgstr "A modul függősége egyértelműnek kell lenni!"
msgctxt "error:ir.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr ""
+msgstr "Hiányzó függőség %s a \"%s\" a \"%s\" modulnak."
msgctxt "error:ir.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
-msgstr ""
+msgstr "A törölni kívánt modulok a telepített moduloktól függenek:"
msgctxt "error:ir.module:"
msgid "The name of the module must be unique!"
-msgstr ""
+msgstr "Egy modul neve csak egyszer adható."
msgctxt "error:ir.module:"
msgid "You can not remove a module that is installed or will be installed"
-msgstr ""
+msgstr "A már telepített vagy telepítésre előjegyzett modul nem törölhető."
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
@@ -246,11 +247,14 @@ msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
msgstr "\"Időzített\" a többi opció egymást kizárja!"
+#, fuzzy
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid PYSON expression on trigger "
"\"%(trigger)s\"."
msgstr ""
+"A Tigger \"%(trigger)s\" feltételei \"%(condition)s\" nem érvényes Python "
+"kifejezés."
msgctxt "error:ir.ui.menu:"
msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
@@ -412,6 +416,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Összefüggés"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Létrehozás dátuma"
@@ -1518,152 +1526,248 @@ msgstr "Szint"
msgctxt "field:ir.module,childs:"
msgid "Childs"
-msgstr ""
+msgstr "Alárendelt (modul)"
msgctxt "field:ir.module,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Létrehozás dátuma"
msgctxt "field:ir.module,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Által létrehozva"
msgctxt "field:ir.module,dependencies:"
msgid "Dependencies"
-msgstr ""
+msgstr "Függőségek"
msgctxt "field:ir.module,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module,name:"
msgid "Name"
-msgstr ""
+msgstr "Név"
msgctxt "field:ir.module,parents:"
msgid "Parents"
-msgstr ""
+msgstr "Fölérendelt (modul)"
msgctxt "field:ir.module,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Név"
msgctxt "field:ir.module,state:"
msgid "State"
-msgstr ""
+msgstr "Státusz"
msgctxt "field:ir.module,version:"
msgid "Version"
-msgstr ""
+msgstr "Verzió"
msgctxt "field:ir.module,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Utolsó módosítás dátuma"
msgctxt "field:ir.module,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Által módosítva"
msgctxt "field:ir.module.config_wizard.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.first,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,action:"
msgid "Action"
-msgstr ""
+msgstr "Művelet"
msgctxt "field:ir.module.config_wizard.item,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Létrehozás dátuma"
msgctxt "field:ir.module.config_wizard.item,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Által létrehozva"
msgctxt "field:ir.module.config_wizard.item,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Név"
msgctxt "field:ir.module.config_wizard.item,sequence:"
msgid "Sequence"
-msgstr ""
+msgstr "Sorrend"
msgctxt "field:ir.module.config_wizard.item,state:"
msgid "State"
-msgstr ""
+msgstr "Státusz"
msgctxt "field:ir.module.config_wizard.item,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Utolsó módosítás dátuma"
msgctxt "field:ir.module.config_wizard.item,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Által módosítva"
msgctxt "field:ir.module.config_wizard.other,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.other,percentage:"
msgid "Percentage"
-msgstr ""
+msgstr "Százalék:"
msgctxt "field:ir.module.dependency,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Létrehozás dátuma"
msgctxt "field:ir.module.dependency,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Által létrehozva"
msgctxt "field:ir.module.dependency,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.dependency,module:"
msgid "Module"
-msgstr ""
+msgstr "Modul"
msgctxt "field:ir.module.dependency,name:"
msgid "Name"
-msgstr ""
+msgstr "Név"
msgctxt "field:ir.module.dependency,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Név"
msgctxt "field:ir.module.dependency,state:"
msgid "State"
-msgstr ""
+msgstr "Státusz"
msgctxt "field:ir.module.dependency,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Utolsó módosítás dátuma"
msgctxt "field:ir.module.dependency,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Által módosítva"
msgctxt "field:ir.module.install_upgrade.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
+msgstr "Aktualizálásra váró modulok"
+
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Létrehozás dátuma"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Által létrehozva"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Utolsó módosítás"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Utolsó használó"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Név"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Forrás"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Utolsó módosítás dátuma"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Által módosítva"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Létrehozás dátuma"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Által létrehozva"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
msgstr ""
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Név"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Felhasználó"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Utolsó módosítás dátuma"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Által módosítva"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Létrehozás dátuma"
@@ -2698,11 +2802,13 @@ msgctxt "help:ir.rule.group,rules:"
msgid "The rule is satisfied if at least one test is True"
msgstr "A szabály akkor érvényes, ha legalább egy teszt pozitív"
+#, fuzzy
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
+"Python kifejezés, mely az adattal (\"self\"által képviselve)van értékelve."
msgctxt "help:ir.trigger,limit_number:"
msgid ""
@@ -2820,6 +2926,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Előjegyzett installációk/aktualizálás végrehajtása"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Tulajdonságok"
@@ -2896,13 +3006,18 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Grafikon"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Művelet aktív ablak"
+#, fuzzy
msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
-msgstr ""
+msgstr "Aktív ablak nézet"
msgctxt ""
"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
@@ -3010,9 +3125,10 @@ msgctxt "model:ir.lang,name:lang_fr"
msgid "French"
msgstr "Francia"
+#, fuzzy
msgctxt "model:ir.lang,name:lang_hu_HU"
msgid "Hungarian"
-msgstr ""
+msgstr "Bolgár"
msgctxt "model:ir.lang,name:lang_it_IT"
msgid "Italian"
@@ -3068,34 +3184,42 @@ msgstr "Grafikon nyomtatása"
msgctxt "model:ir.module,name:"
msgid "Module"
-msgstr ""
+msgstr "Modul"
msgctxt "model:ir.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr ""
+msgstr "Modulkonfigurációs wizard befejezve"
msgctxt "model:ir.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr ""
+msgstr "Konfigurációs wizard első modul "
msgctxt "model:ir.module.config_wizard.item,name:"
msgid "Config wizard to run after installing module"
-msgstr ""
+msgstr "Konfigurációs wizard modul telepítés után"
msgctxt "model:ir.module.config_wizard.other,name:"
msgid "Module Config Wizard Other"
-msgstr ""
+msgstr "Konfigurációs wizard más modul"
msgctxt "model:ir.module.dependency,name:"
msgid "Module dependency"
-msgstr ""
+msgstr "Modultól függő"
msgctxt "model:ir.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr ""
+msgstr "Modul telepítésének aktualizálása megtörtént"
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
+msgstr "Modul telepítésének aktualizálása indít"
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
msgstr ""
msgctxt "model:ir.property,name:"
@@ -3278,6 +3402,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Modulok"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Tulajdonságok"
@@ -3370,9 +3498,10 @@ msgctxt "model:ir.ui.view_search,name:"
msgid "View Search"
msgstr "Keresés nézet"
+#, fuzzy
msgctxt "model:ir.ui.view_tree_state,name:"
msgid "View Tree State"
-msgstr ""
+msgstr "Oszlop széles nézet"
msgctxt "model:ir.ui.view_tree_width,name:"
msgid "View Tree Width"
@@ -3440,55 +3569,55 @@ msgstr "Jobbról balra"
msgctxt "selection:ir.module,state:"
msgid "Installed"
-msgstr ""
+msgstr "Telepítve"
msgctxt "selection:ir.module,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Nincs telepítve"
msgctxt "selection:ir.module,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Telepítendő"
msgctxt "selection:ir.module,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Eltávolítandó"
msgctxt "selection:ir.module,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Frissítendő"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr ""
+msgstr "Kész"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
-msgstr ""
+msgstr "Megnyit"
msgctxt "selection:ir.module.dependency,state:"
msgid "Installed"
-msgstr ""
+msgstr "Telepítve"
msgctxt "selection:ir.module.dependency,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Nincs telepítve"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Telepítendő"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Eltávolítandó"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Frissítendő"
msgctxt "selection:ir.module.dependency,state:"
msgid "Unknown"
-msgstr ""
+msgstr "Ismeretlen"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
@@ -3530,9 +3659,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Minta"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Jelentések"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3592,19 +3722,19 @@ msgstr "Fa"
msgctxt "view:ir.action.act_window.domain:"
msgid "Domain"
-msgstr ""
+msgstr "Értéktartomány"
msgctxt "view:ir.action.act_window.domain:"
msgid "Domains"
-msgstr ""
+msgstr "Értéktartomány"
msgctxt "view:ir.action.act_window.view:"
msgid "View"
-msgstr ""
+msgstr "Nézet"
msgctxt "view:ir.action.act_window.view:"
msgid "Views"
-msgstr ""
+msgstr "Nézetek"
msgctxt "view:ir.action.act_window:"
msgid "General"
@@ -3620,19 +3750,20 @@ msgstr ""
msgctxt "view:ir.action.keyword:"
msgid "Keyword"
-msgstr ""
+msgstr "Kulcsszó"
msgctxt "view:ir.action.keyword:"
msgid "Keywords"
-msgstr ""
+msgstr "Kulcsszavak"
msgctxt "view:ir.action.report:"
msgid "General"
msgstr ""
+#, fuzzy
msgctxt "view:ir.action.report:"
msgid "Report"
-msgstr ""
+msgstr "Jelentések"
msgctxt "view:ir.action.report:"
msgid "Report xml"
@@ -3642,21 +3773,23 @@ msgctxt "view:ir.action.url:"
msgid "General"
msgstr ""
+#, fuzzy
msgctxt "view:ir.action.url:"
msgid "URL"
-msgstr ""
+msgstr "URL"
msgctxt "view:ir.action.wizard:"
msgid "General"
msgstr ""
+#, fuzzy
msgctxt "view:ir.action.wizard:"
msgid "Wizard"
-msgstr ""
+msgstr "Wizards"
msgctxt "view:ir.action:"
msgid "Action"
-msgstr ""
+msgstr "Művelet"
msgctxt "view:ir.action:"
msgid "General"
@@ -3664,27 +3797,33 @@ msgstr ""
msgctxt "view:ir.attachment:"
msgid "Attachments"
-msgstr ""
+msgstr "Mellékletek"
+#, fuzzy
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
-msgstr ""
+msgstr "Utolsó módosítás"
msgctxt "view:ir.cron:"
msgid "Action to trigger"
msgstr ""
msgctxt "view:ir.cron:"
-msgid "Scheduled Action"
+msgid "Run Once"
msgstr ""
+#, fuzzy
+msgctxt "view:ir.cron:"
+msgid "Scheduled Action"
+msgstr "Ütemezett műveletek"
+
msgctxt "view:ir.cron:"
msgid "Scheduled Actions"
-msgstr ""
+msgstr "Ütemezett műveletek"
msgctxt "view:ir.export:"
msgid "Exports"
-msgstr ""
+msgstr "Export"
msgctxt "view:ir.lang:"
msgid "Date Formatting"
@@ -3692,11 +3831,11 @@ msgstr ""
msgctxt "view:ir.lang:"
msgid "Language"
-msgstr ""
+msgstr "Nyelv"
msgctxt "view:ir.lang:"
msgid "Languages"
-msgstr ""
+msgstr "Nyelvek"
msgctxt "view:ir.lang:"
msgid "Numbers Formatting"
@@ -3706,45 +3845,49 @@ msgctxt "view:ir.model.access:"
msgid "Access controls"
msgstr ""
+#, fuzzy
msgctxt "view:ir.model.button:"
msgid "Button"
-msgstr ""
+msgstr "Gombok"
msgctxt "view:ir.model.button:"
msgid "Buttons"
-msgstr ""
+msgstr "Gombok"
+#, fuzzy
msgctxt "view:ir.model.data:"
msgid "Model Data"
-msgstr ""
+msgstr "Minta adat"
msgctxt "view:ir.model.data:"
msgid "Sync"
msgstr ""
+#, fuzzy
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
-msgstr ""
+msgstr "Mező hozzáférésének engedélyezése"
msgctxt "view:ir.model.field:"
msgid "Field"
-msgstr ""
+msgstr "Mező"
msgctxt "view:ir.model.field:"
msgid "Fields"
-msgstr ""
+msgstr "Mezők"
msgctxt "view:ir.model.print_model_graph.start:"
msgid "Print Model Graph"
-msgstr ""
+msgstr "Grafikon nyomtatása"
msgctxt "view:ir.model:"
msgid "Model Description"
-msgstr ""
+msgstr "Minta leírás"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.done:"
msgid "Module configuration"
-msgstr ""
+msgstr "Modul konfiguráció"
msgctxt "view:ir.module.config_wizard.done:"
msgid "The configuration is done."
@@ -3762,7 +3905,7 @@ msgstr ""
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
-msgstr ""
+msgstr "Wizardelemek konfigurációja"
msgctxt "view:ir.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
@@ -3770,11 +3913,12 @@ msgstr ""
msgctxt "view:ir.module.dependency:"
msgid "Dependencies"
-msgstr ""
+msgstr "Függőségek"
+#, fuzzy
msgctxt "view:ir.module.dependency:"
msgid "Dependency"
-msgstr ""
+msgstr "Függőségek"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "System Upgrade Done"
@@ -3822,40 +3966,65 @@ msgstr ""
msgctxt "view:ir.module:"
msgid "Module"
-msgstr ""
+msgstr "Modul"
msgctxt "view:ir.module:"
msgid "Modules"
+msgstr "Modulok"
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Dátum"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
msgstr ""
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Felhasználó"
+
msgctxt "view:ir.property:"
msgid "Properties"
-msgstr ""
+msgstr "Tulajdonságok"
msgctxt "view:ir.property:"
msgid "Property"
-msgstr ""
+msgstr "Tulajdonság"
msgctxt "view:ir.rule.group:"
msgid ""
"If there is no test defined, the rule is always satisfied if not global"
msgstr ""
+#, fuzzy
msgctxt "view:ir.rule.group:"
msgid "Record rules"
-msgstr ""
+msgstr "Adatszabályok"
msgctxt "view:ir.rule.group:"
msgid "The rule is satisfied if at least one test is True"
-msgstr ""
+msgstr "A szabály akkor érvényes, ha legalább egy teszt pozitív"
+#, fuzzy
msgctxt "view:ir.rule:"
msgid "Test"
-msgstr ""
+msgstr "Teszt"
+#, fuzzy
msgctxt "view:ir.sequence.type:"
msgid "Sequence Type"
-msgstr ""
+msgstr "Számkör típus"
msgctxt "view:ir.sequence:"
msgid "${day}"
@@ -3869,29 +4038,31 @@ msgctxt "view:ir.sequence:"
msgid "${year}"
msgstr ""
+#, fuzzy
msgctxt "view:ir.sequence:"
msgid "Day:"
-msgstr ""
+msgstr "Nap"
msgctxt "view:ir.sequence:"
msgid "Incremental"
-msgstr ""
+msgstr "Inkrementális"
msgctxt "view:ir.sequence:"
msgid "Legend (Placeholders for prefix, suffix)"
msgstr ""
+#, fuzzy
msgctxt "view:ir.sequence:"
msgid "Month:"
-msgstr ""
+msgstr "Hónap"
msgctxt "view:ir.sequence:"
msgid "Sequences"
-msgstr ""
+msgstr "Számkör"
msgctxt "view:ir.sequence:"
msgid "Timestamp"
-msgstr ""
+msgstr "Időbélyeg"
msgctxt "view:ir.sequence:"
msgid "Year:"
@@ -3899,35 +4070,39 @@ msgstr ""
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations"
-msgstr ""
+msgstr "Fordítások rendezése"
+#, fuzzy
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations?"
-msgstr ""
+msgstr "Fordítások rendezése"
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations"
-msgstr ""
+msgstr "Fordítások rendezése"
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations Succeed!"
msgstr ""
+#, fuzzy
msgctxt "view:ir.translation.export.result:"
msgid "Export Translation"
-msgstr ""
+msgstr "Fordítások exportálása"
+#, fuzzy
msgctxt "view:ir.translation.export.start:"
msgid "Export Translation"
-msgstr ""
+msgstr "Fordítások exportálása"
msgctxt "view:ir.translation.set.start:"
msgid "Set Translations"
-msgstr ""
+msgstr "Fordítás aktualizálása"
+#, fuzzy
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
-msgstr ""
+msgstr "Fordítás megadás"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
@@ -3935,43 +4110,44 @@ msgstr ""
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
-msgstr ""
+msgstr "Fordítás aktualizálása"
msgctxt "view:ir.translation.update.start:"
msgid "Synchronize Translations"
-msgstr ""
+msgstr "Fordítás megadás"
msgctxt "view:ir.translation:"
msgid "Translations"
-msgstr ""
+msgstr "Fordítások"
msgctxt "view:ir.trigger:"
msgid "Trigger"
-msgstr ""
+msgstr "Trigger"
msgctxt "view:ir.trigger:"
msgid "Triggers"
-msgstr ""
+msgstr "Trigger"
msgctxt "view:ir.ui.icon:"
msgid "Icon"
-msgstr ""
+msgstr "Ikon"
msgctxt "view:ir.ui.icon:"
msgid "Icons"
-msgstr ""
+msgstr "Ikonok"
msgctxt "view:ir.ui.menu.favorite:"
msgid "Menu Favorite"
-msgstr ""
+msgstr "Kedvencek"
+#, fuzzy
msgctxt "view:ir.ui.menu.favorite:"
msgid "Menu Favorites"
-msgstr ""
+msgstr "Kedvencek"
msgctxt "view:ir.ui.menu:"
msgid "Menu"
-msgstr ""
+msgstr "Menü"
msgctxt "view:ir.ui.view:"
msgid "Show"
@@ -3979,31 +4155,35 @@ msgstr ""
msgctxt "view:ir.ui.view:"
msgid "View"
-msgstr ""
+msgstr "Nézet"
msgctxt "view:ir.ui.view_search:"
msgid "View Search"
-msgstr ""
+msgstr "Nézet keresés"
+#, fuzzy
msgctxt "view:ir.ui.view_search:"
msgid "View Searches"
-msgstr ""
+msgstr "Nézet keresés"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "View Tree State"
-msgstr ""
+msgstr "Oszlop széles nézet"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "Views Tree State"
-msgstr ""
+msgstr "Oszlop széles nézet"
msgctxt "view:ir.ui.view_tree_width:"
msgid "View Tree Width"
-msgstr ""
+msgstr "Oszlop széles nézet"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_width:"
msgid "Views Tree Width"
-msgstr ""
+msgstr "Oszlop széles nézet"
msgctxt "wizard_button:ir.model.print_model_graph,start,end:"
msgid "Cancel"
@@ -4015,35 +4195,35 @@ msgstr "Nyomtatás"
msgctxt "wizard_button:ir.module.config_wizard,done,end:"
msgid "OK"
-msgstr ""
+msgstr "OK"
msgctxt "wizard_button:ir.module.config_wizard,first,action:"
msgid "OK"
-msgstr ""
+msgstr "OK"
msgctxt "wizard_button:ir.module.config_wizard,first,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Mégse"
msgctxt "wizard_button:ir.module.config_wizard,other,action:"
msgid "Next"
-msgstr ""
+msgstr "Tovább"
msgctxt "wizard_button:ir.module.config_wizard,other,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Mégse"
msgctxt "wizard_button:ir.module.install_upgrade,done,config:"
msgid "OK"
-msgstr ""
+msgstr "OK"
msgctxt "wizard_button:ir.module.install_upgrade,start,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Mégse"
msgctxt "wizard_button:ir.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
-msgstr ""
+msgstr "Start"
msgctxt "wizard_button:ir.translation.clean,start,clean:"
msgid "Clean"
diff --git a/trytond/ir/locale/it_IT.po b/trytond/ir/locale/it_IT.po
index fea1d5b..63ce554 100644
--- a/trytond/ir/locale/it_IT.po
+++ b/trytond/ir/locale/it_IT.po
@@ -42,6 +42,8 @@ msgstr "Il valore \"%(value)s del campo \"%(field)s\" in \"(model)s\" non esiste
msgctxt "error:ir.action.act_window.domain:"
msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
msgstr ""
+"Dominio o criterio di ricerca \"%(domain)s\" invalido nell'azione "
+"\"%(action)s\"."
msgctxt "error:ir.action.act_window:"
msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
@@ -65,8 +67,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Definizione dell'email invalida nel report \"%s\"."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "Il nome degli allegati dev'essere unico per risorsa"
msgctxt "error:ir.cron:"
@@ -159,11 +162,11 @@ msgstr "Il modello dev'essere unico"
msgctxt "error:ir.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr ""
+msgstr "La dipendenza dev'essere unica per modulo"
msgctxt "error:ir.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr ""
+msgstr "Mancano le dipendenze %s per il modulo \"%s\""
msgctxt "error:ir.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
@@ -171,11 +174,11 @@ msgstr ""
msgctxt "error:ir.module:"
msgid "The name of the module must be unique!"
-msgstr ""
+msgstr "Il nome del modulo dev'essere unico"
msgctxt "error:ir.module:"
msgid "You can not remove a module that is installed or will be installed"
-msgstr ""
+msgstr "Non si può rimuovere un modulo che viene o verrà installato"
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
@@ -246,11 +249,15 @@ msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
msgstr "\"On Time\" e others si escludono a vicenda"
+#, fuzzy
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid PYSON expression on trigger "
"\"%(trigger)s\"."
msgstr ""
+"La condizione \"%(condition)s\" non "
+"ÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂè "
+"un'epressione python valida per il trigger \"%(trigger)s\"."
msgctxt "error:ir.ui.menu:"
msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
@@ -433,6 +440,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Valore Contenuto"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Creazione Data"
@@ -1621,152 +1632,259 @@ msgstr "Livello"
msgctxt "field:ir.module,childs:"
msgid "Childs"
-msgstr ""
+msgstr "Figli"
msgctxt "field:ir.module,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Creazione Data"
msgctxt "field:ir.module,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Creazione Utente"
msgctxt "field:ir.module,dependencies:"
msgid "Dependencies"
-msgstr ""
+msgstr "Dipendenze"
msgctxt "field:ir.module,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module,name:"
msgid "Name"
-msgstr ""
+msgstr "Nome"
msgctxt "field:ir.module,parents:"
msgid "Parents"
-msgstr ""
+msgstr "Padri"
+#, fuzzy
msgctxt "field:ir.module,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nome"
msgctxt "field:ir.module,state:"
msgid "State"
-msgstr ""
+msgstr "Stato"
msgctxt "field:ir.module,version:"
msgid "Version"
-msgstr ""
+msgstr "Versione"
+#, fuzzy
msgctxt "field:ir.module,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Data di Scrittura"
+#, fuzzy
msgctxt "field:ir.module,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Scrivente"
msgctxt "field:ir.module.config_wizard.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.first,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,action:"
msgid "Action"
-msgstr ""
+msgstr "Azione"
msgctxt "field:ir.module.config_wizard.item,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Creazione Data"
msgctxt "field:ir.module.config_wizard.item,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Creazione Utente"
msgctxt "field:ir.module.config_wizard.item,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nome"
msgctxt "field:ir.module.config_wizard.item,sequence:"
msgid "Sequence"
-msgstr ""
+msgstr "Sequenza"
msgctxt "field:ir.module.config_wizard.item,state:"
msgid "State"
-msgstr ""
+msgstr "Stato"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Data di Scrittura"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Scrivente"
msgctxt "field:ir.module.config_wizard.other,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.other,percentage:"
msgid "Percentage"
-msgstr ""
+msgstr "Percentuale"
msgctxt "field:ir.module.dependency,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Creazione Data"
msgctxt "field:ir.module.dependency,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Creazione Utente"
msgctxt "field:ir.module.dependency,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.dependency,module:"
msgid "Module"
-msgstr ""
+msgstr "Modulo"
+#, fuzzy
msgctxt "field:ir.module.dependency,name:"
msgid "Name"
-msgstr ""
+msgstr "Nome"
+#, fuzzy
msgctxt "field:ir.module.dependency,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Nome"
msgctxt "field:ir.module.dependency,state:"
msgid "State"
-msgstr ""
+msgstr "Stato"
+#, fuzzy
msgctxt "field:ir.module.dependency,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Data di Scrittura"
+#, fuzzy
msgctxt "field:ir.module.dependency,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Scrivente"
msgctxt "field:ir.module.install_upgrade.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
+msgstr "Moduli da aggiornare"
+
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Creazione Data"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Creazione Utente"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Ultima Modifica"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Ultimo utente"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
msgstr ""
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nome"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Risorsa"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Data di Scrittura"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Scrivente"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Creazione Data"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Creazione Utente"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nome"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Utente"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Data di Scrittura"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Scrivente"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Creazione Data"
@@ -2323,7 +2441,7 @@ msgstr "Numero Limite"
msgctxt "field:ir.trigger,minimum_time_delay:"
msgid "Minimum Delay"
-msgstr ""
+msgstr "Ritardo minimo"
msgctxt "field:ir.trigger,model:"
msgid "Model"
@@ -2872,11 +2990,12 @@ msgstr ""
"La regola ÃÂÃÂÃÂÃÂÃÂÃÂÃÂè rispettata se almeno uno "
"ÃÂÃÂÃÂÃÂÃÂÃÂÃÂè True"
+#, fuzzy
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
-msgstr ""
+msgstr "Istruzione Python con record rappresentato da \"self\""
msgctxt "help:ir.trigger,limit_number:"
msgid ""
@@ -2884,11 +3003,14 @@ msgid ""
"0 for no limit."
msgstr "Limitare per record il numero delle call ad \"Action Functions\""
+#, fuzzy
msgctxt "help:ir.trigger,minimum_time_delay:"
msgid ""
"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
"empty for no delay."
msgstr ""
+"Imposta un ritardo minimo fra le call ad \"Action Function\" per la stesso "
+"record. 0 per nessun ritardo"
msgctxt "help:ir.ui.view_search,domain:"
msgid "The PYSON domain"
@@ -2990,6 +3112,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Procedere con l'installazione o l'upgrade in sospeso"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "ProprietÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂ "
@@ -3066,6 +3192,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Grafo"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Finestra Action act"
@@ -3181,9 +3311,10 @@ msgctxt "model:ir.lang,name:lang_fr"
msgid "French"
msgstr "Francese"
+#, fuzzy
msgctxt "model:ir.lang,name:lang_hu_HU"
msgid "Hungarian"
-msgstr ""
+msgstr "Bulgaro"
msgctxt "model:ir.lang,name:lang_it_IT"
msgid "Italian"
@@ -3239,34 +3370,42 @@ msgstr "Stampa modello grafico"
msgctxt "model:ir.module,name:"
msgid "Module"
-msgstr ""
+msgstr "Modulo"
msgctxt "model:ir.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr ""
+msgstr "Wizard di config modulo Fatto"
msgctxt "model:ir.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr ""
+msgstr "Wizard di config modulo primo"
msgctxt "model:ir.module.config_wizard.item,name:"
msgid "Config wizard to run after installing module"
-msgstr ""
+msgstr "Lanciare il wizard di configurazione dopo l'installazione del modulo"
msgctxt "model:ir.module.config_wizard.other,name:"
msgid "Module Config Wizard Other"
-msgstr ""
+msgstr "Wizard di config modulo Altro"
msgctxt "model:ir.module.dependency,name:"
msgid "Module dependency"
-msgstr ""
+msgstr "Dipendenza Modulo"
msgctxt "model:ir.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr ""
+msgstr "Installazione Upgrade Modulo Fatto"
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
+msgstr "Installazione Upgrade Modulo "
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
msgstr ""
msgctxt "model:ir.property,name:"
@@ -3449,6 +3588,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Moduli"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "ProprietÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂ "
@@ -3611,55 +3754,55 @@ msgstr "Destra-Sinistra"
msgctxt "selection:ir.module,state:"
msgid "Installed"
-msgstr ""
+msgstr "Installato"
msgctxt "selection:ir.module,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Non Installato"
msgctxt "selection:ir.module,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Da installare"
msgctxt "selection:ir.module,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Da rimuovere"
msgctxt "selection:ir.module,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Da upgradare"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr ""
+msgstr "Fatto"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
-msgstr ""
+msgstr "Apertura"
msgctxt "selection:ir.module.dependency,state:"
msgid "Installed"
-msgstr ""
+msgstr "Installato"
msgctxt "selection:ir.module.dependency,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Non Installato"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Da installare"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Da rimuovere"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Da upgradare"
msgctxt "selection:ir.module.dependency,state:"
msgid "Unknown"
-msgstr ""
+msgstr "URLs"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
@@ -3701,9 +3844,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modello"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Report"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3837,15 +3981,20 @@ msgctxt "view:ir.attachment:"
msgid "Attachments"
msgstr "Allegati"
+#, fuzzy
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
-msgstr ""
+msgstr "Ultima Modifica"
msgctxt "view:ir.cron:"
msgid "Action to trigger"
msgstr "Trigger Azione"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Azione pianificata"
@@ -3916,41 +4065,42 @@ msgstr "Descrizione Modello"
msgctxt "view:ir.module.config_wizard.done:"
msgid "Module configuration"
-msgstr ""
+msgstr "Configurazione Modulo"
msgctxt "view:ir.module.config_wizard.done:"
msgid "The configuration is done."
-msgstr ""
+msgstr "Configurazione effettuata"
msgctxt "view:ir.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
-msgstr ""
+msgstr "Benvenuto al wizard di configurazione del modulo"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.first:"
msgid ""
"You will be able to configure your installation depending on the modules you"
" have installed."
-msgstr ""
+msgstr "Installazione configurabile dall'utente in base ai moduli installati"
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
-msgstr ""
+msgstr "Elementi per wizard di configurazione"
msgctxt "view:ir.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
-msgstr ""
+msgstr "Prossimo step del wizard di configurazione"
msgctxt "view:ir.module.dependency:"
msgid "Dependencies"
-msgstr ""
+msgstr "Dipendenze"
msgctxt "view:ir.module.dependency:"
msgid "Dependency"
-msgstr ""
+msgstr "Dipendenza"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "System Upgrade Done"
-msgstr ""
+msgstr "Upgrade del sistema effettuato"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "The modules have been upgraded / installed !"
@@ -3958,48 +4108,70 @@ msgstr ""
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Note that this operation may take a few minutes."
-msgstr ""
+msgstr "Questa operazione potrebbe durare qualche minuto"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "System Upgrade"
-msgstr ""
+msgstr "Upgrade del sistema"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Your system will be upgraded."
-msgstr ""
+msgstr "Il sistema verrà upgradato"
msgctxt "view:ir.module:"
msgid "Cancel Installation"
-msgstr ""
+msgstr "Cancellare l'installazione"
msgctxt "view:ir.module:"
msgid "Cancel Uninstallation"
-msgstr ""
+msgstr "Cancellare la rimozione"
msgctxt "view:ir.module:"
msgid "Cancel Upgrade"
-msgstr ""
+msgstr "Cancellare l'upgrade"
msgctxt "view:ir.module:"
msgid "Mark for Installation"
-msgstr ""
+msgstr "Seleziona per installare"
msgctxt "view:ir.module:"
msgid "Mark for Uninstallation (beta)"
-msgstr ""
+msgstr "Seleziona per disinstallare (beta)"
msgctxt "view:ir.module:"
msgid "Mark for Upgrade"
-msgstr ""
+msgstr "Seleziona per upgrade"
msgctxt "view:ir.module:"
msgid "Module"
-msgstr ""
+msgstr "Modulo"
msgctxt "view:ir.module:"
msgid "Modules"
+msgstr "Moduli"
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Utente"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "ProprietÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂ "
@@ -4149,9 +4321,10 @@ msgctxt "view:ir.ui.menu:"
msgid "Menu"
msgstr "Menu"
+#, fuzzy
msgctxt "view:ir.ui.view:"
msgid "Show"
-msgstr ""
+msgstr "_Show"
msgctxt "view:ir.ui.view:"
msgid "View"
@@ -4199,15 +4372,15 @@ msgstr ""
msgctxt "wizard_button:ir.module.config_wizard,first,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancella"
msgctxt "wizard_button:ir.module.config_wizard,other,action:"
msgid "Next"
-msgstr ""
+msgstr "Prossimo"
msgctxt "wizard_button:ir.module.config_wizard,other,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancella"
msgctxt "wizard_button:ir.module.install_upgrade,done,config:"
msgid "OK"
@@ -4215,11 +4388,11 @@ msgstr ""
msgctxt "wizard_button:ir.module.install_upgrade,start,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Cancella"
msgctxt "wizard_button:ir.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
-msgstr ""
+msgstr "Avvia l'upgrade"
msgctxt "wizard_button:ir.translation.clean,start,clean:"
msgid "Clean"
diff --git a/trytond/ir/locale/ja_JP.po b/trytond/ir/locale/ja_JP.po
index d71a1fa..d5b3532 100644
--- a/trytond/ir/locale/ja_JP.po
+++ b/trytond/ir/locale/ja_JP.po
@@ -59,7 +59,7 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr ""
msgctxt "error:ir.cron:"
@@ -383,6 +383,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr ""
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr ""
@@ -1635,6 +1639,86 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr ""
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr ""
+
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr ""
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr ""
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr ""
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr ""
@@ -2777,6 +2861,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr ""
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr ""
@@ -2853,6 +2941,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr ""
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr ""
@@ -3055,6 +3147,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr ""
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr ""
@@ -3235,6 +3335,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr ""
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr ""
@@ -3488,7 +3592,7 @@ msgid "Model"
msgstr ""
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
+msgid "Report"
msgstr ""
msgctxt "selection:ir.translation,type:"
@@ -3632,6 +3736,10 @@ msgid "Action to trigger"
msgstr ""
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr ""
@@ -3785,6 +3893,26 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr ""
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr ""
diff --git a/trytond/ir/locale/lt_LT.po b/trytond/ir/locale/lo_LA.po
similarity index 93%
copy from trytond/ir/locale/lt_LT.po
copy to trytond/ir/locale/lo_LA.po
index d71a1fa..0ecec68 100644
--- a/trytond/ir/locale/lt_LT.po
+++ b/trytond/ir/locale/lo_LA.po
@@ -59,7 +59,7 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr ""
msgctxt "error:ir.cron:"
@@ -327,17 +327,19 @@ msgctxt "field:ir.action,groups:"
msgid "Groups"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action,icon:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "field:ir.action,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action,keywords:"
msgid "Keywords"
-msgstr ""
+msgstr "ຂໍ້ຄວາມຫຼັກ"
msgctxt "field:ir.action,name:"
msgid "Name"
@@ -349,7 +351,7 @@ msgstr ""
msgctxt "field:ir.action,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.action,usage:"
msgid "Usage"
@@ -367,9 +369,10 @@ msgctxt "field:ir.action.act_window,act_window_domains:"
msgid "Domains"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.act_window,act_window_views:"
msgid "Views"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "field:ir.action.act_window,action:"
msgid "Action"
@@ -383,6 +386,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr ""
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr ""
@@ -403,17 +410,19 @@ msgctxt "field:ir.action.act_window,groups:"
msgid "Groups"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.act_window,icon:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "field:ir.action.act_window,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.act_window,keywords:"
msgid "Keywords"
-msgstr ""
+msgstr "ຂໍ້ຄວາມຫຼັກ"
msgctxt "field:ir.action.act_window,limit:"
msgid "Limit"
@@ -455,17 +464,19 @@ msgctxt "field:ir.action.act_window,search_value:"
msgid "Search Criteria"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.act_window,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.action.act_window,usage:"
msgid "Usage"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.act_window,views:"
msgid "Views"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "field:ir.action.act_window,window_name:"
msgid "Window Name"
@@ -551,9 +562,10 @@ msgctxt "field:ir.action.act_window.view,sequence:"
msgid "Sequence"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.act_window.view,view:"
msgid "View"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "field:ir.action.act_window.view,write_date:"
msgid "Write Date"
@@ -583,9 +595,10 @@ msgctxt "field:ir.action.keyword,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.keyword,keyword:"
msgid "Keyword"
-msgstr ""
+msgstr "ຄຳລະຫັດ"
msgctxt "field:ir.action.keyword,model:"
msgid "Model"
@@ -635,17 +648,19 @@ msgctxt "field:ir.action.report,groups:"
msgid "Groups"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.report,icon:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "field:ir.action.report,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.report,keywords:"
msgid "Keywords"
-msgstr ""
+msgstr "ຂໍ້ຄວາມຫຼັກ"
msgctxt "field:ir.action.report,model:"
msgid "Model"
@@ -691,9 +706,10 @@ msgctxt "field:ir.action.report,template_extension:"
msgid "Template Extension"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.report,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.action.report,usage:"
msgid "Usage"
@@ -727,17 +743,19 @@ msgctxt "field:ir.action.url,groups:"
msgid "Groups"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.url,icon:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "field:ir.action.url,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.url,keywords:"
msgid "Keywords"
-msgstr ""
+msgstr "ຂໍ້ຄວາມຫຼັກ"
msgctxt "field:ir.action.url,name:"
msgid "Name"
@@ -747,9 +765,10 @@ msgctxt "field:ir.action.url,rec_name:"
msgid "Name"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.url,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.action.url,url:"
msgid "Action Url"
@@ -791,17 +810,19 @@ msgctxt "field:ir.action.wizard,groups:"
msgid "Groups"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.wizard,icon:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "field:ir.action.wizard,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.wizard,keywords:"
msgid "Keywords"
-msgstr ""
+msgstr "ຂໍ້ຄວາມຫຼັກ"
msgctxt "field:ir.action.wizard,model:"
msgid "Model"
@@ -815,9 +836,10 @@ msgctxt "field:ir.action.wizard,rec_name:"
msgid "Name"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.wizard,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.action.wizard,usage:"
msgid "Usage"
@@ -899,9 +921,10 @@ msgctxt "field:ir.attachment,summary:"
msgid "Summary"
msgstr ""
+#, fuzzy
msgctxt "field:ir.attachment,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.attachment,write_date:"
msgid "Write Date"
@@ -1055,9 +1078,10 @@ msgctxt "field:ir.export,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.export,export_fields:"
msgid "Fields"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "field:ir.export,id:"
msgid "ID"
@@ -1183,9 +1207,10 @@ msgctxt "field:ir.model,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.model,fields:"
msgid "Fields"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "field:ir.model,global_search_p:"
msgid "Global Search"
@@ -1435,9 +1460,10 @@ msgctxt "field:ir.model.field.access,description:"
msgid "Description"
msgstr ""
+#, fuzzy
msgctxt "field:ir.model.field.access,field:"
msgid "Field"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "field:ir.model.field.access,group:"
msgid "Group"
@@ -1635,6 +1661,86 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr ""
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr ""
+
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr ""
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr ""
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr ""
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr ""
@@ -1643,9 +1749,10 @@ msgctxt "field:ir.property,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.property,field:"
msgid "Field"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "field:ir.property,id:"
msgid "ID"
@@ -1835,9 +1942,10 @@ msgctxt "field:ir.sequence,timestamp_rounding:"
msgid "Timestamp Rounding"
msgstr ""
+#, fuzzy
msgctxt "field:ir.sequence,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.sequence,write_date:"
msgid "Write Date"
@@ -1911,9 +2019,10 @@ msgctxt "field:ir.sequence.strict,timestamp_rounding:"
msgid "Timestamp Rounding"
msgstr ""
+#, fuzzy
msgctxt "field:ir.sequence.strict,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.sequence.strict,write_date:"
msgid "Write Date"
@@ -2063,9 +2172,10 @@ msgctxt "field:ir.translation,src_md5:"
msgid "Source MD5"
msgstr ""
+#, fuzzy
msgctxt "field:ir.translation,type:"
msgid "Type"
-msgstr ""
+msgstr "ປະເພດ"
msgctxt "field:ir.translation,value:"
msgid "Translation Value"
@@ -2235,9 +2345,10 @@ msgctxt "field:ir.ui.icon,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.ui.icon,icon:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "field:ir.ui.icon,id:"
msgid "ID"
@@ -2307,17 +2418,19 @@ msgctxt "field:ir.ui.menu,groups:"
msgid "Groups"
msgstr ""
+#, fuzzy
msgctxt "field:ir.ui.menu,icon:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "field:ir.ui.menu,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.ui.menu,name:"
msgid "Menu"
-msgstr ""
+msgstr "ລາຍການ"
msgctxt "field:ir.ui.menu,parent:"
msgid "Parent Menu"
@@ -2351,9 +2464,10 @@ msgctxt "field:ir.ui.menu.favorite,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.ui.menu.favorite,menu:"
msgid "Menu"
-msgstr ""
+msgstr "ລາຍການ"
msgctxt "field:ir.ui.menu.favorite,rec_name:"
msgid "Name"
@@ -2539,9 +2653,10 @@ msgctxt "field:ir.ui.view_tree_width,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.ui.view_tree_width,field:"
msgid "Field"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "field:ir.ui.view_tree_width,id:"
msgid "ID"
@@ -2705,9 +2820,10 @@ msgctxt "model:ir.action,name:act_action_wizard_form"
msgid "Wizards"
msgstr ""
+#, fuzzy
msgctxt "model:ir.action,name:act_attachment_form"
msgid "Attachments"
-msgstr ""
+msgstr "ເອກະສານຄັດຕິດ"
msgctxt "model:ir.action,name:act_config_wizard_item_form"
msgid "Config Wizard Items"
@@ -2721,29 +2837,33 @@ msgctxt "model:ir.action,name:act_export_form"
msgid "Exports"
msgstr ""
+#, fuzzy
msgctxt "model:ir.action,name:act_icon_form"
msgid "Icons"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "model:ir.action,name:act_lang_form"
msgid "Languages"
msgstr ""
+#, fuzzy
msgctxt "model:ir.action,name:act_menu_list"
msgid "Menu"
-msgstr ""
+msgstr "ລາຍການ"
+#, fuzzy
msgctxt "model:ir.action,name:act_menu_tree"
msgid "Menu"
-msgstr ""
+msgstr "ລາຍການ"
msgctxt "model:ir.action,name:act_model_access_form"
msgid "Models Access"
msgstr ""
+#, fuzzy
msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
-msgstr ""
+msgstr "ປຸ່ມ"
msgctxt "model:ir.action,name:act_model_data_form"
msgid "Data"
@@ -2753,9 +2873,10 @@ msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
msgstr ""
+#, fuzzy
msgctxt "model:ir.action,name:act_model_fields_form"
msgid "Fields"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "model:ir.action,name:act_model_form"
msgid "Models"
@@ -2777,6 +2898,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr ""
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr ""
@@ -2801,13 +2926,14 @@ msgctxt "model:ir.action,name:act_sequence_type_form"
msgid "Sequence Types"
msgstr ""
+#, fuzzy
msgctxt "model:ir.action,name:act_translation_clean"
msgid "Clean Translations"
-msgstr ""
+msgstr "ລ້າງການແປ"
msgctxt "model:ir.action,name:act_translation_export"
msgid "Export Translations"
-msgstr ""
+msgstr "ສົ່ງການແປອອກ"
msgctxt "model:ir.action,name:act_translation_form"
msgid "Translations"
@@ -2825,9 +2951,10 @@ msgctxt "model:ir.action,name:act_trigger_form"
msgid "Triggers"
msgstr ""
+#, fuzzy
msgctxt "model:ir.action,name:act_view_form"
msgid "Views"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "model:ir.action,name:act_view_search"
msgid "View Search"
@@ -2853,6 +2980,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr ""
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr ""
@@ -2891,9 +3022,10 @@ msgctxt "model:ir.action.wizard,name:"
msgid "Action wizard"
msgstr ""
+#, fuzzy
msgctxt "model:ir.attachment,name:"
msgid "Attachment"
-msgstr ""
+msgstr "ເອກະສານຄັດຕິດ"
msgctxt "model:ir.cache,name:"
msgid "Cache"
@@ -3055,6 +3187,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr ""
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr ""
@@ -3091,21 +3231,25 @@ msgctxt "model:ir.translation,name:"
msgid "Translation"
msgstr ""
+#, fuzzy
msgctxt "model:ir.translation.clean.start,name:"
msgid "Clean translation"
-msgstr ""
+msgstr "ລ້າງການແປ"
+#, fuzzy
msgctxt "model:ir.translation.clean.succeed,name:"
msgid "Clean translation"
-msgstr ""
+msgstr "ລ້າງການແປ"
+#, fuzzy
msgctxt "model:ir.translation.export.result,name:"
msgid "Export translation"
-msgstr ""
+msgstr "ສົ່ງການແປອອກ"
+#, fuzzy
msgctxt "model:ir.translation.export.start,name:"
msgid "Export translation"
-msgstr ""
+msgstr "ສົ່ງການແປອອກ"
msgctxt "model:ir.translation.set.start,name:"
msgid "Set Translation"
@@ -3127,9 +3271,10 @@ msgctxt "model:ir.trigger.log,name:"
msgid "Trigger Log"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.icon,name:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "model:ir.ui.menu,name:"
msgid "UI menu"
@@ -3163,9 +3308,10 @@ msgctxt "model:ir.ui.menu,name:menu_administration"
msgid "Administration"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_attachment_form"
msgid "Attachments"
-msgstr ""
+msgstr "ເອກະສານຄັດຕິດ"
msgctxt "model:ir.ui.menu,name:menu_config_wizard_item_form"
msgid "Config Wizard Items"
@@ -3179,9 +3325,10 @@ msgctxt "model:ir.ui.menu,name:menu_export_form"
msgid "Exports"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_icon_form"
msgid "Icons"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "model:ir.ui.menu,name:menu_ir_sequence_type"
msgid "Sequence Types"
@@ -3195,17 +3342,19 @@ msgctxt "model:ir.ui.menu,name:menu_localization"
msgid "Localization"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_menu_list"
msgid "Menu"
-msgstr ""
+msgstr "ລາຍການ"
msgctxt "model:ir.ui.menu,name:menu_model_access_form"
msgid "Models Access"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
-msgstr ""
+msgstr "ປຸ່ມ"
msgctxt "model:ir.ui.menu,name:menu_model_data_form"
msgid "Data"
@@ -3235,6 +3384,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr ""
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr ""
@@ -3263,21 +3416,24 @@ msgctxt "model:ir.ui.menu,name:menu_sequences"
msgid "Sequences"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_translation_clean"
msgid "Clean Translations"
-msgstr ""
+msgstr "ລ້າງການແປ"
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_translation_export"
msgid "Export Translations"
-msgstr ""
+msgstr "ສົ່ງການແປອອກ"
msgctxt "model:ir.ui.menu,name:menu_translation_form"
msgid "Translations"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_translation_set"
msgid "Set Translations"
-msgstr ""
+msgstr "ລ້າງການແປ"
msgctxt "model:ir.ui.menu,name:menu_translation_update"
msgid "Synchronize Translations"
@@ -3291,9 +3447,10 @@ msgctxt "model:ir.ui.menu,name:menu_ui"
msgid "User Interface"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_view"
msgid "Views"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "model:ir.ui.menu,name:menu_view_search"
msgid "View Search"
@@ -3307,17 +3464,19 @@ msgctxt "model:ir.ui.menu,name:menu_view_tree_width"
msgid "View Tree Width"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.menu,name:model_model_fields_form"
msgid "Fields"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "model:ir.ui.menu.favorite,name:"
msgid "Menu Favorite"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.view,name:"
msgid "View"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "model:ir.ui.view.show.start,name:"
msgid "Show view"
@@ -3327,9 +3486,10 @@ msgctxt "model:ir.ui.view_search,name:"
msgid "View Search"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.view_tree_state,name:"
msgid "View Tree State"
-msgstr ""
+msgstr "ສະຖານະເບິ່ງແບບຕົ້ນໄມ້"
msgctxt "model:ir.ui.view_tree_width,name:"
msgid "View Tree Width"
@@ -3475,9 +3635,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Error"
msgstr ""
+#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "Field"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "selection:ir.translation,type:"
msgid "Help"
@@ -3488,16 +3649,17 @@ msgid "Model"
msgstr ""
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
+msgid "Report"
msgstr ""
msgctxt "selection:ir.translation,type:"
msgid "Selection"
msgstr ""
+#, fuzzy
msgctxt "selection:ir.translation,type:"
msgid "View"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "selection:ir.translation,type:"
msgid "Wizard Button"
@@ -3557,11 +3719,12 @@ msgstr ""
msgctxt "view:ir.action.act_window.view:"
msgid "View"
-msgstr ""
+msgstr "ເບິ່ງ"
+#, fuzzy
msgctxt "view:ir.action.act_window.view:"
msgid "Views"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "view:ir.action.act_window:"
msgid "General"
@@ -3577,11 +3740,11 @@ msgstr ""
msgctxt "view:ir.action.keyword:"
msgid "Keyword"
-msgstr ""
+msgstr "ຄຳລະຫັດ"
msgctxt "view:ir.action.keyword:"
msgid "Keywords"
-msgstr ""
+msgstr "ຂໍ້ຄວາມຫຼັກ"
msgctxt "view:ir.action.report:"
msgid "General"
@@ -3621,7 +3784,7 @@ msgstr ""
msgctxt "view:ir.attachment:"
msgid "Attachments"
-msgstr ""
+msgstr "ເອກະສານຄັດຕິດ"
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
@@ -3632,6 +3795,10 @@ msgid "Action to trigger"
msgstr ""
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr ""
@@ -3663,13 +3830,14 @@ msgctxt "view:ir.model.access:"
msgid "Access controls"
msgstr ""
+#, fuzzy
msgctxt "view:ir.model.button:"
msgid "Button"
-msgstr ""
+msgstr "ປຸ່ມ"
msgctxt "view:ir.model.button:"
msgid "Buttons"
-msgstr ""
+msgstr "ປຸ່ມ"
msgctxt "view:ir.model.data:"
msgid "Model Data"
@@ -3683,13 +3851,14 @@ msgctxt "view:ir.model.field.access:"
msgid "Field Access"
msgstr ""
+#, fuzzy
msgctxt "view:ir.model.field:"
msgid "Field"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "view:ir.model.field:"
msgid "Fields"
-msgstr ""
+msgstr "ຟິນລ໌"
msgctxt "view:ir.model.print_model_graph.start:"
msgid "Print Model Graph"
@@ -3785,6 +3954,26 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr ""
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr ""
@@ -3856,31 +4045,36 @@ msgstr ""
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations"
-msgstr ""
+msgstr "ລ້າງການແປ"
+#, fuzzy
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations?"
-msgstr ""
+msgstr "ລ້າງການແປ"
+#, fuzzy
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations"
-msgstr ""
+msgstr "ລ້າງການແປ"
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations Succeed!"
msgstr ""
+#, fuzzy
msgctxt "view:ir.translation.export.result:"
msgid "Export Translation"
-msgstr ""
+msgstr "ສົ່ງການແປອອກ"
+#, fuzzy
msgctxt "view:ir.translation.export.start:"
msgid "Export Translation"
-msgstr ""
+msgstr "ສົ່ງການແປອອກ"
+#, fuzzy
msgctxt "view:ir.translation.set.start:"
msgid "Set Translations"
-msgstr ""
+msgstr "ລ້າງການແປ"
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
@@ -3890,9 +4084,10 @@ msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
msgstr ""
+#, fuzzy
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
-msgstr ""
+msgstr "ລ້າງການແປ"
msgctxt "view:ir.translation.update.start:"
msgid "Synchronize Translations"
@@ -3912,11 +4107,11 @@ msgstr ""
msgctxt "view:ir.ui.icon:"
msgid "Icon"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "view:ir.ui.icon:"
msgid "Icons"
-msgstr ""
+msgstr "ໄອຄັອນ"
msgctxt "view:ir.ui.menu.favorite:"
msgid "Menu Favorite"
@@ -3928,7 +4123,7 @@ msgstr ""
msgctxt "view:ir.ui.menu:"
msgid "Menu"
-msgstr ""
+msgstr "ລາຍການ"
msgctxt "view:ir.ui.view:"
msgid "Show"
@@ -3936,7 +4131,7 @@ msgstr ""
msgctxt "view:ir.ui.view:"
msgid "View"
-msgstr ""
+msgstr "ເບິ່ງ"
msgctxt "view:ir.ui.view_search:"
msgid "View Search"
@@ -3948,11 +4143,12 @@ msgstr ""
msgctxt "view:ir.ui.view_tree_state:"
msgid "View Tree State"
-msgstr ""
+msgstr "ສະຖານະເບິ່ງແບບຕົ້ນໄມ້"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "Views Tree State"
-msgstr ""
+msgstr "ສະຖານະເບິ່ງແບບຕົ້ນໄມ້"
msgctxt "view:ir.ui.view_tree_width:"
msgid "View Tree Width"
diff --git a/trytond/ir/locale/lt_LT.po b/trytond/ir/locale/lt_LT.po
index d71a1fa..d5b3532 100644
--- a/trytond/ir/locale/lt_LT.po
+++ b/trytond/ir/locale/lt_LT.po
@@ -59,7 +59,7 @@ msgid "Invalid email definition on report \"%s\"."
msgstr ""
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr ""
msgctxt "error:ir.cron:"
@@ -383,6 +383,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr ""
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr ""
@@ -1635,6 +1639,86 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr ""
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr ""
+
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr ""
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr ""
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr ""
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr ""
@@ -2777,6 +2861,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr ""
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr ""
@@ -2853,6 +2941,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr ""
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr ""
@@ -3055,6 +3147,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr ""
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr ""
@@ -3235,6 +3335,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr ""
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr ""
@@ -3488,7 +3592,7 @@ msgid "Model"
msgstr ""
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
+msgid "Report"
msgstr ""
msgctxt "selection:ir.translation,type:"
@@ -3632,6 +3736,10 @@ msgid "Action to trigger"
msgstr ""
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr ""
@@ -3785,6 +3893,26 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr ""
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr ""
diff --git a/trytond/ir/locale/nl_NL.po b/trytond/ir/locale/nl_NL.po
index 669c007..2625f11 100644
--- a/trytond/ir/locale/nl_NL.po
+++ b/trytond/ir/locale/nl_NL.po
@@ -2,11 +2,14 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
+#, fuzzy
msgctxt "error:access_error:"
msgid ""
"You try to bypass an access rule.\n"
"(Document type: %s)"
msgstr ""
+"U probeert een toegangsregel te omzeilen!\n"
+"(Document type: %s)"
msgctxt "error:delete_xml_record:"
msgid "You are not allowed to delete this record."
@@ -58,8 +61,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr ""
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "De namen van de bijlagen moeten uniek zijn per bron!"
msgctxt "error:ir.cron:"
@@ -111,9 +115,10 @@ msgctxt "error:ir.model.access:"
msgid "You can not write in this document! (%s)"
msgstr "U kunt dit document niet muteren! (%s)"
+#, fuzzy
msgctxt "error:ir.model.button:"
msgid "The button name in model must be unique!"
-msgstr ""
+msgstr "De veldnaam in het model moet uniek zijn!"
msgctxt "error:ir.model.data:"
msgid "The triple (fs_id, module, model) must be unique!"
@@ -145,23 +150,24 @@ msgstr "Het model moet uniek zijn!"
msgctxt "error:ir.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr ""
+msgstr "Afhankelijkheid moet uniek zijn per module!"
msgctxt "error:ir.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr ""
+msgstr "Afhankelijkheid %s ontbreekt voor module \"%s\""
msgctxt "error:ir.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
msgstr ""
+"De module die u wilt verwijderen is afhankelijk van geïnstalleerde modules:"
msgctxt "error:ir.module:"
msgid "The name of the module must be unique!"
-msgstr ""
+msgstr "De naam van de module moet uniek zijn!"
msgctxt "error:ir.module:"
msgid "You can not remove a module that is installed or will be installed"
-msgstr ""
+msgstr "U kunt een module niet verwijderen als die geïnstalleerd is of wordt."
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
@@ -239,11 +245,14 @@ msgctxt "error:ir.ui.view:"
msgid "Invalid XML for view \"%s\"."
msgstr ""
+#, fuzzy
msgctxt "error:read_error:"
msgid ""
"You try to read records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
+"U probeert items te lezen die niet meer bestaan!\n"
+"(Document type: %s)"
msgctxt "error:recursion_error:"
msgid ""
@@ -293,11 +302,14 @@ msgctxt "error:too_many_relations_found:"
msgid "Too many relations found: %r in %s"
msgstr "Te veel relaties gevonden: %r in %s"
+#, fuzzy
msgctxt "error:write_error:"
msgid ""
"You try to write on records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
+"U probeert items te lezen die niet meer bestaan!\n"
+"(Document type: %s)"
msgctxt "error:write_xml_record:"
msgid "You are not allowed to modify this record."
@@ -364,9 +376,10 @@ msgctxt "field:ir.action,write_uid:"
msgid "Write User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.action.act_window,act_window_domains:"
msgid "Domains"
-msgstr ""
+msgstr "Domein"
msgctxt "field:ir.action.act_window,act_window_views:"
msgid "Views"
@@ -385,6 +398,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Samenhang waarde"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr ""
@@ -397,9 +414,10 @@ msgctxt "field:ir.action.act_window,domain:"
msgid "Domain Value"
msgstr "Domein waarde"
+#, fuzzy
msgctxt "field:ir.action.act_window,domains:"
msgid "Domains"
-msgstr ""
+msgstr "Domein"
#, fuzzy
msgctxt "field:ir.action.act_window,groups:"
@@ -680,9 +698,10 @@ msgctxt "field:ir.action.report,name:"
msgid "Name"
msgstr "Naam bijlage"
+#, fuzzy
msgctxt "field:ir.action.report,pyson_email:"
msgid "PySON Email"
-msgstr ""
+msgstr "PySON domein"
msgctxt "field:ir.action.report,rec_name:"
msgid "Name"
@@ -889,9 +908,10 @@ msgctxt "field:ir.attachment,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.attachment,data:"
msgid "Data"
-msgstr ""
+msgstr "Datum"
msgctxt "field:ir.attachment,data_size:"
msgid "Data size"
@@ -995,9 +1015,10 @@ msgctxt "field:ir.configuration,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.configuration,language:"
msgid "language"
-msgstr ""
+msgstr "Taal"
#, fuzzy
msgctxt "field:ir.configuration,rec_name:"
@@ -1554,7 +1575,7 @@ msgstr ""
msgctxt "field:ir.module,dependencies:"
msgid "Dependencies"
-msgstr ""
+msgstr "Afhankelijkheden"
msgctxt "field:ir.module,id:"
msgid "ID"
@@ -1562,7 +1583,7 @@ msgstr ""
msgctxt "field:ir.module,name:"
msgid "Name"
-msgstr ""
+msgstr "Naam"
msgctxt "field:ir.module,parents:"
msgid "Parents"
@@ -1570,15 +1591,15 @@ msgstr ""
msgctxt "field:ir.module,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Naam"
msgctxt "field:ir.module,state:"
msgid "State"
-msgstr ""
+msgstr "Status"
msgctxt "field:ir.module,version:"
msgid "Version"
-msgstr ""
+msgstr "Versie"
msgctxt "field:ir.module,write_date:"
msgid "Write Date"
@@ -1598,7 +1619,7 @@ msgstr ""
msgctxt "field:ir.module.config_wizard.item,action:"
msgid "Action"
-msgstr ""
+msgstr "Actie"
msgctxt "field:ir.module.config_wizard.item,create_date:"
msgid "Create Date"
@@ -1614,15 +1635,16 @@ msgstr ""
msgctxt "field:ir.module.config_wizard.item,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Naam"
+#, fuzzy
msgctxt "field:ir.module.config_wizard.item,sequence:"
msgid "Sequence"
-msgstr ""
+msgstr "Reeks"
msgctxt "field:ir.module.config_wizard.item,state:"
msgid "State"
-msgstr ""
+msgstr "Status"
msgctxt "field:ir.module.config_wizard.item,write_date:"
msgid "Write Date"
@@ -1636,9 +1658,10 @@ msgctxt "field:ir.module.config_wizard.other,id:"
msgid "ID"
msgstr ""
+#, fuzzy
msgctxt "field:ir.module.config_wizard.other,percentage:"
msgid "Percentage"
-msgstr ""
+msgstr "Percentage"
msgctxt "field:ir.module.dependency,create_date:"
msgid "Create Date"
@@ -1654,19 +1677,19 @@ msgstr ""
msgctxt "field:ir.module.dependency,module:"
msgid "Module"
-msgstr ""
+msgstr "Module"
msgctxt "field:ir.module.dependency,name:"
msgid "Name"
-msgstr ""
+msgstr "Naam"
msgctxt "field:ir.module.dependency,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Naam"
msgctxt "field:ir.module.dependency,state:"
msgid "State"
-msgstr ""
+msgstr "Status"
msgctxt "field:ir.module.dependency,write_date:"
msgid "Write Date"
@@ -1688,6 +1711,90 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr ""
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr ""
+
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr ""
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Naam"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Middel"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr ""
+
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr ""
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Naam"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Gebruiker"
+
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr ""
+
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr ""
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr ""
@@ -2048,9 +2155,10 @@ msgctxt "field:ir.session.wizard,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.session.wizard,data:"
msgid "Data"
-msgstr ""
+msgstr "Datum"
msgctxt "field:ir.session.wizard,id:"
msgid "ID"
@@ -2341,9 +2449,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Actie"
+#, fuzzy
msgctxt "field:ir.ui.menu,action_keywords:"
msgid "Action Keywords"
-msgstr ""
+msgstr "Actietrefwoord"
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
@@ -2457,9 +2566,10 @@ msgctxt "field:ir.ui.view,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.ui.view,data:"
msgid "Data"
-msgstr ""
+msgstr "Datum"
msgctxt "field:ir.ui.view,domain:"
msgid "Domain"
@@ -2739,11 +2849,14 @@ msgctxt "help:ir.rule.group,rules:"
msgid "The rule is satisfied if at least one test is True"
msgstr "De regel is waar als tenminste aan één voorwaarde wordt voldaan"
+#, fuzzy
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
+"Een Python uitdrukking gekoppeld aan item \"self\"\n"
+"Het start de actie als het waar is."
msgctxt "help:ir.trigger,limit_number:"
msgid ""
@@ -2803,9 +2916,10 @@ msgctxt "model:ir.action,name:act_export_form"
msgid "Exports"
msgstr "Export"
+#, fuzzy
msgctxt "model:ir.action,name:act_icon_form"
msgid "Icons"
-msgstr ""
+msgstr "Icoon"
msgctxt "model:ir.action,name:act_lang_form"
msgid "Languages"
@@ -2828,13 +2942,15 @@ 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 ""
+msgstr "Datum"
+#, fuzzy
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
-msgstr ""
+msgstr "Leesrecht"
msgctxt "model:ir.action,name:act_model_fields_form"
msgid "Fields"
@@ -2860,6 +2976,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Voer installatie / bijwerken uit"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Eigenschappen"
@@ -2936,13 +3056,18 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Grafiek"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Actie uitvoerend scherm"
+#, fuzzy
msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
-msgstr ""
+msgstr "Actie uitvoerend schermaanzicht"
msgctxt ""
"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
@@ -3109,34 +3234,45 @@ msgstr ""
msgctxt "model:ir.module,name:"
msgid "Module"
-msgstr ""
+msgstr "Module"
+#, fuzzy
msgctxt "model:ir.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr ""
+msgstr "Module EHBC"
msgctxt "model:ir.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr ""
+msgstr "Module EHBC"
msgctxt "model:ir.module.config_wizard.item,name:"
msgid "Config wizard to run after installing module"
-msgstr ""
+msgstr "Configuratie assistent die start na installatie van de module"
+#, fuzzy
msgctxt "model:ir.module.config_wizard.other,name:"
msgid "Module Config Wizard Other"
-msgstr ""
+msgstr "Module EHBC"
msgctxt "model:ir.module.dependency,name:"
msgid "Module dependency"
-msgstr ""
+msgstr "Module afhankelijkheid"
+#, fuzzy
msgctxt "model:ir.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr ""
+msgstr "Start module gaan bijwerken"
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
+msgstr "Start module gaan bijwerken"
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
msgstr ""
msgctxt "model:ir.property,name:"
@@ -3195,13 +3331,15 @@ msgctxt "model:ir.translation.export.start,name:"
msgid "Export translation"
msgstr "Vertaling exporteren - bestand"
+#, fuzzy
msgctxt "model:ir.translation.set.start,name:"
msgid "Set Translation"
-msgstr ""
+msgstr "Vertalingen opschonen"
+#, fuzzy
msgctxt "model:ir.translation.set.succeed,name:"
msgid "Set Translation"
-msgstr ""
+msgstr "Vertalingen opschonen"
msgctxt "model:ir.translation.update.start,name:"
msgid "Update translation"
@@ -3268,9 +3406,10 @@ msgctxt "model:ir.ui.menu,name:menu_export_form"
msgid "Exports"
msgstr "Export"
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_icon_form"
msgid "Icons"
-msgstr ""
+msgstr "Icoon"
msgctxt "model:ir.ui.menu,name:menu_ir_sequence_type"
msgid "Sequence Types"
@@ -3297,13 +3436,15 @@ 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 ""
+msgstr "Datum"
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
-msgstr ""
+msgstr "Leesrecht"
msgctxt "model:ir.ui.menu,name:menu_model_form"
msgid "Models"
@@ -3325,6 +3466,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Modulen"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Eigenschappen"
@@ -3365,9 +3510,10 @@ msgctxt "model:ir.ui.menu,name:menu_translation_form"
msgid "Translations"
msgstr "Vertalingen"
+#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_translation_set"
msgid "Set Translations"
-msgstr ""
+msgstr "Vertalingen opschonen"
msgctxt "model:ir.ui.menu,name:menu_translation_update"
msgid "Synchronize Translations"
@@ -3417,9 +3563,10 @@ msgctxt "model:ir.ui.view_search,name:"
msgid "View Search"
msgstr ""
+#, fuzzy
msgctxt "model:ir.ui.view_tree_state,name:"
msgid "View Tree State"
-msgstr ""
+msgstr "Aanzichten boomstructuurbreedte"
msgctxt "model:ir.ui.view_tree_width,name:"
msgid "View Tree Width"
@@ -3449,9 +3596,10 @@ msgctxt "selection:ir.action.keyword,keyword:"
msgid "Print form"
msgstr "Formulier afdrukken"
+#, fuzzy
msgctxt "selection:ir.attachment,type:"
msgid "Data"
-msgstr ""
+msgstr "Datum"
#, fuzzy
msgctxt "selection:ir.attachment,type:"
@@ -3488,55 +3636,55 @@ msgstr "Van rechts naar links"
msgctxt "selection:ir.module,state:"
msgid "Installed"
-msgstr ""
+msgstr "Geïnstalleerd"
msgctxt "selection:ir.module,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Niet geïnstalleerd"
msgctxt "selection:ir.module,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Te installeren"
msgctxt "selection:ir.module,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Te verwijderen"
msgctxt "selection:ir.module,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Bij te werken"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr ""
+msgstr "Klaar"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
-msgstr ""
+msgstr "Open"
msgctxt "selection:ir.module.dependency,state:"
msgid "Installed"
-msgstr ""
+msgstr "Geïnstalleerd"
msgctxt "selection:ir.module.dependency,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Niet geïnstalleerd"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Te installeren"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Te verwijderen"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Bij te werken"
msgctxt "selection:ir.module.dependency,state:"
msgid "Unknown"
-msgstr ""
+msgstr "Onbekend"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
@@ -3578,9 +3726,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Model"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Verslag"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3643,9 +3792,10 @@ msgctxt "view:ir.action.act_window.domain:"
msgid "Domain"
msgstr "Domein"
+#, fuzzy
msgctxt "view:ir.action.act_window.domain:"
msgid "Domains"
-msgstr ""
+msgstr "Domein"
#, fuzzy
msgctxt "view:ir.action.act_window.view:"
@@ -3728,6 +3878,10 @@ msgid "Action to trigger"
msgstr "Actie om uit te voeren"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Geplande actie"
@@ -3767,17 +3921,19 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr ""
+#, fuzzy
msgctxt "view:ir.model.data:"
msgid "Model Data"
-msgstr ""
+msgstr "Model gegevens"
msgctxt "view:ir.model.data:"
msgid "Sync"
msgstr ""
+#, fuzzy
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
-msgstr ""
+msgstr "Leesrecht"
#, fuzzy
msgctxt "view:ir.model.field:"
@@ -3796,9 +3952,10 @@ msgctxt "view:ir.model:"
msgid "Model Description"
msgstr "Model omschrijving"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.done:"
msgid "Module configuration"
-msgstr ""
+msgstr "Module configuratie"
msgctxt "view:ir.module.config_wizard.done:"
msgid "The configuration is done."
@@ -3806,7 +3963,7 @@ msgstr ""
msgctxt "view:ir.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
-msgstr ""
+msgstr "Welkom bij de module configuratie assistent!"
msgctxt "view:ir.module.config_wizard.first:"
msgid ""
@@ -3816,7 +3973,7 @@ msgstr ""
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
-msgstr ""
+msgstr "Conf. assistent items"
msgctxt "view:ir.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
@@ -3824,19 +3981,21 @@ msgstr ""
msgctxt "view:ir.module.dependency:"
msgid "Dependencies"
-msgstr ""
+msgstr "Afhankelijkheden"
+#, fuzzy
msgctxt "view:ir.module.dependency:"
msgid "Dependency"
-msgstr ""
+msgstr "Afhankelijkheden"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "System Upgrade Done"
msgstr ""
+#, fuzzy
msgctxt "view:ir.module.install_upgrade.done:"
msgid "The modules have been upgraded / installed !"
-msgstr ""
+msgstr "De modules zijn bijgewerkt / geïnstalleerd!"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Note that this operation may take a few minutes."
@@ -3852,36 +4011,58 @@ msgstr ""
msgctxt "view:ir.module:"
msgid "Cancel Installation"
-msgstr ""
+msgstr "Installatie annuleren"
msgctxt "view:ir.module:"
msgid "Cancel Uninstallation"
-msgstr ""
+msgstr "Verwijderen ongedaan maken"
msgctxt "view:ir.module:"
msgid "Cancel Upgrade"
-msgstr ""
+msgstr "Bijwerken annuleren"
msgctxt "view:ir.module:"
msgid "Mark for Installation"
-msgstr ""
+msgstr "Selecteer voor installatie"
msgctxt "view:ir.module:"
msgid "Mark for Uninstallation (beta)"
-msgstr ""
+msgstr "Selecteer voor verwijderen (beta)"
msgctxt "view:ir.module:"
msgid "Mark for Upgrade"
-msgstr ""
+msgstr "Selecteer voor bijwerken"
msgctxt "view:ir.module:"
msgid "Module"
-msgstr ""
+msgstr "Module"
msgctxt "view:ir.module:"
msgid "Modules"
+msgstr "Modulen"
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
msgstr ""
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Gebruiker"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Eigenschappen"
@@ -3956,9 +4137,10 @@ msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations"
msgstr "Vertalingen opschonen"
+#, fuzzy
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations?"
-msgstr ""
+msgstr "Vertalingen opschonen"
#, fuzzy
msgctxt "view:ir.translation.clean.succeed:"
@@ -3979,21 +4161,24 @@ msgctxt "view:ir.translation.export.start:"
msgid "Export Translation"
msgstr "Vertaling exporteren"
+#, fuzzy
msgctxt "view:ir.translation.set.start:"
msgid "Set Translations"
-msgstr ""
+msgstr "Vertalingen opschonen"
+#, fuzzy
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
-msgstr ""
+msgstr "Vertaling synchroniseren"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
msgstr ""
+#, fuzzy
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
-msgstr ""
+msgstr "Vertalingen opschonen"
#, fuzzy
msgctxt "view:ir.translation.update.start:"
@@ -4017,9 +4202,10 @@ msgctxt "view:ir.ui.icon:"
msgid "Icon"
msgstr "Icoon"
+#, fuzzy
msgctxt "view:ir.ui.icon:"
msgid "Icons"
-msgstr ""
+msgstr "Icoon"
msgctxt "view:ir.ui.menu.favorite:"
msgid "Menu Favorite"
@@ -4049,13 +4235,15 @@ msgctxt "view:ir.ui.view_search:"
msgid "View Searches"
msgstr ""
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "View Tree State"
-msgstr ""
+msgstr "Aanzichten boomstructuurbreedte"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "Views Tree State"
-msgstr ""
+msgstr "Aanzichten boomstructuurbreedte"
msgctxt "view:ir.ui.view_tree_width:"
msgid "View Tree Width"
@@ -4074,33 +4262,39 @@ msgctxt "wizard_button:ir.model.print_model_graph,start,print_:"
msgid "Print"
msgstr ""
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,done,end:"
msgid "OK"
-msgstr ""
+msgstr "Oké"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,first,action:"
msgid "OK"
-msgstr ""
+msgstr "Oké"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,first,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Annuleren"
msgctxt "wizard_button:ir.module.config_wizard,other,action:"
msgid "Next"
msgstr ""
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,other,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Annuleren"
+#, fuzzy
msgctxt "wizard_button:ir.module.install_upgrade,done,config:"
msgid "OK"
-msgstr ""
+msgstr "Oké"
+#, fuzzy
msgctxt "wizard_button:ir.module.install_upgrade,start,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Annuleren"
msgctxt "wizard_button:ir.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
diff --git a/trytond/ir/locale/pt_BR.po b/trytond/ir/locale/pt_BR.po
index 6232e4b..d21c60e 100644
--- a/trytond/ir/locale/pt_BR.po
+++ b/trytond/ir/locale/pt_BR.po
@@ -68,8 +68,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "E-mail inválido na definição do relatório\"%s\"."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "Os nomes dos anexos devem ser únicos por reurso!"
msgctxt "error:ir.cron:"
@@ -416,6 +417,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Valor do contexto"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Data de criação"
@@ -586,7 +591,7 @@ msgstr "Sequência"
msgctxt "field:ir.action.act_window.view,view:"
msgid "View"
-msgstr "Exibir"
+msgstr "Sumário"
msgctxt "field:ir.action.act_window.view,write_date:"
msgid "Write Date"
@@ -1668,6 +1673,102 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Módulos a atualizar"
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Data de criação"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Criado por"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Última modificação"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Último usuário"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Nome"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Recurso"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Data de edição"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Gravado pelo usuário"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Data de criação"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Criado por"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Nome"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Usuário"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Data de edição"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Gravado pelo usuário"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Data de criação"
@@ -2828,6 +2929,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Realizar instalações/atualizações pendentes"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Propriedades"
@@ -2904,6 +3009,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Grafo"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Janela de ato de ação"
@@ -3105,7 +3214,15 @@ msgstr "Atualização / Instalação do módulo terminada"
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
-msgstr "Atualização / Instalação do módulo iniciada"
+msgstr "Iniciar a Atualização / Instalação do módulo"
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
msgctxt "model:ir.property,name:"
msgid "Property"
@@ -3287,6 +3404,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Módulos"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Propriedades"
@@ -3369,7 +3490,7 @@ msgstr "Menu Favoritos"
msgctxt "model:ir.ui.view,name:"
msgid "View"
-msgstr "Exibir"
+msgstr "Sumário"
msgctxt "model:ir.ui.view.show.start,name:"
msgid "Show view"
@@ -3539,9 +3660,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Modelo"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Relatório"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3549,7 +3671,7 @@ msgstr "Seleção"
msgctxt "selection:ir.translation,type:"
msgid "View"
-msgstr "Exibir"
+msgstr "Sumário"
msgctxt "selection:ir.translation,type:"
msgid "Wizard Button"
@@ -3609,7 +3731,7 @@ msgstr "Domínios"
msgctxt "view:ir.action.act_window.view:"
msgid "View"
-msgstr "Exibir"
+msgstr "Sumário"
msgctxt "view:ir.action.act_window.view:"
msgid "Views"
@@ -3684,6 +3806,10 @@ msgid "Action to trigger"
msgstr "Ação a disparar"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Ação Agendada"
@@ -3821,7 +3947,7 @@ msgstr "Cancelar atualização"
msgctxt "view:ir.module:"
msgid "Mark for Installation"
-msgstr "Cancelar atualização"
+msgstr "Marcar para instalação"
msgctxt "view:ir.module:"
msgid "Mark for Uninstallation (beta)"
@@ -3839,6 +3965,28 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Módulos"
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Usuário"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Propriedades"
@@ -3992,7 +4140,7 @@ msgstr "Mostrar"
msgctxt "view:ir.ui.view:"
msgid "View"
-msgstr "Exibir"
+msgstr "Sumário"
msgctxt "view:ir.ui.view_search:"
msgid "View Search"
diff --git a/trytond/ir/locale/ru_RU.po b/trytond/ir/locale/ru_RU.po
index 386f613..923dee7 100644
--- a/trytond/ir/locale/ru_RU.po
+++ b/trytond/ir/locale/ru_RU.po
@@ -65,8 +65,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Некорректная эл.почта у отчета \"%s\"."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "Имена вложений должны быть уникальны для ресурса!"
msgctxt "error:ir.cron:"
@@ -152,23 +153,24 @@ msgstr "Модель должна быть уникальной!"
msgctxt "error:ir.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr ""
+msgstr "Зависимости должны быть уникальны у модуля!"
msgctxt "error:ir.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr ""
+msgstr "Отсутствует(ют) модуль(и) %s для модуля \"%s\""
msgctxt "error:ir.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
msgstr ""
+"От модулей, которые вы пытаетесь удалить, зависят установленные модули:"
msgctxt "error:ir.module:"
msgid "The name of the module must be unique!"
-msgstr ""
+msgstr "Наименование модуля должно быть уникальным"
msgctxt "error:ir.module:"
msgid "You can not remove a module that is installed or will be installed"
-msgstr ""
+msgstr "Вы не можете удалить модуль, который установлен или будет установлен"
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
@@ -232,11 +234,14 @@ msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
msgstr "\"На время\" и другие варианты взаимоисключающие"
+#, fuzzy
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid PYSON expression on trigger "
"\"%(trigger)s\"."
msgstr ""
+"Условие \"%(condition)s\" не является корректным выражением python для "
+"триггера \"%(trigger)s\"."
msgctxt "error:ir.ui.menu:"
msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
@@ -394,6 +399,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Значение контекста"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Дата создания"
@@ -1501,152 +1510,248 @@ msgstr "Уровень"
msgctxt "field:ir.module,childs:"
msgid "Childs"
-msgstr ""
+msgstr "Подчиненные"
msgctxt "field:ir.module,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Дата создания"
msgctxt "field:ir.module,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Создано пользователем"
msgctxt "field:ir.module,dependencies:"
msgid "Dependencies"
-msgstr ""
+msgstr "Зависимости"
msgctxt "field:ir.module,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module,name:"
msgid "Name"
-msgstr ""
+msgstr "Наименование"
msgctxt "field:ir.module,parents:"
msgid "Parents"
-msgstr ""
+msgstr "Предки"
msgctxt "field:ir.module,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Наименование"
msgctxt "field:ir.module,state:"
msgid "State"
-msgstr ""
+msgstr "Состояние"
msgctxt "field:ir.module,version:"
msgid "Version"
-msgstr ""
+msgstr "Версия"
msgctxt "field:ir.module,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Дата изменения"
msgctxt "field:ir.module,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Изменено пользователем"
msgctxt "field:ir.module.config_wizard.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.first,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,action:"
msgid "Action"
-msgstr ""
+msgstr "Действие"
msgctxt "field:ir.module.config_wizard.item,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Дата создания"
msgctxt "field:ir.module.config_wizard.item,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Создано пользователем"
msgctxt "field:ir.module.config_wizard.item,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.item,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Наименование"
msgctxt "field:ir.module.config_wizard.item,sequence:"
msgid "Sequence"
-msgstr ""
+msgstr "Нумерация"
msgctxt "field:ir.module.config_wizard.item,state:"
msgid "State"
-msgstr ""
+msgstr "Состояние"
msgctxt "field:ir.module.config_wizard.item,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Дата изменения"
msgctxt "field:ir.module.config_wizard.item,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Изменено пользователем"
msgctxt "field:ir.module.config_wizard.other,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.config_wizard.other,percentage:"
msgid "Percentage"
-msgstr ""
+msgstr "Процент"
msgctxt "field:ir.module.dependency,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "Дата создания"
msgctxt "field:ir.module.dependency,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "Создано пользователем"
msgctxt "field:ir.module.dependency,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.dependency,module:"
msgid "Module"
-msgstr ""
+msgstr "Модуль"
msgctxt "field:ir.module.dependency,name:"
msgid "Name"
-msgstr ""
+msgstr "Наименование"
msgctxt "field:ir.module.dependency,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "Наименование"
msgctxt "field:ir.module.dependency,state:"
msgid "State"
-msgstr ""
+msgstr "Состояние"
msgctxt "field:ir.module.dependency,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "Дата изменения"
msgctxt "field:ir.module.dependency,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "Изменено пользователем"
msgctxt "field:ir.module.install_upgrade.done,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,id:"
msgid "ID"
-msgstr ""
+msgstr "ID"
msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
+msgstr "Модули для установки (обновления)"
+
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Дата создания"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Создано пользователем"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Последнее изменение"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Последний пользователь"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
msgstr ""
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Наименование"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Ресурс"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Дата изменения"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Изменено пользователем"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Дата создания"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Создано пользователем"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Наименование"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Пользователь"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Дата изменения"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Изменено пользователем"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Дата создания"
@@ -2287,9 +2392,10 @@ msgctxt "field:ir.ui.menu,action:"
msgid "Action"
msgstr "Действие"
+#, fuzzy
msgctxt "field:ir.ui.menu,action_keywords:"
msgid "Action Keywords"
-msgstr ""
+msgstr "Действие ключевых слов"
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
@@ -2691,11 +2797,14 @@ msgctxt "help:ir.rule.group,rules:"
msgid "The rule is satisfied if at least one test is True"
msgstr "Это правило выполняется, если по крайней мере одно правило истинно"
+#, fuzzy
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
+"Выражение Python вычисляется для записи \"self\".\n"
+"Оно запускает действие при значении \"True\"."
msgctxt "help:ir.trigger,limit_number:"
msgid ""
@@ -2812,6 +2921,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Установить / Обновить"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Свойства"
@@ -2888,6 +3001,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Графика"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Действие окна"
@@ -3003,9 +3120,10 @@ msgctxt "model:ir.lang,name:lang_fr"
msgid "French"
msgstr "Французкий"
+#, fuzzy
msgctxt "model:ir.lang,name:lang_hu_HU"
msgid "Hungarian"
-msgstr ""
+msgstr "Болгарский"
msgctxt "model:ir.lang,name:lang_it_IT"
msgid "Italian"
@@ -3061,34 +3179,43 @@ msgstr "Распечатать граф модели"
msgctxt "model:ir.module,name:"
msgid "Module"
-msgstr ""
+msgstr "Модуль"
+#, fuzzy
msgctxt "model:ir.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr ""
+msgstr "Мастер конфигурации модуля"
msgctxt "model:ir.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr ""
+msgstr "Мастер конфигурации модуля"
msgctxt "model:ir.module.config_wizard.item,name:"
msgid "Config wizard to run after installing module"
-msgstr ""
+msgstr "Запустить мастер конфигурации после установки модуля"
msgctxt "model:ir.module.config_wizard.other,name:"
msgid "Module Config Wizard Other"
-msgstr ""
+msgstr "Мастер конфигурации модуля"
msgctxt "model:ir.module.dependency,name:"
msgid "Module dependency"
-msgstr ""
+msgstr "Зависимости"
msgctxt "model:ir.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr ""
+msgstr "Установка (обновление) модуля завершено"
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
+msgstr "Начать установку (обновление) модуля"
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
msgstr ""
msgctxt "model:ir.property,name:"
@@ -3272,6 +3399,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Модули"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Свойства"
@@ -3364,9 +3495,10 @@ msgctxt "model:ir.ui.view_search,name:"
msgid "View Search"
msgstr "Просмотр поиска"
+#, fuzzy
msgctxt "model:ir.ui.view_tree_state,name:"
msgid "View Tree State"
-msgstr ""
+msgstr "Ширина вида список"
msgctxt "model:ir.ui.view_tree_width,name:"
msgid "View Tree Width"
@@ -3434,55 +3566,55 @@ msgstr "Справа на лево"
msgctxt "selection:ir.module,state:"
msgid "Installed"
-msgstr ""
+msgstr "Установлен"
msgctxt "selection:ir.module,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Не установлен"
msgctxt "selection:ir.module,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Для установки"
msgctxt "selection:ir.module,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Для удаления"
msgctxt "selection:ir.module,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Для обновления"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr ""
+msgstr "Выполнено"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
-msgstr ""
+msgstr "Открыть"
msgctxt "selection:ir.module.dependency,state:"
msgid "Installed"
-msgstr ""
+msgstr "Установлен"
msgctxt "selection:ir.module.dependency,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "Не установлен"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be installed"
-msgstr ""
+msgstr "Для установки"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be removed"
-msgstr ""
+msgstr "Для удаления"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "Для обновления"
msgctxt "selection:ir.module.dependency,state:"
msgid "Unknown"
-msgstr ""
+msgstr "Неизвестно"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
@@ -3524,9 +3656,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Модель"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Отчет"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3661,15 +3794,20 @@ msgctxt "view:ir.attachment:"
msgid "Attachments"
msgstr "Вложения"
+#, fuzzy
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
-msgstr ""
+msgstr "Последнее изменение"
msgctxt "view:ir.cron:"
msgid "Action to trigger"
msgstr "Действие триггера"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Запланированное действие"
@@ -3709,9 +3847,10 @@ msgctxt "view:ir.model.button:"
msgid "Buttons"
msgstr "Кнопки"
+#, fuzzy
msgctxt "view:ir.model.data:"
msgid "Model Data"
-msgstr ""
+msgstr "Данные модели"
msgctxt "view:ir.model.data:"
msgid "Sync"
@@ -3737,9 +3876,10 @@ msgctxt "view:ir.model:"
msgid "Model Description"
msgstr "Описание модели"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.done:"
msgid "Module configuration"
-msgstr ""
+msgstr "Конфигурация модуля"
msgctxt "view:ir.module.config_wizard.done:"
msgid "The configuration is done."
@@ -3747,82 +3887,106 @@ msgstr ""
msgctxt "view:ir.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
-msgstr ""
+msgstr "Добро пожаловать в мастер конфигурации модуля!"
+#, fuzzy
msgctxt "view:ir.module.config_wizard.first:"
msgid ""
"You will be able to configure your installation depending on the modules you"
" have installed."
msgstr ""
+"Вы сможете настроить базу данных в зависимости от установленных модулей."
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
-msgstr ""
+msgstr "Мастер конфигурации"
msgctxt "view:ir.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
-msgstr ""
+msgstr "Следующий шаг Мастера конфигурации!"
msgctxt "view:ir.module.dependency:"
msgid "Dependencies"
-msgstr ""
+msgstr "Зависимости"
msgctxt "view:ir.module.dependency:"
msgid "Dependency"
-msgstr ""
+msgstr "Зависимость"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "System Upgrade Done"
-msgstr ""
+msgstr "Обновление системы выполнено"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "The modules have been upgraded / installed !"
-msgstr ""
+msgstr "Модули были установлены (обновлены)!"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Note that this operation may take a few minutes."
-msgstr ""
+msgstr "Эта операция может занять несколько минут."
msgctxt "view:ir.module.install_upgrade.start:"
msgid "System Upgrade"
-msgstr ""
+msgstr "Обновление системы"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Your system will be upgraded."
-msgstr ""
+msgstr "База данных будет обновлена."
msgctxt "view:ir.module:"
msgid "Cancel Installation"
-msgstr ""
+msgstr "Отменить установку"
msgctxt "view:ir.module:"
msgid "Cancel Uninstallation"
-msgstr ""
+msgstr "Отмена удаления"
msgctxt "view:ir.module:"
msgid "Cancel Upgrade"
-msgstr ""
+msgstr "Отмена обновления"
msgctxt "view:ir.module:"
msgid "Mark for Installation"
-msgstr ""
+msgstr "Установить"
msgctxt "view:ir.module:"
msgid "Mark for Uninstallation (beta)"
-msgstr ""
+msgstr "Удалить (бета)"
msgctxt "view:ir.module:"
msgid "Mark for Upgrade"
-msgstr ""
+msgstr "Обновить"
msgctxt "view:ir.module:"
msgid "Module"
-msgstr ""
+msgstr "Модуль"
msgctxt "view:ir.module:"
msgid "Modules"
+msgstr "Модули"
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Дата"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Пользователь"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Свойства"
@@ -3972,9 +4136,10 @@ msgctxt "view:ir.ui.menu:"
msgid "Menu"
msgstr "Меню"
+#, fuzzy
msgctxt "view:ir.ui.view:"
msgid "Show"
-msgstr ""
+msgstr "Просмотр"
msgctxt "view:ir.ui.view:"
msgid "View"
@@ -3988,13 +4153,15 @@ msgctxt "view:ir.ui.view_search:"
msgid "View Searches"
msgstr "Просмотры поиска"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "View Tree State"
-msgstr ""
+msgstr "Ширина вида список"
+#, fuzzy
msgctxt "view:ir.ui.view_tree_state:"
msgid "Views Tree State"
-msgstr ""
+msgstr "Ширина вида список"
msgctxt "view:ir.ui.view_tree_width:"
msgid "View Tree Width"
@@ -4012,37 +4179,40 @@ msgctxt "wizard_button:ir.model.print_model_graph,start,print_:"
msgid "Print"
msgstr "Печать"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,done,end:"
msgid "OK"
-msgstr ""
+msgstr "Ок"
+#, fuzzy
msgctxt "wizard_button:ir.module.config_wizard,first,action:"
msgid "OK"
-msgstr ""
+msgstr "Ок"
msgctxt "wizard_button:ir.module.config_wizard,first,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Отменить"
msgctxt "wizard_button:ir.module.config_wizard,other,action:"
msgid "Next"
-msgstr ""
+msgstr "Далее"
msgctxt "wizard_button:ir.module.config_wizard,other,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Отменить"
+#, fuzzy
msgctxt "wizard_button:ir.module.install_upgrade,done,config:"
msgid "OK"
-msgstr ""
+msgstr "Ок"
msgctxt "wizard_button:ir.module.install_upgrade,start,end:"
msgid "Cancel"
-msgstr ""
+msgstr "Отменить"
msgctxt "wizard_button:ir.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
-msgstr ""
+msgstr "Начать установку (обновление)"
msgctxt "wizard_button:ir.translation.clean,start,clean:"
msgid "Clean"
diff --git a/trytond/ir/locale/sl_SI.po b/trytond/ir/locale/sl_SI.po
index c76857d..0abc790 100644
--- a/trytond/ir/locale/sl_SI.po
+++ b/trytond/ir/locale/sl_SI.po
@@ -4,14 +4,6 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "error:access_error:"
msgid ""
-"You try to bypass an access rule!\n"
-"(Document type: %s)"
-msgstr ""
-"Poskušate zaobiti pravilo dostopa.\n"
-"(Vrsta dokumenta: %s"
-
-msgctxt "error:access_error:"
-msgid ""
"You try to bypass an access rule.\n"
"(Document type: %s)"
msgstr ""
@@ -78,8 +70,9 @@ msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
msgstr "Neveljavna definicija epošte na izpisu \"%s\"."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
+msgid "The names of attachments must be unique by resource."
msgstr "Imena priponk morajo biti unikatna med resursi."
msgctxt "error:ir.cron:"
@@ -270,14 +263,6 @@ msgstr "Neveljaven XML za pogled \"%s\"."
msgctxt "error:read_error:"
msgid ""
-"You try to read records that don't exist anymore!\n"
-"(Document type: %s)"
-msgstr ""
-"Poskušate brati zapise, ki ne obstajajo več.\n"
-"(Vrsta dokumenta: %s)"
-
-msgctxt "error:read_error:"
-msgid ""
"You try to read records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
@@ -338,14 +323,6 @@ msgstr "Najdenih je preveč vez: %r v %s"
msgctxt "error:write_error:"
msgid ""
-"You try to write on records that don't exist anymore!\n"
-"(Document type: %s)"
-msgstr ""
-"Poskušate zapisati zapise, ki ne obstajajo več.\n"
-"(Vrsta dokumenta: %s)"
-
-msgctxt "error:write_error:"
-msgid ""
"You try to write on records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
@@ -436,6 +413,10 @@ msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
msgstr "Vrednost konteksta"
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
+
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
msgstr "Izdelano"
@@ -1688,6 +1669,102 @@ msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
msgstr "Moduli za posodobitev"
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "Izdelano"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "Izdelal"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "Zadnja sprememba"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "Zadnji uporabnik"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
+msgstr ""
+
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "Naziv"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "Vir"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "Zapisano"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "Zapisal"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
+msgid "Create Date"
+msgstr "Izdelano"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "Izdelal"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "ID"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "Naziv"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "Uporabnik"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "Zapisano"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "Zapisal"
+
msgctxt "field:ir.property,create_date:"
msgid "Create Date"
msgstr "Izdelano"
@@ -2846,6 +2923,10 @@ msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
msgstr "Namesti/nadgradi"
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
msgstr "Lastnosti"
@@ -2922,6 +3003,10 @@ msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
msgstr "Diagram"
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
+
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
msgstr "Ukrep za okna"
@@ -3124,6 +3209,14 @@ msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
msgstr "Začetek nadgradnje modula"
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
+msgstr ""
+
msgctxt "model:ir.property,name:"
msgid "Property"
msgstr "Lastnost"
@@ -3304,6 +3397,10 @@ msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
msgstr "Moduli"
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
+
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
msgstr "Lastnosti"
@@ -3465,10 +3562,6 @@ msgid "Right-to-left"
msgstr "Z desne proti levi"
msgctxt "selection:ir.module,state:"
-msgid ""
-msgstr ""
-
-msgctxt "selection:ir.module,state:"
msgid "Installed"
msgstr "Nameščeno"
@@ -3476,6 +3569,11 @@ msgctxt "selection:ir.module,state:"
msgid "Not Installed"
msgstr "Ni nameščeno"
+#, fuzzy
+msgctxt "selection:ir.module,state:"
+msgid "To be installed"
+msgstr "Za namestiti"
+
msgctxt "selection:ir.module,state:"
msgid "To be removed"
msgstr "Za odstraniti"
@@ -3556,9 +3654,10 @@ msgctxt "selection:ir.translation,type:"
msgid "Model"
msgstr "Model"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "Poročilo"
msgctxt "selection:ir.translation,type:"
msgid "Selection"
@@ -3701,6 +3800,10 @@ msgid "Action to trigger"
msgstr "Sprožitev ukrepa"
msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
+
+msgctxt "view:ir.cron:"
msgid "Scheduled Action"
msgstr "Načrtovan ukrep"
@@ -3854,6 +3957,28 @@ msgctxt "view:ir.module:"
msgid "Modules"
msgstr "Moduli"
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "view:ir.note:"
+msgid "Note"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "Uporabnik"
+
msgctxt "view:ir.property:"
msgid "Properties"
msgstr "Lastnosti"
diff --git a/trytond/ir/locale/nl_NL.po b/trytond/ir/locale/zh_CN.po
similarity index 72%
copy from trytond/ir/locale/nl_NL.po
copy to trytond/ir/locale/zh_CN.po
index 669c007..d9f5739 100644
--- a/trytond/ir/locale/nl_NL.po
+++ b/trytond/ir/locale/zh_CN.po
@@ -7,64 +7,67 @@ msgid ""
"You try to bypass an access rule.\n"
"(Document type: %s)"
msgstr ""
+"试图绕过操作权限规则.\n"
+"(文档类型: %s)"
msgctxt "error:delete_xml_record:"
msgid "You are not allowed to delete this record."
-msgstr "Het is u niet toegestaan dit item te verwijderen."
+msgstr "不允许删除此条记录"
msgctxt "error:digits_validation_record:"
msgid ""
"The number of digits \"%(digits)s\" of field \"%(field)s\" on \"%(value)s\" "
"exceeds its limit."
-msgstr ""
+msgstr " 数据\"%(field)s\"的值\"%(value)s\" 的位数\"%(digits)s\"超过限制."
msgctxt "error:domain_validation_record:"
msgid ""
"The value of the field \"%(field)s\" on \"%(model)s\" is not valid according"
" to its domain."
-msgstr ""
+msgstr "域设置下模型\"%(model)s\" 的数据 \"%(field)s\"无效."
msgctxt "error:foreign_model_exist:"
msgid ""
"Could not delete the records because they are used on field \"%(field)s\" of"
" \"%(model)s\"."
-msgstr ""
+msgstr "模型\"%(model)s\"的数据 \"%(field)s\" 正在使用,无法删除项目."
msgctxt "error:foreign_model_missing:"
msgid "The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" doesn't exist."
-msgstr ""
+msgstr "模型\"%(model)s\"的数据 \"%(field)s\" 的值\"%(value)s\" 不存在."
msgctxt "error:ir.action.act_window.domain:"
msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
-msgstr ""
+msgstr "动作\"%(action)s\"的域配置或查找表达式\"%(domain)s\"无效."
msgctxt "error:ir.action.act_window:"
msgid "Invalid context \"%(context)s\" on action \"%(action)s\"."
-msgstr ""
+msgstr "动作\"%(action)s\"的条件\"%(context)s\"设置无效."
msgctxt "error:ir.action.act_window:"
msgid "Invalid domain or search criteria \"%(domain)s\" on action \"%(action)s\"."
-msgstr ""
+msgstr "动作\"%(action)s\"的域配置或查找表达式\"%(domain)s\"无效."
msgctxt "error:ir.action.act_window:"
msgid "Invalid view \"%(view)s\" for action \"%(action)s\"."
-msgstr ""
+msgstr "动作\"%(action)s\"的视图\"%(view)s\"无效."
msgctxt "error:ir.action.keyword:"
msgid "Wrong wizard model in keyword action \"%s\"."
-msgstr ""
+msgstr "关键字操作\"%s\"的向导模型有误."
msgctxt "error:ir.action.report:"
msgid "Invalid email definition on report \"%s\"."
-msgstr ""
+msgstr "报表\"%s\"的email定义无效."
+#, fuzzy
msgctxt "error:ir.attachment:"
-msgid "The names of attachments must be unique by resource!"
-msgstr "De namen van de bijlagen moeten uniek zijn per bron!"
+msgid "The names of attachments must be unique by resource."
+msgstr "附件文档名必须唯一 !"
msgctxt "error:ir.cron:"
msgid "Scheduled action failed"
-msgstr "Geplande actie mislukt"
+msgstr "计划动作失败"
msgctxt "error:ir.cron:"
msgid ""
@@ -74,3525 +77,3574 @@ msgid ""
"\n"
"%s\n"
msgstr ""
+"下列动作没有执行: \"%s\"\n"
+"%s\n"
+"Traceback: \n"
+"\n"
+"%s\n"
msgctxt "error:ir.lang:"
msgid "Default language can not be deleted."
-msgstr ""
+msgstr "默认语言不可删除."
msgctxt "error:ir.lang:"
msgid "Invalid date format \"%(format)s\" on \"%(language)s\" language."
-msgstr ""
+msgstr "语言\"%(language)s\"中的日期格式 \"%(format)s\"设置无效."
msgctxt "error:ir.lang:"
msgid "Invalid grouping \"%(grouping)s\" on \"%(language)s\" language."
-msgstr ""
+msgstr "语言\"%(language)s\"中的分组 \"%(grouping)s\"设置无效."
msgctxt "error:ir.lang:"
msgid "The default language must be translatable."
-msgstr ""
+msgstr "默认语言应设置为可翻译成其他语言."
msgctxt "error:ir.lang:"
msgid "decimal_point and thousands_sep must be different!"
-msgstr "Decimaal teken en duizendtal teken moet verschillend zijn!"
+msgstr "小数点和千位分隔符不能相同!"
msgctxt "error:ir.model.access:"
msgid "You can not create this kind of document! (%s)"
-msgstr "U kunt dit type document niet aanmaken! (%s)"
+msgstr "(%s) 类型文档无创建权限!"
msgctxt "error:ir.model.access:"
msgid "You can not delete this document! (%s)"
-msgstr "U kunt dit document niet verwijderen! (%s)"
+msgstr "文件 (%s) 无删除权限!"
msgctxt "error:ir.model.access:"
msgid "You can not read this document! (%s)"
-msgstr "U kunt dit document openen! (%s)"
+msgstr "文件 (%s) 无读取权限!"
msgctxt "error:ir.model.access:"
msgid "You can not write in this document! (%s)"
-msgstr "U kunt dit document niet muteren! (%s)"
+msgstr "文件 (%s) 无写入权限!"
msgctxt "error:ir.model.button:"
msgid "The button name in model must be unique!"
-msgstr ""
+msgstr "模型中按钮的名称必须唯一!"
msgctxt "error:ir.model.data:"
msgid "The triple (fs_id, module, model) must be unique!"
-msgstr "Het drietal (fs_id, module, model) moet uniek zijn!"
+msgstr " (fs_id, module, model) 组合必须唯一!"
msgctxt "error:ir.model.field.access:"
msgid "You can not read the field! (%s.%s)"
-msgstr ""
+msgstr "数据 (%s.%s) 无读取权限 !"
msgctxt "error:ir.model.field.access:"
msgid "You can not write on the field! (%s.%s)"
-msgstr ""
+msgstr "数据 (%s.%s) 无写入权限 !"
msgctxt "error:ir.model.field:"
msgid "Model Field name \"%s\" is not a valid python identifier."
-msgstr ""
+msgstr "模型数据值 \"%s\" 不是合法的python identifier."
msgctxt "error:ir.model.field:"
msgid "The field name in model must be unique!"
-msgstr "De veldnaam in het model moet uniek zijn!"
+msgstr "模型的数据名称必须唯一!"
msgctxt "error:ir.model:"
msgid "Module name \"%s\" is not a valid python identifier."
-msgstr ""
+msgstr "模块名 \"%s\" 不是合法的python identifier."
msgctxt "error:ir.model:"
msgid "The model must be unique!"
-msgstr "Het model moet uniek zijn!"
+msgstr "模型必须唯一!"
msgctxt "error:ir.module.dependency:"
msgid "Dependency must be unique by module!"
-msgstr ""
+msgstr "模块的依赖对象必须唯一!"
msgctxt "error:ir.module:"
msgid "Missing dependencies %s for module \"%s\""
-msgstr ""
+msgstr "模块\"%s\"的依赖 %s缺失"
msgctxt "error:ir.module:"
msgid "The modules you are trying to uninstall depends on installed modules:"
-msgstr ""
+msgstr "卸载模块同已安装模块的依赖关系:"
msgctxt "error:ir.module:"
msgid "The name of the module must be unique!"
-msgstr ""
+msgstr "模块名称必须唯一!"
msgctxt "error:ir.module:"
msgid "You can not remove a module that is installed or will be installed"
-msgstr ""
+msgstr "无法移除未安装或者待装状态的模块"
msgctxt "error:ir.rule.group:"
msgid "Global and Default are mutually exclusive!"
-msgstr "Globaal en standaard sluiten elkaar uit!"
+msgstr "全局和默认是互斥的关系!"
msgctxt "error:ir.rule:"
msgid "Invalid domain in rule \"%s\"."
-msgstr ""
+msgstr "规则\"%s\"的域配置无效."
msgctxt "error:ir.sequence.strict:"
msgid "Invalid prefix \"%(prefix)s\" on sequence \"%(sequence)s\"."
-msgstr ""
+msgstr "序列 \"%(sequence)s\"前缀\"%(prefix)s\"无效."
msgctxt "error:ir.sequence.strict:"
msgid "Invalid suffix \"%(suffix)s\" on sequence \"%(sequence)s\"."
-msgstr ""
+msgstr "序列 \"%(sequence)s\"后缀\"%(suffix)s\"无效."
msgctxt "error:ir.sequence.strict:"
msgid "Last Timestamp cannot be in the future on sequence \"%s\"."
-msgstr ""
+msgstr "上一时间戳不可用在序列\"%s\"将来态."
msgctxt "error:ir.sequence.strict:"
msgid "Missing sequence."
-msgstr ""
+msgstr "序列缺失."
msgctxt "error:ir.sequence.strict:"
msgid "Timestamp rounding should be greater than 0"
-msgstr ""
+msgstr "时间戳的近似整数应大于0"
msgctxt "error:ir.sequence:"
msgid "Invalid prefix \"%(prefix)s\" on sequence \"%(sequence)s\"."
-msgstr ""
+msgstr "序列 \"%(sequence)s\"前缀\"%(prefix)s\"无效."
msgctxt "error:ir.sequence:"
msgid "Invalid suffix \"%(suffix)s\" on sequence \"%(sequence)s\"."
-msgstr ""
+msgstr "序列 \"%(sequence)s\"后缀\"%(suffix)s\"无效."
msgctxt "error:ir.sequence:"
msgid "Last Timestamp cannot be in the future on sequence \"%s\"."
-msgstr ""
+msgstr "上一时间戳不可用在序列\"%s\"将来态."
msgctxt "error:ir.sequence:"
msgid "Missing sequence."
-msgstr ""
+msgstr "序列缺失."
msgctxt "error:ir.sequence:"
msgid "Timestamp rounding should be greater than 0"
-msgstr ""
+msgstr "时间戳的近似整数应大于0"
msgctxt "error:ir.translation:"
msgid "Translation must be unique"
-msgstr "Vertaling moet uniek zijn!"
+msgstr "翻译字符串必须唯一!"
msgctxt "error:ir.translation:"
msgid ""
"You can not export translation %(name)s because it is an overridden "
"translation by module %(overriding_module)s"
-msgstr ""
+msgstr "翻译%(name)s源于模块%(overriding_module)s的条目替换,无法导出."
msgctxt "error:ir.trigger:"
msgid "\"On Time\" and others are mutually exclusive!"
-msgstr "\"Op tijd\" en anderen sluiten elkaar uit!"
+msgstr "“On Time\"和其他触发器互不相容."
msgctxt "error:ir.trigger:"
msgid ""
"Condition \"%(condition)s\" is not a valid PYSON expression on trigger "
"\"%(trigger)s\"."
-msgstr ""
+msgstr "触发器\"%(trigger)s\"的条件\"%(condition)s\"配置不是合法的PYSON表达式."
msgctxt "error:ir.ui.menu:"
msgid "\"%s\" is not a valid menu name because it is not allowed to contain \" / \"."
-msgstr ""
+msgstr "\"%s\"含有\" / \",不可作为菜单名称."
msgctxt "error:ir.ui.view:"
msgid "Invalid XML for view \"%s\"."
-msgstr ""
+msgstr "视图\"%s\"的XML表达不合法."
msgctxt "error:read_error:"
msgid ""
"You try to read records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
+"试图读取不存在的项目.\n"
+"(文件类型: %s)"
msgctxt "error:recursion_error:"
msgid ""
"Recursion error: Record \"%(rec_name)s\" with parent \"%(parent_rec_name)s\""
" was configured as ancestor of itself."
-msgstr ""
+msgstr "递归错误:数据 \"%(rec_name)s不可同时为\"%(parent_rec_name)s\"的父级和子级."
msgctxt "error:reference_syntax_error:"
msgid "Syntax error for reference %r in %s"
-msgstr "Foutieve verwijzing voor %r in %s"
+msgstr "reference %r in %s 语法错误"
msgctxt "error:relation_not_found:"
msgid "Relation not found: %r in %s"
-msgstr "Relatie niet gevonden: %r in %s"
+msgstr "不是包含关系: %r in %s"
msgctxt "error:required_field:"
msgid "The field \"%(field)s\" on \"%(model)s\" is required."
-msgstr ""
+msgstr "模型 \"%(model)s\" 的 \"%(field)s\"为必填数据."
msgctxt "error:required_validation_record:"
msgid "The field \"%(field)s\" on \"%(model)s\" is required."
-msgstr ""
+msgstr "模型 \"%(model)s\" 的 \"%(field)s\"为必填数据."
msgctxt "error:search_function_missing:"
msgid "Missing search function on field \"%s\"."
-msgstr "Zoekfunctie ontbreekt voor veld \" %s\"."
+msgstr "数据 \"%s\" 缺少搜索方法."
msgctxt "error:selection_validation_record:"
msgid ""
"The value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" is not in "
"the selection."
-msgstr ""
+msgstr "此段不含模型\"%(model)s\"的数据\"%(field)s\"的值\"%(value)s\" ."
msgctxt "error:selection_value_notfound:"
msgid "Value not in the selection for field \"%s\"."
-msgstr ""
+msgstr "此段含数据 \"%s\"的值."
msgctxt "error:size_validation_record:"
msgid "The size \"%(size)s\" of the field \"%(field)s\" on \"%(model)s\" is too long."
-msgstr ""
+msgstr "模型\"%(model)s\"的数据 \"%(field)s\"的大小\"%(size)s\"位数太长."
msgctxt "error:time_format_validation_record:"
msgid "The time value \"%(value)s\" of field \"%(field)s\" on \"%(model)s\" is not valid."
-msgstr ""
+msgstr "模型\"%(model)s\"的数据 \"%(field)s\" 的时间值\"%(value)s\" 不存在."
msgctxt "error:too_many_relations_found:"
msgid "Too many relations found: %r in %s"
-msgstr "Te veel relaties gevonden: %r in %s"
+msgstr "关联太多: %r in %s"
msgctxt "error:write_error:"
msgid ""
"You try to write on records that don't exist anymore.\n"
"(Document type: %s)"
msgstr ""
+"试图写入不存在的项目.\n"
+"(文件类型: %s)"
msgctxt "error:write_xml_record:"
msgid "You are not allowed to modify this record."
-msgstr "Het is u niet toegestaan dit item te muteren."
+msgstr "不允许修改此条记录."
msgctxt "error:xml_id_syntax_error:"
msgid "Syntax error for XML id %r in %s"
-msgstr "Foutieve verwijzing voor XML id %r in %s"
+msgstr " XML id %r in %s 语法错误"
msgctxt "error:xml_record_desc:"
msgid "This record is part of the base configuration."
-msgstr "Dit item is onderdeel van de basis configuratie."
+msgstr "此项是基本设置."
msgctxt "field:ir.action,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.action,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期:"
msgctxt "field:ir.action,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.action,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
-#, fuzzy
msgctxt "field:ir.action,icon:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "field:ir.action,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.action,keywords:"
msgid "Keywords"
-msgstr "Trefwoorden"
+msgstr "关键词"
msgctxt "field:ir.action,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.action,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.action,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
msgctxt "field:ir.action,usage:"
msgid "Usage"
-msgstr "Gebruik"
+msgstr "用法"
msgctxt "field:ir.action,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.action.act_window,act_window_domains:"
msgid "Domains"
-msgstr ""
+msgstr "域配置"
msgctxt "field:ir.action.act_window,act_window_views:"
msgid "Views"
-msgstr "Aanzichten"
+msgstr "视图"
msgctxt "field:ir.action.act_window,action:"
msgid "Action"
-msgstr "Actie"
+msgstr "操作"
-#, fuzzy
msgctxt "field:ir.action.act_window,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.action.act_window,context:"
msgid "Context Value"
-msgstr "Samenhang waarde"
+msgstr "场景值"
+
+msgctxt "field:ir.action.act_window,context_model:"
+msgid "Context Model"
+msgstr ""
msgctxt "field:ir.action.act_window,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期:"
msgctxt "field:ir.action.act_window,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.action.act_window,domain:"
msgid "Domain Value"
-msgstr "Domein waarde"
+msgstr "域配置"
msgctxt "field:ir.action.act_window,domains:"
msgid "Domains"
-msgstr ""
+msgstr "域配置"
-#, fuzzy
msgctxt "field:ir.action.act_window,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
-#, fuzzy
msgctxt "field:ir.action.act_window,icon:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "field:ir.action.act_window,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.action.act_window,keywords:"
msgid "Keywords"
-msgstr "Trefwoorden"
+msgstr "关键词"
msgctxt "field:ir.action.act_window,limit:"
msgid "Limit"
-msgstr "Begrenzing"
+msgstr "限制"
-#, fuzzy
msgctxt "field:ir.action.act_window,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.action.act_window,order:"
msgid "Order Value"
-msgstr ""
+msgstr "顺序"
msgctxt "field:ir.action.act_window,pyson_context:"
msgid "PySON Context"
-msgstr "PySON verband"
+msgstr "PYSON 场景"
msgctxt "field:ir.action.act_window,pyson_domain:"
msgid "PySON Domain"
-msgstr "PySON domein"
+msgstr "PYSON 域"
msgctxt "field:ir.action.act_window,pyson_order:"
msgid "PySON Order"
-msgstr ""
+msgstr "PySON 顺序"
msgctxt "field:ir.action.act_window,pyson_search_value:"
msgid "PySON Search Criteria"
-msgstr "PySON zoekargument"
+msgstr "PySON查找表达式"
msgctxt "field:ir.action.act_window,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.action.act_window,res_model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.action.act_window,search_value:"
msgid "Search Criteria"
-msgstr "Zoekargumenten"
+msgstr "查找表达式"
-#, fuzzy
msgctxt "field:ir.action.act_window,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
-#, fuzzy
msgctxt "field:ir.action.act_window,usage:"
msgid "Usage"
-msgstr "Gebruik"
+msgstr "用法"
msgctxt "field:ir.action.act_window,views:"
msgid "Views"
-msgstr "Aanzichten"
+msgstr "视图"
msgctxt "field:ir.action.act_window,window_name:"
msgid "Window Name"
-msgstr "Aanzicht naam"
+msgstr "窗口"
msgctxt "field:ir.action.act_window,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action.act_window,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
-#, fuzzy
msgctxt "field:ir.action.act_window.domain,act_window:"
msgid "Action"
-msgstr "Actie"
+msgstr "动作"
-#, fuzzy
msgctxt "field:ir.action.act_window.domain,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.action.act_window.domain,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期:"
msgctxt "field:ir.action.act_window.domain,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.action.act_window.domain,domain:"
msgid "Domain"
-msgstr "Domein"
+msgstr "域配置"
msgctxt "field:ir.action.act_window.domain,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.action.act_window.domain,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.action.act_window.domain,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.action.act_window.domain,sequence:"
msgid "Sequence"
-msgstr "Reeks"
+msgstr "序列"
msgctxt "field:ir.action.act_window.domain,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action.act_window.domain,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.action.act_window.view,act_window:"
msgid "Action"
-msgstr "Actie"
+msgstr "动作"
-#, fuzzy
msgctxt "field:ir.action.act_window.view,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.action.act_window.view,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期:"
msgctxt "field:ir.action.act_window.view,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.action.act_window.view,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.action.act_window.view,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.action.act_window.view,sequence:"
msgid "Sequence"
-msgstr "Reeks"
+msgstr "序列"
msgctxt "field:ir.action.act_window.view,view:"
msgid "View"
-msgstr "Overzicht"
+msgstr "视图"
msgctxt "field:ir.action.act_window.view,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action.act_window.view,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.action.keyword,action:"
msgid "Action"
-msgstr "Actie"
+msgstr "动作"
msgctxt "field:ir.action.keyword,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.action.keyword,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.action.keyword,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
msgctxt "field:ir.action.keyword,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.action.keyword,keyword:"
msgid "Keyword"
-msgstr "Trefwoord"
+msgstr "关键词"
msgctxt "field:ir.action.keyword,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.action.keyword,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.action.keyword,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action.keyword,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.action.report,action:"
msgid "Action"
-msgstr "Actie"
+msgstr "动作"
-#, fuzzy
msgctxt "field:ir.action.report,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.action.report,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.action.report,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.action.report,direct_print:"
msgid "Direct Print"
-msgstr "Direct afdrukken"
+msgstr "直接打印"
msgctxt "field:ir.action.report,email:"
msgid "Email"
-msgstr "E-mail"
+msgstr "电子邮箱"
msgctxt "field:ir.action.report,extension:"
msgid "Extension"
-msgstr "Uitbreiding"
+msgstr "扩展"
-#, fuzzy
msgctxt "field:ir.action.report,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
-#, fuzzy
msgctxt "field:ir.action.report,icon:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "field:ir.action.report,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.action.report,keywords:"
msgid "Keywords"
-msgstr "Trefwoorden"
+msgstr "关键词"
msgctxt "field:ir.action.report,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.action.report,module:"
msgid "Module"
-msgstr "Module"
+msgstr "模块"
-#, fuzzy
msgctxt "field:ir.action.report,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.action.report,pyson_email:"
msgid "PySON Email"
-msgstr ""
+msgstr "PySON 邮箱"
msgctxt "field:ir.action.report,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.action.report,report:"
msgid "Path"
-msgstr "Pad"
+msgstr "路径"
msgctxt "field:ir.action.report,report_content:"
msgid "Content"
-msgstr "Inhoud"
+msgstr "内容"
-#, fuzzy
msgctxt "field:ir.action.report,report_content_custom:"
msgid "Content"
-msgstr "Inhoud"
+msgstr "内容"
msgctxt "field:ir.action.report,report_content_name:"
msgid "Content Name"
-msgstr ""
+msgstr "内容标识"
msgctxt "field:ir.action.report,report_name:"
msgid "Internal Name"
-msgstr "Interne naam"
+msgstr "内部标识"
msgctxt "field:ir.action.report,template_extension:"
msgid "Template Extension"
-msgstr ""
+msgstr "模版扩展"
-#, fuzzy
msgctxt "field:ir.action.report,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
-#, fuzzy
msgctxt "field:ir.action.report,usage:"
msgid "Usage"
-msgstr "Gebruik"
+msgstr "用法"
msgctxt "field:ir.action.report,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action.report,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.action.url,action:"
msgid "Action"
-msgstr "Actie"
+msgstr "动作"
-#, fuzzy
msgctxt "field:ir.action.url,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.action.url,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.action.url,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.action.url,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
-#, fuzzy
msgctxt "field:ir.action.url,icon:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "field:ir.action.url,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.action.url,keywords:"
msgid "Keywords"
-msgstr "Trefwoorden"
+msgstr "关键词"
-#, fuzzy
msgctxt "field:ir.action.url,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.action.url,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.action.url,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
msgctxt "field:ir.action.url,url:"
msgid "Action Url"
-msgstr "Actie URL"
+msgstr "动作 Url"
-#, fuzzy
msgctxt "field:ir.action.url,usage:"
msgid "Usage"
-msgstr "Gebruik"
+msgstr "用法"
msgctxt "field:ir.action.url,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action.url,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.action.wizard,action:"
msgid "Action"
-msgstr "Actie"
+msgstr "动作"
-#, fuzzy
msgctxt "field:ir.action.wizard,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.action.wizard,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.action.wizard,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.action.wizard,email:"
msgid "Email"
-msgstr "E-mail"
+msgstr "电子邮箱"
-#, fuzzy
msgctxt "field:ir.action.wizard,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
-#, fuzzy
msgctxt "field:ir.action.wizard,icon:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "field:ir.action.wizard,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.action.wizard,keywords:"
msgid "Keywords"
-msgstr "Trefwoorden"
+msgstr "关键词"
msgctxt "field:ir.action.wizard,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
-#, fuzzy
msgctxt "field:ir.action.wizard,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.action.wizard,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.action.wizard,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
-#, fuzzy
msgctxt "field:ir.action.wizard,usage:"
msgid "Usage"
-msgstr "Gebruik"
+msgstr "用法"
msgctxt "field:ir.action.wizard,window:"
msgid "Window"
-msgstr "Scherm"
+msgstr "窗口"
msgctxt "field:ir.action.wizard,wiz_name:"
msgid "Wizard name"
-msgstr "Assistent naam"
+msgstr "向导名称"
msgctxt "field:ir.action.wizard,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action.wizard,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.attachment,collision:"
msgid "Collision"
-msgstr "Botsing"
+msgstr "冲突"
msgctxt "field:ir.attachment,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.attachment,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.attachment,data:"
msgid "Data"
-msgstr ""
+msgstr "数据"
msgctxt "field:ir.attachment,data_size:"
msgid "Data size"
-msgstr ""
+msgstr "大小"
msgctxt "field:ir.attachment,description:"
msgid "Description"
-msgstr "Omschrijving"
+msgstr "描述"
msgctxt "field:ir.attachment,digest:"
msgid "Digest"
-msgstr "Verwerken"
+msgstr "吸收"
msgctxt "field:ir.attachment,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.attachment,last_modification:"
msgid "Last Modification"
-msgstr ""
+msgstr "最近更改"
msgctxt "field:ir.attachment,last_user:"
msgid "Last User"
-msgstr ""
+msgstr "最近用户"
msgctxt "field:ir.attachment,link:"
msgid "Link"
-msgstr "Verbinding"
+msgstr "链接"
-#, fuzzy
msgctxt "field:ir.attachment,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.attachment,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.attachment,resource:"
msgid "Resource"
-msgstr "Middel"
+msgstr "附件文档资源"
msgctxt "field:ir.attachment,summary:"
msgid "Summary"
-msgstr ""
+msgstr "摘要"
-#, fuzzy
msgctxt "field:ir.attachment,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
msgctxt "field:ir.attachment,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.attachment,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.cache,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.cache,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.cache,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.cache,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.cache,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.cache,timestamp:"
msgid "Timestamp"
-msgstr "Tijdmarkering"
+msgstr "时间戳"
msgctxt "field:ir.cache,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.cache,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.configuration,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.configuration,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.configuration,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.configuration,language:"
msgid "language"
-msgstr ""
+msgstr "语言"
-#, fuzzy
msgctxt "field:ir.configuration,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.configuration,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.configuration,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.cron,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.cron,args:"
msgid "Arguments"
-msgstr "Argumenten"
+msgstr "参数"
msgctxt "field:ir.cron,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.cron,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.cron,function:"
msgid "Function"
-msgstr "Functie"
+msgstr "方法"
msgctxt "field:ir.cron,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.cron,interval_number:"
msgid "Interval Number"
-msgstr "Interval nummer"
+msgstr "间隔"
msgctxt "field:ir.cron,interval_type:"
msgid "Interval Unit"
-msgstr "Interval eenheid"
+msgstr "单位"
msgctxt "field:ir.cron,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.cron,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.cron,next_call:"
msgid "Next Call"
-msgstr ""
+msgstr "下次调用"
msgctxt "field:ir.cron,number_calls:"
msgid "Number of Calls"
-msgstr ""
+msgstr "调用次数"
msgctxt "field:ir.cron,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.cron,repeat_missed:"
msgid "Repeat Missed"
-msgstr ""
+msgstr "重启错过项目"
msgctxt "field:ir.cron,request_user:"
msgid "Request User"
-msgstr "Verzoek gebruiker"
+msgstr "请求用户"
msgctxt "field:ir.cron,user:"
msgid "Execution User"
-msgstr "Uitvoerende gebruiker"
+msgstr "执行用户"
msgctxt "field:ir.cron,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.cron,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.date,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.export,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.export,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.export,export_fields:"
msgid "Fields"
-msgstr "Velden"
+msgstr "数据"
msgctxt "field:ir.export,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.export,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.export,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.export,resource:"
msgid "Resource"
-msgstr "Middel"
+msgstr "资源"
msgctxt "field:ir.export,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.export,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.export.line,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.export.line,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.export.line,export:"
msgid "Export"
-msgstr "Exporteren"
+msgstr "导出"
msgctxt "field:ir.export.line,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.export.line,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.export.line,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.export.line,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.export.line,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.lang,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.lang,code:"
msgid "Code"
-msgstr "Code"
+msgstr "语言编码"
msgctxt "field:ir.lang,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.lang,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.lang,date:"
msgid "Date"
-msgstr "Datum"
+msgstr "日期格式"
msgctxt "field:ir.lang,decimal_point:"
msgid "Decimal Separator"
-msgstr "Decimaalteken"
+msgstr "小数点"
msgctxt "field:ir.lang,direction:"
msgid "Direction"
-msgstr "Richting"
+msgstr "语言方向"
msgctxt "field:ir.lang,grouping:"
msgid "Grouping"
-msgstr "Groeperen"
+msgstr "类别"
msgctxt "field:ir.lang,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.lang,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.lang,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.lang,thousands_sep:"
msgid "Thousands Separator"
-msgstr "Duizendtal teken"
+msgstr "千位分隔符"
msgctxt "field:ir.lang,translatable:"
msgid "Translatable"
-msgstr "Vertaalbaar"
+msgstr "可翻译"
msgctxt "field:ir.lang,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.lang,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.model,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.model,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.model,fields:"
msgid "Fields"
-msgstr "Velden"
+msgstr "数据"
msgctxt "field:ir.model,global_search_p:"
msgid "Global Search"
-msgstr ""
+msgstr "全局查找"
msgctxt "field:ir.model,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.model,info:"
msgid "Information"
-msgstr "Informatie"
+msgstr "信息"
msgctxt "field:ir.model,model:"
msgid "Model Name"
-msgstr "Naam module"
+msgstr "模型名称"
msgctxt "field:ir.model,module:"
msgid "Module"
-msgstr "Module"
+msgstr "模块"
msgctxt "field:ir.model,name:"
msgid "Model Description"
-msgstr "Model omschrijving"
+msgstr "模型描述"
msgctxt "field:ir.model,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.model,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.model,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.model.access,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.model.access,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.model.access,description:"
msgid "Description"
-msgstr "Omschrijving"
+msgstr "描述"
msgctxt "field:ir.model.access,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:ir.model.access,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.model.access,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.model.access,perm_create:"
msgid "Create Access"
-msgstr "Mag aanmaken"
+msgstr "新建"
msgctxt "field:ir.model.access,perm_delete:"
msgid "Delete Access"
-msgstr "Toegang verwijderen"
+msgstr "删除"
msgctxt "field:ir.model.access,perm_read:"
msgid "Read Access"
-msgstr "Leesrecht"
+msgstr "读取"
msgctxt "field:ir.model.access,perm_write:"
msgid "Write Access"
-msgstr "Schrijfrecht"
+msgstr "写入"
msgctxt "field:ir.model.access,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.model.access,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.model.access,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.model.button,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.model.button,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.model.button,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
msgctxt "field:ir.model.button,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.model.button,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
-#, fuzzy
msgctxt "field:ir.model.button,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.model.button,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.model.button,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.model.button,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.model.data,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.model.data,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.model.data,db_id:"
msgid "Resource ID"
-msgstr "Middel ID"
+msgstr "资源 ID"
msgctxt "field:ir.model.data,fs_id:"
msgid "Identifier on File System"
-msgstr "Kenmerk voor bestandssysteem"
+msgstr "文件系统标识"
msgctxt "field:ir.model.data,fs_values:"
msgid "Values on File System"
-msgstr ""
+msgstr "文件系统值"
msgctxt "field:ir.model.data,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.model.data,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.model.data,module:"
msgid "Module"
-msgstr "Module"
+msgstr "模块"
msgctxt "field:ir.model.data,noupdate:"
msgid "No Update"
-msgstr "Niet bij te werken"
+msgstr "不更新"
msgctxt "field:ir.model.data,out_of_sync:"
msgid "Out of Sync"
-msgstr ""
+msgstr "未同步"
msgctxt "field:ir.model.data,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.model.data,values:"
msgid "Values"
-msgstr "Waarden"
+msgstr "值"
msgctxt "field:ir.model.data,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.model.data,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.model.field,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.model.field,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.model.field,field_description:"
msgid "Field Description"
-msgstr "Veld omschrijving"
+msgstr "数据描述"
msgctxt "field:ir.model.field,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
msgctxt "field:ir.model.field,help:"
msgid "Help"
-msgstr "Help"
+msgstr "帮助"
msgctxt "field:ir.model.field,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.model.field,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.model.field,module:"
msgid "Module"
-msgstr "Module"
+msgstr "模块"
msgctxt "field:ir.model.field,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.model.field,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.model.field,relation:"
msgid "Model Relation"
-msgstr "Model relatie"
+msgstr "模型关联"
msgctxt "field:ir.model.field,ttype:"
msgid "Field Type"
-msgstr "Veld type"
+msgstr "数据类型"
msgctxt "field:ir.model.field,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.model.field,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.model.field.access,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.model.field.access,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.model.field.access,description:"
msgid "Description"
-msgstr "Specificatie"
+msgstr "描述"
-#, fuzzy
msgctxt "field:ir.model.field.access,field:"
msgid "Field"
-msgstr "Veld"
+msgstr "数据"
-#, fuzzy
msgctxt "field:ir.model.field.access,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:ir.model.field.access,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.model.field.access,perm_create:"
msgid "Create Access"
-msgstr "Mag aanmaken"
+msgstr "新建"
-#, fuzzy
msgctxt "field:ir.model.field.access,perm_delete:"
msgid "Delete Access"
-msgstr "Toegang verwijderen"
+msgstr "删除"
-#, fuzzy
msgctxt "field:ir.model.field.access,perm_read:"
msgid "Read Access"
-msgstr "Leesrecht"
+msgstr "读取"
-#, fuzzy
msgctxt "field:ir.model.field.access,perm_write:"
msgid "Write Access"
-msgstr "Schrijfrecht"
+msgstr "写入"
-#, fuzzy
msgctxt "field:ir.model.field.access,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.model.field.access,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.model.field.access,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.model.print_model_graph.start,filter:"
msgid "Filter"
-msgstr ""
+msgstr "筛选"
msgctxt "field:ir.model.print_model_graph.start,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.model.print_model_graph.start,level:"
msgid "Level"
-msgstr ""
+msgstr "层级"
msgctxt "field:ir.module,childs:"
msgid "Childs"
-msgstr ""
+msgstr "子项"
msgctxt "field:ir.module,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.module,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.module,dependencies:"
msgid "Dependencies"
-msgstr ""
+msgstr "依赖"
msgctxt "field:ir.module,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.module,name:"
msgid "Name"
-msgstr ""
+msgstr "名称"
msgctxt "field:ir.module,parents:"
msgid "Parents"
-msgstr ""
+msgstr "父级"
msgctxt "field:ir.module,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "名称"
msgctxt "field:ir.module,state:"
msgid "State"
-msgstr ""
+msgstr "状态"
msgctxt "field:ir.module,version:"
msgid "Version"
-msgstr ""
+msgstr "版本"
msgctxt "field:ir.module,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.module,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.module.config_wizard.done,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.module.config_wizard.first,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.module.config_wizard.item,action:"
msgid "Action"
-msgstr ""
+msgstr "动作"
msgctxt "field:ir.module.config_wizard.item,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.module.config_wizard.item,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.module.config_wizard.item,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.module.config_wizard.item,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "名称"
msgctxt "field:ir.module.config_wizard.item,sequence:"
msgid "Sequence"
-msgstr ""
+msgstr "序列"
msgctxt "field:ir.module.config_wizard.item,state:"
msgid "State"
-msgstr ""
+msgstr "状态"
msgctxt "field:ir.module.config_wizard.item,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.module.config_wizard.item,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.module.config_wizard.other,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.module.config_wizard.other,percentage:"
msgid "Percentage"
-msgstr ""
+msgstr "百分比"
msgctxt "field:ir.module.dependency,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.module.dependency,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "添加用户"
msgctxt "field:ir.module.dependency,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.module.dependency,module:"
msgid "Module"
-msgstr ""
+msgstr "模块"
msgctxt "field:ir.module.dependency,name:"
msgid "Name"
-msgstr ""
+msgstr "名称"
msgctxt "field:ir.module.dependency,rec_name:"
msgid "Name"
-msgstr ""
+msgstr "名称"
msgctxt "field:ir.module.dependency,state:"
msgid "State"
-msgstr ""
+msgstr "状态"
msgctxt "field:ir.module.dependency,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.module.dependency,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.module.install_upgrade.done,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.module.install_upgrade.start,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.module.install_upgrade.start,module_info:"
msgid "Modules to update"
+msgstr "待更新模块"
+
+#, fuzzy
+msgctxt "field:ir.note,create_date:"
+msgid "Create Date"
+msgstr "创建日期:"
+
+#, fuzzy
+msgctxt "field:ir.note,create_uid:"
+msgid "Create User"
+msgstr "创建者"
+
+#, fuzzy
+msgctxt "field:ir.note,id:"
+msgid "ID"
+msgstr "标识"
+
+#, fuzzy
+msgctxt "field:ir.note,last_modification:"
+msgid "Last Modification"
+msgstr "最近更改"
+
+#, fuzzy
+msgctxt "field:ir.note,last_user:"
+msgid "Last User"
+msgstr "最近用户"
+
+msgctxt "field:ir.note,message:"
+msgid "Message"
msgstr ""
-msgctxt "field:ir.property,create_date:"
+msgctxt "field:ir.note,message_wrapped:"
+msgid "Message"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,rec_name:"
+msgid "Name"
+msgstr "名称"
+
+#, fuzzy
+msgctxt "field:ir.note,resource:"
+msgid "Resource"
+msgstr "附件文档资源"
+
+msgctxt "field:ir.note,unread:"
+msgid "Unread"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:ir.note,write_date:"
+msgid "Write Date"
+msgstr "写入日期"
+
+#, fuzzy
+msgctxt "field:ir.note,write_uid:"
+msgid "Write User"
+msgstr "写入帐号"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_date:"
msgid "Create Date"
+msgstr "创建日期:"
+
+#, fuzzy
+msgctxt "field:ir.note.read,create_uid:"
+msgid "Create User"
+msgstr "创建者"
+
+#, fuzzy
+msgctxt "field:ir.note.read,id:"
+msgid "ID"
+msgstr "标识"
+
+msgctxt "field:ir.note.read,note:"
+msgid "Note"
msgstr ""
+#, fuzzy
+msgctxt "field:ir.note.read,rec_name:"
+msgid "Name"
+msgstr "名称"
+
+#, fuzzy
+msgctxt "field:ir.note.read,user:"
+msgid "User"
+msgstr "用户"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_date:"
+msgid "Write Date"
+msgstr "写入日期"
+
+#, fuzzy
+msgctxt "field:ir.note.read,write_uid:"
+msgid "Write User"
+msgstr "写入帐号"
+
+msgctxt "field:ir.property,create_date:"
+msgid "Create Date"
+msgstr "创建日期"
+
msgctxt "field:ir.property,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "添加用户"
msgctxt "field:ir.property,field:"
msgid "Field"
-msgstr "Veld"
+msgstr "数据"
msgctxt "field:ir.property,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.property,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.property,res:"
msgid "Resource"
-msgstr "Middel"
+msgstr "资源"
msgctxt "field:ir.property,value:"
msgid "Value"
-msgstr "Waarde"
+msgstr "值"
msgctxt "field:ir.property,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.property,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.rule,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.rule,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "添加用户"
-#, fuzzy
msgctxt "field:ir.rule,domain:"
msgid "Domain"
-msgstr "Domein"
+msgstr "域配置"
msgctxt "field:ir.rule,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.rule,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.rule,rule_group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:ir.rule,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.rule,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.rule.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.rule.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.rule.group,default_p:"
msgid "Default"
-msgstr "Standaard"
+msgstr "某人"
msgctxt "field:ir.rule.group,global_p:"
msgid "Global"
-msgstr "Globaal"
+msgstr "全局"
msgctxt "field:ir.rule.group,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
msgctxt "field:ir.rule.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.rule.group,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.rule.group,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.rule.group,perm_create:"
msgid "Create Access"
-msgstr "Mag aanmaken"
+msgstr "新建"
msgctxt "field:ir.rule.group,perm_delete:"
msgid "Delete Access"
-msgstr "Toegang verwijderen"
+msgstr "删除"
msgctxt "field:ir.rule.group,perm_read:"
msgid "Read Access"
-msgstr "Leesrecht"
+msgstr "读取"
msgctxt "field:ir.rule.group,perm_write:"
msgid "Write Access"
-msgstr "Schrijfrecht"
+msgstr "写入"
msgctxt "field:ir.rule.group,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.rule.group,rules:"
msgid "Tests"
-msgstr "Testen"
+msgstr "测试"
msgctxt "field:ir.rule.group,users:"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "用户"
msgctxt "field:ir.rule.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.rule.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.sequence,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.sequence,code:"
msgid "Sequence Code"
-msgstr "Reeks code"
+msgstr "序列编号"
msgctxt "field:ir.sequence,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.sequence,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.sequence,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.sequence,last_timestamp:"
msgid "Last Timestamp"
-msgstr "Laatste tijdmarkering"
+msgstr "上一时间戳"
msgctxt "field:ir.sequence,name:"
msgid "Sequence Name"
-msgstr "Reeks naam"
+msgstr "序列名"
msgctxt "field:ir.sequence,number_increment:"
msgid "Increment Number"
-msgstr "Oplopende stap"
+msgstr "递增数"
msgctxt "field:ir.sequence,number_next:"
msgid "Next Number"
-msgstr "Volgende nummer"
+msgstr "下一个数"
-#, fuzzy
msgctxt "field:ir.sequence,number_next_internal:"
msgid "Next Number"
-msgstr "Volgende nummer"
+msgstr "下一个数"
msgctxt "field:ir.sequence,padding:"
msgid "Number padding"
-msgstr "Voorloopnullen"
+msgstr "数字补白"
msgctxt "field:ir.sequence,prefix:"
msgid "Prefix"
-msgstr "Voorvoegsel"
+msgstr "前缀"
msgctxt "field:ir.sequence,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.sequence,suffix:"
msgid "Suffix"
-msgstr "Toevoeging"
+msgstr "后缀"
msgctxt "field:ir.sequence,timestamp_offset:"
msgid "Timestamp Offset"
-msgstr "Tijdmarkering verschuiving"
+msgstr "时间偏移"
msgctxt "field:ir.sequence,timestamp_rounding:"
msgid "Timestamp Rounding"
-msgstr "Tijdmarkering afronding"
+msgstr "整数时间"
msgctxt "field:ir.sequence,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
msgctxt "field:ir.sequence,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.sequence,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.sequence.strict,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.sequence.strict,code:"
msgid "Sequence Code"
-msgstr "Reeks code"
+msgstr "序列编号"
msgctxt "field:ir.sequence.strict,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.sequence.strict,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "添加用户"
msgctxt "field:ir.sequence.strict,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.sequence.strict,last_timestamp:"
msgid "Last Timestamp"
-msgstr "Laatste tijdmarkering"
+msgstr "上一时间戳"
msgctxt "field:ir.sequence.strict,name:"
msgid "Sequence Name"
-msgstr "Reeks naam"
+msgstr "序列名"
msgctxt "field:ir.sequence.strict,number_increment:"
msgid "Increment Number"
-msgstr "Oplopende stap"
+msgstr "递增数"
msgctxt "field:ir.sequence.strict,number_next:"
msgid "Next Number"
-msgstr "Volgende nummer"
+msgstr "下一个数"
-#, fuzzy
msgctxt "field:ir.sequence.strict,number_next_internal:"
msgid "Next Number"
-msgstr "Volgende nummer"
+msgstr "下一个数"
msgctxt "field:ir.sequence.strict,padding:"
msgid "Number padding"
-msgstr "Voorloopnullen"
+msgstr "数字补白"
msgctxt "field:ir.sequence.strict,prefix:"
msgid "Prefix"
-msgstr "Voorvoegsel"
+msgstr "前缀"
msgctxt "field:ir.sequence.strict,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.sequence.strict,suffix:"
msgid "Suffix"
-msgstr "Toevoeging"
+msgstr "后缀"
msgctxt "field:ir.sequence.strict,timestamp_offset:"
msgid "Timestamp Offset"
-msgstr "Tijdmarkering verschuiving"
+msgstr "时间偏移"
msgctxt "field:ir.sequence.strict,timestamp_rounding:"
msgid "Timestamp Rounding"
-msgstr "Tijdmarkering afronding"
+msgstr "整数时间"
msgctxt "field:ir.sequence.strict,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
msgctxt "field:ir.sequence.strict,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.sequence.strict,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.sequence.type,code:"
msgid "Sequence Code"
-msgstr "Reeks code"
+msgstr "序列编号"
msgctxt "field:ir.sequence.type,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.sequence.type,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "添加用户"
msgctxt "field:ir.sequence.type,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.sequence.type,name:"
msgid "Sequence Name"
-msgstr "Reeks naam"
+msgstr "序列名"
msgctxt "field:ir.sequence.type,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.sequence.type,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.sequence.type,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.session,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.session,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.session,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.session,key:"
msgid "Key"
-msgstr ""
+msgstr "密匙"
-#, fuzzy
msgctxt "field:ir.session,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.session,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.session,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.session.wizard,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.session.wizard,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.session.wizard,data:"
msgid "Data"
-msgstr ""
+msgstr "数据"
msgctxt "field:ir.session.wizard,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.session.wizard,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.session.wizard,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.session.wizard,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.translation,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.translation,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.translation,fuzzy:"
msgid "Fuzzy"
-msgstr "Onzeker"
+msgstr "模糊"
msgctxt "field:ir.translation,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.translation,lang:"
msgid "Language"
-msgstr "Taal"
+msgstr "语言"
msgctxt "field:ir.translation,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.translation,module:"
msgid "Module"
-msgstr "Module"
+msgstr "模块"
msgctxt "field:ir.translation,name:"
msgid "Field Name"
-msgstr "Veldnaam"
+msgstr "列名"
msgctxt "field:ir.translation,overriding_module:"
msgid "Overriding Module"
-msgstr ""
+msgstr "来源模块"
msgctxt "field:ir.translation,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.translation,res_id:"
msgid "Resource ID"
-msgstr "Middel ID"
+msgstr "资源 ID"
msgctxt "field:ir.translation,src:"
msgid "Source"
-msgstr "Bron"
+msgstr "源"
msgctxt "field:ir.translation,src_md5:"
msgid "Source MD5"
-msgstr ""
+msgstr "源 MD5"
msgctxt "field:ir.translation,type:"
msgid "Type"
-msgstr "Type"
+msgstr "类型"
msgctxt "field:ir.translation,value:"
msgid "Translation Value"
-msgstr "Vertaling"
+msgstr "翻译"
msgctxt "field:ir.translation,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.translation,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.translation.clean.start,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.translation.clean.succeed,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.translation.export.result,file:"
msgid "File"
-msgstr ""
+msgstr "文件"
msgctxt "field:ir.translation.export.result,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.translation.export.start,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.translation.export.start,language:"
msgid "Language"
-msgstr "Taal"
+msgstr "语言"
-#, fuzzy
msgctxt "field:ir.translation.export.start,module:"
msgid "Module"
-msgstr "Module"
+msgstr "模块"
msgctxt "field:ir.translation.set.start,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.translation.set.succeed,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.translation.update.start,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.translation.update.start,language:"
msgid "Language"
-msgstr "Taal"
+msgstr "语言"
msgctxt "field:ir.trigger,action_function:"
msgid "Action Function"
-msgstr "Actiefunctie"
+msgstr "动作方法"
msgctxt "field:ir.trigger,action_model:"
msgid "Action Model"
-msgstr "Actie model"
+msgstr "动作模型"
msgctxt "field:ir.trigger,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.trigger,condition:"
msgid "Condition"
-msgstr "Voorwaarde"
+msgstr "条件"
msgctxt "field:ir.trigger,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.trigger,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.trigger,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.trigger,limit_number:"
msgid "Limit Number"
-msgstr "Begrenzing nummer"
+msgstr "数量限制"
msgctxt "field:ir.trigger,minimum_time_delay:"
msgid "Minimum Delay"
-msgstr ""
+msgstr "最小延迟"
msgctxt "field:ir.trigger,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.trigger,name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.trigger,on_create:"
msgid "On Create"
-msgstr "Bij aanmaken"
+msgstr "新建触发"
msgctxt "field:ir.trigger,on_delete:"
msgid "On Delete"
-msgstr "Bij verwijderen"
+msgstr "删除触发"
msgctxt "field:ir.trigger,on_time:"
msgid "On Time"
-msgstr "Op tijd"
+msgstr "时基触发"
msgctxt "field:ir.trigger,on_write:"
msgid "On Write"
-msgstr "Bij muteren"
+msgstr "写入触发"
msgctxt "field:ir.trigger,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.trigger,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.trigger,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.trigger.log,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.trigger.log,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.trigger.log,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.trigger.log,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.trigger.log,record_id:"
msgid "Record ID"
-msgstr "Item ID"
+msgstr "记录标识"
msgctxt "field:ir.trigger.log,trigger:"
msgid "Trigger"
-msgstr "Starter"
+msgstr "触发器"
msgctxt "field:ir.trigger.log,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.trigger.log,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.ui.icon,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.ui.icon,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.ui.icon,icon:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "field:ir.ui.icon,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.ui.icon,module:"
msgid "Module"
-msgstr "Module"
+msgstr "模块"
-#, fuzzy
msgctxt "field:ir.ui.icon,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.ui.icon,path:"
msgid "SVG Path"
-msgstr ""
+msgstr "SVG Path"
-#, fuzzy
msgctxt "field:ir.ui.icon,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.ui.icon,sequence:"
msgid "Sequence"
-msgstr "Reeks"
+msgstr "序列"
msgctxt "field:ir.ui.icon,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.ui.icon,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.ui.menu,action:"
msgid "Action"
-msgstr "Actie"
+msgstr "动作"
msgctxt "field:ir.ui.menu,action_keywords:"
msgid "Action Keywords"
-msgstr ""
+msgstr "动作关键词"
msgctxt "field:ir.ui.menu,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.ui.menu,childs:"
msgid "Children"
-msgstr "Onderliggende niveaus"
+msgstr "子项"
msgctxt "field:ir.ui.menu,complete_name:"
msgid "Complete Name"
-msgstr "Volledige naam"
+msgstr "全名"
msgctxt "field:ir.ui.menu,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.ui.menu,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.ui.menu,favorite:"
msgid "Favorite"
-msgstr ""
+msgstr "收藏"
msgctxt "field:ir.ui.menu,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
msgctxt "field:ir.ui.menu,icon:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "field:ir.ui.menu,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.ui.menu,name:"
msgid "Menu"
-msgstr "Menu"
+msgstr "菜单"
msgctxt "field:ir.ui.menu,parent:"
msgid "Parent Menu"
-msgstr "Hoofdmenu"
+msgstr "上一级菜单"
msgctxt "field:ir.ui.menu,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.ui.menu,sequence:"
msgid "Sequence"
-msgstr "Reeks"
+msgstr "序列"
msgctxt "field:ir.ui.menu,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.ui.menu,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.ui.menu.favorite,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.ui.menu.favorite,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.ui.menu.favorite,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.ui.menu.favorite,menu:"
msgid "Menu"
-msgstr "Menu"
+msgstr "菜单"
-#, fuzzy
msgctxt "field:ir.ui.menu.favorite,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.ui.menu.favorite,sequence:"
msgid "Sequence"
-msgstr "Reeks"
+msgstr "序列"
-#, fuzzy
msgctxt "field:ir.ui.menu.favorite,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "field:ir.ui.menu.favorite,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.ui.menu.favorite,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.ui.view,arch:"
msgid "View Architecture"
-msgstr "Bekijk opbouw"
+msgstr "视图层次"
msgctxt "field:ir.ui.view,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.ui.view,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.ui.view,data:"
msgid "Data"
-msgstr ""
+msgstr "数据"
msgctxt "field:ir.ui.view,domain:"
msgid "Domain"
-msgstr "Domein"
+msgstr "域配置"
msgctxt "field:ir.ui.view,field_childs:"
msgid "Children Field"
-msgstr "Veld onderliggende niveaus"
+msgstr "子项"
msgctxt "field:ir.ui.view,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.ui.view,inherit:"
msgid "Inherited View"
-msgstr "Aanzicht overnemen"
+msgstr "继承的视图"
msgctxt "field:ir.ui.view,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.ui.view,module:"
msgid "Module"
-msgstr "Module"
+msgstr "模块"
-#, fuzzy
msgctxt "field:ir.ui.view,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.ui.view,priority:"
msgid "Priority"
-msgstr "Prioriteit"
+msgstr "优先级"
msgctxt "field:ir.ui.view,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.ui.view,type:"
msgid "View Type"
-msgstr "Aanzicht type"
+msgstr "视图类型"
msgctxt "field:ir.ui.view,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.ui.view,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.ui.view.show.start,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.ui.view_search,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.ui.view_search,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.ui.view_search,domain:"
msgid "Domain"
-msgstr "Domein"
+msgstr "域配置"
msgctxt "field:ir.ui.view_search,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.ui.view_search,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
-#, fuzzy
msgctxt "field:ir.ui.view_search,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.ui.view_search,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.ui.view_search,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "field:ir.ui.view_search,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.ui.view_search,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.ui.view_tree_state,child_name:"
msgid "Child Name"
-msgstr ""
+msgstr "子项名称"
msgctxt "field:ir.ui.view_tree_state,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.ui.view_tree_state,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.ui.view_tree_state,domain:"
msgid "Domain"
-msgstr "Domein"
+msgstr "域配置"
msgctxt "field:ir.ui.view_tree_state,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.ui.view_tree_state,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.ui.view_tree_state,nodes:"
msgid "Expanded Nodes"
-msgstr ""
+msgstr "展开节点"
-#, fuzzy
msgctxt "field:ir.ui.view_tree_state,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.ui.view_tree_state,selected_nodes:"
msgid "Selected Nodes"
-msgstr ""
+msgstr "选中节点"
-#, fuzzy
msgctxt "field:ir.ui.view_tree_state,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "field:ir.ui.view_tree_state,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.ui.view_tree_state,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.ui.view_tree_width,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.ui.view_tree_width,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.ui.view_tree_width,field:"
msgid "Field"
-msgstr "Veld"
+msgstr "数据"
msgctxt "field:ir.ui.view_tree_width,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:ir.ui.view_tree_width,model:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "field:ir.ui.view_tree_width,rec_name:"
msgid "Name"
-msgstr "Naam"
+msgstr "名称"
msgctxt "field:ir.ui.view_tree_width,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "field:ir.ui.view_tree_width,width:"
msgid "Width"
-msgstr "Breedte"
+msgstr "宽"
msgctxt "field:ir.ui.view_tree_width,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.ui.view_tree_width,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "help:ir.action.act_window,limit:"
msgid "Default limit for the list view"
-msgstr "Standaard begrenzing voor dit aanzicht"
+msgstr "列表视图默认限制"
msgctxt "help:ir.action.act_window,search_value:"
msgid "Default search criteria for the list view"
-msgstr "Standaard zoekopdracht voor lijst"
+msgstr "列表视图默认搜索表达式"
msgctxt "help:ir.action.act_window,window_name:"
msgid "Use the action name as window name"
-msgstr "Gebruik de naam van de actie als schermnaam"
+msgstr "动作名做窗体名"
msgctxt "help:ir.action.report,email:"
msgid ""
"Python dictonary where keys define \"to\" \"cc\" \"subject\"\n"
"Example: {'to': 'test at example.com', 'cc': 'user at example.com'}"
msgstr ""
+"定义键 \"to\" \"cc\" \"subject\"的python dictionary\n"
+"如: {'to': 'test at example.com', 'cc': 'user at example.com'}"
msgctxt "help:ir.action.report,extension:"
msgid ""
"Leave empty for the same as template, see unoconv documentation for "
"compatible format"
-msgstr ""
+msgstr "参考unoconv文档有关格式部分,模版空白不要改动"
msgctxt "help:ir.action.wizard,window:"
msgid "Run wizard in a new window"
-msgstr "Start assistent in nieuw venster"
+msgstr "打开新窗口运行向导"
msgctxt "help:ir.cron,number_calls:"
msgid ""
"Number of times the function is called, a negative number indicates that the"
" function will always be called"
-msgstr ""
+msgstr "方法被调用的次数,负数表示方法总是被调用"
msgctxt "help:ir.cron,request_user:"
msgid "The user who will receive requests in case of failure"
-msgstr "De gebruiker die de verzoeken krijgt in geval van falen"
+msgstr "运行失败后由哪个用户接收请求"
msgctxt "help:ir.cron,user:"
msgid "The user used to execute this action"
-msgstr "De gebruiker die gebruikt wordt voor deze actie"
+msgstr "执行动作的用户"
msgctxt "help:ir.lang,code:"
msgid "RFC 4646 tag: http://tools.ietf.org/html/rfc4646"
-msgstr ""
+msgstr "RFC 4646 tag: http://tools.ietf.org/html/rfc4646"
msgctxt "help:ir.model,module:"
msgid "Module in which this model is defined"
-msgstr "Module waarin dit veld is gedefinieerd"
+msgstr "定义该模型的模块"
msgctxt "help:ir.model.data,db_id:"
msgid "The id of the record in the database."
-msgstr "Het ID van het item in de database."
+msgstr "数据库里的记录ID"
msgctxt "help:ir.model.data,fs_id:"
msgid "The id of the record as known on the file system."
-msgstr "Het ID van het item zoals bekend in het bestandssysteem."
+msgstr "文件系统里的记录ID"
msgctxt "help:ir.model.field,module:"
msgid "Module in which this field is defined"
-msgstr "Module waarin dit veld is gedefinieerd"
+msgstr "定义该数据的模块"
msgctxt "help:ir.model.print_model_graph.start,filter:"
msgid ""
"Entering a Python Regular Expression will exclude matching models from the "
"graph."
-msgstr ""
+msgstr "输入Python表达式从图表中排除匹配的的模型"
msgctxt "help:ir.rule,domain:"
msgid ""
"Domain is evaluated with a PYSON context containing:\n"
"- \"user\" as the current user"
-msgstr ""
+msgstr "PYSON场景满足当前用户名为“用户名”, 域配置生效."
msgctxt "help:ir.rule.group,default_p:"
msgid "Add this rule to all users by default"
-msgstr "Voeg deze regel standaard toe bij alle gebruikers"
+msgstr "默认向所有用户启用本规则"
msgctxt "help:ir.rule.group,global_p:"
msgid ""
"Make the rule global \n"
"so every users must follow this rule"
-msgstr "Maak de regel globaal"
+msgstr ""
+"本规则设定为“全局”\n"
+" 对所有用户有效"
msgctxt "help:ir.rule.group,rules:"
msgid "The rule is satisfied if at least one test is True"
-msgstr "De regel is waar als tenminste aan één voorwaarde wordt voldaan"
+msgstr "任意一项测试结果为真,即判断符合规则."
msgctxt "help:ir.trigger,condition:"
msgid ""
"A PYSON statement evaluated with record represented by \"self\"\n"
"It triggers the action if true."
msgstr ""
+"用“self”提交的数据校验PYSON声明完成\n"
+" 符合条件则触发该动作."
msgctxt "help:ir.trigger,limit_number:"
msgid ""
"Limit the number of call to \"Action Function\" by records.\n"
"0 for no limit."
msgstr ""
-"Beperk het aantal aanroepen van \"Uitvoerende functie\" door items.\n"
-"Gebruik 0 voor geen beperking."
+"限制数据调用“动作方法”次数\n"
+" 0表示无限制."
msgctxt "help:ir.trigger,minimum_time_delay:"
msgid ""
"Set a minimum time delay between call to \"Action Function\" for the same record.\n"
"empty for no delay."
msgstr ""
+"设置对相同数据调用\"动作方法\" 的最小间隔时间\n"
+" 留空表示不限制"
msgctxt "help:ir.ui.view_search,domain:"
msgid "The PYSON domain"
-msgstr ""
+msgstr "PYSON 域"
msgctxt "model:ir.action,name:"
msgid "Action"
-msgstr "Actie"
+msgstr "动作"
msgctxt "model:ir.action,name:act_action_act_window_form"
msgid "Window Actions"
-msgstr "Schermacties"
+msgstr "窗口动作"
msgctxt "model:ir.action,name:act_action_form"
msgid "Actions"
-msgstr "Acties"
+msgstr "动作"
msgctxt "model:ir.action,name:act_action_report_form"
msgid "Reports"
-msgstr "Rapportage"
+msgstr "报表"
msgctxt "model:ir.action,name:act_action_url_form"
msgid "URLs"
-msgstr "URL's\n"
+msgstr "URL"
msgctxt "model:ir.action,name:act_action_wizard_form"
msgid "Wizards"
-msgstr "Assistenten"
+msgstr "向导"
msgctxt "model:ir.action,name:act_attachment_form"
msgid "Attachments"
-msgstr "Bijlagen"
+msgstr "附件文档"
msgctxt "model:ir.action,name:act_config_wizard_item_form"
msgid "Config Wizard Items"
-msgstr "Conf. assistent items"
+msgstr "设置向导项目"
msgctxt "model:ir.action,name:act_cron_form"
msgid "Scheduled Actions"
-msgstr "Geplande acties"
+msgstr "计划动作"
msgctxt "model:ir.action,name:act_export_form"
msgid "Exports"
-msgstr "Export"
+msgstr "导出"
msgctxt "model:ir.action,name:act_icon_form"
msgid "Icons"
-msgstr ""
+msgstr "图标"
msgctxt "model:ir.action,name:act_lang_form"
msgid "Languages"
-msgstr "Talen"
+msgstr "语言"
-#, fuzzy
msgctxt "model:ir.action,name:act_menu_list"
msgid "Menu"
-msgstr "Menu"
+msgstr "菜单"
msgctxt "model:ir.action,name:act_menu_tree"
msgid "Menu"
-msgstr "Menu"
+msgstr "菜单"
msgctxt "model:ir.action,name:act_model_access_form"
msgid "Models Access"
-msgstr "Toegang tot modellen"
+msgstr "模型权限"
msgctxt "model:ir.action,name:act_model_button_form"
msgid "Buttons"
-msgstr ""
+msgstr "按钮"
msgctxt "model:ir.action,name:act_model_data_form"
msgid "Data"
-msgstr ""
+msgstr "数据"
msgctxt "model:ir.action,name:act_model_field_access_form"
msgid "Fields Access"
-msgstr ""
+msgstr "数据权限"
msgctxt "model:ir.action,name:act_model_fields_form"
msgid "Fields"
-msgstr "Velden"
+msgstr "数据"
msgctxt "model:ir.action,name:act_model_form"
msgid "Models"
-msgstr "Modellen"
+msgstr "模型"
msgctxt "model:ir.action,name:act_module_config"
msgid "Configure Modules"
-msgstr ""
+msgstr "设置模块"
msgctxt "model:ir.action,name:act_module_config_wizard"
msgid "Module Configuration"
-msgstr "Module configuratie"
+msgstr "模块设置"
msgctxt "model:ir.action,name:act_module_form"
msgid "Modules"
-msgstr "Modulen"
+msgstr "模块"
msgctxt "model:ir.action,name:act_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
-msgstr "Voer installatie / bijwerken uit"
+msgstr "执行未完成的”安装“ “升级”"
+
+msgctxt "model:ir.action,name:act_note_form"
+msgid "Notes"
+msgstr ""
msgctxt "model:ir.action,name:act_property_form"
msgid "Properties"
-msgstr "Eigenschappen"
+msgstr "属性"
msgctxt "model:ir.action,name:act_property_form_default"
msgid "Default Properties"
-msgstr "Standaard eigenschappen"
+msgstr "默认属性"
msgctxt "model:ir.action,name:act_rule_group_form"
msgid "Record Rules"
-msgstr "Item regels"
+msgstr "数据规则"
msgctxt "model:ir.action,name:act_sequence_form"
msgid "Sequences"
-msgstr "Reeksen"
+msgstr "序列"
msgctxt "model:ir.action,name:act_sequence_strict_form"
msgid "Sequences Strict"
-msgstr "Vaste reeksen"
+msgstr "严格序列"
msgctxt "model:ir.action,name:act_sequence_type_form"
msgid "Sequence Types"
-msgstr "Reeks typen"
+msgstr "序列类型"
msgctxt "model:ir.action,name:act_translation_clean"
msgid "Clean Translations"
-msgstr "Vertalingen opschonen"
+msgstr "清理翻译"
msgctxt "model:ir.action,name:act_translation_export"
msgid "Export Translations"
-msgstr "Vertalingen exporteren"
+msgstr "导出翻译条目"
msgctxt "model:ir.action,name:act_translation_form"
msgid "Translations"
-msgstr "Vertalingen"
+msgstr "翻译"
msgctxt "model:ir.action,name:act_translation_set"
msgid "Set Report Translations"
-msgstr ""
+msgstr "设置报表翻译"
msgctxt "model:ir.action,name:act_translation_update"
msgid "Synchronize Translations"
-msgstr "Vertaling synchroniseren"
+msgstr "使翻译生效"
msgctxt "model:ir.action,name:act_trigger_form"
msgid "Triggers"
-msgstr "Starters"
+msgstr "触发器"
msgctxt "model:ir.action,name:act_view_form"
msgid "Views"
-msgstr "Aanzichten"
+msgstr "视图"
msgctxt "model:ir.action,name:act_view_search"
msgid "View Search"
-msgstr ""
+msgstr "搜索视图"
msgctxt "model:ir.action,name:act_view_show"
msgid "Show View"
-msgstr ""
+msgstr "显示视图"
msgctxt "model:ir.action,name:act_view_tree_state"
msgid "Tree State"
-msgstr ""
+msgstr "三态"
msgctxt "model:ir.action,name:act_view_tree_width_form"
msgid "View Tree Width"
-msgstr "Aanzicht boomstructuurbreedte"
+msgstr "导航树宽度"
msgctxt "model:ir.action,name:print_model_graph"
msgid "Graph"
-msgstr "Grafiek"
+msgstr "图表"
msgctxt "model:ir.action,name:report_model_graph"
msgid "Graph"
-msgstr "Grafiek"
+msgstr "图表"
+
+msgctxt "model:ir.action,name:report_model_workflow_graph"
+msgid "Workflow Graph"
+msgstr ""
msgctxt "model:ir.action.act_window,name:"
msgid "Action act window"
-msgstr "Actie uitvoerend scherm"
+msgstr "动作窗体"
msgctxt "model:ir.action.act_window.domain,name:"
msgid "Action act window domain"
-msgstr ""
+msgstr "动作窗体域设置"
msgctxt ""
"model:ir.action.act_window.domain,name:act_model_data_form_domain_all"
msgid "All"
-msgstr ""
+msgstr "全部"
msgctxt ""
"model:ir.action.act_window.domain,name:act_model_data_form_domain_out_of_sync"
msgid "Out of Sync"
-msgstr ""
+msgstr "未同步"
msgctxt "model:ir.action.act_window.view,name:"
msgid "Action act window view"
-msgstr "Actie uitvoerend schermaanzicht"
+msgstr "动作窗体视图"
msgctxt "model:ir.action.keyword,name:"
msgid "Action keyword"
-msgstr "Actietrefwoord"
+msgstr "动作关键词"
msgctxt "model:ir.action.report,name:"
msgid "Action report"
-msgstr "Actie rapport"
+msgstr "动作报表"
msgctxt "model:ir.action.url,name:"
msgid "Action URL"
-msgstr "Actie URL"
+msgstr "动作 Url"
msgctxt "model:ir.action.wizard,name:"
msgid "Action wizard"
-msgstr "Actie assistent"
+msgstr "动作向导"
msgctxt "model:ir.attachment,name:"
msgid "Attachment"
-msgstr "Bijlage"
+msgstr "附件文档"
msgctxt "model:ir.cache,name:"
msgid "Cache"
-msgstr "Tijdelijk geheugen"
+msgstr "缓存"
-#, fuzzy
msgctxt "model:ir.configuration,name:"
msgid "Configuration"
-msgstr "Instellingen"
+msgstr "设置"
msgctxt "model:ir.cron,name:"
msgid "Cron"
-msgstr "Cyclische opdracht"
+msgstr "Cron"
msgctxt "model:ir.date,name:"
msgid "Date"
-msgstr "Datum"
+msgstr "日期"
msgctxt "model:ir.export,name:"
msgid "Export"
-msgstr "Exporteren"
+msgstr "导出"
msgctxt "model:ir.export.line,name:"
msgid "Export line"
-msgstr "Exporterregel"
+msgstr "导出行"
msgctxt "model:ir.lang,name:"
msgid "Language"
-msgstr "Taal"
+msgstr "语言"
msgctxt "model:ir.lang,name:lang_ar"
msgid "Spanish (Argentina)"
-msgstr ""
+msgstr "西班牙语(阿根廷)"
msgctxt "model:ir.lang,name:lang_bg"
msgid "Bulgarian"
-msgstr ""
+msgstr "保加利亚语"
msgctxt "model:ir.lang,name:lang_ca"
msgid "Català"
-msgstr ""
+msgstr "加泰罗尼亚语"
msgctxt "model:ir.lang,name:lang_cs"
msgid "Czech"
-msgstr "Tsjechisch"
+msgstr "捷克语"
msgctxt "model:ir.lang,name:lang_de"
msgid "German"
-msgstr "Duits"
+msgstr "德语"
msgctxt "model:ir.lang,name:lang_ec"
msgid "Spanish (Ecuador)"
-msgstr ""
+msgstr "西班牙语(厄瓜多尔)"
msgctxt "model:ir.lang,name:lang_en"
msgid "English"
-msgstr "Engels"
+msgstr "英语"
msgctxt "model:ir.lang,name:lang_es"
msgid "Spanish (Spain)"
-msgstr "Spaans (Spanje)"
+msgstr "西班牙语(西班牙)"
msgctxt "model:ir.lang,name:lang_es_CO"
msgid "Spanish (Colombia)"
-msgstr "Spaans (Colombia)"
+msgstr "西班牙语(哥伦比亚)"
msgctxt "model:ir.lang,name:lang_es_MX"
msgid "Spanish (Mexico)"
-msgstr ""
+msgstr "西班牙语(墨西哥)"
msgctxt "model:ir.lang,name:lang_fr"
msgid "French"
-msgstr "Frans"
+msgstr "法语"
msgctxt "model:ir.lang,name:lang_hu_HU"
msgid "Hungarian"
-msgstr ""
+msgstr "匈牙利语"
msgctxt "model:ir.lang,name:lang_it_IT"
msgid "Italian"
-msgstr ""
+msgstr "意大利语"
msgctxt "model:ir.lang,name:lang_lt"
msgid "Lithuanian"
-msgstr ""
+msgstr "立陶宛语"
msgctxt "model:ir.lang,name:lang_nl"
msgid "Dutch"
-msgstr ""
+msgstr "荷兰语"
msgctxt "model:ir.lang,name:lang_pt_BR"
msgid "Portuguese (Brazil)"
-msgstr ""
+msgstr "葡萄牙语"
msgctxt "model:ir.lang,name:lang_ru"
msgid "Russian"
-msgstr "Russisch"
+msgstr "俄语"
msgctxt "model:ir.lang,name:lang_sl"
msgid "Slovenian"
-msgstr ""
+msgstr "斯洛维尼亚语"
msgctxt "model:ir.model,name:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
msgctxt "model:ir.model.access,name:"
msgid "Model access"
-msgstr "Toegang tot model"
+msgstr "模型权限"
msgctxt "model:ir.model.button,name:"
msgid "Model Button"
-msgstr ""
+msgstr "按钮模型"
msgctxt "model:ir.model.data,name:"
msgid "Model data"
-msgstr "Model gegevens"
+msgstr "模型数据"
msgctxt "model:ir.model.field,name:"
msgid "Model field"
-msgstr "Model veld"
+msgstr "数据"
msgctxt "model:ir.model.field.access,name:"
msgid "Model Field Access"
-msgstr ""
+msgstr "数据权限"
msgctxt "model:ir.model.print_model_graph.start,name:"
msgid "Print Model Graph"
-msgstr ""
+msgstr "打印模型图表"
msgctxt "model:ir.module,name:"
msgid "Module"
-msgstr ""
+msgstr "模块"
msgctxt "model:ir.module.config_wizard.done,name:"
msgid "Module Config Wizard Done"
-msgstr ""
+msgstr "模块设置向导完成"
msgctxt "model:ir.module.config_wizard.first,name:"
msgid "Module Config Wizard First"
-msgstr ""
+msgstr "模块设置向导开始"
msgctxt "model:ir.module.config_wizard.item,name:"
msgid "Config wizard to run after installing module"
-msgstr ""
+msgstr "设置模块安装完成开始向导"
msgctxt "model:ir.module.config_wizard.other,name:"
msgid "Module Config Wizard Other"
-msgstr ""
+msgstr "模块设置向导其他"
msgctxt "model:ir.module.dependency,name:"
msgid "Module dependency"
-msgstr ""
+msgstr "模块依赖关系"
msgctxt "model:ir.module.install_upgrade.done,name:"
msgid "Module Install Upgrade Done"
-msgstr ""
+msgstr "模块安装升级完成"
msgctxt "model:ir.module.install_upgrade.start,name:"
msgid "Module Install Upgrade Start"
+msgstr "模块安装升级开始"
+
+msgctxt "model:ir.note,name:"
+msgid "Note"
+msgstr ""
+
+msgctxt "model:ir.note.read,name:"
+msgid "Note Read"
msgstr ""
msgctxt "model:ir.property,name:"
msgid "Property"
-msgstr "Eigenschap"
+msgstr "属性"
msgctxt "model:ir.rule,name:"
msgid "Rule"
-msgstr "Regel"
+msgstr "规则"
msgctxt "model:ir.rule.group,name:"
msgid "Rule group"
-msgstr "Regelgroep"
+msgstr "规则组"
msgctxt "model:ir.sequence,name:"
msgid "Sequence"
-msgstr "Reeks"
+msgstr "序列"
msgctxt "model:ir.sequence.strict,name:"
msgid "Sequence Strict"
-msgstr "Vaste reeks"
+msgstr "严格序列"
msgctxt "model:ir.sequence.type,name:"
msgid "Sequence type"
-msgstr "Reeks type"
+msgstr "序列类型"
msgctxt "model:ir.session,name:"
msgid "Session"
-msgstr ""
+msgstr "进程"
msgctxt "model:ir.session.wizard,name:"
msgid "Session Wizard"
-msgstr ""
+msgstr "进程设置向导"
msgctxt "model:ir.translation,name:"
msgid "Translation"
-msgstr "Vertaling"
+msgstr "翻译"
-#, fuzzy
msgctxt "model:ir.translation.clean.start,name:"
msgid "Clean translation"
-msgstr "Start vertalingen opschonen"
+msgstr "翻译"
-#, fuzzy
msgctxt "model:ir.translation.clean.succeed,name:"
msgid "Clean translation"
-msgstr "Start vertalingen opschonen"
+msgstr "翻译"
-#, fuzzy
msgctxt "model:ir.translation.export.result,name:"
msgid "Export translation"
-msgstr "Vertaling exporteren - bestand"
+msgstr "导出翻译条目"
-#, fuzzy
msgctxt "model:ir.translation.export.start,name:"
msgid "Export translation"
-msgstr "Vertaling exporteren - bestand"
+msgstr "导出翻译条目"
msgctxt "model:ir.translation.set.start,name:"
msgid "Set Translation"
-msgstr ""
+msgstr "保存翻译"
msgctxt "model:ir.translation.set.succeed,name:"
msgid "Set Translation"
-msgstr ""
+msgstr "保存翻译"
msgctxt "model:ir.translation.update.start,name:"
msgid "Update translation"
-msgstr ""
+msgstr "更新翻译"
msgctxt "model:ir.trigger,name:"
msgid "Trigger"
-msgstr "Starter"
+msgstr "触发器"
msgctxt "model:ir.trigger.log,name:"
msgid "Trigger Log"
-msgstr "Starter logboek"
+msgstr "触发器运行日志"
-#, fuzzy
msgctxt "model:ir.ui.icon,name:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "model:ir.ui.menu,name:"
msgid "UI menu"
-msgstr "Gebruiker menu"
+msgstr "UI 菜单"
msgctxt "model:ir.ui.menu,name:menu_act_action"
msgid "Actions"
-msgstr "Acties"
+msgstr "动作"
msgctxt "model:ir.ui.menu,name:menu_action"
msgid "Actions"
-msgstr "Acties"
+msgstr "动作"
msgctxt "model:ir.ui.menu,name:menu_action_act_window"
msgid "Window Actions"
-msgstr "Schermacties"
+msgstr "窗口动作"
msgctxt "model:ir.ui.menu,name:menu_action_report_form"
msgid "Reports"
-msgstr "Rapportage"
+msgstr "报表"
msgctxt "model:ir.ui.menu,name:menu_action_url"
msgid "URLs"
-msgstr "URL's"
+msgstr "URL"
msgctxt "model:ir.ui.menu,name:menu_action_wizard"
msgid "Wizards"
-msgstr "Assistenten"
+msgstr "向导"
msgctxt "model:ir.ui.menu,name:menu_administration"
msgid "Administration"
-msgstr "Systeembeheer"
+msgstr "Administration"
msgctxt "model:ir.ui.menu,name:menu_attachment_form"
msgid "Attachments"
-msgstr "Bijlagen"
+msgstr "附件文档"
msgctxt "model:ir.ui.menu,name:menu_config_wizard_item_form"
msgid "Config Wizard Items"
-msgstr "Conf. assistent items"
+msgstr "设置向导"
msgctxt "model:ir.ui.menu,name:menu_cron_form"
msgid "Scheduled Actions"
-msgstr "Geplande acties"
+msgstr "计划动作"
msgctxt "model:ir.ui.menu,name:menu_export_form"
msgid "Exports"
-msgstr "Export"
+msgstr "导出项目"
msgctxt "model:ir.ui.menu,name:menu_icon_form"
msgid "Icons"
-msgstr ""
+msgstr "图标"
msgctxt "model:ir.ui.menu,name:menu_ir_sequence_type"
msgid "Sequence Types"
-msgstr "Reeks typen"
+msgstr "序列类型"
msgctxt "model:ir.ui.menu,name:menu_lang_form"
msgid "Languages"
-msgstr "Talen"
+msgstr "语言"
msgctxt "model:ir.ui.menu,name:menu_localization"
msgid "Localization"
-msgstr "Lokalisatie"
+msgstr "地域"
-#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_menu_list"
msgid "Menu"
-msgstr "Menu"
+msgstr "菜单"
msgctxt "model:ir.ui.menu,name:menu_model_access_form"
msgid "Models Access"
-msgstr "Toegang tot modellen"
+msgstr "模型权限"
msgctxt "model:ir.ui.menu,name:menu_model_button_form"
msgid "Buttons"
-msgstr ""
+msgstr "按钮"
msgctxt "model:ir.ui.menu,name:menu_model_data_form"
msgid "Data"
-msgstr ""
+msgstr "数据"
msgctxt "model:ir.ui.menu,name:menu_model_field_access_form"
msgid "Fields Access"
-msgstr ""
+msgstr "数据权限"
msgctxt "model:ir.ui.menu,name:menu_model_form"
msgid "Models"
-msgstr "Modellen"
+msgstr "模型"
msgctxt "model:ir.ui.menu,name:menu_models"
msgid "Models"
-msgstr "Modellen"
+msgstr "模型"
msgctxt "model:ir.ui.menu,name:menu_module_form"
msgid "Modules"
-msgstr "Modulen"
+msgstr "模块"
msgctxt "model:ir.ui.menu,name:menu_module_install_upgrade"
msgid "Perform Pending Installation/Upgrade"
-msgstr "Voer installatie / bijwerken uit"
+msgstr "执行未完成的”安装“ “升级”"
msgctxt "model:ir.ui.menu,name:menu_modules"
msgid "Modules"
-msgstr "Modulen"
+msgstr "模块"
+
+msgctxt "model:ir.ui.menu,name:menu_note_form"
+msgid "Notes"
+msgstr ""
msgctxt "model:ir.ui.menu,name:menu_property_form"
msgid "Properties"
-msgstr "Eigenschappen"
+msgstr "属性"
msgctxt "model:ir.ui.menu,name:menu_property_form_default"
msgid "Default Properties"
-msgstr "Standaard eigenschappen"
+msgstr "默认属性"
msgctxt "model:ir.ui.menu,name:menu_rule_group_form"
msgid "Record Rules"
-msgstr "Item regels"
+msgstr "数据规则"
msgctxt "model:ir.ui.menu,name:menu_scheduler"
msgid "Scheduler"
-msgstr "Planner"
+msgstr "计划"
msgctxt "model:ir.ui.menu,name:menu_sequence_form"
msgid "Sequences"
-msgstr "Reeksen"
+msgstr "序列"
msgctxt "model:ir.ui.menu,name:menu_sequence_strict_form"
msgid "Sequences Strict"
-msgstr "Vaste reeksen"
+msgstr "严格序列"
msgctxt "model:ir.ui.menu,name:menu_sequences"
msgid "Sequences"
-msgstr "Reeksen"
+msgstr "序列"
msgctxt "model:ir.ui.menu,name:menu_translation_clean"
msgid "Clean Translations"
-msgstr "Vertalingen opschonen"
+msgstr "清理翻译"
msgctxt "model:ir.ui.menu,name:menu_translation_export"
msgid "Export Translations"
-msgstr "Vertalingen exporteren"
+msgstr "导出翻译条目"
msgctxt "model:ir.ui.menu,name:menu_translation_form"
msgid "Translations"
-msgstr "Vertalingen"
+msgstr "翻译"
msgctxt "model:ir.ui.menu,name:menu_translation_set"
msgid "Set Translations"
-msgstr ""
+msgstr "保存翻译"
msgctxt "model:ir.ui.menu,name:menu_translation_update"
msgid "Synchronize Translations"
-msgstr "Vertaling synchroniseren"
+msgstr "使翻译生效"
msgctxt "model:ir.ui.menu,name:menu_trigger_form"
msgid "Triggers"
-msgstr "Starters"
+msgstr "触发器"
msgctxt "model:ir.ui.menu,name:menu_ui"
msgid "User Interface"
-msgstr "Gebruikers omgeving"
+msgstr "界面"
msgctxt "model:ir.ui.menu,name:menu_view"
msgid "Views"
-msgstr "Aanzichten"
+msgstr "视图"
msgctxt "model:ir.ui.menu,name:menu_view_search"
msgid "View Search"
-msgstr ""
+msgstr "搜索视图"
msgctxt "model:ir.ui.menu,name:menu_view_tree_state"
msgid "Tree State"
-msgstr ""
+msgstr "导航树状态"
msgctxt "model:ir.ui.menu,name:menu_view_tree_width"
msgid "View Tree Width"
-msgstr "Aanzicht boomstructuurbreedte"
+msgstr "导航树宽度"
msgctxt "model:ir.ui.menu,name:model_model_fields_form"
msgid "Fields"
-msgstr "Velden"
+msgstr "数据"
msgctxt "model:ir.ui.menu.favorite,name:"
msgid "Menu Favorite"
-msgstr ""
+msgstr "收藏"
msgctxt "model:ir.ui.view,name:"
msgid "View"
-msgstr "Overzicht"
+msgstr "视图"
msgctxt "model:ir.ui.view.show.start,name:"
msgid "Show view"
-msgstr ""
+msgstr "显示视图"
msgctxt "model:ir.ui.view_search,name:"
msgid "View Search"
-msgstr ""
+msgstr "搜索视图"
msgctxt "model:ir.ui.view_tree_state,name:"
msgid "View Tree State"
-msgstr ""
+msgstr "导航树状态"
msgctxt "model:ir.ui.view_tree_width,name:"
msgid "View Tree Width"
-msgstr "Aanzicht boomstructuurbreedte"
+msgstr "导航树宽度"
msgctxt "selection:ir.action.keyword,keyword:"
msgid "Action form"
-msgstr "Actieformulier"
+msgstr "动作表单"
msgctxt "selection:ir.action.keyword,keyword:"
msgid "Action tree"
-msgstr "Actie boomstructuur"
+msgstr "动作树形列表"
msgctxt "selection:ir.action.keyword,keyword:"
msgid "Form relate"
-msgstr "Formulier relatie"
+msgstr "表单关联"
msgctxt "selection:ir.action.keyword,keyword:"
msgid "Open Graph"
-msgstr "Open een grafiek"
+msgstr "打开表单"
msgctxt "selection:ir.action.keyword,keyword:"
msgid "Open tree"
-msgstr "Open boomstructuur"
+msgstr "打开树形列表"
msgctxt "selection:ir.action.keyword,keyword:"
msgid "Print form"
-msgstr "Formulier afdrukken"
+msgstr "打印表单"
msgctxt "selection:ir.attachment,type:"
msgid "Data"
-msgstr ""
+msgstr "数据"
-#, fuzzy
msgctxt "selection:ir.attachment,type:"
msgid "Link"
-msgstr "Verbinding"
+msgstr "链接"
msgctxt "selection:ir.cron,interval_type:"
msgid "Days"
-msgstr "Dagen"
+msgstr "日"
msgctxt "selection:ir.cron,interval_type:"
msgid "Hours"
-msgstr "Uren"
+msgstr "小时"
msgctxt "selection:ir.cron,interval_type:"
msgid "Minutes"
-msgstr "Minuten"
+msgstr "分钟"
msgctxt "selection:ir.cron,interval_type:"
msgid "Months"
-msgstr "Maanden"
+msgstr "月"
msgctxt "selection:ir.cron,interval_type:"
msgid "Weeks"
-msgstr "Weken"
+msgstr "周"
msgctxt "selection:ir.lang,direction:"
msgid "Left-to-right"
-msgstr "Van links naar rechts"
+msgstr "从左向右"
msgctxt "selection:ir.lang,direction:"
msgid "Right-to-left"
-msgstr "Van rechts naar links"
+msgstr "从右向左"
msgctxt "selection:ir.module,state:"
msgid "Installed"
-msgstr ""
+msgstr "已安装"
msgctxt "selection:ir.module,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "未安装"
msgctxt "selection:ir.module,state:"
msgid "To be installed"
-msgstr ""
+msgstr "待安装"
msgctxt "selection:ir.module,state:"
msgid "To be removed"
-msgstr ""
+msgstr "待删除"
msgctxt "selection:ir.module,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "待升级"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Done"
-msgstr ""
+msgstr "完成"
msgctxt "selection:ir.module.config_wizard.item,state:"
msgid "Open"
-msgstr ""
+msgstr "打开"
msgctxt "selection:ir.module.dependency,state:"
msgid "Installed"
-msgstr ""
+msgstr "已安装"
msgctxt "selection:ir.module.dependency,state:"
msgid "Not Installed"
-msgstr ""
+msgstr "未安装"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be installed"
-msgstr ""
+msgstr "待安装"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be removed"
-msgstr ""
+msgstr "待删除"
msgctxt "selection:ir.module.dependency,state:"
msgid "To be upgraded"
-msgstr ""
+msgstr "待升级"
msgctxt "selection:ir.module.dependency,state:"
msgid "Unknown"
-msgstr ""
+msgstr "未知"
msgctxt "selection:ir.sequence,type:"
msgid "Decimal Timestamp"
-msgstr "Tijdmarkering"
+msgstr "十进制时间戳"
msgctxt "selection:ir.sequence,type:"
msgid "Hexadecimal Timestamp"
-msgstr "Hexadecimale tijdmarkering"
+msgstr "十六进制时间戳"
msgctxt "selection:ir.sequence,type:"
msgid "Incremental"
-msgstr "Oplopend"
+msgstr "升序"
msgctxt "selection:ir.sequence.strict,type:"
msgid "Decimal Timestamp"
-msgstr "Tijdmarkering"
+msgstr "十进制时间戳"
msgctxt "selection:ir.sequence.strict,type:"
msgid "Hexadecimal Timestamp"
-msgstr "Hexadecimale tijdmarkering"
+msgstr "十六进制时间戳"
msgctxt "selection:ir.sequence.strict,type:"
msgid "Incremental"
-msgstr "Oplopend"
+msgstr "升序"
msgctxt "selection:ir.translation,type:"
msgid "Error"
-msgstr "Fout"
+msgstr "错误"
msgctxt "selection:ir.translation,type:"
msgid "Field"
-msgstr "Veld"
+msgstr "数据"
msgctxt "selection:ir.translation,type:"
msgid "Help"
-msgstr "Help"
+msgstr "帮助"
msgctxt "selection:ir.translation,type:"
msgid "Model"
-msgstr "Model"
+msgstr "模型"
+#, fuzzy
msgctxt "selection:ir.translation,type:"
-msgid "ODT"
-msgstr "ODT"
+msgid "Report"
+msgstr "报告..."
msgctxt "selection:ir.translation,type:"
msgid "Selection"
-msgstr "Selectie"
+msgstr "选择"
msgctxt "selection:ir.translation,type:"
msgid "View"
-msgstr "Aanzicht"
+msgstr "视图"
msgctxt "selection:ir.translation,type:"
msgid "Wizard Button"
-msgstr "Assistent knop"
+msgstr "向导按钮"
msgctxt "selection:ir.ui.menu,action:"
msgid ""
@@ -3620,80 +3672,75 @@ msgstr ""
msgctxt "selection:ir.ui.view,type:"
msgid "Board"
-msgstr "Bord"
+msgstr "板块"
msgctxt "selection:ir.ui.view,type:"
msgid "Calendar"
-msgstr ""
+msgstr "日历"
msgctxt "selection:ir.ui.view,type:"
msgid "Form"
-msgstr "Formulier"
+msgstr "表单"
msgctxt "selection:ir.ui.view,type:"
msgid "Graph"
-msgstr "Grafiek"
+msgstr "图标"
msgctxt "selection:ir.ui.view,type:"
msgid "Tree"
-msgstr "Boomstructuur"
+msgstr "树形"
-#, fuzzy
msgctxt "view:ir.action.act_window.domain:"
msgid "Domain"
-msgstr "Domein"
+msgstr "域配置"
msgctxt "view:ir.action.act_window.domain:"
msgid "Domains"
-msgstr ""
+msgstr "域配置"
-#, fuzzy
msgctxt "view:ir.action.act_window.view:"
msgid "View"
-msgstr "Overzicht"
+msgstr "视图"
-#, fuzzy
msgctxt "view:ir.action.act_window.view:"
msgid "Views"
-msgstr "Aanzichten"
+msgstr "视图"
msgctxt "view:ir.action.act_window:"
msgid "General"
-msgstr "Algemeen"
+msgstr "基本"
msgctxt "view:ir.action.act_window:"
msgid "Open Window"
-msgstr "Open scherm"
+msgstr "打开窗口"
msgctxt "view:ir.action.act_window:"
msgid "Open a Window"
-msgstr "Open een scherm"
+msgstr "打开窗口"
-#, fuzzy
msgctxt "view:ir.action.keyword:"
msgid "Keyword"
-msgstr "Trefwoord"
+msgstr "关键词"
-#, fuzzy
msgctxt "view:ir.action.keyword:"
msgid "Keywords"
-msgstr "Trefwoorden"
+msgstr "关键词"
msgctxt "view:ir.action.report:"
msgid "General"
-msgstr "Algemeen"
+msgstr "基本"
msgctxt "view:ir.action.report:"
msgid "Report"
-msgstr "Verslag"
+msgstr "报告..."
msgctxt "view:ir.action.report:"
msgid "Report xml"
-msgstr "Rapportage xml"
+msgstr "报表xml"
msgctxt "view:ir.action.url:"
msgid "General"
-msgstr "Algemeen"
+msgstr "基本"
msgctxt "view:ir.action.url:"
msgid "URL"
@@ -3701,464 +3748,473 @@ msgstr "URL"
msgctxt "view:ir.action.wizard:"
msgid "General"
-msgstr "Algemeen"
+msgstr "基本"
msgctxt "view:ir.action.wizard:"
msgid "Wizard"
-msgstr "Assistent"
+msgstr "向导"
msgctxt "view:ir.action:"
msgid "Action"
-msgstr "Actie"
+msgstr "操作"
msgctxt "view:ir.action:"
msgid "General"
-msgstr "Algemeen"
+msgstr "基本"
msgctxt "view:ir.attachment:"
msgid "Attachments"
-msgstr "Bijlagen"
+msgstr "附件文档"
msgctxt "view:ir.attachment:"
msgid "Last Modification Time"
-msgstr ""
+msgstr "最近更改时间"
msgctxt "view:ir.cron:"
msgid "Action to trigger"
-msgstr "Actie om uit te voeren"
+msgstr "待执行"
+
+msgctxt "view:ir.cron:"
+msgid "Run Once"
+msgstr ""
msgctxt "view:ir.cron:"
msgid "Scheduled Action"
-msgstr "Geplande actie"
+msgstr "计划动作"
msgctxt "view:ir.cron:"
msgid "Scheduled Actions"
-msgstr "Geplande acties"
+msgstr "计划动作"
msgctxt "view:ir.export:"
msgid "Exports"
-msgstr "Export"
+msgstr "导出"
msgctxt "view:ir.lang:"
msgid "Date Formatting"
-msgstr "Datum format"
+msgstr "日期格式"
msgctxt "view:ir.lang:"
msgid "Language"
-msgstr "Taal"
+msgstr "语言"
msgctxt "view:ir.lang:"
msgid "Languages"
-msgstr "Talen"
+msgstr "语言"
msgctxt "view:ir.lang:"
msgid "Numbers Formatting"
-msgstr "Getal weergave"
+msgstr "数字书写格式"
msgctxt "view:ir.model.access:"
msgid "Access controls"
-msgstr "Toegangcontrole"
+msgstr "权限控制"
msgctxt "view:ir.model.button:"
msgid "Button"
-msgstr ""
+msgstr "按钮"
msgctxt "view:ir.model.button:"
msgid "Buttons"
-msgstr ""
+msgstr "按钮"
msgctxt "view:ir.model.data:"
msgid "Model Data"
-msgstr ""
+msgstr "模型数据"
msgctxt "view:ir.model.data:"
msgid "Sync"
-msgstr ""
+msgstr "同步"
msgctxt "view:ir.model.field.access:"
msgid "Field Access"
-msgstr ""
+msgstr "数据权限"
-#, fuzzy
msgctxt "view:ir.model.field:"
msgid "Field"
-msgstr "Veld"
+msgstr "数据"
msgctxt "view:ir.model.field:"
msgid "Fields"
-msgstr "Velden"
+msgstr "数据"
msgctxt "view:ir.model.print_model_graph.start:"
msgid "Print Model Graph"
-msgstr ""
+msgstr "打印模型图表"
msgctxt "view:ir.model:"
msgid "Model Description"
-msgstr "Model omschrijving"
+msgstr "模型描述"
msgctxt "view:ir.module.config_wizard.done:"
msgid "Module configuration"
-msgstr ""
+msgstr "模块设置"
msgctxt "view:ir.module.config_wizard.done:"
msgid "The configuration is done."
-msgstr ""
+msgstr "设置结束."
msgctxt "view:ir.module.config_wizard.first:"
msgid "Welcome to the module configuration wizard!"
-msgstr ""
+msgstr "欢迎使用模块设置向导!"
msgctxt "view:ir.module.config_wizard.first:"
msgid ""
"You will be able to configure your installation depending on the modules you"
" have installed."
-msgstr ""
+msgstr "通过本向导可以对安装的模块进行设置."
msgctxt "view:ir.module.config_wizard.item:"
msgid "Config Wizard Items"
-msgstr ""
+msgstr "设置向导项目"
msgctxt "view:ir.module.config_wizard.other:"
msgid "Configuration Wizard Next Step!"
-msgstr ""
+msgstr "设置向导下一步!"
msgctxt "view:ir.module.dependency:"
msgid "Dependencies"
-msgstr ""
+msgstr "依赖"
msgctxt "view:ir.module.dependency:"
msgid "Dependency"
-msgstr ""
+msgstr "模块依赖关系"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "System Upgrade Done"
-msgstr ""
+msgstr "系统升级完成"
msgctxt "view:ir.module.install_upgrade.done:"
msgid "The modules have been upgraded / installed !"
-msgstr ""
+msgstr "所选模块升级/安装成功 !"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Note that this operation may take a few minutes."
-msgstr ""
+msgstr "完成本操作可能会花费一些时间."
msgctxt "view:ir.module.install_upgrade.start:"
msgid "System Upgrade"
-msgstr ""
+msgstr "系统升级"
msgctxt "view:ir.module.install_upgrade.start:"
msgid "Your system will be upgraded."
-msgstr ""
+msgstr "将进行系统升级."
msgctxt "view:ir.module:"
msgid "Cancel Installation"
-msgstr ""
+msgstr "取消“安装”"
msgctxt "view:ir.module:"
msgid "Cancel Uninstallation"
-msgstr ""
+msgstr "取消“卸载”"
msgctxt "view:ir.module:"
msgid "Cancel Upgrade"
-msgstr ""
+msgstr "取消“升级”"
msgctxt "view:ir.module:"
msgid "Mark for Installation"
-msgstr ""
+msgstr "标记“安装”"
msgctxt "view:ir.module:"
msgid "Mark for Uninstallation (beta)"
-msgstr ""
+msgstr "标记“卸载”"
msgctxt "view:ir.module:"
msgid "Mark for Upgrade"
-msgstr ""
+msgstr "标记“升级”"
msgctxt "view:ir.module:"
msgid "Module"
-msgstr ""
+msgstr "模块"
msgctxt "view:ir.module:"
msgid "Modules"
+msgstr "模块"
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "Date"
+msgstr "日期格式"
+
+msgctxt "view:ir.note:"
+msgid "Note"
msgstr ""
+msgctxt "view:ir.note:"
+msgid "Notes"
+msgstr ""
+
+msgctxt "view:ir.note:"
+msgid "Time"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:ir.note:"
+msgid "User"
+msgstr "用户"
+
msgctxt "view:ir.property:"
msgid "Properties"
-msgstr "Eigenschappen"
+msgstr "属性"
msgctxt "view:ir.property:"
msgid "Property"
-msgstr "Eigenschap"
+msgstr "属性"
msgctxt "view:ir.rule.group:"
msgid ""
"If there is no test defined, the rule is always satisfied if not global"
-msgstr ""
-"Als er geen test is gedefinieerd is de regel altijd waar mits niet globaal"
+msgstr "非全局规则有明确的测试定义才有效."
msgctxt "view:ir.rule.group:"
msgid "Record rules"
-msgstr "Item regels"
+msgstr "数据规则"
msgctxt "view:ir.rule.group:"
msgid "The rule is satisfied if at least one test is True"
-msgstr "De regel is waar als tenminste aan één voorwaarde wordt voldaan"
+msgstr "任意一项测试结果为真,即判断符合规则."
msgctxt "view:ir.rule:"
msgid "Test"
-msgstr "Test"
+msgstr "测试"
msgctxt "view:ir.sequence.type:"
msgid "Sequence Type"
-msgstr "Reeks type"
+msgstr "序列类型"
msgctxt "view:ir.sequence:"
msgid "${day}"
-msgstr "${dag}"
+msgstr "${day}"
msgctxt "view:ir.sequence:"
msgid "${month}"
-msgstr "${maand}"
+msgstr "${month}"
msgctxt "view:ir.sequence:"
msgid "${year}"
-msgstr "${jaar}"
+msgstr "${year}"
msgctxt "view:ir.sequence:"
msgid "Day:"
-msgstr "Dag:"
+msgstr "日"
msgctxt "view:ir.sequence:"
msgid "Incremental"
-msgstr "Oplopend"
+msgstr "升序"
msgctxt "view:ir.sequence:"
msgid "Legend (Placeholders for prefix, suffix)"
-msgstr "Legenda (sjabloon voor voorvoegsel, toevoeging)"
+msgstr "前后缀占位符"
msgctxt "view:ir.sequence:"
msgid "Month:"
-msgstr "Maand:"
+msgstr "月份:"
msgctxt "view:ir.sequence:"
msgid "Sequences"
-msgstr "Reeksen"
+msgstr "序列"
msgctxt "view:ir.sequence:"
msgid "Timestamp"
-msgstr "Tijdmarkering"
+msgstr "时间戳"
msgctxt "view:ir.sequence:"
msgid "Year:"
-msgstr "Jaar:"
+msgstr "年份"
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations"
-msgstr "Vertalingen opschonen"
+msgstr "清理翻译"
msgctxt "view:ir.translation.clean.start:"
msgid "Clean Translations?"
-msgstr ""
+msgstr "清理翻译 ?"
-#, fuzzy
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations"
-msgstr "Vertalingen opschonen"
+msgstr "清理翻译"
-#, fuzzy
msgctxt "view:ir.translation.clean.succeed:"
msgid "Clean Translations Succeed!"
-msgstr "Vertalingen opschonen gelukt!"
+msgstr "翻译清理成功!"
-#, fuzzy
msgctxt "view:ir.translation.export.result:"
msgid "Export Translation"
-msgstr "Vertaling exporteren"
+msgstr "导出翻译条目"
msgctxt "view:ir.translation.export.start:"
msgid "Export Translation"
-msgstr "Vertaling exporteren"
+msgstr "导出翻译条目"
msgctxt "view:ir.translation.set.start:"
msgid "Set Translations"
-msgstr ""
+msgstr "保存翻译"
msgctxt "view:ir.translation.set.start:"
msgid "Synchronize Translations?"
-msgstr ""
+msgstr "使翻译生效?"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Succeed!"
-msgstr ""
+msgstr "保存成功!"
msgctxt "view:ir.translation.set.succeed:"
msgid "Set Translations"
-msgstr ""
+msgstr "保存翻译"
-#, fuzzy
msgctxt "view:ir.translation.update.start:"
msgid "Synchronize Translations"
-msgstr "Vertaling synchroniseren"
+msgstr "使翻译生效"
msgctxt "view:ir.translation:"
msgid "Translations"
-msgstr "Vertalingen"
+msgstr "翻译"
msgctxt "view:ir.trigger:"
msgid "Trigger"
-msgstr "Starter"
+msgstr "触发器"
msgctxt "view:ir.trigger:"
msgid "Triggers"
-msgstr "Starters"
+msgstr "触发器"
-#, fuzzy
msgctxt "view:ir.ui.icon:"
msgid "Icon"
-msgstr "Icoon"
+msgstr "图标"
msgctxt "view:ir.ui.icon:"
msgid "Icons"
-msgstr ""
+msgstr "图标"
msgctxt "view:ir.ui.menu.favorite:"
msgid "Menu Favorite"
-msgstr ""
+msgstr "收藏"
msgctxt "view:ir.ui.menu.favorite:"
msgid "Menu Favorites"
-msgstr ""
+msgstr "收藏"
msgctxt "view:ir.ui.menu:"
msgid "Menu"
-msgstr "Menu"
+msgstr "菜单"
msgctxt "view:ir.ui.view:"
msgid "Show"
-msgstr ""
+msgstr "显示"
msgctxt "view:ir.ui.view:"
msgid "View"
-msgstr "Overzicht"
+msgstr "视图"
msgctxt "view:ir.ui.view_search:"
msgid "View Search"
-msgstr ""
+msgstr "搜索视图"
msgctxt "view:ir.ui.view_search:"
msgid "View Searches"
-msgstr ""
+msgstr "搜索视图"
msgctxt "view:ir.ui.view_tree_state:"
msgid "View Tree State"
-msgstr ""
+msgstr "导航树状态"
msgctxt "view:ir.ui.view_tree_state:"
msgid "Views Tree State"
-msgstr ""
+msgstr "导航树状态"
msgctxt "view:ir.ui.view_tree_width:"
msgid "View Tree Width"
-msgstr "Aanzicht boomstructuurbreedte"
+msgstr "导航树宽度"
msgctxt "view:ir.ui.view_tree_width:"
msgid "Views Tree Width"
-msgstr "Aanzichten boomstructuurbreedte"
+msgstr "导航树宽度"
-#, fuzzy
msgctxt "wizard_button:ir.model.print_model_graph,start,end:"
msgid "Cancel"
-msgstr "Annuleren"
+msgstr "取消"
msgctxt "wizard_button:ir.model.print_model_graph,start,print_:"
msgid "Print"
-msgstr ""
+msgstr "打印"
msgctxt "wizard_button:ir.module.config_wizard,done,end:"
msgid "OK"
-msgstr ""
+msgstr "确定"
msgctxt "wizard_button:ir.module.config_wizard,first,action:"
msgid "OK"
-msgstr ""
+msgstr "确定"
msgctxt "wizard_button:ir.module.config_wizard,first,end:"
msgid "Cancel"
-msgstr ""
+msgstr "取消"
msgctxt "wizard_button:ir.module.config_wizard,other,action:"
msgid "Next"
-msgstr ""
+msgstr "下一步"
msgctxt "wizard_button:ir.module.config_wizard,other,end:"
msgid "Cancel"
-msgstr ""
+msgstr "取消"
msgctxt "wizard_button:ir.module.install_upgrade,done,config:"
msgid "OK"
-msgstr ""
+msgstr "确定"
msgctxt "wizard_button:ir.module.install_upgrade,start,end:"
msgid "Cancel"
-msgstr ""
+msgstr "取消"
msgctxt "wizard_button:ir.module.install_upgrade,start,upgrade:"
msgid "Start Upgrade"
-msgstr ""
+msgstr "升级"
msgctxt "wizard_button:ir.translation.clean,start,clean:"
msgid "Clean"
-msgstr ""
+msgstr "清理"
-#, fuzzy
msgctxt "wizard_button:ir.translation.clean,start,end:"
msgid "Cancel"
-msgstr "Annuleren"
+msgstr "取消"
-#, fuzzy
msgctxt "wizard_button:ir.translation.clean,succeed,end:"
msgid "OK"
-msgstr "Oké"
+msgstr "确定"
-#, fuzzy
msgctxt "wizard_button:ir.translation.export,result,end:"
msgid "Close"
-msgstr "Sluiten"
+msgstr "关闭"
-#, fuzzy
msgctxt "wizard_button:ir.translation.export,start,end:"
msgid "Cancel"
-msgstr "Annuleren"
+msgstr "取消"
-#, fuzzy
msgctxt "wizard_button:ir.translation.export,start,export:"
msgid "Export"
-msgstr "Exporteren"
+msgstr "导出"
-#, fuzzy
msgctxt "wizard_button:ir.translation.set,start,end:"
msgid "Cancel"
-msgstr "Annuleren"
+msgstr "取消"
msgctxt "wizard_button:ir.translation.set,start,set_:"
msgid "Set"
-msgstr ""
+msgstr "设置"
-#, fuzzy
msgctxt "wizard_button:ir.translation.set,succeed,end:"
msgid "OK"
-msgstr "Oké"
+msgstr "确定"
-#, fuzzy
msgctxt "wizard_button:ir.translation.update,start,end:"
msgid "Cancel"
-msgstr "Annuleren"
+msgstr "取消"
msgctxt "wizard_button:ir.translation.update,start,update:"
msgid "Update"
-msgstr ""
+msgstr "更新"
-#, fuzzy
msgctxt "wizard_button:ir.ui.view.show,start,end:"
msgid "Close"
-msgstr "Sluiten"
+msgstr "关闭"
diff --git a/trytond/ir/model.py b/trytond/ir/model.py
index 01e0b11..9045980 100644
--- a/trytond/ir/model.py
+++ b/trytond/ir/model.py
@@ -12,7 +12,7 @@ try:
except ImportError:
import json
-from ..model import ModelView, ModelSQL, fields, Unique
+from ..model import ModelView, ModelSQL, Workflow, fields, Unique
from ..report import Report
from ..wizard import Wizard, StateView, StateAction, Button
from ..transaction import Transaction
@@ -22,7 +22,7 @@ from ..pyson import Bool, Eval
from ..rpc import RPC
from .. import backend
from ..protocols.jsonrpc import JSONDecoder, JSONEncoder
-from ..tools import is_instance_method
+from ..tools import is_instance_method, cursor_dict
try:
from ..tools.StringMatcher import StringMatcher
except ImportError:
@@ -31,6 +31,7 @@ except ImportError:
__all__ = [
'Model', 'ModelField', 'ModelAccess', 'ModelFieldAccess', 'ModelButton',
'ModelData', 'PrintModelGraphStart', 'PrintModelGraph', 'ModelGraph',
+ 'ModelWorkflowGraph',
]
IDENTIFIER = re.compile(r'^[a-zA-z_][a-zA-Z0-9_]*$')
@@ -84,7 +85,7 @@ class Model(ModelSQL, ModelView):
def register(cls, model, module_name):
pool = Pool()
Property = pool.get('ir.property')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
ir_model = cls.__table__()
cursor.execute(*ir_model.select(ir_model.id,
@@ -265,7 +266,7 @@ class ModelField(ModelSQL, ModelView):
def register(cls, model, module_name, model_id):
pool = Pool()
Model = pool.get('ir.model')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
ir_model_field = cls.__table__()
ir_model = Model.__table__()
@@ -280,7 +281,7 @@ class ModelField(ModelSQL, ModelView):
ir_model_field.module.as_('module'),
ir_model_field.help.as_('help'),
where=ir_model.model == model.__name__))
- model_fields = dict((f['name'], f) for f in cursor.dictfetchall())
+ model_fields = {f['name']: f for f in cursor_dict(cursor)}
for field_name, field in model._fields.iteritems():
if hasattr(field, 'model_name'):
@@ -374,7 +375,7 @@ class ModelField(ModelSQL, ModelView):
else:
model_ids.add(rec['model'])
model_ids = list(model_ids)
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
model = Model.__table__()
cursor.execute(*model.select(model.id, model.model,
where=model.id.in_(model_ids)))
@@ -449,11 +450,10 @@ class ModelAccess(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
super(ModelAccess, cls).__register__(module_name)
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.6 (model, group) no more unique
table.drop_constraint('model_group_uniq')
@@ -488,7 +488,7 @@ class ModelAccess(ModelSQL, ModelView):
pool = Pool()
Model = pool.get('ir.model')
UserGroup = pool.get('res.user-res.group')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
user = Transaction().user
model_access = cls.__table__()
ir_model = Model.__table__()
@@ -628,11 +628,10 @@ class ModelFieldAccess(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
super(ModelFieldAccess, cls).__register__(module_name)
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.6 (field, group) no more unique
table.drop_constraint('field_group_uniq')
@@ -669,7 +668,6 @@ class ModelFieldAccess(ModelSQL, ModelView):
Model = pool.get('ir.model')
ModelField = pool.get('ir.model.field')
UserGroup = pool.get('res.user-res.group')
- cursor = Transaction().cursor
user = Transaction().user
field_access = cls.__table__()
ir_model = Model.__table__()
@@ -687,6 +685,7 @@ class ModelFieldAccess(ModelSQL, ModelView):
default = {}
accesses = dict((m, default) for m in models)
+ cursor = Transaction().connection.cursor()
cursor.execute(*field_access.join(model_field,
condition=field_access.field == model_field.id
).join(ir_model,
@@ -856,12 +855,12 @@ class ModelData(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
model_data = cls.__table__()
super(ModelData, cls).__register__(module_name)
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.6: remove inherit
if table.column_exist('inherit'):
@@ -1097,3 +1096,58 @@ class ModelGraph(Report):
edge = pydot.Edge(str(tail), str(head), **args)
graph.add_edge(edge)
+
+
+class ModelWorkflowGraph(Report):
+ __name__ = 'ir.model.workflow_graph'
+
+ @classmethod
+ def execute(cls, ids, data):
+ import pydot
+ pool = Pool()
+ Model = pool.get('ir.model')
+ ActionReport = pool.get('ir.action.report')
+
+ action_report_ids = ActionReport.search([
+ ('report_name', '=', cls.__name__)
+ ])
+ if not action_report_ids:
+ raise Exception('Error', 'Report (%s) not find!' % cls.__name__)
+ action_report = ActionReport(action_report_ids[0])
+
+ models = Model.browse(ids)
+
+ graph = pydot.Dot()
+ graph.set('center', '1')
+ graph.set('ratio', 'auto')
+ direction = Transaction().context.get('language_direction', 'ltr')
+ graph.set('rankdir', {'ltr': 'LR', 'rtl': 'RL'}[direction])
+ cls.fill_graph(models, graph)
+ data = graph.create(prog='dot', format='png')
+ return ('png', fields.Binary.cast(data), False, action_report.name)
+
+ @classmethod
+ def fill_graph(cls, models, graph):
+ 'Fills pydot graph with models wizard.'
+ import pydot
+ pool = Pool()
+
+ for record in models:
+ Model = pool.get(record.model)
+
+ if not issubclass(Model, Workflow):
+ continue
+
+ subgraph = pydot.Cluster('%s' % record.id, label=record.model)
+ graph.add_subgraph(subgraph)
+
+ state_field = getattr(Model, Model._transition_state)
+ for state, _ in state_field.selection:
+ node = pydot.Node(
+ '"%s"' % state, shape='octagon', label=state)
+ subgraph.add_node(node)
+
+ for from_, to in Model._transitions:
+ edge = pydot.Edge('"%s"' % from_, '"%s"' % to,
+ arrowhead='normal')
+ subgraph.add_edge(edge)
diff --git a/trytond/ir/model.xml b/trytond/ir/model.xml
index ae4d69d..4983161 100644
--- a/trytond/ir/model.xml
+++ b/trytond/ir/model.xml
@@ -143,6 +143,18 @@ this repository contains the full copyright notices and license terms. -->
<field name="action" ref="print_model_graph"/>
</record>
+ <record model="ir.action.report" id="report_model_workflow_graph">
+ <field name="name">Workflow Graph</field>
+ <field name="model">ir.model</field>
+ <field name="report_name">ir.model.workflow_graph</field>
+ </record>
+ <record model="ir.action.keyword"
+ id="print_model_workflow_graph_keyword">
+ <field name="keyword">form_print</field>
+ <field name="model">ir.model,-1</field>
+ <field name="action" ref="report_model_workflow_graph"/>
+ </record>
+
<record model="ir.ui.view" id="model_button_view_list">
<field name="model">ir.model.button</field>
<field name="type">tree</field>
diff --git a/trytond/ir/module.py b/trytond/ir/module.py
index d2312f2..1514a1b 100644
--- a/trytond/ir/module.py
+++ b/trytond/ir/module.py
@@ -97,12 +97,11 @@ class Module(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
# Migration from 3.6: remove double module
old_table = 'ir_module_module'
- if TableHandler.table_exist(cursor, old_table):
- TableHandler.table_rename(cursor, old_table, cls._table)
+ if TableHandler.table_exist(old_table):
+ TableHandler.table_rename(old_table, cls._table)
super(Module, cls).__register__(module_name)
@@ -241,7 +240,7 @@ class Module(ModelSQL, ModelView):
Dependency = pool.get('ir.module.dependency')
module_table = Module.__table__()
dep_table = Dependency.__table__()
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
for module in modules:
cursor.execute(*dep_table.join(module_table,
condition=(dep_table.module == module_table.id)
@@ -344,12 +343,11 @@ class ModuleDependency(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
# Migration from 3.6: remove double module
old_table = 'ir_module_module_dependency'
- if TableHandler.table_exist(cursor, old_table):
- TableHandler.table_rename(cursor, old_table, cls._table)
+ if TableHandler.table_exist(old_table):
+ TableHandler.table_rename(old_table, cls._table)
super(ModuleDependency, cls).__register__(module_name)
@@ -385,22 +383,22 @@ class ModuleConfigWizardItem(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
pool = Pool()
ModelData = pool.get('ir.model.data')
model_data = ModelData.__table__()
# Migration from 3.6: remove double module
old_table = 'ir_module_module_config_wizard_item'
- if TableHandler.table_exist(cursor, old_table):
- TableHandler.table_rename(cursor, old_table, cls._table)
+ if TableHandler.table_exist(old_table):
+ TableHandler.table_rename(old_table, cls._table)
cursor.execute(*model_data.update(
columns=[model_data.model],
values=[cls.__name__],
where=(model_data.model ==
'ir.module.module.config_wizard.item')))
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migrate from 2.2 remove name
table.drop_column('name')
@@ -481,7 +479,7 @@ class ModuleConfigWizard(Wizard):
action = ConfigStateAction()
done = StateView('ir.module.config_wizard.done',
'ir.module_config_wizard_done_view_form', [
- Button('OK', 'end', 'tryton-close', default=True),
+ Button('OK', 'end', 'tryton-ok', default=True),
])
def transition_start(self):
@@ -533,8 +531,8 @@ class ModuleInstallUpgrade(Wizard):
@classmethod
def check_access(cls):
- # Use new cursor to prevent lock when installing modules
- with Transaction().new_cursor():
+ # Use new transaction to prevent lock when installing modules
+ with Transaction().new_transaction():
super(ModuleInstallUpgrade, cls).check_access()
@staticmethod
@@ -559,7 +557,7 @@ class ModuleInstallUpgrade(Wizard):
pool = Pool()
Module = pool.get('ir.module')
Lang = pool.get('ir.lang')
- with Transaction().new_cursor() as transaction:
+ with Transaction().new_transaction():
modules = Module.search([
('state', 'in', ['to upgrade', 'to remove', 'to install']),
])
@@ -568,7 +566,6 @@ class ModuleInstallUpgrade(Wizard):
('translatable', '=', True),
])
lang = [x.code for x in langs]
- transaction.cursor.commit()
if update:
pool.init(update=update, lang=lang)
return 'done'
diff --git a/trytond/ir/note.py b/trytond/ir/note.py
new file mode 100644
index 0000000..7a8c33a
--- /dev/null
+++ b/trytond/ir/note.py
@@ -0,0 +1,116 @@
+# 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 textwrap import TextWrapper
+
+from sql import Null
+from sql.conditionals import Case
+
+from ..model import ModelView, ModelSQL, ModelStorage, fields
+from ..pool import Pool
+from ..transaction import Transaction
+from ..tools import grouped_slice, reduce_ids
+from ..pyson import Eval
+from .resource import ResourceMixin
+
+__all__ = ['Note', 'NoteRead']
+
+
+class Note(ResourceMixin, ModelSQL, ModelView):
+ "Note"
+ __name__ = 'ir.note'
+ message = fields.Text('Message', states={
+ 'readonly': Eval('id', 0) > 0,
+ })
+ message_wrapped = fields.Function(fields.Text('Message'),
+ 'on_change_with_message_wrapped')
+ unread = fields.Function(fields.Boolean('Unread'), 'get_unread',
+ searcher='search_unread', setter='set_unread')
+
+ @staticmethod
+ def default_unread():
+ return False
+
+ @classmethod
+ def get_wrapper(cls):
+ return TextWrapper(width=79)
+
+ @fields.depends('message')
+ def on_change_with_message_wrapped(self, name=None):
+ wrapper = self.get_wrapper()
+ return '\n'.join(map(wrapper.fill, self.message.splitlines()))
+
+ @classmethod
+ def get_unread(cls, ids, name):
+ pool = Pool()
+ Read = pool.get('ir.note.read')
+ cursor = Transaction().connection.cursor()
+ user_id = Transaction().user
+ table = cls.__table__()
+ read = Read.__table__()
+
+ unread = {}
+ for sub_ids in grouped_slice(ids):
+ where = reduce_ids(table.id, sub_ids)
+ query = table.join(read, 'LEFT',
+ condition=(table.id == read.note)
+ & (read.user == user_id)
+ ).select(table.id,
+ Case((read.user != Null, False), else_=True),
+ where=where)
+ cursor.execute(*query)
+ unread.update(cursor.fetchall())
+ return unread
+
+ @classmethod
+ def search_unread(cls, name, clause):
+ pool = Pool()
+ Read = pool.get('ir.note.read')
+ user_id = Transaction().user
+ table = cls.__table__()
+ read = Read.__table__()
+
+ _, operator, value = clause
+ assert operator in ['=', '!=']
+ Operator = fields.SQL_OPERATORS[operator]
+
+ where = Operator(Case((read.user != Null, False), else_=True), value)
+ query = table.join(read, 'LEFT',
+ condition=(table.id == read.note)
+ & (read.user == user_id)
+ ).select(table.id, where=where)
+ return [('id', 'in', query)]
+
+ @classmethod
+ def set_unread(cls, notes, name, value):
+ pool = Pool()
+ Read = pool.get('ir.note.read')
+ user_id = Transaction().user
+ if not value:
+ Read.create([{'note': n.id, 'user': user_id} for n in notes])
+ else:
+ reads = []
+ for sub_notes in grouped_slice(notes):
+ reads += Read.search([
+ ('note', 'in', [n.id for n in sub_notes]),
+ ('user', '=', user_id),
+ ])
+ Read.delete(reads)
+
+ @classmethod
+ def write(cls, notes, values, *args):
+ # Avoid changing write meta data if only unread is set
+ if args or values.keys() != ['unread']:
+ super(Note, cls).write(notes, values, *args)
+ else:
+ # Check access write and clean cache
+ ModelStorage.write(notes, values)
+ cls.set_unread(notes, 'unread', values['unread'])
+
+
+class NoteRead(ModelSQL):
+ "Note Read"
+ __name__ = 'ir.note.read'
+ note = fields.Many2One('ir.note', 'Note', required=True,
+ ondelete='CASCADE')
+ user = fields.Many2One('res.user', 'User', required=True,
+ ondelete='CASCADE')
diff --git a/trytond/ir/note.xml b/trytond/ir/note.xml
new file mode 100644
index 0000000..81d683f
--- /dev/null
+++ b/trytond/ir/note.xml
@@ -0,0 +1,36 @@
+<?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. -->
+<tryton>
+ <data>
+ <record model="ir.ui.view" id="note_view_form">
+ <field name="model">ir.note</field>
+ <field name="type">form</field>
+ <field name="name">note_form</field>
+ </record>
+ <record model="ir.ui.view" id="note_view_list">
+ <field name="model">ir.note</field>
+ <field name="type">tree</field>
+ <field name="name">note_list</field>
+ </record>
+ <record model="ir.action.act_window" id="act_note_form">
+ <field name="name">Notes</field>
+ <field name="type">ir.action.act_window</field>
+ <field name="res_model">ir.note</field>
+ </record>
+ <record model="ir.action.act_window.view"
+ id="act_note_form_view1">
+ <field name="sequence" eval="1"/>
+ <field name="view" ref="note_view_list"/>
+ <field name="act_window" ref="act_note_form"/>
+ </record>
+ <record model="ir.action.act_window.view"
+ id="act_note_form_view2">
+ <field name="sequence" eval="2"/>
+ <field name="view" ref="note_view_form"/>
+ <field name="act_window" ref="act_note_form"/>
+ </record>
+ <menuitem parent="ir.menu_models" action="act_note_form"
+ id="menu_note_form"/>
+ </data>
+</tryton>
diff --git a/trytond/ir/property.py b/trytond/ir/property.py
index 9764b81..f4d0221 100644
--- a/trytond/ir/property.py
+++ b/trytond/ir/property.py
@@ -34,7 +34,7 @@ class Property(ModelSQL, ModelView):
models = cls._models_get_cache.get(None)
if models:
return models
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
model = Model.__table__()
cursor.execute(*model.select(model.model, model.name,
order_by=model.name.asc))
diff --git a/trytond/ir/resource.py b/trytond/ir/resource.py
new file mode 100644
index 0000000..0613592
--- /dev/null
+++ b/trytond/ir/resource.py
@@ -0,0 +1,113 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+from sql.conditionals import Coalesce
+
+from ..model import ModelSQL, ModelView, fields
+from ..pool import Pool
+from ..transaction import Transaction
+from ..pyson import Eval
+
+__all__ = ['ResourceMixin']
+
+
+class ResourceMixin(ModelSQL, ModelView):
+
+ resource = fields.Reference('Resource', selection='get_models',
+ required=True, select=True)
+ last_user = fields.Function(fields.Char('Last User',
+ states={
+ 'invisible': ~Eval('last_user'),
+ }),
+ 'get_last_user')
+ last_modification = fields.Function(fields.DateTime('Last Modification',
+ states={
+ 'invisible': ~Eval('last_modification'),
+ }),
+ 'get_last_modification')
+
+ @classmethod
+ def __setup__(cls):
+ super(ResourceMixin, cls).__setup__()
+ cls._order.insert(0, ('last_modification', 'DESC'))
+
+ @staticmethod
+ def default_resource():
+ return Transaction().context.get('resource')
+
+ @staticmethod
+ def get_models():
+ pool = Pool()
+ Model = pool.get('ir.model')
+ ModelAccess = pool.get('ir.model.access')
+ models = Model.search([])
+ access = ModelAccess.get_access([m.model for m in models])
+ return [(m.model, m.name) for m in models if access[m.model]['read']]
+
+ def get_last_user(self, name):
+ return (self.write_uid.rec_name if self.write_uid
+ else self.create_uid.rec_name)
+
+ def get_last_modification(self, name):
+ return (self.write_date if self.write_date else self.create_date
+ ).replace(microsecond=0)
+
+ @staticmethod
+ def order_last_modification(tables):
+ table, _ = tables[None]
+ return [Coalesce(table.write_date, table.create_date)]
+
+ @classmethod
+ def check_access(cls, ids, mode='read'):
+ pool = Pool()
+ ModelAccess = pool.get('ir.model.access')
+ if ((Transaction().user == 0)
+ or not Transaction().context.get('_check_access')):
+ return
+ model_names = set()
+ with Transaction().set_context(_check_access=False):
+ for record in cls.browse(ids):
+ if record.resource:
+ model_names.add(record.resource.__name__)
+ for model_name in model_names:
+ ModelAccess.check(model_name, mode=mode)
+
+ @classmethod
+ def read(cls, ids, fields_names=None):
+ cls.check_access(ids, mode='read')
+ return super(ResourceMixin, cls).read(ids, fields_names=fields_names)
+
+ @classmethod
+ def delete(cls, records):
+ cls.check_access([a.id for a in records], mode='delete')
+ super(ResourceMixin, cls).delete(records)
+
+ @classmethod
+ def write(cls, records, values, *args):
+ all_records = []
+ actions = iter((records, values) + args)
+ for other_records, _ in zip(actions, actions):
+ all_records += other_records
+ cls.check_access([a.id for a in all_records], mode='write')
+ super(ResourceMixin, cls).write(records, values, *args)
+ cls.check_access(all_records, mode='write')
+
+ @classmethod
+ def create(cls, vlist):
+ records = super(ResourceMixin, cls).create(vlist)
+ cls.check_access([r.id for r in records], mode='create')
+ return records
+
+ @classmethod
+ def view_header_get(cls, value, view_type='form'):
+ pool = Pool()
+ Model = pool.get('ir.model')
+ value = super(ResourceMixin, cls).view_header_get(
+ value, view_type=view_type)
+ resource = Transaction().context.get('resource')
+ if resource:
+ model_name, record_id = resource.split(',', 1)
+ model, = Model.search([('model', '=', model_name)])
+ Resource = pool.get(model_name)
+ record = Resource(int(record_id))
+ value = '%s - %s - %s' % (model.name, record.rec_name, value)
+ return value
diff --git a/trytond/ir/rule.py b/trytond/ir/rule.py
index f3dc849..e78e14a 100644
--- a/trytond/ir/rule.py
+++ b/trytond/ir/rule.py
@@ -113,7 +113,7 @@ class Rule(ModelSQL, ModelView):
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
super(Rule, cls).__register__(module_name)
- table = TableHandler(Transaction().cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.6: replace field, operator and operand by domain
table.not_null_action('field', action='remove')
@@ -181,7 +181,7 @@ class Rule(ModelSQL, ModelView):
RuleGroup_Group = pool.get('ir.rule.group-res.group')
User_Group = pool.get('res.user-res.group')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
rule_table = cls.__table__()
rule_group = RuleGroup.__table__()
rule_group_user = RuleGroup_User.__table__()
diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py
index f687760..19a0637 100644
--- a/trytond/ir/sequence.py
+++ b/trytond/ir/sequence.py
@@ -103,8 +103,7 @@ class Sequence(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.0 rename number_next into number_next_internal
table.column_rename('number_next', 'number_next_internal')
@@ -117,7 +116,7 @@ class Sequence(ModelSQL, ModelView):
for sequence in sequences:
if sequence.type != 'incremental':
continue
- if not TableHandler.sequence_exist(cursor,
+ if not TableHandler.sequence_exist(
sequence._sql_sequence_name):
sequence.create_sql_sequence(sequence.number_next_internal)
@@ -158,7 +157,9 @@ class Sequence(ModelSQL, ModelView):
return Transaction().context.get('code')
def get_number_next(self, name):
- cursor = Transaction().cursor
+ if self.type != 'incremental':
+ return
+ cursor = Transaction().connection.cursor()
sql_name = self._sql_sequence_name
if sql_sequence and not self._strict:
cursor.execute('SELECT '
@@ -258,7 +259,7 @@ class Sequence(ModelSQL, ModelView):
def create_sql_sequence(self, number_next=None):
'Create the SQL sequence'
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
param = Flavor.get().param
if self.type != 'incremental':
return
@@ -271,9 +272,9 @@ class Sequence(ModelSQL, ModelView):
def update_sql_sequence(self, number_next=None):
'Update the SQL sequence'
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
param = Flavor.get().param
- exist = TableHandler.sequence_exist(cursor, self._sql_sequence_name)
+ exist = TableHandler.sequence_exist(self._sql_sequence_name)
if self.type != 'incremental':
if exist:
self.delete_sql_sequence()
@@ -289,7 +290,7 @@ class Sequence(ModelSQL, ModelView):
def delete_sql_sequence(self):
'Delete the SQL sequence'
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
if self.type != 'incremental':
return
cursor.execute('DROP SEQUENCE "%s"'
@@ -319,7 +320,7 @@ class Sequence(ModelSQL, ModelView):
def _get_sequence(cls, sequence):
if sequence.type == 'incremental':
if sql_sequence and not cls._strict:
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
cursor.execute('SELECT nextval(\'"%s"\')'
% sequence._sql_sequence_name)
number_next, = cursor.fetchone()
@@ -382,5 +383,6 @@ class SequenceStrict(Sequence):
@classmethod
def get_id(cls, clause):
- Transaction().cursor.lock(cls._table)
+ transaction = Transaction()
+ transaction.database.lock(transaction.connection, cls._table)
return super(SequenceStrict, cls).get_id(clause)
diff --git a/trytond/ir/sequence.xml b/trytond/ir/sequence.xml
index c9c3506..32ecb63 100644
--- a/trytond/ir/sequence.xml
+++ b/trytond/ir/sequence.xml
@@ -39,12 +39,12 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.ui.view" id="sequence_strict_view_form">
<field name="model">ir.sequence.strict</field>
- <field name="type">form</field>
+ <field name="type" eval="None"/>
<field name="inherit" ref="sequence_view_form"/>
</record>
<record model="ir.ui.view" id="sequence_strict_view_tree">
<field name="model">ir.sequence.strict</field>
- <field name="type">tree</field>
+ <field name="type" eval="None"/>
<field name="inherit" ref="sequence_view_tree"/>
</record>
<record model="ir.action.act_window" id="act_sequence_strict_form">
diff --git a/trytond/ir/session.py b/trytond/ir/session.py
index 99e8e8a..c1c8c78 100644
--- a/trytond/ir/session.py
+++ b/trytond/ir/session.py
@@ -10,7 +10,6 @@ import datetime
from trytond.model import ModelSQL, fields
from trytond.config import config
from .. import backend
-from ..transaction import Transaction
__all__ = [
'Session', 'SessionWizard',
@@ -34,7 +33,7 @@ class Session(ModelSQL):
TableHandler = backend.get('TableHandler')
super(Session, cls).__register__(module_name)
- table = TableHandler(Transaction().cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.index_action('create_uid', 'add')
@staticmethod
diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py
index 4e3f69f..e57e20f 100644
--- a/trytond/ir/translation.py
+++ b/trytond/ir/translation.py
@@ -1,9 +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.
-try:
- import cStringIO as StringIO
-except ImportError:
- import StringIO
import zipfile
import polib
import xml.dom.minidom
@@ -12,6 +8,7 @@ import os
from hashlib import md5
from lxml import etree
from itertools import izip
+from io import BytesIO
from sql import Column, Null
from sql.functions import Substring, Position
@@ -19,10 +16,13 @@ from sql.conditionals import Case
from sql.operators import Or, And
from sql.aggregate import Max
+from genshi.filters.i18n import extract as genshi_extract
+from relatorio.reporting import MIMETemplateLoader
+
from ..model import ModelView, ModelSQL, fields, Unique
from ..wizard import Wizard, StateView, StateTransition, StateAction, \
Button
-from ..tools import file_open, reduce_ids, grouped_slice
+from ..tools import file_open, reduce_ids, grouped_slice, cursor_dict
from .. import backend
from ..pyson import PYSONEncoder, Eval
from ..transaction import Transaction
@@ -40,7 +40,7 @@ __all__ = ['Translation',
TRANSLATION_TYPE = [
('field', 'Field'),
('model', 'Model'),
- ('odt', 'ODT'),
+ ('report', 'Report'),
('selection', 'Selection'),
('view', 'View'),
('wizard_button', 'Wizard Button'),
@@ -96,9 +96,10 @@ class Translation(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
ir_translation = cls.__table__()
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 1.8: new field src_md5
src_md5_exist = table.column_exist('src_md5')
if not src_md5_exist:
@@ -114,7 +115,7 @@ class Translation(ModelSQL, ModelView):
# Migration from 1.8: fill new field src_md5
if not src_md5_exist:
offset = 0
- limit = cursor.IN_MAX
+ limit = transaction.database.IN_MAX
translations = True
while translations:
translations = cls.search([], offset=offset, limit=limit)
@@ -124,7 +125,7 @@ class Translation(ModelSQL, ModelView):
cls.write([translation], {
'src_md5': src_md5,
})
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.not_null_action('src_md5', action='add')
# Migration from 2.2 and 2.8
@@ -132,12 +133,18 @@ class Translation(ModelSQL, ModelView):
[-1], where=(ir_translation.res_id == Null)
| (ir_translation.res_id == 0)))
- table = TableHandler(Transaction().cursor, cls, module_name)
+ # Migration from 3.8: rename odt type in report
+ cursor.execute(*ir_translation.update(
+ [ir_translation.type],
+ ['report'],
+ where=ir_translation.type == 'odt'))
+
+ table = TableHandler(cls, module_name)
table.index_action(['lang', 'type', 'name'], 'add')
@classmethod
def register_model(cls, model, module_name):
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
ir_translation = cls.__table__()
if not model.__doc__:
@@ -176,7 +183,7 @@ class Translation(ModelSQL, ModelView):
@classmethod
def register_fields(cls, model, module_name):
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
ir_translation = cls.__table__()
# Prefetch field translations
@@ -192,7 +199,7 @@ class Translation(ModelSQL, ModelView):
& ir_translation.type.in_(
('field', 'help', 'selection'))
& ir_translation.name.in_(names))))
- for trans in cursor.dictfetchall():
+ for trans in cursor_dict(cursor):
if trans['type'] == 'field':
trans_fields[trans['name']] = trans
elif trans['type'] == 'help':
@@ -263,7 +270,7 @@ class Translation(ModelSQL, ModelView):
@classmethod
def register_error_messages(cls, model, module_name):
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
ir_translation = cls.__table__()
cursor.execute(*ir_translation.select(
@@ -271,7 +278,7 @@ class Translation(ModelSQL, ModelView):
where=((ir_translation.lang == 'en_US')
& (ir_translation.type == 'error')
& (ir_translation.name == model.__name__))))
- trans_error = dict((t['src'], t) for t in cursor.dictfetchall())
+ trans_error = {t['src']: t for t in cursor_dict(cursor)}
errors = model._get_error_messages()
for error in set(errors):
@@ -288,7 +295,7 @@ class Translation(ModelSQL, ModelView):
@classmethod
def register_wizard(cls, wizard, module_name):
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
ir_translation = cls.__table__()
# Prefetch button translations
@@ -297,7 +304,7 @@ class Translation(ModelSQL, ModelView):
where=((ir_translation.lang == 'en_US')
& (ir_translation.type == 'wizard_button')
& (ir_translation.name.like(wizard.__name__ + ',%')))))
- trans_buttons = dict((t['name'], t) for t in cursor.dictfetchall())
+ trans_buttons = {t['name']: t for t in cursor_dict(cursor)}
def update_insert_button(state_name, button):
trans_name = '%s,%s,%s' % (
@@ -433,12 +440,13 @@ class Translation(ModelSQL, ModelView):
else:
to_fetch = ids
if to_fetch:
- cursor = Transaction().cursor
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
table = cls.__table__()
fuzzy_sql = table.fuzzy == False
if Transaction().context.get('fuzzy_translation', False):
fuzzy_sql = None
- in_max = cursor.IN_MAX / 7
+ in_max = transaction.database.IN_MAX // 7
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),
@@ -472,8 +480,8 @@ class Translation(ModelSQL, ModelView):
ModelFields = pool.get('ir.model.field')
Model = pool.get('ir.model')
Config = pool.get('ir.configuration')
- cursor = Transaction().cursor
- in_max = cursor.IN_MAX
+ transaction = Transaction()
+ in_max = transaction.database.IN_MAX
if len(ids) > in_max:
for i in range(0, len(ids), in_max):
@@ -614,7 +622,7 @@ class Translation(ModelSQL, ModelView):
if trans != -1:
return trans
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
table = cls.__table__()
where = ((table.lang == lang)
& (table.type == ttype)
@@ -643,9 +651,10 @@ class Translation(ModelSQL, ModelView):
'''
res = {}
clause = []
- cursor = Transaction().cursor
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
table = cls.__table__()
- if len(args) > cursor.IN_MAX:
+ if len(args) > transaction.database.IN_MAX:
for sub_args in grouped_slice(args):
res.update(cls.get_sources(list(sub_args)))
return res
@@ -673,7 +682,7 @@ class Translation(ModelSQL, ModelView):
where &= table.src == source
clause.append(where)
if clause:
- in_max = cursor.IN_MAX / 7
+ in_max = transaction.database.IN_MAX // 7
for sub_clause in grouped_slice(clause, in_max):
cursor.execute(*table.select(
table.lang, table.type, table.name, table.src,
@@ -699,14 +708,15 @@ class Translation(ModelSQL, ModelView):
ModelView._fields_view_get_cache.clear()
vlist = [x.copy() for x in vlist]
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
table = cls.__table__()
for vals in vlist:
if not vals.get('module'):
if Transaction().context.get('module'):
vals['module'] = Transaction().context['module']
- elif vals.get('type', '') in ('odt', 'view', 'wizard_button',
- 'selection', 'error'):
+ elif vals.get('type', '') in {
+ 'report', 'view', 'wizard_button', 'selection',
+ 'error'}:
cursor.execute(*table.select(table.module,
where=(table.name == vals.get('name') or '')
& (table.res_id == vals.get('res_id') or -1)
@@ -754,7 +764,8 @@ class Translation(ModelSQL, ModelView):
@property
def unique_key(self):
- if self.type in ('odt', 'view', 'wizard_button', 'selection', 'error'):
+ if self.type in {
+ 'report', 'view', 'wizard_button', 'selection', 'error'}:
return (self.name, self.res_id, self.type, self.src)
elif self.type in ('field', 'model', 'help'):
return (self.name, self.res_id, self.type)
@@ -825,8 +836,9 @@ class Translation(ModelSQL, ModelView):
('type', '=', new_translation.type),
('module', '=', res_id_module),
]
- if new_translation.type in ('odt', 'view', 'wizard_button',
- 'selection', 'error'):
+ if new_translation.type in {
+ 'report', 'view', 'wizard_button', 'selection',
+ 'error'}:
domain.append(('src', '=', new_translation.src))
translation, = cls.search(domain)
if translation.value != new_translation.value:
@@ -886,12 +898,15 @@ class Translation(ModelSQL, ModelView):
if not ids:
to_save.append(translation)
- elif not noupdate:
+ else:
for translation_id in ids:
old_translation = id2translation[translation_id]
- old_translation.value = translation.value
- old_translation.fuzzy = translation.fuzzy
- to_save.append(old_translation)
+ if not noupdate:
+ old_translation.value = translation.value
+ old_translation.fuzzy = translation.fuzzy
+ to_save.append(old_translation)
+ else:
+ translations.add(old_translation)
cls.save(to_save)
translations |= set(to_save)
@@ -988,23 +1003,70 @@ class TranslationSet(Wizard):
Button('OK', 'end', 'tryton-ok', default=True),
])
- def _translate_report(self, node):
- strings = []
-
- if node.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
- if node.parentNode \
- and node.parentNode.tagName in ('text:placeholder',
- 'text:page-number', 'text:page-count'):
- return strings
-
- if node.nodeValue:
- txt = node.nodeValue.strip()
- if txt:
- strings.append(txt)
+ def extract_report_opendocument(self, content):
+ def extract(node):
+ if node.nodeType in {node.CDATA_SECTION_NODE, node.TEXT_NODE}:
+ if (node.parentNode
+ and node.parentNode.tagName in {
+ 'text:placeholder',
+ 'text:page-number',
+ 'text:page-count',
+ }):
+ return
+ if node.nodeValue:
+ txt = node.nodeValue.strip()
+ if txt:
+ yield txt
+
+ for child in [x for x in node.childNodes]:
+ for string in extract(child):
+ yield string
+
+ content = BytesIO(content)
+ try:
+ content = zipfile.ZipFile(content, mode='r')
+ except zipfile.BadZipfile:
+ return
- for child in [x for x in node.childNodes]:
- strings.extend(self._translate_report(child))
- return strings
+ content_xml = content.read('content.xml')
+ document = xml.dom.minidom.parseString(content_xml)
+ for string in extract(document.documentElement):
+ yield string
+
+ style_xml = content.read('styles.xml')
+ document = xml.dom.minidom.parseString(style_xml)
+ for string in extract(document.documentElement):
+ yield string
+ extract_report_odt = extract_report_opendocument
+ extract_report_odp = extract_report_opendocument
+ extract_report_ods = extract_report_opendocument
+ extract_report_odg = extract_report_opendocument
+
+ def extract_report_genshi(template_class):
+ def method(self, content,
+ keywords=None, comment_tags=None, **options):
+ options['template_class'] = template_class
+ content = BytesIO(content)
+ if keywords is None:
+ keywords = []
+ if comment_tags is None:
+ comment_tags = []
+
+ for _, _, string, _ in genshi_extract(
+ content, keywords, comment_tags, options):
+ yield string
+ if not template_class:
+ raise ValueError('a template class is required')
+ return method
+ factories = MIMETemplateLoader().factories
+ extract_report_plain = extract_report_genshi(factories['text'])
+ extract_report_xml = extract_report_genshi(
+ factories.get('markup', factories.get('xml')))
+ extract_report_html = extract_report_genshi(
+ factories.get('markup', factories.get('xml')))
+ extract_report_xhtml = extract_report_genshi(
+ factories.get('markup', factories.get('xml')))
+ del factories
def set_report(self):
pool = Pool()
@@ -1017,43 +1079,28 @@ class TranslationSet(Wizard):
if not reports:
return
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
translation = Translation.__table__()
for report in reports:
cursor.execute(*translation.select(
translation.id, translation.name, translation.src,
where=(translation.lang == 'en_US')
- & (translation.type == 'odt')
+ & (translation.type == 'report')
& (translation.name == report.report_name)
& (translation.module == report.module or '')))
- trans_reports = {}
- for trans in cursor.dictfetchall():
- trans_reports[trans['src']] = trans
-
- strings = []
+ trans_reports = {t['src']: t for t in cursor_dict(cursor)}
- odt_content = ''
+ content = None
if report.report:
with file_open(report.report.replace('/', os.sep),
mode='rb') as fp:
- odt_content = fp.read()
- for content in (report.report_content_custom, odt_content):
+ content = fp.read()
+ strings = []
+ for content in [report.report_content_custom, content]:
if not content:
continue
-
- content_io = StringIO.StringIO(content)
- try:
- content_z = zipfile.ZipFile(content_io, mode='r')
- except zipfile.BadZipfile:
- continue
-
- content_xml = content_z.read('content.xml')
- document = xml.dom.minidom.parseString(content_xml)
- strings = self._translate_report(document.documentElement)
-
- style_xml = content_z.read('styles.xml')
- document = xml.dom.minidom.parseString(style_xml)
- strings += self._translate_report(document.documentElement)
+ func_name = 'extract_report_%s' % report.template_extension
+ strings.extend(getattr(self, func_name)(content))
for string in {}.fromkeys(strings).keys():
src_md5 = Translation.get_src_md5(string)
@@ -1076,7 +1123,7 @@ class TranslationSet(Wizard):
translation.src_md5],
[string, True, src_md5],
where=(translation.name == report.report_name)
- & (translation.type == 'odt')
+ & (translation.type == 'report')
& (translation.src == string_trans)
& (translation.module == report.module)))
del trans_reports[string_trans]
@@ -1089,12 +1136,12 @@ class TranslationSet(Wizard):
translation.value, translation.module,
translation.fuzzy, translation.src_md5,
translation.res_id],
- [[report.report_name, 'en_US', 'odt', string, '',
- report.module, False, src_md5, -1]]))
+ [[report.report_name, 'en_US', 'report', string,
+ '', report.module, False, src_md5, -1]]))
if strings:
cursor.execute(*translation.delete(
where=(translation.name == report.report_name)
- & (translation.type == 'odt')
+ & (translation.type == 'report')
& (translation.module == report.module)
& ~translation.src.in_(strings)))
@@ -1119,7 +1166,7 @@ class TranslationSet(Wizard):
if not views:
return
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
translation = Translation.__table__()
for view in views:
cursor.execute(*translation.select(
@@ -1128,9 +1175,7 @@ class TranslationSet(Wizard):
& (translation.type == 'view')
& (translation.name == view.model)
& (translation.module == view.module)))
- trans_views = {}
- for trans in cursor.dictfetchall():
- trans_views[trans['src']] = trans
+ trans_views = {t['src']: t for t in cursor_dict(cursor)}
xml = (view.arch or '').strip()
if not xml:
@@ -1145,7 +1190,7 @@ class TranslationSet(Wizard):
('module', '=', view.module),
])
for view2 in views2:
- xml2 = view2.arch.strip()
+ xml2 = view2.arch
if not xml2:
continue
tree2 = etree.fromstring(xml2)
@@ -1261,7 +1306,7 @@ class TranslationClean(Wizard):
return True
@staticmethod
- def _clean_odt(translation):
+ def _clean_report(translation):
pool = Pool()
Report = pool.get('ir.action.report')
with Transaction().set_context(active_test=False):
@@ -1432,7 +1477,7 @@ class TranslationUpdateStart(ModelView):
@staticmethod
def default_language():
Lang = Pool().get('ir.lang')
- code = Transaction().context.get('language', False)
+ code = Transaction().context.get('language')
try:
lang, = Lang.search([
('code', '=', code),
@@ -1447,7 +1492,7 @@ class TranslationUpdate(Wizard):
"Update translation"
__name__ = "ir.translation.update"
- _source_types = ['odt', 'view', 'wizard_button', 'selection', 'error']
+ _source_types = ['report', 'view', 'wizard_button', 'selection', 'error']
_ressource_types = ['field', 'model', 'help']
_updatable_types = ['field', 'model', 'selection', 'help']
@@ -1465,7 +1510,8 @@ class TranslationUpdate(Wizard):
def do_update(self, action):
pool = Pool()
Translation = pool.get('ir.translation')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
+ cursor_update = Transaction().connection.cursor()
translation = Translation.__table__()
lang = self.start.language.code
columns = [translation.name.as_('name'),
@@ -1478,7 +1524,7 @@ class TranslationUpdate(Wizard):
where=(translation.lang == lang)
& translation.type.in_(self._source_types))))
to_create = []
- for row in cursor.dictfetchall():
+ for row in cursor_dict(cursor):
to_create.append({
'name': row['name'],
'res_id': row['res_id'],
@@ -1499,7 +1545,7 @@ class TranslationUpdate(Wizard):
where=(translation.lang == lang)
& translation.type.in_(self._ressource_types))))
to_create = []
- for row in cursor.dictfetchall():
+ for row in cursor_dict(cursor):
to_create.append({
'name': row['name'],
'res_id': row['res_id'],
@@ -1518,8 +1564,8 @@ class TranslationUpdate(Wizard):
- translation.select(*columns,
where=(translation.lang == lang)
& translation.type.in_(self._updatable_types))))
- for row in cursor.dictfetchall():
- cursor.execute(*translation.update(
+ for row in cursor_dict(cursor):
+ cursor_update.execute(*translation.update(
[translation.fuzzy, translation.src],
[True, row['src']],
where=(translation.name == row['name'])
@@ -1541,15 +1587,15 @@ class TranslationUpdate(Wizard):
& (translation.value != Null),
group_by=translation.src))
- for row in cursor.dictfetchall():
- cursor.execute(*translation.update(
+ for row in cursor_dict(cursor):
+ cursor_update.execute(*translation.update(
[translation.fuzzy, translation.value],
[True, row['value']],
where=(translation.src == row['src'])
& ((translation.value == '') | (translation.value == Null))
& (translation.lang == lang)))
- cursor.execute(*translation.update(
+ cursor_update.execute(*translation.update(
[translation.fuzzy],
[False],
where=((translation.value == '') | (translation.value == Null))
@@ -1579,7 +1625,7 @@ class TranslationExportStart(ModelView):
@classmethod
def default_language(cls):
Lang = Pool().get('ir.lang')
- code = Transaction().context.get('language', False)
+ code = Transaction().context.get('language')
domain = [('code', '=', code)] + cls.language.domain
try:
lang, = Lang.search(domain, limit=1)
diff --git a/trytond/ir/trigger.py b/trytond/ir/trigger.py
index 18b8e8e..29b4683 100644
--- a/trytond/ir/trigger.py
+++ b/trytond/ir/trigger.py
@@ -74,8 +74,8 @@ class Trigger(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
- table = TableHandler(cursor, cls, module_name)
+ cursor = Transaction().connection.cursor()
+ table = TableHandler(cls, module_name)
sql_table = cls.__table__()
super(Trigger, cls).__register__(module_name)
@@ -187,7 +187,7 @@ class Trigger(ModelSQL, ModelView):
TriggerLog = pool.get('ir.trigger.log')
Model = pool.get(trigger.model.model)
ActionModel = pool.get(trigger.action_model.model)
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
trigger_log = TriggerLog.__table__()
ids = map(int, records)
@@ -218,7 +218,7 @@ class Trigger(ModelSQL, ModelView):
red_sql = reduce_ids(trigger_log.record_id, sub_ids)
cursor.execute(*trigger_log.select(
trigger_log.record_id, Max(trigger_log.create_date),
- where=red_sql & (trigger_log.trigger == trigger.id),
+ where=(red_sql & (trigger_log.trigger == trigger.id)),
group_by=trigger_log.record_id))
delay = dict(cursor.fetchall())
for record_id in sub_ids:
@@ -236,8 +236,8 @@ class Trigger(ModelSQL, ModelView):
microseconds = int(timepart_full[1])
else:
microseconds = 0
- delay[record_id] = datetime.datetime(year, month, day,
- hours, minutes, seconds, microseconds)
+ delay[record_id] = datetime.datetime(year, month,
+ day, hours, minutes, seconds, microseconds)
if (datetime.datetime.now() - delay[record_id]
>= trigger.minimum_time_delay):
new_ids.append(record_id)
@@ -307,5 +307,5 @@ class TriggerLog(ModelSQL):
TableHandler = backend.get('TableHandler')
super(TriggerLog, cls).__register__(module_name)
- table = TableHandler(Transaction().cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.index_action(['trigger', 'record_id'], 'add')
diff --git a/trytond/ir/tryton.cfg b/trytond/ir/tryton.cfg
index d905607..152b3e1 100644
--- a/trytond/ir/tryton.cfg
+++ b/trytond/ir/tryton.cfg
@@ -9,6 +9,7 @@ xml:
model.xml
sequence.xml
attachment.xml
+ note.xml
cron.xml
lang.xml
translation.xml
diff --git a/trytond/ir/ui/form.rnc b/trytond/ir/ui/form.rnc
index 6c1350a..432ab2f 100644
--- a/trytond/ir/ui/form.rnc
+++ b/trytond/ir/ui/form.rnc
@@ -88,9 +88,6 @@ attlist.field &= attribute view_ids { text }?
attlist.field &= attribute product { text }?
attlist.field &=
[ a:defaultValue = "0" ] attribute invisible { "0" | "1" }?
-attlist.field &= attribute sum { text }?
-attlist.field &= attribute key { text }?
-attlist.field &= attribute color { text }?
attlist.field &=
[ a:defaultValue = "left_to_right" ] attribute orientation {
"left_to_right"
diff --git a/trytond/ir/ui/form.rng b/trytond/ir/ui/form.rng
index 8d105cc..74b01fc 100644
--- a/trytond/ir/ui/form.rng
+++ b/trytond/ir/ui/form.rng
@@ -302,21 +302,6 @@
</define>
<define name="attlist.field" combine="interleave">
<optional>
- <attribute name="sum"/>
- </optional>
- </define>
- <define name="attlist.field" combine="interleave">
- <optional>
- <attribute name="key"/>
- </optional>
- </define>
- <define name="attlist.field" combine="interleave">
- <optional>
- <attribute name="color"/>
- </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/icon.py b/trytond/ir/ui/icon.py
index b42a16f..2f419a3 100644
--- a/trytond/ir/ui/icon.py
+++ b/trytond/ir/ui/icon.py
@@ -50,5 +50,5 @@ class Icon(ModelSQL, ModelView):
def get_icon(self, name):
path = os.path.join(self.module, self.path.replace('/', os.sep))
- with file_open(path, subdir='modules') as fp:
+ with file_open(path, subdir='modules', mode='rb') as fp:
return fp.read()
diff --git a/trytond/ir/ui/menu.py b/trytond/ir/ui/menu.py
index e7ee292..00ac991 100644
--- a/trytond/ir/ui/menu.py
+++ b/trytond/ir/ui/menu.py
@@ -225,9 +225,9 @@ class UIMenu(ModelSQL, ModelView):
pool = Pool()
ActionKeyword = pool.get('ir.action.keyword')
action_keywords = []
- cursor = Transaction().cursor
- for i in range(0, len(menus), cursor.IN_MAX):
- sub_menus = menus[i:i + cursor.IN_MAX]
+ transaction = Transaction()
+ for i in range(0, len(menus), transaction.database.IN_MAX):
+ sub_menus = menus[i:i + transaction.database.IN_MAX]
action_keywords += ActionKeyword.search([
('keyword', '=', 'tree_open'),
('model', 'in', [str(menu) for menu in sub_menus]),
diff --git a/trytond/ir/ui/tree.rnc b/trytond/ir/ui/tree.rnc
index 2b61f69..c044817 100644
--- a/trytond/ir/ui/tree.rnc
+++ b/trytond/ir/ui/tree.rnc
@@ -62,6 +62,7 @@ attlist.field &= [a:defaultValue = "1"] attribute completion { "0" | "1" }?
attlist.field &= attribute string { text }?
attlist.field &= [a:defaultValue = "1"] attribute factor { text }?
attlist.field &= attribute filename { text }?
+attlist.field &= attribute view_ids { text }?
prefix = element prefix { attlist.affix, empty }
suffix = element suffix { attlist.affix, empty }
attlist.affix &= attribute string { text }?
diff --git a/trytond/ir/ui/tree.rng b/trytond/ir/ui/tree.rng
index 4e4a987..3b5d21d 100644
--- a/trytond/ir/ui/tree.rng
+++ b/trytond/ir/ui/tree.rng
@@ -198,6 +198,11 @@
<attribute name="filename"/>
</optional>
</define>
+ <define name="attlist.field" combine="interleave">
+ <optional>
+ <attribute name="view_ids"/>
+ </optional>
+ </define>
<define name="prefix">
<element name="prefix">
<ref name="attlist.affix"/>
diff --git a/trytond/ir/ui/view.py b/trytond/ir/ui/view.py
index 0492ad4..3b6c9bc 100644
--- a/trytond/ir/ui/view.py
+++ b/trytond/ir/ui/view.py
@@ -11,7 +11,7 @@ except ImportError:
from lxml import etree
from trytond.model import ModelView, ModelSQL, fields
from trytond import backend
-from trytond.pyson import Eval, Bool, PYSONDecoder
+from trytond.pyson import Eval, Bool, PYSONDecoder, If
from trytond.tools import file_open
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateView, Button
@@ -42,7 +42,13 @@ class View(ModelSQL, ModelView):
('graph', 'Graph'),
('calendar', 'Calendar'),
('board', 'Board'),
- ], 'View Type', select=True)
+ ], 'View Type', select=True,
+ domain=[
+ If(Bool(Eval('inherit')),
+ ('type', '=', None),
+ ('type', '!=', None)),
+ ],
+ depends=['inherit'])
data = fields.Text('Data')
name = fields.Char('Name', states={
'invisible': ~(Eval('module') & Eval('name')),
@@ -79,8 +85,7 @@ class View(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.4 arch moved into data
if table.column_exist('arch'):
@@ -89,7 +94,7 @@ class View(ModelSQL, ModelView):
super(View, cls).__register__(module_name)
# New instance to refresh definition
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 1.0 arch no more required
table.not_null_action('arch', action='remove')
@@ -115,13 +120,22 @@ class View(ModelSQL, ModelView):
key = (cls.__name__, type_)
rng = cls._get_rng_cache.get(key)
if rng is None:
- rng_name = os.path.join(os.path.dirname(
- unicode(__file__, sys.getfilesystemencoding())),
- type_ + '.rng')
- rng = etree.fromstring(open(rng_name).read())
+ if sys.version_info < (3,):
+ filename = __file__.decode(sys.getfilesystemencoding())
+ else:
+ filename = __file__
+ rng_name = os.path.join(os.path.dirname(filename), type_ + '.rng')
+ with open(rng_name, 'rb') as fp:
+ rng = etree.fromstring(fp.read())
cls._get_rng_cache.set(key, rng)
return rng
+ @property
+ def rng_type(self):
+ if self.inherit:
+ return self.inherit.rng_type
+ return self.type
+
@classmethod
def validate(cls, views):
super(View, cls).validate(views)
@@ -139,14 +153,14 @@ class View(ModelSQL, ModelView):
tree = etree.fromstring(xml)
if hasattr(etree, 'RelaxNG'):
- rng_type = view.inherit.type if view.inherit else view.type
- validator = etree.RelaxNG(etree=cls.get_rng(rng_type))
+ validator = etree.RelaxNG(etree=cls.get_rng(view.rng_type))
if not validator.validate(tree):
- error_log = reduce(lambda x, y: str(x) + '\n' + str(y),
- validator.error_log.filter_from_errors())
- logger.error('Invalid xml view:\n%s',
- str(error_log) + '\n' + xml)
- cls.raise_user_error('invalid_xml', (view.rec_name,))
+ error_log = '\n'.join(map(str,
+ validator.error_log.filter_from_errors()))
+ logger.error('Invalid XML view %s:\n%s\n%s',
+ view.rec_name, error_log, xml)
+ cls.raise_user_error(
+ 'invalid_xml', (view.rec_name,), error_log)
root_element = tree.getroottree().getroot()
# validate pyson attributes
@@ -156,29 +170,30 @@ class View(ModelSQL, ModelView):
def encode(element):
for attr in ('states', 'domain', 'spell'):
- if element.get(attr):
- try:
- value = PYSONDecoder().decode(element.get(attr))
- validates.get(attr, lambda a: True)(value)
- except Exception, e:
- logger.error('Invalid pyson view element "%s:%s":'
- '\n%s\n%s',
- element.get('id') or element.get('name'), attr,
- str(e), xml)
- return False
+ if not element.get(attr):
+ continue
+ try:
+ value = PYSONDecoder().decode(element.get(attr))
+ validates.get(attr, lambda a: True)(value)
+ except Exception, e:
+ error_log = '%s: <%s %s="%s"/>' % (
+ e, element.get('id') or element.get('name'), attr,
+ element.get(attr))
+ logger.error(
+ 'Invalid XML view %s:\n%s\n%s',
+ view.rec_name, error_log, xml)
+ cls.raise_user_error(
+ 'invalid_xml', (view.rec_name,), error_log)
for child in element:
- if not encode(child):
- return False
- return True
- if not encode(root_element):
- cls.raise_user_error('invalid_xml', (view.rec_name,))
+ encode(child)
+ encode(root_element)
def get_arch(self, name):
value = None
if self.name and self.module:
path = os.path.join(self.module, 'view', self.name + '.xml')
try:
- with file_open(path, subdir='modules') as fp:
+ with file_open(path, subdir='modules', mode='rb') as fp:
value = fp.read()
except IOError:
pass
@@ -320,16 +335,14 @@ class ViewTreeState(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.8: table name changed
- table.table_rename(cursor, 'ir_ui_view_tree_expanded_state',
- cls._table)
+ table.table_rename('ir_ui_view_tree_expanded_state', cls._table)
super(ViewTreeState, cls).__register__(module_name)
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.index_action(['model', 'domain', 'user', 'child_name'], 'add')
@staticmethod
diff --git a/trytond/ir/view/action_act_window_form.xml b/trytond/ir/view/action_act_window_form.xml
index 2cef84f..36d112d 100644
--- a/trytond/ir/view/action_act_window_form.xml
+++ b/trytond/ir/view/action_act_window_form.xml
@@ -7,25 +7,27 @@ this repository contains the full copyright notices and license terms. -->
<label name="active"/>
<field name="active" xexpand="0" width="100"/>
<notebook colspan="4">
- <page string="General" id="general" col="6">
+ <page string="General" id="general">
<label name="res_model"/>
<field name="res_model"/>
+ <label name="context_model"/>
+ <field name="context_model"/>
<label name="usage"/>
<field name="usage"/>
<label name="icon"/>
<field name="icon"/>
- <field name="act_window_views" colspan="6"
+ <field name="act_window_views" colspan="4"
view_ids="ir.act_window_view_view_list2"/>
- <field name="act_window_domains" colspan="6"
+ <field name="act_window_domains" colspan="4"
view_ids="ir.act_window_domain_view_list2"/>
<label name="domain"/>
- <field name="domain" colspan="5"/>
+ <field name="domain" colspan="3"/>
<label name="context"/>
- <field name="context" colspan="5"/>
+ <field name="context" colspan="3"/>
<label name="order"/>
- <field name="order" colspan="5"/>
+ <field name="order" colspan="3"/>
<label name="search_value"/>
- <field name="search_value" colspan="5"/>
+ <field name="search_value" colspan="3"/>
<label name="limit"/>
<field name="limit"/>
<label name="window_name"/>
diff --git a/trytond/ir/view/cron_form.xml b/trytond/ir/view/cron_form.xml
index 7367844..7f3355d 100644
--- a/trytond/ir/view/cron_form.xml
+++ b/trytond/ir/view/cron_form.xml
@@ -20,6 +20,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="next_call"/>
<label name="repeat_missed"/>
<field name="repeat_missed"/>
+ <button name="run_once" colspan="2" string="Run Once"/>
<separator string="Action to trigger" colspan="4"
id="action_trigger"/>
<label name="model"/>
diff --git a/trytond/ir/view/cron_list.xml b/trytond/ir/view/cron_list.xml
index b8a2e86..190fa4f 100644
--- a/trytond/ir/view/cron_list.xml
+++ b/trytond/ir/view/cron_list.xml
@@ -11,4 +11,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="interval_type"/>
<field name="number_calls"/>
<field name="active"/>
+ <button name="run_once" string="Run Once" tree_invisible="1"/>
</tree>
diff --git a/trytond/ir/view/note_form.xml b/trytond/ir/view/note_form.xml
new file mode 100644
index 0000000..cea7919
--- /dev/null
+++ b/trytond/ir/view/note_form.xml
@@ -0,0 +1,14 @@
+<?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="Note" col="6">
+ <label name="resource"/>
+ <field name="resource" colspan="5"/>
+ <label name="last_user"/>
+ <field name="last_user"/>
+ <label name="last_modification"/>
+ <field name="last_modification"/>
+ <label name="unread"/>
+ <field name="unread"/>
+ <field name="message" colspan="6"/>
+</form>
diff --git a/trytond/ir/view/note_list.xml b/trytond/ir/view/note_list.xml
new file mode 100644
index 0000000..cbb08b0
--- /dev/null
+++ b/trytond/ir/view/note_list.xml
@@ -0,0 +1,11 @@
+<?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="Notes">
+ <field name="unread"/>
+ <field name="resource"/>
+ <field name="last_user" string="User"/>
+ <field name="last_modification" widget="date" string="Date"/>
+ <field name="last_modification" widget="time" string="Time"/>
+ <field name="message_wrapped" expand="1"/>
+</tree>
diff --git a/trytond/model/fields/binary.py b/trytond/model/fields/binary.py
index ab65b91..43795d4 100644
--- a/trytond/model/fields/binary.py
+++ b/trytond/model/fields/binary.py
@@ -45,8 +45,8 @@ class Binary(Field):
res = {}
converter = cls.cast
default = None
- format_ = Transaction().context.pop('%s.%s' % (model.__name__, name),
- '')
+ format_ = Transaction().context.get(
+ '%s.%s' % (model.__name__, name), '')
if format_ == 'size':
converter = len
default = 0
diff --git a/trytond/model/fields/char.py b/trytond/model/fields/char.py
index d2d6b61..83ba323 100644
--- a/trytond/model/fields/char.py
+++ b/trytond/model/fields/char.py
@@ -1,5 +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.
+import sys
import warnings
from sql import Query, Expression
@@ -53,7 +54,7 @@ class Char(FieldTranslate):
return value
if value is None:
return None
- elif isinstance(value, str):
+ elif isinstance(value, str) and sys.version_info < (3,):
return unicode(value, 'utf-8')
assert isinstance(value, unicode)
return value
diff --git a/trytond/model/fields/field.py b/trytond/model/fields/field.py
index d09bdb0..aff2085 100644
--- a/trytond/model/fields/field.py
+++ b/trytond/model/fields/field.py
@@ -229,6 +229,8 @@ class Field(object):
if inst is None:
return self
assert self.name is not None
+ if self.name == 'id':
+ return inst._id
return inst.__getattr__(self.name)
def __set__(self, inst, value):
diff --git a/trytond/model/fields/many2many.py b/trytond/model/fields/many2many.py
index b0b81f8..58514e4 100644
--- a/trytond/model/fields/many2many.py
+++ b/trytond/model/fields/many2many.py
@@ -241,23 +241,39 @@ class Many2Many(Field):
value = tuple(instance(x) for x in (value or []))
super(Many2Many, self).__set__(inst, value)
- def convert_domain_child(self, domain, tables):
+ def convert_domain_tree(self, domain, tables):
Target = self.get_target()
table, _ = tables[None]
name, operator, ids = domain
- ids = list(ids) # Ensure it is a list for concatenation
+ ids = set(ids) # Ensure it is a set for concatenation
def get_child(ids):
if not ids:
- return []
+ return set()
children = Target.search([
(name, 'in', ids),
(name, '!=', None),
], order=[])
- child_ids = get_child([c.id for c in children])
- return ids + child_ids
- expression = table.id.in_(ids + get_child(ids))
- if operator == 'not child_of':
+ child_ids = get_child(set(c.id for c in children))
+ return ids | child_ids
+
+ def get_parent(ids):
+ if not ids:
+ return set()
+ parent_ids = set()
+ for parent in Target.browse(ids):
+ parent_ids.update(p.id for p in getattr(parent, name))
+ return ids | get_parent(parent_ids)
+
+ if operator.endswith('child_of'):
+ ids = list(get_child(ids))
+ else:
+ ids = list(get_parent(ids))
+ if not ids:
+ expression = Literal(False)
+ else:
+ expression = table.id.in_(ids)
+ if operator.startswith('not'):
return ~expression
return expression
@@ -270,6 +286,7 @@ class Many2Many(Field):
transaction = Transaction()
table, _ = tables[None]
name, operator, value = domain[:3]
+ assert operator not in {'where', 'not where'} or '.' not in name
if Relation._history and transaction.context.get('_datetime'):
relation = Relation.__table_history__()
@@ -290,10 +307,15 @@ class Many2Many(Field):
target = getattr(Relation, self.target).sql_column(relation)
if '.' not in name:
- if operator in ('child_of', 'not child_of'):
+ if operator.endswith('child_of') or operator.endswith('parent_of'):
if Target != Model:
- query = Target.search([(domain[3], 'child_of', value)],
- order=[], query=True)
+ if operator.endswith('child_of'):
+ target_operator = 'child_of'
+ else:
+ target_operator = 'parent_of'
+ query = Target.search([
+ (domain[3], target_operator, value),
+ ], order=[], query=True)
where = (target.in_(query) & (origin != Null))
if history_where:
where &= history_where
@@ -301,7 +323,7 @@ class Many2Many(Field):
where &= origin_where
query = relation.select(origin, where=where)
expression = table.id.in_(query)
- if operator == 'not child_of':
+ if operator.startswith('not'):
return ~expression
return expression
if isinstance(value, basestring):
@@ -313,12 +335,12 @@ class Many2Many(Field):
else:
ids = value
if not ids:
- expression = table.id.in_([None])
- if operator == 'not child_of':
+ expression = Literal(False)
+ if operator.startswith('not'):
return ~expression
return expression
else:
- return self.convert_domain_child(
+ return self.convert_domain_tree(
(name, operator, ids), tables)
if value is None:
@@ -340,11 +362,18 @@ class Many2Many(Field):
else:
_, target_name = name.split('.', 1)
- relation_domain = [('%s.%s' % (self.target, target_name),)
- + tuple(domain[1:])]
- if origin_field._type == 'reference':
- relation_domain.append(
- (self.origin, 'like', Model.__name__ + ',%'))
+ if operator not in {'where', 'not where'}:
+ relation_domain = [('%s.%s' % (self.target, target_name),)
+ + tuple(domain[1:])]
+ if origin_field._type == 'reference':
+ relation_domain.append(
+ (self.origin, 'like', Model.__name__ + ',%'))
+ else:
+ relation_domain = []
+ for clause in value:
+ relation_domain.append(
+ ('%s.%s' % (self.target, clause[0]),)
+ + tuple(clause[1:]))
rule_domain = Rule.domain_get(Relation.__name__, mode='read')
if rule_domain:
relation_domain = [relation_domain, rule_domain]
@@ -355,4 +384,8 @@ class Many2Many(Field):
relation_domain, tables=relation_tables)
query_table = convert_from(None, relation_tables)
query = query_table.select(origin, where=expression)
- return table.id.in_(query)
+ expression = table.id.in_(query)
+
+ if operator == 'not where':
+ expression = ~expression
+ return expression
diff --git a/trytond/model/fields/many2one.py b/trytond/model/fields/many2one.py
index 740ea5d..f5bfaed 100644
--- a/trytond/model/fields/many2one.py
+++ b/trytond/model/fields/many2one.py
@@ -1,6 +1,5 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-from types import NoneType
from sql import Query, Expression, Literal
from sql.operators import Or
@@ -76,7 +75,7 @@ class Many2One(Field):
value = Target(**value)
elif isinstance(value, (int, long)):
value = Target(value)
- assert isinstance(value, (Target, NoneType))
+ assert isinstance(value, (Target, type(None)))
super(Many2One, self).__set__(inst, value)
@staticmethod
@@ -97,8 +96,8 @@ class Many2One(Field):
else:
return SQLType('INTEGER', 'INTEGER')
- def convert_domain_child_mptt(self, domain, tables):
- cursor = Transaction().cursor
+ def convert_domain_mptt(self, domain, tables):
+ cursor = Transaction().connection.cursor()
table, _ = tables[None]
name, operator, ids = domain
red_sql = reduce_ids(table.id, ids)
@@ -108,30 +107,48 @@ class Many2One(Field):
cursor.execute(*table.select(left, right, where=red_sql))
where = Or()
for l, r in cursor.fetchall():
- where.append((left >= l) & (right <= r))
+ if operator.endswith('child_of'):
+ where.append((left >= l) & (right <= r))
+ else:
+ where.append((left <= l) & (right >= r))
if not where:
where = Literal(False)
- if operator == 'not child_of':
+ if operator.startswith('not'):
return ~where
return where
- def convert_domain_child(self, domain, tables):
+ def convert_domain_tree(self, domain, tables):
Target = self.get_target()
table, _ = tables[None]
name, operator, ids = domain
- ids = list(ids) # Ensure it is a list for concatenation
+ ids = set(ids) # Ensure it is a set for concatenation
def get_child(ids):
if not ids:
- return []
+ return set()
children = Target.search([
(name, 'in', ids),
(name, '!=', None),
], order=[])
- child_ids = get_child([c.id for c in children])
- return ids + child_ids
- expression = table.id.in_(ids + get_child(ids))
- if operator == 'not child_of':
+ child_ids = get_child(set(c.id for c in children))
+ return ids | child_ids
+
+ def get_parent(ids):
+ if not ids:
+ return set()
+ parent_ids = set(getattr(p, name).id
+ for p in Target.browse(ids) if getattr(p, name))
+ return ids | get_parent(parent_ids)
+
+ if operator.endswith('child_of'):
+ ids = list(get_child(ids))
+ else:
+ ids = list(get_parent(ids))
+ if not ids:
+ expression = Literal(False)
+ else:
+ expression = table.id.in_(ids)
+ if operator.startswith('not'):
return ~expression
return expression
@@ -144,12 +161,17 @@ class Many2One(Field):
name, operator, value = domain[:3]
column = self.sql_column(table)
if '.' not in name:
- if operator in ('child_of', 'not child_of'):
+ if operator.endswith('child_of') or operator.endswith('parent_of'):
if Target != Model:
- query = Target.search([(domain[3], 'child_of', value)],
- order=[], query=True)
+ if operator.endswith('child_of'):
+ target_operator = 'child_of'
+ else:
+ target_operator = 'parent_of'
+ query = Target.search([
+ (domain[3], target_operator, value),
+ ], order=[], query=True)
expression = column.in_(query)
- if operator == 'not child_of':
+ if operator.startswith('not'):
return ~expression
return expression
@@ -162,15 +184,15 @@ class Many2One(Field):
else:
ids = value
if not ids:
- expression = column.in_([None])
- if operator == 'not child_of':
+ expression = Literal(False)
+ if operator.startswith('not'):
return ~expression
return expression
elif self.left and self.right:
- return self.convert_domain_child_mptt(
+ return self.convert_domain_mptt(
(name, operator, ids), tables)
else:
- return self.convert_domain_child(
+ return self.convert_domain_tree(
(name, operator, ids), tables)
if not isinstance(value, basestring):
diff --git a/trytond/model/fields/one2many.py b/trytond/model/fields/one2many.py
index d2c1a43..a94b720 100644
--- a/trytond/model/fields/one2many.py
+++ b/trytond/model/fields/one2many.py
@@ -231,6 +231,7 @@ class One2Many(Field):
transaction = Transaction()
table, _ = tables[None]
name, operator, value = domain[:3]
+ assert operator not in {'where', 'not where'} or '.' not in name
if Target._history and transaction.context.get('_datetime'):
target = Target.__table_history__()
@@ -268,7 +269,10 @@ class One2Many(Field):
target_name = 'id'
else:
_, target_name = name.split('.', 1)
- target_domain = [(target_name,) + tuple(domain[1:])]
+ if operator not in {'where', 'not where'}:
+ target_domain = [(target_name,) + tuple(domain[1:])]
+ else:
+ target_domain = value
if origin_field._type == 'reference':
target_domain.append(
(self.field, 'like', Model.__name__ + ',%'))
@@ -282,4 +286,8 @@ class One2Many(Field):
target_domain, tables=target_tables)
query_table = convert_from(None, target_tables)
query = query_table.select(origin, where=expression)
- return table.id.in_(query)
+ expression = table.id.in_(query)
+
+ if operator == 'not where':
+ expression = ~expression
+ return expression
diff --git a/trytond/model/fields/one2one.py b/trytond/model/fields/one2one.py
index 60139dc..3b9e322 100644
--- a/trytond/model/fields/one2one.py
+++ b/trytond/model/fields/one2one.py
@@ -1,6 +1,5 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-from types import NoneType
from trytond.model.fields.field import Field
from trytond.model.fields.many2many import Many2Many
@@ -59,5 +58,5 @@ class One2One(Many2Many):
value = Target(*value)
elif isinstance(value, (int, long)):
value = Target(value)
- assert isinstance(value, (Target, NoneType))
+ assert isinstance(value, (Target, type(None)))
Field.__set__(self, inst, value)
diff --git a/trytond/model/fields/property.py b/trytond/model/fields/property.py
index 4cac907..e04da69 100644
--- a/trytond/model/fields/property.py
+++ b/trytond/model/fields/property.py
@@ -72,7 +72,7 @@ class Property(Function):
Property = pool.get('ir.property')
IrModel = pool.get('ir.model')
Field = pool.get('ir.model.field')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
name, operator, value = domain
@@ -135,7 +135,7 @@ class Property(Function):
return [('id', 'in', [x[0] for x in props])]
else:
- other_ids = [x[0] for x in cursor.fetchall()]
+ other_ids = [x[0] for x in fetchall]
res_ids = Model.search(['OR',
('id', 'in', [x[0] for x in props]),
diff --git a/trytond/model/fields/reference.py b/trytond/model/fields/reference.py
index 970351c..8489a8a 100644
--- a/trytond/model/fields/reference.py
+++ b/trytond/model/fields/reference.py
@@ -1,6 +1,5 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-from types import NoneType
import warnings
from sql import Cast, Literal, Query, Expression
@@ -93,7 +92,7 @@ class Reference(Field):
def __set__(self, inst, value):
from ..model import Model
- if not isinstance(value, (Model, NoneType)):
+ if not isinstance(value, (Model, type(None))):
if isinstance(value, basestring):
target, value = value.split(',')
else:
diff --git a/trytond/model/fields/selection.py b/trytond/model/fields/selection.py
index bd29efe..92221f4 100644
--- a/trytond/model/fields/selection.py
+++ b/trytond/model/fields/selection.py
@@ -5,6 +5,7 @@ import warnings
from sql.conditionals import Case
from ... import backend
+from ...transaction import Transaction
from .field import Field, SQLType
@@ -82,7 +83,9 @@ class TranslatedSelection(object):
def __get__(self, inst, cls):
if inst is None:
return self
- selection = dict(cls.fields_get([self.name])[self.name]['selection'])
+ with Transaction().set_context(getattr(inst, '_context', {})):
+ 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 == '':
diff --git a/trytond/model/model.py b/trytond/model/model.py
index 17861e4..68177bf 100644
--- a/trytond/model/model.py
+++ b/trytond/model/model.py
@@ -3,7 +3,6 @@
import copy
import collections
-import warnings
from functools import total_ordering
from trytond.model import fields
@@ -347,42 +346,34 @@ class Model(WarningErrorMixin, URLMixin, PoolBase):
super(Model, self).__init__()
if id is not None:
id = int(id)
- self.__dict__['id'] = id
- self._values = None
- parent_values = {}
- for name, value in kwargs.iteritems():
- if not name.startswith('_parent_'):
- setattr(self, name, value)
- else:
- parent_values[name] = value
- for name, value in parent_values.iteritems():
- parent_name, field = name.split('.', 1)
- parent_name = parent_name[8:] # Strip '_parent_'
- parent = getattr(self, parent_name, None)
- if parent is not None:
- setattr(parent, field, value)
- else:
- setattr(self, parent_name, {field: value})
- self._init_values = self._values.copy() if self._values else None
+ self._id = id
+ if kwargs:
+ self._values = {}
+ parent_values = {}
+ for name, value in kwargs.iteritems():
+ if not name.startswith('_parent_'):
+ setattr(self, name, value)
+ else:
+ parent_values[name] = value
+ for name, value in parent_values.iteritems():
+ parent_name, field = name.split('.', 1)
+ parent_name = parent_name[8:] # Strip '_parent_'
+ parent = getattr(self, parent_name, None)
+ if parent is not None:
+ setattr(parent, field, value)
+ else:
+ setattr(self, parent_name, {field: value})
+ self._init_values = self._values.copy()
+ else:
+ self._values = None
+ self._init_values = None
def __getattr__(self, name):
- if name == 'id':
- return self.__dict__['id']
- elif self._values and name in self._values:
- return self._values.get(name)
- raise AttributeError("'%s' Model has no attribute '%s': %s"
- % (self.__name__, name, self._values))
-
- def __setattr__(self, name, value):
- if name == 'id':
- self.__dict__['id'] = value
- return
- super(Model, self).__setattr__(name, value)
-
- def __getitem__(self, name):
- warnings.warn('Use __getattr__ instead of __getitem__',
- DeprecationWarning, stacklevel=2)
- return getattr(self, name)
+ try:
+ return self._values[name]
+ except (KeyError, TypeError):
+ raise AttributeError("'%s' Model has no attribute '%s': %s"
+ % (self.__name__, name, self._values))
def __contains__(self, name):
return name in self._fields
diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py
index 068c567..50482de 100644
--- a/trytond/model/modelsql.py
+++ b/trytond/model/modelsql.py
@@ -13,7 +13,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, grouped_slice
+from trytond.tools import reduce_ids, grouped_slice, cursor_dict
from trytond.const import OPERATORS
from trytond.transaction import Transaction
from trytond.pool import Pool
@@ -126,6 +126,8 @@ class ModelSQL(ModelStorage):
@classmethod
def __register__(cls, module_name):
+ sql_table = cls.__table__()
+ cursor = Transaction().connection.cursor()
TableHandler = backend.get('TableHandler')
super(ModelSQL, cls).__register__(module_name)
@@ -135,10 +137,9 @@ class ModelSQL(ModelStorage):
pool = Pool()
# create/update table in the database
- table = TableHandler(Transaction().cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
if cls._history:
- history_table = TableHandler(Transaction().cursor, cls,
- module_name, history=True)
+ history_table = TableHandler(cls, module_name, history=True)
history_table.index_action('id', action='add')
for field_name, field in cls._fields.iteritems():
@@ -205,17 +206,24 @@ class ModelSQL(ModelStorage):
if isinstance(field, fields.Many2One) \
and field.model_name == cls.__name__ \
and field.left and field.right:
- cls._rebuild_tree(field_name, None, 0)
+ left_default = cls._defaults.get(field.left, lambda: None)()
+ right_default = cls._defaults.get(field.right, lambda: None)()
+ cursor.execute(*sql_table.select(sql_table.id,
+ where=(Column(sql_table, field.left) == left_default)
+ | (Column(sql_table, field.left) == Null)
+ | (Column(sql_table, field.right) == right_default)
+ | (Column(sql_table, field.right) == Null),
+ limit=1))
+ if cursor.fetchone():
+ cls._rebuild_tree(field_name, None, 0)
for ident, constraint, _ in cls._sql_constraints:
table.add_constraint(ident, constraint)
if cls._history:
cls._update_history_table()
- cursor = Transaction().cursor
- table = cls.__table__()
history_table = cls.__table_history__()
- cursor.execute(*table.select(table.id))
+ cursor.execute(*sql_table.select(sql_table.id))
if cursor.fetchone():
cursor.execute(*history_table.select(history_table.id))
if not cursor.fetchone():
@@ -223,7 +231,7 @@ class ModelSQL(ModelStorage):
if not hasattr(f, 'set')]
cursor.execute(*history_table.insert(
[Column(history_table, c) for c in columns],
- table.select(*(Column(table, c)
+ sql_table.select(*(Column(sql_table, c)
for c in columns))))
cursor.execute(*history_table.update(
[history_table.write_date], [None]))
@@ -232,9 +240,8 @@ class ModelSQL(ModelStorage):
def _update_history_table(cls):
TableHandler = backend.get('TableHandler')
if cls._history:
- table = TableHandler(Transaction().cursor, cls)
- history_table = TableHandler(Transaction().cursor, cls,
- history=True)
+ table = TableHandler(cls)
+ history_table = TableHandler(cls, history=True)
for column_name in table._columns:
string = ''
if column_name in cls._fields:
@@ -289,10 +296,10 @@ class ModelSQL(ModelStorage):
cls.raise_user_error('foreign_model_missing',
error_args=error_args)
for name, _, error in cls._sql_constraints:
- if name in str(exception[0]):
+ if name in str(exception):
cls.raise_user_error(error)
for name, error in cls._sql_error_messages.iteritems():
- if name in str(exception[0]):
+ if name in str(exception):
cls.raise_user_error(error)
@classmethod
@@ -300,7 +307,7 @@ class ModelSQL(ModelStorage):
pool = Pool()
ModelAccess = pool.get('ir.model.access')
User = pool.get('res.user')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
ModelAccess.check(cls.__name__, 'read')
@@ -330,7 +337,7 @@ class ModelSQL(ModelStorage):
@classmethod
def __insert_history(cls, ids, deleted=False):
transaction = Transaction()
- cursor = transaction.cursor
+ cursor = transaction.connection.cursor()
if not cls._history:
return
user = transaction.user
@@ -357,7 +364,7 @@ class ModelSQL(ModelStorage):
cursor.execute(*history.insert(hcolumns,
table.select(*columns, where=where)))
else:
- if cursor.has_multirow_insert():
+ if transaction.database.has_multirow_insert():
cursor.execute(*history.insert(hcolumns,
[[id_, CurrentTimestamp(), user]
for id_ in sub_ids]))
@@ -371,7 +378,7 @@ class ModelSQL(ModelStorage):
if not cls._history:
return
transaction = Transaction()
- cursor = transaction.cursor
+ cursor = transaction.connection.cursor()
table = cls.__table__()
history = cls.__table_history__()
columns = []
@@ -440,7 +447,7 @@ class ModelSQL(ModelStorage):
@classmethod
def __check_timestamp(cls, ids):
transaction = Transaction()
- cursor = transaction.cursor
+ cursor = transaction.connection.cursor()
table = cls.__table__()
if not transaction.timestamp:
return
@@ -467,7 +474,7 @@ class ModelSQL(ModelStorage):
def create(cls, vlist):
DatabaseIntegrityError = backend.get('DatabaseIntegrityError')
transaction = Transaction()
- cursor = transaction.cursor
+ cursor = transaction.connection.cursor()
pool = Pool()
Translation = pool.get('ir.translation')
Rule = pool.get('ir.rule')
@@ -479,6 +486,7 @@ class ModelSQL(ModelStorage):
table = cls.__table__()
modified_fields = set()
+ defaults_cache = {} # Store already computed default values
new_ids = []
vlist = [v.copy() for v in vlist]
for values in vlist:
@@ -495,11 +503,16 @@ class ModelSQL(ModelStorage):
if (f not in values
and f not in ('create_uid', 'create_date',
'write_uid', 'write_date', 'id')):
- default.append(f)
+ if f in defaults_cache:
+ values[f] = defaults_cache[f]
+ else:
+ default.append(f)
if default:
defaults = cls.default_get(default, with_rec_name=False)
- values.update(cls._clean_defaults(defaults))
+ defaults = cls._clean_defaults(defaults)
+ values.update(defaults)
+ defaults_cache.update(defaults)
insert_columns = [table.create_uid, table.create_date]
insert_values = [transaction.user, CurrentTimestamp()]
@@ -512,12 +525,13 @@ class ModelSQL(ModelStorage):
insert_values.append(field.sql_format(value))
try:
- if cursor.has_returning():
+ if transaction.database.has_returning():
cursor.execute(*table.insert(insert_columns,
[insert_values], [table.id]))
id_new, = cursor.fetchone()
else:
- id_new = cursor.nextid(cls._table)
+ id_new = transaction.database.nextid(
+ transaction.connection, cls._table)
if id_new:
insert_columns.append(table.id)
insert_values.append(id_new)
@@ -526,10 +540,10 @@ class ModelSQL(ModelStorage):
else:
cursor.execute(*table.insert(insert_columns,
[insert_values]))
- id_new = cursor.lastid()
+ id_new = transaction.database.lastid(cursor)
new_ids.append(id_new)
except DatabaseIntegrityError, exception:
- with Transaction().new_cursor(), \
+ with Transaction().new_transaction(), \
Transaction().set_context(_check_access=False):
cls.__raise_integrity_error(exception, values)
raise
@@ -562,8 +576,14 @@ class ModelSQL(ModelStorage):
translation_values.setdefault(
'%s,%s' % (cls.__name__, fname), {})[new_id] = value
if hasattr(field, 'set'):
- fields_to_set.setdefault(fname, []).extend(
- ([new_id], value))
+ args = fields_to_set.setdefault(fname, [])
+ actions = iter(args)
+ for ids, val in zip(actions, actions):
+ if val == value:
+ ids.append(new_id)
+ break
+ else:
+ args.extend(([new_id], value))
if translation_values:
for name, translations in translation_values.iteritems():
@@ -599,7 +619,8 @@ class ModelSQL(ModelStorage):
mode='read'):
fields_names.append(field_name)
super(ModelSQL, cls).read(ids, fields_names=fields_names)
- cursor = Transaction().cursor
+ transaction = Transaction()
+ cursor = Transaction().connection.cursor()
if not ids:
return []
@@ -625,12 +646,12 @@ class ModelSQL(ModelStorage):
table = cls.__table__()
table_query = cls.table_query()
- in_max = cursor.IN_MAX
+ in_max = transaction.database.IN_MAX
history_order = None
history_clause = None
history_limit = None
if (cls._history
- and Transaction().context.get('_datetime')
+ and transaction.context.get('_datetime')
and not table_query):
in_max = 1
table = cls.__table_history__()
@@ -669,8 +690,8 @@ class ModelSQL(ModelStorage):
where &= dom_exp
cursor.execute(*from_.select(*columns, where=where,
order_by=history_order, limit=history_limit))
- dictfetchall = cursor.dictfetchall()
- if not len(dictfetchall) == len({}.fromkeys(sub_ids)):
+ fetchall = list(cursor_dict(cursor))
+ if not len(fetchall) == len({}.fromkeys(sub_ids)):
if domain:
where = red_sql
if history_clause:
@@ -684,7 +705,7 @@ class ModelSQL(ModelStorage):
if rowcount == len({}.fromkeys(sub_ids)):
cls.raise_user_error('access_error', cls.__name__)
cls.raise_user_error('read_error', cls.__name__)
- result.extend(dictfetchall)
+ result.extend(fetchall)
else:
result = [{'id': x} for x in ids]
@@ -826,7 +847,7 @@ class ModelSQL(ModelStorage):
def write(cls, records, values, *args):
DatabaseIntegrityError = backend.get('DatabaseIntegrityError')
transaction = Transaction()
- cursor = transaction.cursor
+ cursor = transaction.connection.cursor()
pool = Pool()
Translation = pool.get('ir.translation')
Config = pool.get('ir.configuration')
@@ -903,8 +924,8 @@ class ModelSQL(ModelStorage):
cursor.execute(*table.update(columns, update_values,
where=red_sql))
except DatabaseIntegrityError, exception:
- with Transaction().new_cursor(), \
- Transaction().set_context(_check_access=False):
+ with Transaction().new_transaction() as transaction, \
+ transaction.set_context(_check_access=False):
cls.__raise_integrity_error(exception, values,
values.keys())
raise
@@ -936,7 +957,7 @@ class ModelSQL(ModelStorage):
def delete(cls, records):
DatabaseIntegrityError = backend.get('DatabaseIntegrityError')
transaction = Transaction()
- cursor = transaction.cursor
+ cursor = transaction.connection.cursor()
pool = Pool()
Translation = pool.get('ir.translation')
Rule = pool.get('ir.rule')
@@ -1007,7 +1028,7 @@ class ModelSQL(ModelStorage):
if rowcount == -1 or rowcount is None:
rowcount = len(cursor.fetchall())
if not rowcount == len({}.fromkeys(sub_ids)):
- cls.raise_user_error('access_error', cls._get_name())
+ cls.raise_user_error('access_error', cls.__name__)
cls.trigger_delete(records)
@@ -1061,7 +1082,7 @@ class ModelSQL(ModelStorage):
try:
cursor.execute(*table.delete(where=red_sql))
except DatabaseIntegrityError, exception:
- with Transaction().new_cursor():
+ with Transaction().new_transaction():
cls.__raise_integrity_error(exception, {})
raise
@@ -1077,7 +1098,7 @@ class ModelSQL(ModelStorage):
pool = Pool()
Rule = pool.get('ir.rule')
transaction = Transaction()
- cursor = transaction.cursor
+ cursor = transaction.connection.cursor()
# Get domain clauses
tables, expression = cls.search_domain(domain)
@@ -1137,8 +1158,8 @@ class ModelSQL(ModelStorage):
return select
cursor.execute(*select)
- rows = cursor.dictfetchmany(cursor.IN_MAX)
- cache = cursor.get_cache()
+ rows = list(cursor_dict(cursor, transaction.database.IN_MAX))
+ cache = transaction.get_cache()
if cls.__name__ not in cache:
cache[cls.__name__] = LRUDict(cache_size())
delete_records = transaction.delete_records.setdefault(cls.__name__,
@@ -1186,7 +1207,7 @@ class ModelSQL(ModelStorage):
# Can not cache the history value if we are not sure to have fetch all
# the rows for each records
if (not (cls._history and transaction.context.get('_datetime'))
- or len(rows) < cursor.IN_MAX):
+ or len(rows) < transaction.database.IN_MAX):
rows = list(filter_history(rows))
keys = None
for data in islice(rows, 0, cache.size_limit):
@@ -1206,9 +1227,9 @@ class ModelSQL(ModelStorage):
del data[k]
cache[cls.__name__].setdefault(data['id'], {}).update(data)
- if len(rows) >= cursor.IN_MAX:
+ if len(rows) >= transaction.database.IN_MAX:
if (cls._history
- and Transaction().context.get('_datetime')
+ and transaction.context.get('_datetime')
and not query):
columns = columns[:3]
else:
@@ -1216,7 +1237,7 @@ class ModelSQL(ModelStorage):
cursor.execute(*table.select(*columns,
where=expression, order_by=order_by,
limit=limit, offset=offset))
- rows = filter_history(cursor.dictfetchall())
+ rows = filter_history(list(cursor_dict(cursor)))
return cls.browse([x['id'] for x in rows])
@@ -1269,8 +1290,7 @@ class ModelSQL(ModelStorage):
@classmethod
def _update_mptt(cls, field_names, list_ids, values=None):
- cursor = Transaction().cursor
- count = None
+ cursor = Transaction().connection.cursor()
for field_name, ids in zip(field_names, list_ids):
field = cls._fields[field_name]
if (isinstance(field, fields.Many2One)
@@ -1294,11 +1314,7 @@ class ModelSQL(ModelStorage):
& (Column(parent, field.right) == 0)))
nested_create = cursor.fetchone()
- if count is None:
- cursor.execute(*table.select(Count(Literal(1))))
- count, = cursor.fetchone()
-
- if not nested_create and len(ids) < count / 4:
+ if not nested_create and len(ids) < 2:
for id_ in ids:
cls._update_tree(id_, field_name,
field.left, field.right)
@@ -1310,7 +1326,7 @@ class ModelSQL(ModelStorage):
'''
Rebuild left, right value for the tree.
'''
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
table = cls.__table__()
right = left + 1
@@ -1338,7 +1354,7 @@ class ModelSQL(ModelStorage):
- the value (right - left - 1) / 2 will not give
the number of children node
'''
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
table = cls.__table__()
left = Column(table, left)
right = Column(table, right)
@@ -1392,8 +1408,9 @@ class ModelSQL(ModelStorage):
@classmethod
def validate(cls, records):
super(ModelSQL, cls).validate(records)
- cursor = Transaction().cursor
- if cursor.has_constraint():
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
+ if transaction.database.has_constraint():
return
# Works only for a single transaction
ids = map(int, records)
@@ -1402,7 +1419,7 @@ class ModelSQL(ModelStorage):
if isinstance(sql, Unique):
columns = [Column(table, c.name) for c in sql.columns]
columns.insert(0, table.id)
- in_max = cursor.IN_MAX / (len(columns) + 1)
+ in_max = transaction.database.IN_MAX // (len(columns) + 1)
for sub_ids in grouped_slice(ids, in_max):
red_sql = reduce_ids(table.id, sub_ids)
diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py
index 0d447dc..4269278 100644
--- a/trytond/model/modelstorage.py
+++ b/trytond/model/modelstorage.py
@@ -5,10 +5,6 @@ import datetime
import time
import csv
import warnings
-try:
- import cStringIO as StringIO
-except ImportError:
- import StringIO
from decimal import Decimal
from itertools import islice, ifilter, chain, izip
@@ -25,7 +21,7 @@ from trytond.const import OPERATORS
from trytond.config import config
from trytond.transaction import Transaction
from trytond.pool import Pool
-from trytond.cache import LRUDict, freeze
+from trytond.cache import LRUDict, LRUDictTransaction, freeze
from trytond import backend
from trytond.rpc import RPC
from .modelview import ModelView
@@ -170,8 +166,8 @@ class ModelStorage(Model):
if local_cache:
local_cache.clear()
- # Clean cursor cache
- for cache in Transaction().cursor.cache.itervalues():
+ # Clean transaction cache
+ for cache in Transaction().cache.itervalues():
if cls.__name__ in cache:
for record in all_records:
if record.id in cache[cls.__name__]:
@@ -224,8 +220,8 @@ class ModelStorage(Model):
# Increase transaction counter
Transaction().counter += 1
- # Clean cursor cache
- for cache in Transaction().cursor.cache.values():
+ # Clean transaction cache
+ for cache in Transaction().cache.values():
for cache in (cache, cache.get('_language_cache', {}).values()):
if cls.__name__ in cache:
for record in records:
@@ -454,9 +450,14 @@ class ModelStorage(Model):
'''
Return a list of instance for the ids
'''
+ transaction = Transaction()
ids = map(int, ids)
- local_cache = LRUDict(cache_size())
- return [cls(int(x), _ids=ids, _local_cache=local_cache) for x in ids]
+ local_cache = LRUDictTransaction(cache_size())
+ transaction_cache = transaction.get_cache()
+ return [cls(x, _ids=ids,
+ _local_cache=local_cache,
+ _transaction_cache=transaction_cache,
+ _transaction=transaction) for x in ids]
@staticmethod
def __export_row(record, fields_names):
@@ -569,7 +570,7 @@ class ModelStorage(Model):
return None
res = []
Relation = pool.get(relation)
- for word in csv.reader(StringIO.StringIO(value), delimiter=',',
+ for word in csv.reader(value.splitlines(), delimiter=',',
quoting=csv.QUOTE_NONE, escapechar='\\').next():
res2 = Relation.search([
('rec_name', '=', word),
@@ -618,7 +619,7 @@ class ModelStorage(Model):
relation = None
ftype = fields_def[field[-1][:-3]]['type']
if ftype == 'many2many':
- value = csv.reader(StringIO.StringIO(value), delimiter=',',
+ value = csv.reader(value.splitlines(), delimiter=',',
quoting=csv.QUOTE_NONE, escapechar='\\').next()
elif ftype == 'reference':
try:
@@ -785,7 +786,7 @@ class ModelStorage(Model):
if values is None:
return False
for model_data in models_data:
- if not model_data.values:
+ if not model_data.values or model_data.noupdate:
continue
xml_values = ModelData.load_values(model_data.values)
for key, val in values.iteritems():
@@ -1136,6 +1137,8 @@ class ModelStorage(Model):
pool = Pool()
vals = {}
for field in defaults.keys():
+ if '.' in field: # skip all related fields
+ continue
fld_def = cls._fields[field]
if fld_def._type in ('many2one', 'one2one'):
if isinstance(defaults[field], (list, tuple)):
@@ -1159,9 +1162,13 @@ class ModelStorage(Model):
def __init__(self, id=None, **kwargs):
_ids = kwargs.pop('_ids', None)
_local_cache = kwargs.pop('_local_cache', None)
- self._cursor = Transaction().cursor
- self._user = Transaction().user
- self._context = Transaction().context
+ _transaction_cache = kwargs.pop('_transaction_cache', None)
+ transaction = kwargs.pop('_transaction', None)
+ if transaction is None:
+ transaction = Transaction()
+ self._transaction = transaction
+ self._user = transaction.user
+ self._context = transaction.context
if id is not None:
id = int(id)
if _ids is not None:
@@ -1170,19 +1177,22 @@ class ModelStorage(Model):
else:
self._ids = [id]
- self._cursor_cache = self._cursor.get_cache()
+ if _transaction_cache is not None:
+ self._transaction_cache = _transaction_cache
+ else:
+ self._transaction_cache = transaction.get_cache()
if _local_cache is not None:
+ assert isinstance(_local_cache, LRUDictTransaction)
self._local_cache = _local_cache
else:
- self._local_cache = LRUDict(cache_size())
- self._local_cache.counter = Transaction().counter
+ self._local_cache = LRUDictTransaction(cache_size())
super(ModelStorage, self).__init__(id, **kwargs)
@property
def _cache(self):
- cache = self._cursor_cache
+ cache = self._transaction_cache
if self.__name__ not in cache:
cache[self.__name__] = LRUDict(cache_size())
return cache[self.__name__]
@@ -1191,13 +1201,10 @@ class ModelStorage(Model):
try:
return super(ModelStorage, self).__getattr__(name)
except AttributeError:
- if self.id < 0:
+ if self.id is None or self.id < 0:
raise
- counter = Transaction().counter
- if self._local_cache.counter != counter:
- self._local_cache.clear()
- self._local_cache.counter = counter
+ self._local_cache.refresh()
# fetch the definition of the field
try:
@@ -1279,7 +1286,8 @@ class ModelStorage(Model):
index = self._ids.index(self.id)
ids = chain(islice(self._ids, index, None),
islice(self._ids, 0, max(index - 1, 0)))
- ids = islice(unique(ifilter(filter_, ids)), self._cursor.IN_MAX)
+ ids = islice(unique(ifilter(filter_, ids)),
+ self._transaction.database.IN_MAX)
def instantiate(field, value, data):
if field._type in ('many2one', 'one2one', 'reference'):
@@ -1300,6 +1308,7 @@ class ModelStorage(Model):
Model = field.get_target()
except KeyError:
return value
+ transaction = Transaction()
ctx = {}
if field.context:
pyson_context = PYSONEncoder().encode(field.context)
@@ -1308,30 +1317,33 @@ class ModelStorage(Model):
if getattr(field, 'datetime_field', None):
datetime_ = data.get(field.datetime_field)
ctx = {'_datetime': datetime_}
- with Transaction().set_context(**ctx):
+ with transaction.set_context(**ctx):
+ kwargs = {}
key = (Model, freeze(ctx))
- local_cache = model2cache.setdefault(key,
- LRUDict(cache_size()))
- ids = model2ids.setdefault(key, [])
+ kwargs['_local_cache'] = model2cache.setdefault(key,
+ LRUDictTransaction(cache_size()))
+ kwargs['_ids'] = ids = model2ids.setdefault(key, [])
+ kwargs['_transaction_cache'] = transaction.get_cache()
+ kwargs['_transaction'] = transaction
if field._type in ('many2one', 'one2one', 'reference'):
+ value = int(value)
ids.append(value)
- return Model(value, _ids=ids, _local_cache=local_cache)
+ return Model(value, **kwargs)
elif field._type in ('one2many', 'many2many'):
- ids.extend(value)
- return tuple(Model(id, _ids=ids, _local_cache=local_cache)
- for id in value)
+ ids.extend(int(x) for x in value)
+ return tuple(Model(id, **kwargs) for id in value)
model2ids = {}
model2cache = {}
# Read the data
- with Transaction().set_cursor(self._cursor), \
- Transaction().set_user(self._user), \
- Transaction().set_context(self._context):
+ with Transaction().set_current_transaction(self._transaction), \
+ self._transaction.set_user(self._user), \
+ self._transaction.set_context(self._context):
if self.id in self._cache and name in self._cache[self.id]:
# Use values from cache
ids = islice(chain(islice(self._ids, index, None),
islice(self._ids, 0, max(index - 1, 0))),
- self._cursor.IN_MAX)
+ self._transaction.database.IN_MAX)
ffields = {name: ffields[name]}
read_data = [{'id': i, name: self._cache[i][name]}
for i in ids
@@ -1373,7 +1385,8 @@ class ModelStorage(Model):
field = self._fields[fname]
if field._type in ('many2one', 'one2one', 'reference'):
if value:
- if value.id < 0 and field._type != 'reference':
+ if ((value.id is None or value.id < 0)
+ and field._type != 'reference'):
value.save()
if field._type == 'reference':
value = str(value)
@@ -1381,7 +1394,7 @@ class ModelStorage(Model):
value = value.id
if field._type in ('one2many', 'many2many'):
targets = value
- if self.id >= 0:
+ if self.id is not None and self.id >= 0:
_values, self._values = self._values, None
try:
to_remove = [t.id for t in getattr(self, fname)]
@@ -1393,7 +1406,7 @@ class ModelStorage(Model):
to_create = []
to_write = []
for target in targets:
- if target.id < 0:
+ if target.id is None or target.id < 0:
if field._type == 'one2many':
# Don't store old target link
setattr(target, field.field, None)
@@ -1427,11 +1440,11 @@ class ModelStorage(Model):
save_values = {}
to_create = []
to_write = []
- cursor = records[0]._cursor
+ transaction = records[0]._transaction
user = records[0]._user
context = records[0]._context
for record in records:
- assert cursor == record._cursor
+ assert transaction == record._transaction
assert user == record._user
assert context == record._context
save_values[record] = record._save_values
@@ -1443,14 +1456,14 @@ class ModelStorage(Model):
to_write.append(record)
transaction = Transaction()
try:
- with transaction.set_cursor(cursor), \
+ with transaction.set_current_transaction(transaction), \
transaction.set_user(user), \
transaction.set_context(context):
if to_create:
news = cls.create([save_values[r] for r in to_create])
for record, new in izip(to_create, news):
record._ids.remove(record.id)
- record.id = new.id
+ record._id = new.id
record._ids.append(record.id)
if to_write:
cls.write(*sum(
diff --git a/trytond/model/modelview.py b/trytond/model/modelview.py
index 4e709fb..d88aed7 100644
--- a/trytond/model/modelview.py
+++ b/trytond/model/modelview.py
@@ -25,20 +25,18 @@ def _find(tree, element):
return None
-def _inherit_apply(src, inherit):
- tree_src = etree.fromstring(src)
- tree_inherit = etree.fromstring(inherit)
- root_inherit = tree_inherit.getroottree().getroot()
+def _inherit_apply(tree, inherit):
+ root_inherit = inherit.getroottree().getroot()
for element2 in root_inherit:
if element2.tag != 'xpath':
continue
- element = _find(tree_src, element2)
+ element = _find(tree, element2)
if element is not None:
pos = element2.get('position', 'inside')
if pos == 'replace':
parent = element.getparent()
if parent is None:
- tree_src, = element2
+ tree, = element2
continue
enext = element.getnext()
if enext is not None:
@@ -75,7 +73,7 @@ def _inherit_apply(src, inherit):
raise AttributeError(
'Couldn\'t find tag (%s: %s) in parent view!'
% (element2.tag, element2.get('expr')))
- return etree.tostring(tree_src, encoding='utf-8')
+ return tree
def on_change(func):
@@ -243,13 +241,13 @@ class ModelView(Model):
else:
domain = [
('model', '=', cls.__name__),
- ('type', '=', view_type),
['OR',
('inherit', '=', None),
('inherit.model', '!=', cls.__name__),
],
]
views = View.search(domain)
+ views = filter(lambda v: v.rng_type == view_type, views)
if views:
view = views[0]
if view:
@@ -260,7 +258,7 @@ class ModelView(Model):
# if a view was found
if view:
- result['type'] = view.type
+ result['type'] = view.rng_type
result['view_id'] = view_id
result['arch'] = view.arch
result['field_childs'] = view.field_childs
@@ -294,6 +292,8 @@ class ModelView(Model):
# There is perhaps a new module in the directory
ModelView._reset_modules_list()
raise_p = True
+ parser = etree.XMLParser(remove_comments=True)
+ tree = etree.fromstring(result['arch'], parser=parser)
for view in views:
if view.domain:
if not PYSONDecoder({'context': Transaction().context}
@@ -301,7 +301,10 @@ class ModelView(Model):
continue
if not view.arch or not view.arch.strip():
continue
- result['arch'] = _inherit_apply(result['arch'], view.arch)
+ tree_inherit = etree.fromstring(view.arch, parser=parser)
+ tree = _inherit_apply(tree, tree_inherit)
+ result['arch'] = etree.tostring(
+ tree, encoding='utf-8').decode('utf-8')
# otherwise, build some kind of default view
else:
@@ -429,8 +432,11 @@ class ModelView(Model):
parent.remove(element)
elif type == 'form':
element.tag = 'label'
+ colspan = element.attrib.get('colspan')
element.attrib.clear()
element.attrib['id'] = 'hidden %s-%s' % (field, i)
+ if colspan is not None:
+ element.attrib['colspan'] = colspan
if type == 'tree':
ViewTreeWidth = pool.get('ir.ui.view_tree_width')
@@ -463,7 +469,8 @@ class ModelView(Model):
if 'active' in cls._fields:
fields_def.setdefault('active', {'name': 'active'})
- arch = etree.tostring(tree, encoding='utf-8', pretty_print=False)
+ arch = etree.tostring(
+ tree, encoding='utf-8', pretty_print=False).decode('utf-8')
fields2 = cls.fields_get(fields_def.keys())
for field in fields_def:
if field in fields2:
@@ -485,59 +492,64 @@ class ModelView(Model):
fields_attrs = {}
else:
fields_attrs = copy.deepcopy(fields_attrs)
- childs = True
- if element.tag in ('field', 'label', 'separator', 'group', 'suffix',
- 'prefix'):
- for attr in ('name', 'icon'):
- if element.get(attr):
- fields_attrs.setdefault(element.get(attr), {})
- if type != 'form':
- continue
+ def set_view_ids(element):
+ view_ids = []
+ if element.get('view_ids'):
+ for view_id in element.get('view_ids').split(','):
try:
- field = cls._fields[element.get(attr)]
- if hasattr(field, 'model_name'):
- relation = field.model_name
- else:
- relation = field.get_target().__name__
- except Exception:
- relation = False
- if relation and element.tag == 'field':
- childs = False
- views = {}
- mode = (element.attrib.pop('mode', None)
- or 'tree,form').split(',')
- view_ids = []
- if element.get('view_ids'):
- for view_id in element.get('view_ids').split(','):
- try:
- view_ids.append(int(view_id))
- except ValueError:
- view_ids.append(ModelData.get_id(
- *view_id.split('.')))
- Relation = pool.get(relation)
- if (not len(element)
- and type == 'form'
- and field._type in ('one2many', 'many2many')):
- # Prefetch only the first view to prevent infinite
- # loop
- if view_ids:
- for view_id in view_ids:
- view = Relation.fields_view_get(
- view_id=view_id)
- views[str(view_id)] = view
- break
- else:
- for view_type in mode:
- views[view_type] = \
- Relation.fields_view_get(
- view_type=view_type)
- break
- element.attrib['mode'] = ','.join(mode)
- element.attrib['view_ids'] = ','.join(
- map(str, view_ids))
- fields_attrs[element.get(attr)].setdefault('views', {}
- ).update(views)
+ view_ids.append(int(view_id))
+ except ValueError:
+ view_ids.append(ModelData.get_id(*view_id.split('.')))
+ element.attrib['view_ids'] = ','.join(map(str, view_ids))
+ return view_ids
+
+ def get_relation(field):
+ if hasattr(field, 'model_name'):
+ return field.model_name
+ elif hasattr(field, 'get_target'):
+ return field.get_target().__name__
+
+ def get_views(relation, view_ids, mode):
+ Relation = pool.get(relation)
+ views = {}
+ if field._type in ['one2many', 'many2many']:
+ # Prefetch only the first view to prevent infinite loop
+ if view_ids:
+ for view_id in view_ids:
+ view = Relation.fields_view_get(view_id=view_id)
+ views[str(view_id)] = view
+ break
+ else:
+ for view_type in mode:
+ views[view_type] = (
+ Relation.fields_view_get(view_type=view_type))
+ break
+ return views
+
+ for attr in ('name', 'icon'):
+ if not element.get(attr):
+ continue
+ fields_attrs.setdefault(element.get(attr), {})
+
+ if element.tag == 'field' and type in ['tree', 'form']:
+ for attr in ('name', 'icon'):
+ fname = element.get(attr)
+ if not fname:
+ continue
+ view_ids = set_view_ids(element)
+ if type != 'form':
+ continue
+ field = cls._fields[fname]
+ relation = get_relation(field)
+ if not relation:
+ continue
+ mode = (
+ element.attrib.pop('mode', None) or 'tree,form').split(',')
+ views = get_views(relation, view_ids, mode)
+ element.attrib['mode'] = ','.join(mode)
+ fields_attrs[fname].setdefault('views', {}).update(views)
+
if type == 'tree' and element.get('name') in fields_width:
element.set('width', str(fields_width[element.get('name')]))
@@ -585,10 +597,9 @@ class ModelView(Model):
if element.get(attr):
fields_attrs.setdefault(element.get(attr), {})
- if childs:
- for field in element:
- fields_attrs = cls.__view_look_dom(field, type,
- fields_width=fields_width, fields_attrs=fields_attrs)
+ for field in element:
+ fields_attrs = cls.__view_look_dom(field, type,
+ fields_width=fields_width, fields_attrs=fields_attrs)
return fields_attrs
@staticmethod
diff --git a/trytond/model/workflow.py b/trytond/model/workflow.py
index 5d79934..940eec9 100644
--- a/trytond/model/workflow.py
+++ b/trytond/model/workflow.py
@@ -1,5 +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 collections import OrderedDict
from functools import wraps
@@ -20,7 +21,7 @@ class Workflow(object):
@wraps(func)
def wrapper(cls, records, *args, **kwargs):
filtered = []
- to_update = {}
+ to_update = OrderedDict()
for record in records:
current_state = getattr(record, cls._transition_state)
@@ -36,7 +37,7 @@ class Workflow(object):
current_state = getattr(record, cls._transition_state)
if current_state != to_update[record]:
del to_update[record]
- cls.write(to_update.keys(), {
+ cls.write(list(to_update), {
cls._transition_state: state,
})
return result
diff --git a/trytond/modules/__init__.py b/trytond/modules/__init__.py
index 1a78cea..9a453ef 100644
--- a/trytond/modules/__init__.py
+++ b/trytond/modules/__init__.py
@@ -205,92 +205,99 @@ def load_module_graph(graph, pool, update=None, lang=None):
update = []
modules_todo = []
models_to_update_history = set()
- cursor = Transaction().cursor
- modules = [x.name for x in graph]
- cursor.execute(*ir_module.select(ir_module.name, ir_module.state,
- where=ir_module.name.in_(modules)))
- module2state = dict(cursor.fetchall())
+ with Transaction().connection.cursor() as cursor:
+ modules = [x.name for x in graph]
+ cursor.execute(*ir_module.select(ir_module.name, ir_module.state,
+ where=ir_module.name.in_(modules)))
+ module2state = dict(cursor.fetchall())
- for package in graph:
- module = package.name
- if module not in MODULES:
- continue
- logger.info(module)
- classes = pool.setup(module)
- package_state = module2state.get(module, 'uninstalled')
- 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':
- package_state = 'to upgrade'
- elif package_state != 'to remove':
- package_state = 'to install'
- for child in package.childs:
- module2state[child.name] = package_state
- for type in classes.keys():
- for cls in classes[type]:
- logger.info('%s:register %s', module, cls.__name__)
- cls.__register__(module)
- for model in classes['model']:
- if hasattr(model, '_history'):
- models_to_update_history.add(model.__name__)
-
- # Instanciate a new parser for the package:
- tryton_parser = convert.TrytondXmlHandler(pool=pool, module=module,
- module_state=package_state)
-
- for filename in package.info.get('xml', []):
- filename = filename.replace('/', os.sep)
- logger.info('%s:loading %s', module, filename)
- # Feed the parser with xml content:
- with tools.file_open(OPJ(module, filename)) as fp:
- tryton_parser.parse_xmlstream(fp)
-
- modules_todo.append((module, list(tryton_parser.to_delete)))
-
- localedir = '%s/%s' % (package.info['directory'], 'locale')
- for filename in itertools.chain(
- iglob('%s/*.po' % localedir),
- iglob('%s/override/*.po' % localedir)):
- filename = filename.replace('/', os.sep)
- lang2 = os.path.splitext(os.path.basename(filename))[0]
- if lang2 not in lang:
- continue
- logger.info('%s:loading %s', module,
- filename[len(package.info['directory']) + 1:])
- Translation = pool.get('ir.translation')
- Translation.translation_import(lang2, module, filename)
-
- if package_state == 'to remove':
+ for package in graph:
+ module = package.name
+ if module not in MODULES:
continue
- cursor.execute(*ir_module.select(ir_module.id,
- where=(ir_module.name == package.name)))
- try:
- module_id, = cursor.fetchone()
- cursor.execute(*ir_module.update([ir_module.state],
- ['installed'], where=(ir_module.id == module_id)))
- except TypeError:
- cursor.execute(*ir_module.insert(
- [ir_module.create_uid, ir_module.create_date,
- ir_module.name, ir_module.state],
- [[0, CurrentTimestamp(), package.name, 'installed']]))
- module2state[package.name] = 'installed'
-
- cursor.commit()
-
- for model_name in models_to_update_history:
- model = pool.get(model_name)
- if model._history:
- logger.info('history:update %s', model.__name__)
- model._update_history_table()
-
- # Vacuum :
- while modules_todo:
- (module, to_delete) = modules_todo.pop()
- convert.post_import(pool, module, to_delete)
-
- cursor.commit()
+ logger.info(module)
+ classes = pool.fill(module)
+ if update:
+ pool.setup(classes)
+ package_state = module2state.get(module, 'uninstalled')
+ if (is_module_to_install(module, update)
+ or (update
+ and package_state in ('to install', 'to upgrade'))):
+ if package_state not in ('to install', 'to upgrade'):
+ if package_state == 'installed':
+ package_state = 'to upgrade'
+ elif package_state != 'to remove':
+ package_state = 'to install'
+ for child in package.childs:
+ module2state[child.name] = package_state
+ for type in classes.keys():
+ for cls in classes[type]:
+ logger.info('%s:register %s', module, cls.__name__)
+ cls.__register__(module)
+ for model in classes['model']:
+ if hasattr(model, '_history'):
+ models_to_update_history.add(model.__name__)
+
+ # Instanciate a new parser for the package:
+ tryton_parser = convert.TrytondXmlHandler(pool=pool,
+ module=module, module_state=package_state)
+
+ for filename in package.info.get('xml', []):
+ filename = filename.replace('/', os.sep)
+ logger.info('%s:loading %s', module, filename)
+ # Feed the parser with xml content:
+ with tools.file_open(OPJ(module, filename), 'rb') as fp:
+ tryton_parser.parse_xmlstream(fp)
+
+ modules_todo.append((module, list(tryton_parser.to_delete)))
+
+ localedir = '%s/%s' % (package.info['directory'], 'locale')
+ for filename in itertools.chain(
+ iglob('%s/*.po' % localedir),
+ iglob('%s/override/*.po' % localedir)):
+ filename = filename.replace('/', os.sep)
+ lang2 = os.path.splitext(os.path.basename(filename))[0]
+ if lang2 not in lang:
+ continue
+ logger.info('%s:loading %s', module,
+ filename[len(package.info['directory']) + 1:])
+ Translation = pool.get('ir.translation')
+ Translation.translation_import(lang2, module, filename)
+
+ if package_state == 'to remove':
+ continue
+ cursor.execute(*ir_module.select(ir_module.id,
+ where=(ir_module.name == package.name)))
+ try:
+ module_id, = cursor.fetchone()
+ cursor.execute(*ir_module.update([ir_module.state],
+ ['installed'], where=(ir_module.id == module_id)))
+ except TypeError:
+ cursor.execute(*ir_module.insert(
+ [ir_module.create_uid, ir_module.create_date,
+ ir_module.name, ir_module.state],
+ [[0, CurrentTimestamp(), package.name,
+ 'installed'],
+ ]))
+ module2state[package.name] = 'installed'
+
+ Transaction().connection.commit()
+
+ if not update:
+ pool.setup()
+
+ for model_name in models_to_update_history:
+ model = pool.get(model_name)
+ if model._history:
+ logger.info('history:update %s', model.__name__)
+ model._update_history_table()
+
+ # Vacuum :
+ while modules_todo:
+ (module, to_delete) = modules_todo.pop()
+ convert.post_import(pool, module, to_delete)
+ logger.info('all modules loaded')
def get_module_list():
@@ -307,7 +314,6 @@ def get_module_list():
module_list.update(EGG_MODULES.keys())
module_list.add('ir')
module_list.add('res')
- module_list.add('webdav')
module_list.add('tests')
return list(module_list)
@@ -320,8 +326,6 @@ def register_classes():
trytond.ir.register()
import trytond.res
trytond.res.register()
- import trytond.webdav
- trytond.webdav.register()
import trytond.tests
trytond.tests.register()
@@ -329,7 +333,7 @@ def register_classes():
module = package.name
logger.info('%s:registering classes', module)
- if module in ('ir', 'res', 'webdav', 'tests'):
+ if module in ('ir', 'res', 'tests'):
MODULES.append(module)
continue
@@ -370,63 +374,61 @@ def load_modules(database_name, pool, update=None, lang=None):
def _load_modules():
global res
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
-
- # Migration from 3.6: remove double module
- old_table = 'ir_module_module'
- new_table = 'ir_module'
- if TableHandler.table_exist(cursor, old_table):
- TableHandler.table_rename(cursor, old_table, new_table)
- if update:
- 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:
- module_list += update
- graph = create_graph(module_list)[0]
-
- try:
+ transaction = Transaction()
+
+ with transaction.connection.cursor() as cursor:
+ # Migration from 3.6: remove double module
+ old_table = 'ir_module_module'
+ new_table = 'ir_module'
+ if TableHandler.table_exist(old_table):
+ TableHandler.table_rename(old_table, new_table)
+ if update:
+ 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:
+ module_list += update
+ graph = create_graph(module_list)[0]
+
load_module_graph(graph, pool, update, lang)
- except Exception:
- cursor.rollback()
- raise
-
- if update:
- cursor.execute(*ir_module.select(ir_module.name,
- where=(ir_module.state == 'to remove')))
- fetchall = cursor.fetchall()
- if fetchall:
- for (mod_name,) in fetchall:
- # TODO check if ressource not updated by the user
- cursor.execute(*ir_model_data.select(ir_model_data.model,
- ir_model_data.db_id,
- where=(ir_model_data.module == mod_name),
- order_by=ir_model_data.id.desc))
- for rmod, rid in cursor.fetchall():
- Model = pool.get(rmod)
- Model.delete([Model(rid)])
- cursor.commit()
- cursor.execute(*ir_module.update([ir_module.state],
- ['uninstalled'],
- where=(ir_module.state == 'to remove')))
- cursor.commit()
- res = False
- Module = pool.get('ir.module')
- Module.update_list()
- cursor.commit()
+ if update:
+ cursor.execute(*ir_module.select(ir_module.name,
+ where=(ir_module.state == 'to remove')))
+ fetchall = cursor.fetchall()
+ if fetchall:
+ for (mod_name,) in fetchall:
+ # TODO check if ressource not updated by the user
+ cursor.execute(*ir_model_data.select(
+ ir_model_data.model, ir_model_data.db_id,
+ where=(ir_model_data.module == mod_name),
+ order_by=ir_model_data.id.desc))
+ for rmod, rid in cursor.fetchall():
+ Model = pool.get(rmod)
+ Model.delete([Model(rid)])
+ Transaction().connection.commit()
+ cursor.execute(*ir_module.update([ir_module.state],
+ ['uninstalled'],
+ where=(ir_module.state == 'to remove')))
+ Transaction().connection.commit()
+ res = False
+
+ Module = pool.get('ir.module')
+ Module.update_list()
+ # Need to commit to unlock SQLite database
+ transaction.commit()
Cache.resets(database_name)
- if not Transaction().cursor:
+ if not Transaction().connection:
with Transaction().start(database_name, 0):
_load_modules()
else:
- with Transaction().new_cursor(), \
+ with Transaction().new_transaction(), \
Transaction().set_user(0), \
Transaction().reset_context():
_load_modules()
diff --git a/trytond/monitor.py b/trytond/monitor.py
deleted file mode 100644
index 866d594..0000000
--- a/trytond/monitor.py
+++ /dev/null
@@ -1,87 +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.
-import sys
-import os
-import subprocess
-from threading import Lock
-from trytond.modules import get_module_list
-
-_LOCK = Lock()
-_TIMES = {}
-_MODULES = None
-
-
-def _modified(path):
- _LOCK.acquire()
- try:
- try:
- if not os.path.isfile(path):
- return path in _TIMES
-
- mtime = os.stat(path).st_mtime
- if path not in _TIMES:
- _TIMES[path] = mtime
-
- if mtime != _TIMES[path]:
- _TIMES[path] = mtime
- return True
- except Exception:
- return True
- finally:
- _LOCK.release()
- return False
-
-
-def monitor(files):
- '''
- 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.'):
- continue
- if not hasattr(sys.modules[module], '__file__'):
- continue
- path = getattr(sys.modules[module], '__file__')
- if not path:
- continue
- if os.path.splitext(path)[1] in ['.pyc', '.pyo', '.pyd']:
- path = path[:-1]
- if _modified(path):
- if subprocess.call((sys.executable, '-c', 'import %s' % module),
- cwd=os.path.dirname(os.path.abspath(os.path.normpath(
- os.path.join(__file__, '..'))))):
- modified = False
- break
- modified = True
-
- # Check view XML
- directory = os.path.dirname(path)
- if directory not in directories:
- directories.add(directory)
- view_dir = os.path.join(directory, 'view')
- if os.path.isdir(view_dir):
- for view in os.listdir(view_dir):
- view = os.path.join(view_dir, view)
- if os.path.splitext(view)[1] == '.xml':
- if _modified(view):
- modified = True
-
- modules = set(get_module_list())
- if _MODULES is None:
- _MODULES = modules
- for module in modules.difference(_MODULES):
- if subprocess.call((sys.executable, '-c',
- 'import trytond.modules.%s' % module)):
- modified = False
- break
- modified = True
- _MODULES = modules
- return modified
diff --git a/trytond/pool.py b/trytond/pool.py
index 3ad6176..3b9a6ee 100644
--- a/trytond/pool.py
+++ b/trytond/pool.py
@@ -55,7 +55,7 @@ class Pool(object):
def __new__(cls, database_name=None):
if database_name is None:
- database_name = Transaction().cursor.database_name
+ database_name = Transaction().database.name
result = cls._instances.get(database_name)
if result:
return result
@@ -69,7 +69,7 @@ class Pool(object):
def __init__(self, database_name=None):
if database_name is None:
- database_name = Transaction().cursor.database_name
+ database_name = Transaction().database.name
self.database_name = database_name
@staticmethod
@@ -196,10 +196,10 @@ class Pool(object):
'''
return self._pool[self.database_name][type].iteritems()
- def setup(self, module):
+ def fill(self, module):
'''
- Setup classes for module and return a list of classes for each type in
- a dictionary.
+ Fill the pool with the registered class from the module.
+ Return a list of classes for each type in a dictionary.
'''
classes = {}
for type_ in self.classes.keys():
@@ -212,13 +212,22 @@ class Pool(object):
pass
if not issubclass(cls, PoolBase):
continue
- cls.__setup__()
self.add(cls, type=type_)
classes[type_].append(cls)
- for cls in classes[type_]:
- cls.__post_setup__()
return classes
+ def setup(self, classes=None):
+ logger.info('setup pool for "%s"', self.database_name)
+ if classes is None:
+ classes = {}
+ for type_ in self._pool[self.database_name]:
+ classes[type_] = self._pool[self.database_name][type_].values()
+ for type_, lst in classes.iteritems():
+ for cls in lst:
+ cls.__setup__()
+ for cls in lst:
+ cls.__post_setup__()
+
def isregisteredby(obj, module, type_='model'):
pool = Pool()
diff --git a/trytond/protocols/common.py b/trytond/protocols/common.py
deleted file mode 100644
index c38ac17..0000000
--- a/trytond/protocols/common.py
+++ /dev/null
@@ -1,53 +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.
-import errno
-import os
-import socket
-import threading
-from SocketServer import StreamRequestHandler
-
-
-def endsocket(sock):
- if os.name != 'nt':
- try:
- sock.shutdown(getattr(socket, 'SHUT_RDWR', 2))
- except socket.error, e:
- if e.errno != errno.ENOTCONN:
- raise
- sock.close()
-
-
-class daemon(threading.Thread):
- def __init__(self, interface, port, secure, name=None):
- threading.Thread.__init__(self, name=name)
- self.secure = secure
- self.ipv6 = False
- for family, _, _, _, _ in socket.getaddrinfo(interface or None, port,
- socket.AF_UNSPEC, socket.SOCK_STREAM):
- if family == socket.AF_INET6:
- self.ipv6 = True
- break
-
- def stop(self):
- self.server.shutdown()
- self.server.socket.shutdown(socket.SHUT_RDWR)
- self.server.server_close()
- return
-
- def run(self):
- self.server.serve_forever()
- return True
-
-
-class RegisterHandlerMixin:
-
- def setup(self):
- self.server.handlers.add(self)
- StreamRequestHandler.setup(self)
-
- def finish(self):
- StreamRequestHandler.finish(self)
- try:
- self.server.handlers.remove(self)
- except KeyError:
- pass
diff --git a/trytond/protocols/dispatcher.py b/trytond/protocols/dispatcher.py
index 4061fbf..7414ec3 100644
--- a/trytond/protocols/dispatcher.py
+++ b/trytond/protocols/dispatcher.py
@@ -4,7 +4,9 @@
import logging
import time
import pydoc
+from functools import wraps
+from werkzeug.utils import redirect
from sql import Table
from trytond.pool import Pool
@@ -14,9 +16,9 @@ from trytond.config import config
from trytond import __version__
from trytond.transaction import Transaction
from trytond.cache import Cache
-from trytond.exceptions import UserError, UserWarning, NotLogged, \
- ConcurrencyException
+from trytond.exceptions import UserError, UserWarning, ConcurrencyException
from trytond.tools import is_instance_method
+from trytond.wsgi import app
logger = logging.getLogger(__name__)
@@ -26,133 +28,170 @@ ir_module = Table('ir_module')
res_user = Table('res_user')
-def dispatch(host, port, protocol, database_name, user, session, object_type,
- object_name, method, *args, **kwargs):
- Database = backend.get('Database')
- DatabaseOperationalError = backend.get('DatabaseOperationalError')
- if object_type == 'common':
- if method == 'login':
- try:
- database = Database(database_name).connect()
- cursor = database.cursor()
- cursor.close()
- except Exception:
- return False
- res = security.login(database_name, user, session)
- 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)
- return res or False
- elif method == 'logout':
- name = security.logout(database_name, user, session)
- logger.info('logout \'%s\' from %s:%d '
- 'using %s on database \'%s\'',
- name, host, port, protocol, database_name)
- return True
- elif method == 'version':
- return __version__
- elif method == 'list_lang':
- return [
- ('bg_BG', 'Български'),
- ('ca_ES', 'Català'),
- ('cs_CZ', 'Čeština'),
- ('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)'),
- ('es_MX', 'Español (México)'),
- ('fr_FR', 'Français'),
- ('hu_HU', 'Magyar'),
- ('it_IT', 'Italiano'),
- ('lt_LT', 'Lietuvių'),
- ('nl_NL', 'Nederlands'),
- ('pt_BR', 'Português (Brasil)'),
- ('ru_RU', 'Russian'),
- ('sl_SI', 'Slovenščina'),
- ]
- elif method == 'db_exist':
- try:
- database = Database(*args, **kwargs).connect()
- cursor = database.cursor()
- cursor.close(close=True)
- return True
- except Exception:
- return False
- elif method == 'list':
- if not config.getboolean('database', 'list'):
- raise Exception('AccessDenied')
- with Transaction().start(None, 0, close=True) as transaction:
- return transaction.database.list(transaction.cursor)
- elif method == 'create':
- return create(*args, **kwargs)
- elif method == 'restore':
- return restore(*args, **kwargs)
- elif method == 'drop':
- return drop(*args, **kwargs)
- elif method == 'dump':
- return dump(*args, **kwargs)
- return
- elif object_type == 'system':
- database = Database(database_name).connect()
+def with_pool(func):
+ @wraps(func)
+ def wrapper(request, database_name, *args, **kwargs):
database_list = Pool.database_list()
pool = Pool(database_name)
if database_name not in database_list:
- pool.init()
- if method == 'listMethods':
- res = []
- for type in ('model', 'wizard', 'report'):
- for object_name, obj in pool.iterobject(type=type):
- for method in obj.__rpc__:
- res.append(type + '.' + object_name + '.' + method)
- return res
- elif method == 'methodSignature':
- return 'signatures not supported'
- elif method == 'methodHelp':
- res = []
- args_list = args[0].split('.')
- object_type = args_list[0]
- object_name = '.'.join(args_list[1:-1])
- method = args_list[-1]
- obj = pool.get(object_name, type=object_type)
- return pydoc.getdoc(getattr(obj, method))
+ with Transaction().start(
+ database_name, request.user_id, readonly=True):
+ pool.init()
+ return func(request, pool, *args, **kwargs)
+ return wrapper
- for count in range(config.getint('database', 'retry'), -1, -1):
- try:
- user = security.check(database_name, user, session)
- except DatabaseOperationalError:
- if count:
- continue
- raise
- break
- database_list = Pool.database_list()
- pool = Pool(database_name)
- if database_name not in database_list:
- with Transaction().start(database_name, user,
- readonly=True) as transaction:
- pool.init()
- obj = pool.get(object_name, type=object_type)
+ at app.route('/<string:database_name>/', methods=['POST'])
+def rpc(request, database_name):
+ methods = {
+ 'common.db.login': login,
+ 'common.db.logout': logout,
+ 'common.db.db_exist': db_exist,
+ 'common.db.create': create,
+ 'common.db.restore': restore,
+ 'common.db.drop': drop,
+ 'common.db.dump': dump,
+ 'system.listMethods': list_method,
+ 'system.methodHelp': help_method,
+ 'system.methodSignature': lambda *a: 'signatures not supported',
+ }
+ return methods.get(request.method, _dispatch)(
+ request, database_name, *request.params)
+
+
+def login(request, database_name, user, password):
+ Database = backend.get('Database')
+ DatabaseOperationalError = backend.get('DatabaseOperationalError')
+ try:
+ Database(database_name).connect()
+ except DatabaseOperationalError:
+ logger.error('fail to connect to %s', database_name, exc_info=True)
+ return False
+ session = security.login(database_name, user, password)
+ with Transaction().start(database_name, 0):
+ Cache.clean(database_name)
+ Cache.resets(database_name)
+ msg = 'successful login' if session else 'bad login or password'
+ logger.info('%s \'%s\' from %s using %s on database \'%s\'',
+ msg, user, request.remote_addr, request.scheme, database_name)
+ return session
+
+
+ at app.auth_required
+def logout(request, database_name):
+ auth = request.authorization
+ name = security.logout(
+ database_name, auth.get('userid'), auth.get('session'))
+ logger.info('logout \'%s\' from %s using %s on database \'%s\'',
+ name, request.remote_addr, request.scheme, database_name)
+ return True
+
+
+ at app.route('/', methods=['POST'])
+def root(request, *args):
+ methods = {
+ 'common.server.version': lambda *a: __version__,
+ 'common.db.list_lang': list_lang,
+ 'common.db.list': db_list,
+ }
+ return methods[request.method](request, *request.params)
+
+
+ at app.route('/', methods=['GET'])
+def home(request):
+ return redirect('/index.html') # XXX find a better way
+
+
+def list_lang(*args):
+ return [
+ ('bg_BG', 'Български'),
+ ('ca_ES', 'Català'),
+ ('cs_CZ', 'Čeština'),
+ ('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)'),
+ ('es_MX', 'Español (México)'),
+ ('fr_FR', 'Français'),
+ ('hu_HU', 'Magyar'),
+ ('it_IT', 'Italiano'),
+ ('lt_LT', 'Lietuvių'),
+ ('lo_LA', 'ລາວ'),
+ ('nl_NL', 'Nederlands'),
+ ('pt_BR', 'Português (Brasil)'),
+ ('ru_RU', 'Russian'),
+ ('sl_SI', 'Slovenščina'),
+ ('zh_CN', '中国(简体)'),
+ ]
+
+def db_exist(request, database_name):
+ Database = backend.get('Database')
+ try:
+ Database(database_name).connect()
+ return True
+ except Exception:
+ return False
+
+
+def db_list(*args):
+ if not config.getboolean('database', 'list'):
+ raise Exception('AccessDenied')
+ with Transaction().start(None, 0, close=True) as transaction:
+ return transaction.database.list()
+
+
+ at app.auth_required
+ at with_pool
+def list_method(request, pool):
+ methods = []
+ for type in ('model', 'wizard', 'report'):
+ for object_name, obj in pool.iterobject(type=type):
+ for method in obj.__rpc__:
+ methods.append(type + '.' + object_name + '.' + method)
+ return methods
+
+
+def get_object_method(request, pool):
+ method = request.method
+ type, _ = method.split('.', 1)
+ name = '.'.join(method.split('.')[1:-1])
+ method = method.split('.')[-1]
+ return pool.get(name, type=type), method
+
+
+ at app.auth_required
+ at with_pool
+def help_method(request, pool):
+ obj, method = get_object_method(request, pool)
+ return pydoc.getdoc(getattr(obj, method))
+
+
+ at app.auth_required
+ at with_pool
+def _dispatch(request, pool, *args, **kwargs):
+ DatabaseOperationalError = backend.get('DatabaseOperationalError')
+
+ obj, method = get_object_method(request, pool)
if method in obj.__rpc__:
rpc = obj.__rpc__[method]
else:
- raise UserError('Calling method %s on %s %s is not allowed!'
- % (method, object_type, object_name))
-
- log_message = '%s.%s.%s(*%s, **%s) from %s@%s:%d/%s'
- log_args = (object_type, object_name, method, args, kwargs,
- user, host, port, database_name)
+ raise UserError('Calling method %s on %s is not allowed'
+ % (method, obj))
+ log_message = '%s.%s(*%s, **%s) from %s@%s/%s'
+ log_args = (obj, method, args, kwargs,
+ request.authorization.username, request.remote_addr, request.path)
logger.info(log_message, *log_args)
+
+ user = request.user_id
+
for count in range(config.getint('database', 'retry'), -1, -1):
- with Transaction().start(database_name, user,
+ with Transaction().start(pool.database_name, user,
readonly=rpc.readonly) as transaction:
- Cache.clean(database_name)
+ Cache.clean(pool.database_name)
try:
c_args, c_kwargs, transaction.context, transaction.timestamp \
= rpc.convert(obj, *args, **kwargs)
@@ -168,38 +207,33 @@ def dispatch(host, port, protocol, database_name, user, session, object_type,
else:
result = [rpc.result(meth(i, *c_args, **c_kwargs))
for i in inst]
- if not rpc.readonly:
- transaction.cursor.commit()
except DatabaseOperationalError:
- transaction.cursor.rollback()
if count and not rpc.readonly:
+ transaction.rollback()
continue
+ logger.error(log_message, *log_args, exc_info=True)
raise
- except (NotLogged, ConcurrencyException, UserError, UserWarning):
+ except (ConcurrencyException, UserError, UserWarning):
logger.debug(log_message, *log_args, exc_info=True)
- transaction.cursor.rollback()
raise
except Exception:
logger.error(log_message, *log_args, exc_info=True)
- 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')
+ # Need to commit to unlock SQLite database
+ transaction.commit()
+ Cache.resets(pool.database_name)
+ if request.authorization.type == 'session':
try:
- Session.reset(session)
+ with Transaction().start(pool.database_name, 0) as transaction:
+ Session = pool.get('ir.session')
+ Session.reset(request.authorization.get('session'))
except DatabaseOperationalError:
logger.debug('Reset session failed', exc_info=True)
- # Silently fail when reseting session
- transaction.cursor.rollback()
- else:
- transaction.cursor.commit()
logger.debug('Result: %s', result)
return result
-def create(database_name, password, lang, admin_password):
+def create(request, database_name, password, lang, admin_password):
'''
Create a database
@@ -216,14 +250,13 @@ def create(database_name, password, lang, admin_password):
try:
with Transaction().start(None, 0, close=True, autocommit=True) \
as transaction:
- transaction.database.create(transaction.cursor, database_name)
- transaction.cursor.commit()
+ transaction.database.create(transaction.connection, database_name)
- with Transaction().start(database_name, 0) as transaction:
- Database.init(transaction.cursor)
- transaction.cursor.execute(*ir_configuration.insert(
+ with Transaction().start(database_name, 0) as transaction,\
+ transaction.connection.cursor() as cursor:
+ Database(database_name).init()
+ cursor.execute(*ir_configuration.insert(
[ir_configuration.language], [[lang]]))
- transaction.cursor.commit()
pool = Pool(database_name)
pool.init(update=['res', 'ir'], lang=[lang])
@@ -244,7 +277,6 @@ def create(database_name, password, lang, admin_password):
Module = pool.get('ir.module')
if Module:
Module.update_list()
- transaction.cursor.commit()
res = True
except Exception:
logger.error('CREATE DB: %s failed', database_name, exc_info=True)
@@ -254,19 +286,18 @@ def create(database_name, password, lang, admin_password):
return res
-def drop(database_name, password):
+def drop(request, database_name, password):
Database = backend.get('Database')
security.check_super(password)
- Database(database_name).close()
+ database = Database(database_name)
+ database.close()
# Sleep to let connections close
time.sleep(1)
with Transaction().start(None, 0, close=True, autocommit=True) \
as transaction:
- cursor = transaction.cursor
try:
- Database.drop(cursor, database_name)
- cursor.commit()
+ database.drop(transaction.connection, database_name)
except Exception:
logger.error('DROP DB: %s failed', database_name, exc_info=True)
raise
@@ -277,7 +308,7 @@ def drop(database_name, password):
return True
-def dump(database_name, password):
+def dump(request, database_name, password):
Database = backend.get('Database')
security.check_super(password)
Database(database_name).close()
@@ -292,13 +323,11 @@ def dump(database_name, password):
return bytes(data)
-def restore(database_name, password, data, update=False):
+def restore(request, database_name, password, data, update=False):
Database = backend.get('Database')
security.check_super(password)
try:
- database = Database(database_name).connect()
- cursor = database.cursor()
- cursor.close(close=True)
+ Database(database_name).connect()
existing = True
except Exception:
existing = False
@@ -307,8 +336,8 @@ def restore(database_name, password, data, update=False):
Database.restore(database_name, data)
logger.info('RESTORE DB: %s', database_name)
if update:
- with Transaction().start(database_name, 0) as transaction:
- cursor = transaction.cursor
+ with Transaction().start(database_name, 0) as transaction,\
+ transaction.connection.cursor() as cursor:
cursor.execute(*ir_lang.select(ir_lang.code,
where=ir_lang.translatable))
lang = [x[0] for x in cursor.fetchall()]
diff --git a/trytond/protocols/jsonrpc.py b/trytond/protocols/jsonrpc.py
index df9f46e..69298ef 100644
--- a/trytond/protocols/jsonrpc.py
+++ b/trytond/protocols/jsonrpc.py
@@ -1,24 +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.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
-import SimpleXMLRPCServer
-import SimpleHTTPServer
-import SocketServer
import traceback
-import socket
import sys
-import os
-try:
- import fcntl
-except ImportError:
- fcntl = None
-import posixpath
-import urllib
import datetime
from decimal import Decimal
try:
@@ -26,11 +9,13 @@ try:
except ImportError:
import json
import base64
-import encodings
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
+
+from werkzeug.wrappers import Response
+from werkzeug.utils import cached_property
+from werkzeug.exceptions import BadRequest
+
+from trytond.protocols.wrappers import Request
+from trytond.exceptions import TrytonException
class JSONDecoder(object):
@@ -61,7 +46,7 @@ JSONDecoder.register('timedelta',
def _bytes_decoder(dct):
cast = bytearray if bytes == str else bytes
- return cast(base64.decodestring(dct['base64']))
+ return cast(base64.decodestring(dct['base64'].encode('utf-8')))
JSONDecoder.register('bytes', _bytes_decoder)
JSONDecoder.register('Decimal', lambda dct: Decimal(dct['decimal']))
@@ -118,7 +103,7 @@ JSONEncoder.register(datetime.timedelta,
})
_bytes_encoder = lambda o: {
'__class__': 'bytes',
- 'base64': base64.encodestring(o),
+ 'base64': base64.encodestring(o).decode('utf-8'),
}
JSONEncoder.register(bytes, _bytes_encoder)
JSONEncoder.register(bytearray, _bytes_encoder)
@@ -129,266 +114,50 @@ JSONEncoder.register(Decimal,
})
-class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
- """Mix-in class that dispatches JSON-RPC requests.
+class JSONRequest(Request):
+ parsed_content_type = 'json'
+
+ @cached_property
+ def parsed_data(self):
+ if self.parsed_content_type in self.environ.get('CONTENT_TYPE', ''):
+ try:
+ return json.loads(self.decoded_data, object_hook=JSONDecoder())
+ except Exception:
+ raise BadRequest('Unable to read JSON request')
+ else:
+ raise BadRequest('Not a JSON request')
- This class is used to register JSON-RPC method handlers
- and then to dispatch them. There should never be any
- reason to instantiate this class directly.
- """
+ @cached_property
+ def method(self):
+ return self.parsed_data['method']
- def _marshaled_dispatch(self, data, dispatch_method=None, path=None):
- """Dispatches an JSON-RPC method from marshalled (JSON) data.
+ @cached_property
+ def params(self):
+ return self.parsed_data['params']
- JSON-RPC methods are dispatched from the marshalled (JSON) data
- using the _dispatch method and the result is returned as
- marshalled data. For backwards compatibility, a dispatch
- function can be provided as an argument (see comment in
- SimpleJSONRPCRequestHandler.do_POST) but overriding the
- existing method through subclassing is the prefered means
- of changing method dispatch behavior.
- """
- rawreq = json.loads(data, object_hook=JSONDecoder())
- req_id = rawreq.get('id', 0)
- method = rawreq['method']
- params = rawreq.get('params', [])
+class JSONProtocol:
+ content_type = 'json'
- response = {'id': req_id}
+ @classmethod
+ def request(cls, environ):
+ return JSONRequest(environ)
- try:
- # generate response
- if dispatch_method is not None:
- response['result'] = dispatch_method(method, params)
- else:
- response['result'] = self._dispatch(method, params)
- except (UserError, UserWarning, NotLogged,
- ConcurrencyException), exception:
- response['error'] = exception.args
- except Exception:
+ @classmethod
+ def response(cls, data, request):
+ if isinstance(request, JSONRequest):
+ response = {'id': request.parsed_data.get('id', 0)}
+ else:
+ response = {}
+ if isinstance(data, TrytonException):
+ response['error'] = data.args
+ elif isinstance(data, Exception):
tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
for path in sys.path:
tb_s = tb_s.replace(path, '')
# report exception back to server
- response['error'] = (str(sys.exc_value), tb_s)
-
- return json.dumps(response, cls=JSONEncoder)
-
-
-class GenericJSONRPCRequestHandler:
-
- def _dispatch(self, method, params):
- host, port = self.client_address[:2]
- database_name = self.path[1:]
- if database_name.startswith('sao/'):
- database_name = database_name[4:]
- method_list = method.split('.')
- object_type = method_list[0]
- object_name = '.'.join(method_list[1:-1])
- method = method_list[-1]
- args = (host, port, 'JSON-RPC', database_name, params[0], params[1],
- object_type, object_name, method) + tuple(params[2:])
- res = dispatch(*args)
- return res
-
-
-class SimpleJSONRPCRequestHandler(RegisterHandlerMixin,
- GenericJSONRPCRequestHandler,
- SimpleXMLRPCServer.SimpleXMLRPCRequestHandler,
- SimpleHTTPServer.SimpleHTTPRequestHandler):
- """Simple JSON-RPC request handler class.
-
- Handles all HTTP POST requests and attempts to decode them as
- JSON-RPC requests.
- """
- protocol_version = "HTTP/1.1"
- rpc_paths = None
- encode_threshold = 1400 # common MTU
-
- def send_header(self, keyword, value):
- if keyword == 'Content-type' and value == 'text/xml':
- value = 'application/json-rpc'
- SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.send_header(self,
- keyword, value)
-
- def do_GET(self):
- if self.is_tryton_url(self.path):
- self.send_tryton_url(self.path)
- return
- SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
-
- def do_HEAD(self):
- if self.is_tryton_url(self.path):
- self.send_tryton_url(self.path)
- return
- SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
-
- def translate_path(self, path):
- """Translate a /-separated PATH to the local filename syntax.
-
- Components that mean special things to the local file system
- (e.g. drive or directory names) are ignored. (XXX They should
- probably be diagnosed.)
-
- """
- # abandon query parameters
- path = path.split('?', 1)[0]
- path = path.split('#', 1)[0]
- path = posixpath.normpath(urllib.unquote(path))
- words = path.split('/')
- words = filter(None, words)
- path = config.get('jsonrpc', 'data')
- for word in words:
- drive, word = os.path.splitdrive(word)
- head, word = os.path.split(word)
- if word in (os.curdir, os.pardir):
- continue
- path = os.path.join(path, word)
- return path
-
- def is_tryton_url(self, path):
- words = path.split('/')
- try:
- return words[2] in ('model', 'wizard', 'report')
- except IndexError:
- return False
-
- def send_tryton_url(self, path):
- self.send_response(300)
- hostname = (config.get('jsonrpc', 'hostname')
- or unicode(socket.getfqdn(), 'utf8'))
- hostname = '.'.join(encodings.idna.ToASCII(part) for part in
- hostname.split('.'))
- values = {
- 'hostname': hostname,
- 'path': path,
- }
- content = StringIO()
- content.write('<html')
- content.write('<head>')
- content.write('<meta http-equiv="Refresh" '
- 'content="0;url=tryton://%(hostname)s%(path)s"/>' % values)
- content.write('<title>Moved</title>')
- content.write('</head>')
- content.write('<body>')
- content.write('<h1>Moved</h1>')
- content.write('<p>This page has moved to '
- '<a href="tryton://%(hostname)s%(path)s">'
- 'tryton://%(hostname)s%(path)s</a>.</p>' % values)
- content.write('</body>')
- content.write('</html>')
- length = content.tell()
- content.seek(0)
- self.send_header('Location', 'tryton://%(hostname)s%(path)s' % values)
- self.send_header('Content-type', 'text/html')
- self.send_header('Content-Length', str(length))
- self.end_headers()
- self.copyfile(content, self.wfile)
- content.close()
-
-SimpleJSONRPCRequestHandler.extensions_map.update({
- '.svg': 'image/svg+xml',
- })
-
-
-class SecureJSONRPCRequestHandler(SimpleJSONRPCRequestHandler):
-
- def setup(self):
- self.request = SSLSocket(self.request)
- SimpleJSONRPCRequestHandler.setup(self)
-
-
-class SimpleJSONRPCServer(SocketServer.TCPServer,
- SimpleJSONRPCDispatcher):
- """Simple JSON-RPC server.
-
- Simple JSON-RPC server that allows functions and a single instance
- to be installed to handle requests. The default implementation
- attempts to dispatch JSON-RPC calls to the functions or instance
- installed in the server. Override the _dispatch method inhereted
- from SimpleJSONRPCDispatcher to change this behavior.
- """
-
- allow_reuse_address = True
-
- # Warning: this is for debugging purposes only! Never set this to True in
- # production code, as will be sending out sensitive information (exception
- # and stack trace details) when exceptions are raised inside
- # SimpleJSONRPCRequestHandler.do_POST
- _send_traceback_header = False
-
- def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
- logRequests=True, allow_none=False, encoding=None,
- bind_and_activate=True):
- self.handlers = set()
- self.logRequests = logRequests
-
- SimpleJSONRPCDispatcher.__init__(self, allow_none, encoding)
- try:
- SocketServer.TCPServer.__init__(self, addr, requestHandler,
- bind_and_activate)
- except TypeError:
- SocketServer.TCPServer.__init__(self, addr, requestHandler)
-
- # [Bug #1222790] If possible, set close-on-exec flag; if a
- # method spawns a subprocess, the subprocess shouldn't have
- # the listening socket open.
- if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
- flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
- flags |= fcntl.FD_CLOEXEC
- fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
-
- def server_close(self):
- SocketServer.TCPServer.server_close(self)
- for handler in self.handlers.copy():
- self.shutdown_request(handler.request)
-
-
-class SimpleThreadedJSONRPCServer(SocketServer.ThreadingMixIn,
- SimpleJSONRPCServer):
- timeout = 1
- daemon_threads = True
- disable_nagle_algorithm = True
-
- def server_bind(self):
- self.socket.setsockopt(socket.SOL_SOCKET,
- socket.SO_REUSEADDR, 1)
- self.socket.setsockopt(socket.SOL_SOCKET,
- socket.SO_KEEPALIVE, 1)
- SimpleJSONRPCServer.server_bind(self)
-
-
-class SimpleThreadedJSONRPCServer6(SimpleThreadedJSONRPCServer):
- address_family = socket.AF_INET6
-
-
-class SecureThreadedJSONRPCServer(SimpleThreadedJSONRPCServer):
-
- def __init__(self, server_address, HandlerClass, logRequests=1):
- SimpleThreadedJSONRPCServer.__init__(self, server_address,
- HandlerClass, logRequests)
- self.socket = socket.socket(self.address_family,
- self.socket_type)
- self.server_bind()
- self.server_activate()
-
-
-class SecureThreadedJSONRPCServer6(SecureThreadedJSONRPCServer):
- address_family = socket.AF_INET6
-
-
-class JSONRPCDaemon(daemon):
-
- def __init__(self, interface, port, secure=False):
- daemon.__init__(self, interface, port, secure, name='JSONRPCDaemon')
- if self.secure:
- handler_class = SecureJSONRPCRequestHandler
- server_class = SecureThreadedJSONRPCServer
- if self.ipv6:
- server_class = SecureThreadedJSONRPCServer6
+ response['error'] = (str(data), tb_s)
else:
- handler_class = SimpleJSONRPCRequestHandler
- server_class = SimpleThreadedJSONRPCServer
- if self.ipv6:
- server_class = SimpleThreadedJSONRPCServer6
- self.server = server_class((interface, port), handler_class, 0)
+ response['result'] = data
+ return Response(json.dumps(response, cls=JSONEncoder),
+ content_type='application/json')
diff --git a/trytond/protocols/sslsocket.py b/trytond/protocols/sslsocket.py
deleted file mode 100644
index 4d12720..0000000
--- a/trytond/protocols/sslsocket.py
+++ /dev/null
@@ -1,13 +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.
-from trytond.config import config
-
-
-def SSLSocket(socket):
- # Let the import error raise only when used
- import ssl
- return ssl.wrap_socket(socket,
- server_side=True,
- 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
deleted file mode 100644
index 77d2822..0000000
--- a/trytond/protocols/webdav.py
+++ /dev/null
@@ -1,594 +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.
-import SocketServer
-import socket
-import BaseHTTPServer
-import urlparse
-import time
-import urllib
-import logging
-from threading import local
-import xml.dom.minidom
-import base64
-from pywebdav.lib import WebDAVServer, iface
-from pywebdav.lib.errors import DAV_Error, DAV_NotFound, DAV_Secret, \
- DAV_Forbidden, DAV_Requested_Range_Not_Satisfiable
-from pywebdav.lib.constants import COLLECTION, DAV_VERSION_1, DAV_VERSION_2
-from pywebdav.lib.utils import get_urifilename, quote_uri
-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.security import login
-from trytond import __version__
-from trytond.tools.misc import LocalDict
-from trytond import backend
-from trytond.pool import Pool
-from trytond.transaction import Transaction
-from trytond.cache import Cache
-from trytond.exceptions import UserError, UserWarning, NotLogged, \
- ConcurrencyException
-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):
-
- def __init__(self, value=0):
- super(LocalInt, self).__init__()
- self.value = value
-
- def __int__(self):
- return int(self.value)
-
-CACHE = LocalDict()
-
-
-def setupConfig():
-
- class ConfigDAV:
- lockemulation = False
- verbose = False
- baseurl = ''
-
- def getboolean(self, name):
- return bool(self.get(name))
-
- def get(self, name, default=None):
- return getattr(self, name, default)
-
- class Config:
- DAV = ConfigDAV()
-
- return Config()
-
-
-class BaseThreadedHTTPServer(SocketServer.ThreadingMixIn,
- BaseHTTPServer.HTTPServer):
- timeout = 1
-
- def server_bind(self):
- self.socket.setsockopt(socket.SOL_SOCKET,
- socket.SO_REUSEADDR, 1)
- self.socket.setsockopt(socket.SOL_SOCKET,
- socket.SO_KEEPALIVE, 1)
- BaseHTTPServer.HTTPServer.server_bind(self)
-
-
-class SecureThreadedHTTPServer(BaseThreadedHTTPServer):
-
- def __init__(self, server_address, HandlerClass):
- BaseThreadedHTTPServer.__init__(self, server_address, HandlerClass)
- self.socket = socket.socket(self.address_family, self.socket_type)
- self.server_bind()
- self.server_activate()
-
-
-class WebDAVServerThread(daemon):
-
- def __init__(self, interface, port, secure=False):
- daemon.__init__(self, interface, port, secure,
- name='WebDAVServerThread')
- if self.secure:
- handler_class = SecureWebDAVAuthRequestHandler
- server_class = SecureThreadedHTTPServer
- if self.ipv6:
- server_class = SecureThreadedHTTPServer6
- else:
- handler_class = WebDAVAuthRequestHandler
- server_class = BaseThreadedHTTPServer
- if self.ipv6:
- server_class = BaseThreadedHTTPServer6
- handler_class._config = setupConfig()
- handler_class.IFACE_CLASS = TrytonDAVInterface(interface, port, secure)
- handler_class.IFACE_CLASS.baseurl = handler_class._config.DAV.baseurl
- self.server = server_class((interface, port), handler_class)
-
-
-class BaseThreadedHTTPServer6(BaseThreadedHTTPServer):
- address_family = socket.AF_INET6
-
-
-class SecureThreadedHTTPServer6(SecureThreadedHTTPServer):
- address_family = socket.AF_INET6
-
-
-class TrytonDAVInterface(iface.dav_interface):
-
- def __init__(self, interface, port, secure=False):
- if secure:
- protocol = 'https'
- else:
- protocol = 'http'
- self.baseuri = '%s://%s:%s/' % (protocol, interface or
- socket.gethostname(), port)
- self.verbose = False
-
- def _log_exception(self, exception):
- if isinstance(exception, (NotLogged, ConcurrencyException, UserError,
- UserWarning, DAV_Error, DAV_NotFound, DAV_Secret,
- DAV_Forbidden)):
- logger.debug('Exception %s', exception, exc_info=True)
- else:
- logger.error('Exception %s', exception, exc_info=True)
-
- @staticmethod
- def get_dburi(uri):
- uri = urlparse.urlsplit(uri)[2]
- if uri and uri[0] == '/':
- uri = uri[1:]
- dbname, uri = (uri.split('/', 1) + [None])[0:2]
- if dbname:
- dbname = urllib.unquote_plus(dbname)
- if uri:
- uri = urllib.unquote_plus(uri)
- return dbname, uri
-
- def _get_dburi(self, uri):
- return TrytonDAVInterface.get_dburi(uri)
-
- def get_childs(self, uri, filter=None):
- res = []
- dbname, dburi = self._get_dburi(uri)
- if not dbname:
- database = backend.get('Database')().connect()
- cursor = database.cursor()
- try:
- lists = database.list(cursor)
- except Exception:
- lists = []
- finally:
- cursor.close()
- for dbname in lists:
- res.append(urlparse.urljoin(uri, dbname))
- return res
- pool = Pool(Transaction().cursor.database_name)
- try:
- Collection = pool.get('webdav.collection')
- scheme, netloc, path, params, query, fragment = \
- urlparse.urlparse(uri)
- if path[-1:] != '/':
- path += '/'
- for child in Collection.get_childs(dburi, filter=filter,
- cache=CACHE):
- res.append(urlparse.urlunparse((scheme, netloc,
- path + child.encode('utf-8'), params, query,
- fragment)))
- except KeyError:
- return res
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- return res
-
- def get_data(self, uri, range=None):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or (self.exists(uri) and self.is_collection(uri)):
- res = ('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 '
- 'Transitional//EN">')
- res += '<html>'
- res += '<head>'
- res += ('<meta http-equiv="Content-Type" content="text/html; '
- 'charset=utf-8">')
- res += '<title>Tryton - WebDAV - %s</title>' % dbname or 'root'
- res += '</head>'
- res += '<body>'
- res += '<h2>Collection: %s</h2>' % (get_urifilename(uri) or '/')
- res += '<ul>'
- if dbname:
- scheme, netloc, path, params, query, fragment = \
- urlparse.urlparse(uri)
- if path[-1:] != '/':
- path += '/'
- res += ('<li><a href="%s">..</a></li>'
- % urlparse.urlunparse((scheme, netloc, path + '..',
- params, query, fragment)))
- childs = self.get_childs(uri)
- childs.sort()
- for child in childs:
- res += ('<li><a href="%s">%s</a></li>'
- % (quote_uri(child), get_urifilename(child)))
- res += '</ul>'
- res += '<hr noshade>'
- res += ('<em>Powered by <a href="http://www.tryton.org/">'
- 'Tryton</a> version %s</em>' % __version__)
- res += '</body>'
- res += '</html>'
- return res
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.get_data(dburi, cache=CACHE)
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- if range is None:
- return res
- size = len(res)
- if range[1] == '':
- range[1] = size
- else:
- range[1] = int(range[1])
- if range[1] > size:
- range[1] = size
- if range[0] == '':
- range[0] = size - range[1]
- else:
- range[0] = int(range[0])
- if range[0] > size:
- raise DAV_Requested_Range_Not_Satisfiable
- return res[range[0]:range[1]]
-
- def put(self, uri, data, content_type=''):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- raise DAV_Forbidden
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.put(dburi, data, content_type, cache=CACHE)
- Transaction().cursor.commit()
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- Transaction().cursor.rollback()
- raise
- except Exception, exception:
- self._log_exception(exception)
- Transaction().cursor.rollback()
- raise DAV_Error(500)
- if res:
- uparts = list(urlparse.urlsplit(uri))
- uparts[2] = res
- res = urlparse.urlunsplit(uparts)
- return res
-
- def mkcol(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- raise DAV_Forbidden
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.mkcol(dburi, cache=CACHE)
- Transaction().cursor.commit()
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- Transaction().cursor.rollback()
- raise
- except Exception, exception:
- self._log_exception(exception)
- Transaction().cursor.rollback()
- raise DAV_Error(500)
- return res
-
- def _get_dav_resourcetype(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- return COLLECTION
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.get_resourcetype(dburi, cache=CACHE)
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- return res
-
- def _get_dav_displayname(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- return uri.split('/')[-1]
- pool = Pool(Transaction().cursor.database_name)
- try:
- Collection = pool.get('webdav.collection')
- res = Collection.get_displayname(dburi, cache=CACHE)
- except KeyError:
- raise DAV_NotFound
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- return res
-
- def _get_dav_getcontentlength(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- return '0'
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.get_contentlength(dburi, cache=CACHE)
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- return res
-
- def _get_dav_getcontenttype(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or self.is_collection(uri):
- return "text/html"
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.get_contenttype(dburi, cache=CACHE)
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- return res
-
- def _get_dav_getetag(self, uri):
- return '"' + str(self.get_lastmodified(uri)) + '"'
-
- def get_creationdate(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- return time.time()
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.get_creationdate(dburi, cache=CACHE)
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- return res
-
- def get_lastmodified(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- return time.time()
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.get_lastmodified(dburi, cache=CACHE)
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- return res
-
- def rmcol(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- return 403
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.rmcol(dburi, cache=CACHE)
- Transaction().cursor.commit()
- except Exception, exception:
- self._log_exception(exception)
- Transaction().cursor.rollback()
- return 500
- return res
-
- def rm(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- return 403
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.rm(dburi, cache=CACHE)
- Transaction().cursor.commit()
- except Exception, exception:
- self._log_exception(exception)
- Transaction().cursor.rollback()
- return 500
- return res
-
- def exists(self, uri):
- dbname, dburi = self._get_dburi(uri)
- if not dbname or not dburi:
- return 1
- pool = Pool(Transaction().cursor.database_name)
- Collection = pool.get('webdav.collection')
- try:
- res = Collection.exists(dburi, cache=CACHE)
- except (DAV_Error, DAV_NotFound, DAV_Secret, DAV_Forbidden), exception:
- self._log_exception(exception)
- raise
- except Exception, exception:
- self._log_exception(exception)
- raise DAV_Error(500)
- return res
-
- def is_collection(self, uri):
- if self._get_dav_resourcetype(uri) == COLLECTION:
- return 1
- return 0
-
- def copyone(self, src, dst, overwrite):
- return copyone(self, src, dst, overwrite)
-
- def copytree(self, src, dst, overwrite):
- return copytree(self, src, dst, overwrite)
-
- def moveone(self, src, dst, overwrite):
- return moveone(self, src, dst, overwrite)
-
- def movetree(self, src, dst, overwrite):
- return movetree(self, src, dst, overwrite)
-
- def delone(self, uri):
- return delone(self, uri)
-
- def deltree(self, uri):
- return deltree(self, uri)
-
- def copy(self, src, dst):
- content = self._get_dav_getcontenttype(src)
- data = self.get_data(src)
- self.put(dst, data, content)
- return 201
-
- def copycol(self, src, dst):
- return self.mkcol(dst)
-
- def _get_dav_current_user_privilege_set(self, uri):
- dbname, dburi = self._get_dburi(uri)
- privileges = []
- if not dbname or not dburi:
- privileges = ['create', 'read', 'write', 'delete']
- else:
- pool = Pool(Transaction().cursor.database_name)
- try:
- Collection = pool.get('webdav.collection')
- privileges = Collection.current_user_privilege_set(dburi,
- cache=CACHE)
- except KeyError:
- pass
- except Exception, exception:
- self._log_exception(exception)
- pass
- doc = domimpl.createDocument(None, 'privilege', None)
- privilege = doc.documentElement
- privilege.tagName = 'D:privilege'
- if 'create' in privileges:
- bind = doc.createElement('D:bind')
- privilege.appendChild(bind)
- if 'read' in privileges:
- read = doc.createElement('D:read')
- privilege.appendChild(read)
- read_acl = doc.createElement('D:read-acl')
- privilege.appendChild(read_acl)
- if 'write' in privileges:
- write = doc.createElement('D:write')
- privilege.appendChild(write)
- write_content = doc.createElement('D:write-content')
- privilege.appendChild(write_content)
- write_properties = doc.createElement('D:write-properties')
- privilege.appendChild(write_properties)
- if 'delete' in privileges:
- unbind = doc.createElement('D:unbind')
- privilege.appendChild(unbind)
- return privilege
-
-TrytonDAVInterface.PROPS['DAV:'] = tuple(list(TrytonDAVInterface.PROPS['DAV:']
- ) + ['current-user-privilege-set'])
-
-
-class WebDAVAuthRequestHandler(WebDAVServer.DAVRequestHandler):
-
- def finish(self):
- WebDAVServer.DAVRequestHandler.finish(self)
-
- global CACHE
- CACHE = LocalDict()
- if not Transaction().cursor:
- return
- dbname = Transaction().cursor.database_name
- Transaction().stop()
- if dbname:
- with Transaction().start(dbname, 0):
- Cache.resets(dbname)
-
- def parse_request(self):
- if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
- return False
-
- authorization = self.headers.get('Authorization', '')
- if authorization:
- scheme, credentials = authorization.split()
- if scheme != 'Basic':
- self.send_error(501)
- return False
- credentials = base64.decodestring(credentials)
- user, password = credentials.split(':', 2)
- if not self.get_userinfo(user, password, self.command):
- self.send_autherror(401, "Authorization Required")
- return False
- else:
- if not self.get_userinfo(None, None, self.command):
- self.send_autherror(401, "Authorization Required")
- return False
- return True
-
- def get_userinfo(self, user, password, command=''):
- path = urlparse.urlparse(self.path).path
- dbname = urllib.unquote_plus(path.split('/', 2)[1])
- database = backend.get('Database')().connect()
- cursor = database.cursor()
- databases = database.list(cursor)
- cursor.close()
- if not dbname or dbname not in databases:
- return True
- if user:
- user = int(login(dbname, user, password, cache=False))
- if not user:
- return None
- else:
- url = urlparse.urlparse(self.path)
- query = urlparse.parse_qs(url.query)
- path = url.path[len(dbname) + 2:]
- if 'key' in query:
- key, = query['key']
- with Transaction().start(dbname, 0) as transaction:
- database_list = Pool.database_list()
- pool = Pool(dbname)
- if dbname not in database_list:
- pool.init()
- Share = pool.get('webdav.share')
- user = Share.get_login(key, command, path)
- transaction.cursor.commit()
- if not user:
- return None
-
- Transaction().start(dbname, user, context={
- '_check_access': True,
- }, autocommit=True)
- Cache.clean(dbname)
- return user
-
-
-class SecureWebDAVAuthRequestHandler(WebDAVAuthRequestHandler):
-
- def setup(self):
- self.request = SSLSocket(self.request)
- WebDAVAuthRequestHandler.setup(self)
diff --git a/trytond/protocols/wrappers.py b/trytond/protocols/wrappers.py
new file mode 100644
index 0000000..bf53950
--- /dev/null
+++ b/trytond/protocols/wrappers.py
@@ -0,0 +1,87 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import base64
+import gzip
+from io import BytesIO
+
+from werkzeug.wrappers import Request as _Request
+from werkzeug.utils import cached_property
+from werkzeug.http import wsgi_to_bytes, bytes_to_wsgi
+from werkzeug.datastructures import Authorization
+from werkzeug.exceptions import abort
+
+from trytond import security
+
+
+class Request(_Request):
+
+ view_args = None
+
+ @property
+ def decoded_data(self):
+ if self.content_encoding == 'gzip':
+ zipfile = gzip.GzipFile(fileobj=BytesIO(self.data), mode='rb')
+ return zipfile.read()
+ else:
+ return self.data
+
+ @property
+ def parsed_data(self):
+ return self.data
+
+ @property
+ def method(self):
+ return
+
+ @property
+ def params(self):
+ return
+
+ @cached_property
+ def authorization(self):
+ authorization = super(Request, self).authorization
+ if authorization is None:
+ header = self.environ.get('HTTP_AUTHORIZATION')
+ return parse_authorization_header(header)
+ return authorization
+
+ @cached_property
+ def user_id(self):
+ database_name = self.view_args['database_name']
+ auth = self.authorization
+ if not auth:
+ abort(401)
+ if auth.type == 'session':
+ user_id = security.check(
+ database_name, auth.get('userid'), auth.get('session'))
+ if not user_id:
+ abort(403)
+ else:
+ user_id = security.login(
+ database_name, auth.username, auth.password, cache=False)
+ if not user_id:
+ abort(401)
+ return user_id
+
+
+def parse_authorization_header(value):
+ if not value:
+ return
+ value = wsgi_to_bytes(value)
+ try:
+ auth_type, auth_info = value.split(None, 1)
+ auth_type = auth_type.lower()
+ except ValueError:
+ return
+ if auth_type == b'session':
+ try:
+ username, userid, session = base64.b64decode(auth_info).split(
+ b':', 3)
+ userid = int(userid)
+ except Exception:
+ return
+ return Authorization('session', {
+ 'username': bytes_to_wsgi(username),
+ 'userid': userid,
+ 'session': bytes_to_wsgi(session),
+ })
diff --git a/trytond/protocols/xmlrpc.py b/trytond/protocols/xmlrpc.py
index 9febacf..502f226 100644
--- a/trytond/protocols/xmlrpc.py
+++ b/trytond/protocols/xmlrpc.py
@@ -1,23 +1,19 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-from trytond.protocols.sslsocket import SSLSocket
-from trytond.protocols.dispatcher import dispatch
-from trytond.protocols.common import daemon, RegisterHandlerMixin
-from trytond.exceptions import UserError, UserWarning, NotLogged, \
- ConcurrencyException
-from trytond import security
-import SimpleXMLRPCServer
-import SocketServer
-import xmlrpclib
-import socket
-import base64
+import xmlrpclib as client
import datetime
-from types import DictType
import logging
# convert decimal to float before marshalling:
from decimal import Decimal
+from werkzeug.wrappers import Response
+from werkzeug.utils import cached_property
+from werkzeug.exceptions import BadRequest
+
+from trytond.protocols.wrappers import Request
+from trytond.exceptions import TrytonException
+
logger = logging.getLogger(__name__)
@@ -30,7 +26,7 @@ def dump_decimal(self, value, write):
def dump_bytes(self, value, write):
self.write = write
- value = xmlrpclib.Binary(value)
+ value = client.Binary(value)
value.encode(self)
del self.write
@@ -60,18 +56,17 @@ def dump_timedelta(self, value, write):
}
self.dump_struct(value, write)
-xmlrpclib.Marshaller.dispatch[Decimal] = dump_decimal
-xmlrpclib.Marshaller.dispatch[type(None)] = \
+client.Marshaller.dispatch[Decimal] = dump_decimal
+client.Marshaller.dispatch[type(None)] = \
lambda self, value, write: write("<value><nil/></value>")
-xmlrpclib.Marshaller.dispatch[datetime.date] = dump_date
-xmlrpclib.Marshaller.dispatch[datetime.time] = dump_time
-xmlrpclib.Marshaller.dispatch[datetime.timedelta] = dump_timedelta
-if bytes != str:
- xmlrpclib.Marshaller.dispatch[bytes] = dump_bytes
-xmlrpclib.Marshaller.dispatch[bytearray] = dump_bytes
+client.Marshaller.dispatch[datetime.date] = dump_date
+client.Marshaller.dispatch[datetime.time] = dump_time
+client.Marshaller.dispatch[datetime.timedelta] = dump_timedelta
+if bytes == str:
+ client.Marshaller.dispatch[bytearray] = dump_bytes
-def dump_struct(self, value, write, escape=xmlrpclib.escape):
+def dump_struct(self, value, write, escape=client.escape):
converted_value = {}
for k, v in value.items():
if type(k) in (int, long):
@@ -81,7 +76,7 @@ def dump_struct(self, value, write, escape=xmlrpclib.escape):
converted_value[k] = v
return self.dump_struct(converted_value, write, escape=escape)
-xmlrpclib.Marshaller.dispatch[DictType] = dump_struct
+client.Marshaller.dispatch[dict] = dump_struct
class XMLRPCDecoder(object):
@@ -114,159 +109,70 @@ def end_struct(self, data):
dct = {}
items = self._stack[mark:]
for i in range(0, len(items), 2):
- dct[xmlrpclib._stringify(items[i])] = items[i + 1]
+ dct[items[i]] = items[i + 1]
dct = XMLRPCDecoder()(dct)
self._stack[mark:] = [dct]
self._value = 0
-xmlrpclib.Unmarshaller.dispatch['struct'] = end_struct
+client.Unmarshaller.dispatch['struct'] = end_struct
def _end_dateTime(self, data):
- value = xmlrpclib.DateTime()
+ value = client.DateTime()
value.decode(data)
- value = xmlrpclib._datetime_type(data)
+ value = client._datetime_type(data)
self.append(value)
-xmlrpclib.Unmarshaller.dispatch["dateTime.iso8601"] = _end_dateTime
+client.Unmarshaller.dispatch["dateTime.iso8601"] = _end_dateTime
def _end_base64(self, data):
- value = xmlrpclib.Binary()
- value.decode(data)
+ value = client.Binary()
+ value.decode(data.encode('ascii'))
cast = bytearray if bytes == str else bytes
self.append(cast(value.data))
self._value = 0
-xmlrpclib.Unmarshaller.dispatch['base64'] = _end_base64
+if bytes == str:
+ client.Unmarshaller.dispatch['base64'] = _end_base64
-class GenericXMLRPCRequestHandler:
+class XMLRequest(Request):
+ parsed_content_type = 'xml'
- def _dispatch(self, method, params):
- host, port = self.client_address[:2]
- database_name = self.path[1:]
- user = self.tryton['user']
- session = self.tryton['session']
- exception_message = 'Exception calling %s%s' % (method, params)
- try:
+ @cached_property
+ def parsed_data(self):
+ if self.parsed_content_type in self.environ.get('CONTENT_TYPE', ''):
try:
- method_list = method.split('.')
- object_type = method_list[0]
- object_name = '.'.join(method_list[1:-1])
- method = method_list[-1]
- if object_type == 'system' and method == 'getCapabilities':
- return {
- 'introspect': {
- 'specUrl': ('http://xmlrpc-c.sourceforge.net/'
- 'xmlrpc-c/introspection.html'),
- 'specVersion': 1,
- },
- }
- return dispatch(host, port, 'XML-RPC', database_name, user,
- session, object_type, object_name, method, *params)
- except (NotLogged, ConcurrencyException), exception:
- logger.debug(exception_message, exc_info=True)
- raise xmlrpclib.Fault(exception.code, str(exception))
- except (UserError, UserWarning), exception:
- logger.debug(exception_message, exc_info=True)
- error, description = exception.args
- raise xmlrpclib.Fault(exception.code, str(exception))
- except Exception, exception:
- logger.error(exception_message, exc_info=True)
- raise xmlrpclib.Fault(255, str(exception))
- finally:
- security.logout(database_name, user, session)
-
-
-class SimpleXMLRPCRequestHandler(RegisterHandlerMixin,
- GenericXMLRPCRequestHandler,
- SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
- protocol_version = "HTTP/1.1"
- rpc_paths = None
- encode_threshold = 1400 # common MTU
-
- def parse_request(self):
- res = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.parse_request(self)
- if not res:
- return res
- database_name = self.path[1:]
- if not database_name:
- self.tryton = {'user': None, 'session': None}
- return res
- try:
- method, up64 = self.headers['Authorization'].split(None, 1)
- if method.strip().lower() == 'basic':
- user, password = base64.decodestring(up64).split(':', 1)
- user_id, session = security.login(database_name, user,
- password)
- self.tryton = {'user': user_id, 'session': session}
- return res
- except Exception:
- pass
- self.send_error(401, 'Unauthorized')
- self.send_header("WWW-Authenticate", 'Basic realm="Tryton"')
- return False
-
-
-class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
-
- def setup(self):
- self.request = SSLSocket(self.request)
- SimpleXMLRPCRequestHandler.setup(self)
-
-
-class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn,
- SimpleXMLRPCServer.SimpleXMLRPCServer):
- timeout = 1
- daemon_threads = True
-
- def __init__(self, server_address, HandlerClass, logRequests=1):
- self.handlers = set()
- SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, server_address,
- HandlerClass, logRequests)
-
- def server_bind(self):
- self.socket.setsockopt(socket.SOL_SOCKET,
- socket.SO_REUSEADDR, 1)
- self.socket.setsockopt(socket.SOL_SOCKET,
- socket.SO_KEEPALIVE, 1)
- SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self)
-
- def server_close(self):
- SimpleXMLRPCServer.SimpleXMLRPCServer.server_close(self)
- for handler in self.handlers.copy():
- self.shutdown_request(handler.request)
-
-
-class SimpleThreadedXMLRPCServer6(SimpleThreadedXMLRPCServer):
- address_family = socket.AF_INET6
-
-
-class SecureThreadedXMLRPCServer(SimpleThreadedXMLRPCServer):
-
- def __init__(self, server_address, HandlerClass, logRequests=1):
- SimpleThreadedXMLRPCServer.__init__(self, server_address, HandlerClass,
- logRequests)
- self.socket = socket.socket(self.address_family, self.socket_type)
- self.server_bind()
- self.server_activate()
-
-
-class SecureThreadedXMLRPCServer6(SecureThreadedXMLRPCServer):
- address_family = socket.AF_INET6
-
-
-class XMLRPCDaemon(daemon):
-
- def __init__(self, interface, port, secure=False):
- daemon.__init__(self, interface, port, secure, name='XMLRPCDaemon')
- if self.secure:
- handler_class = SecureXMLRPCRequestHandler
- server_class = SecureThreadedXMLRPCServer
- if self.ipv6:
- server_class = SecureThreadedXMLRPCServer6
+ # TODO replace by own loads
+ return client.loads(self.decoded_data)
+ except Exception:
+ raise BadRequest('Unable to read XMl request')
+ else:
+ raise BadRequest('Not an XML request')
+
+ @property
+ def method(self):
+ return self.parsed_data[1]
+
+ @property
+ def params(self):
+ return self.parsed_data[0]
+
+
+class XMLProtocol:
+ content_type = 'xml'
+
+ @classmethod
+ def request(cls, environ):
+ return XMLRequest(environ)
+
+ @classmethod
+ def response(cls, data, request):
+ if isinstance(data, TrytonException):
+ data = client.Fault(data.code, str(data))
+ elif isinstance(data, Exception):
+ data = client.Fault(255, str(data))
else:
- handler_class = SimpleXMLRPCRequestHandler
- server_class = SimpleThreadedXMLRPCServer
- if self.ipv6:
- server_class = SimpleThreadedXMLRPCServer6
- self.server = server_class((interface, port), handler_class, 0)
+ data = (data,)
+ return Response(client.dumps(
+ data, methodresponse=True, allow_none=True),
+ content_type='text/xml')
diff --git a/trytond/report/report.py b/trytond/report/report.py
index 078b1ea..4ace05e 100644
--- a/trytond/report/report.py
+++ b/trytond/report/report.py
@@ -25,6 +25,10 @@ MIMETYPES = {
'odp': 'application/vnd.oasis.opendocument.presentation',
'ods': 'application/vnd.oasis.opendocument.spreadsheet',
'odg': 'application/vnd.oasis.opendocument.graphics',
+ 'plain': 'text/plain',
+ 'xml': 'text/xml',
+ 'html': 'text/html',
+ 'xhtml': 'text/xhtml',
}
FORMAT2EXT = {
'doc6': 'doc',
@@ -70,7 +74,7 @@ class TranslateFactory:
self.cache[self.language] = {}
translations = self.translation.search([
('lang', '=', self.language),
- ('type', '=', 'odt'),
+ ('type', '=', 'report'),
('name', '=', self.report_name),
('value', '!=', ''),
('value', '!=', None),
@@ -141,7 +145,8 @@ class Report(URLMixin, PoolBase):
report_context = cls.get_context(records, data)
oext, content = cls.convert(action_report,
cls.render(action_report, report_context))
- content = bytearray(content) if bytes == str else bytes(content)
+ if not isinstance(content, unicode):
+ content = bytearray(content) if bytes == str else bytes(content)
return (oext, content, action_report.direct_print, action_report.name)
@classmethod
diff --git a/trytond/res/ir.py b/trytond/res/ir.py
index ddebcab..754a622 100644
--- a/trytond/res/ir.py
+++ b/trytond/res/ir.py
@@ -2,7 +2,6 @@
# this repository contains the full copyright notices and license terms.
from ..model import ModelSQL, fields
from .. import backend
-from ..transaction import Transaction
from ..pool import Pool, PoolMeta
__all__ = [
@@ -11,7 +10,6 @@ __all__ = [
'SequenceTypeGroup', 'Sequence', 'SequenceStrict',
'ModuleConfigWizardItem',
]
-__metaclass__ = PoolMeta
class UIMenuGroup(ModelSQL):
@@ -25,13 +23,12 @@ class UIMenuGroup(ModelSQL):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
# Migration from 1.0 table name change
- TableHandler.table_rename(cursor, 'ir_ui_menu_group_rel', cls._table)
- TableHandler.sequence_rename(cursor, 'ir_ui_menu_group_rel_id_seq',
+ TableHandler.table_rename('ir_ui_menu_group_rel', cls._table)
+ TableHandler.sequence_rename('ir_ui_menu_group_rel_id_seq',
cls._table + '_id_seq')
# Migration from 2.0 menu_id and gid renamed into menu group
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.column_rename('menu_id', 'menu')
table.column_rename('gid', 'group')
super(UIMenuGroup, cls).__register__(module_name)
@@ -67,13 +64,12 @@ class ActionGroup(ModelSQL):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
# Migration from 1.0 table name change
- TableHandler.table_rename(cursor, 'ir_action_group_rel', cls._table)
- TableHandler.sequence_rename(cursor, 'ir_action_group_rel_id_seq',
+ TableHandler.table_rename('ir_action_group_rel', cls._table)
+ TableHandler.sequence_rename('ir_action_group_rel_id_seq',
cls._table + '_id_seq')
# Migration from 2.0 action_id and gid renamed into action and group
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.column_rename('action_id', 'action')
table.column_rename('gid', 'group')
super(ActionGroup, cls).__register__(module_name)
@@ -122,13 +118,11 @@ class ModelFieldGroup(ModelSQL):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
# Migration from 1.0 table name change
- TableHandler.table_rename(cursor, 'ir_model_field_group_rel',
- cls._table)
- TableHandler.sequence_rename(cursor, 'ir_model_field_group_rel_id_seq',
+ TableHandler.table_rename('ir_model_field_group_rel', cls._table)
+ TableHandler.sequence_rename('ir_model_field_group_rel_id_seq',
cls._table + '_id_seq')
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.6: field_id and group_id renamed to field and group
table.column_rename('field_id', 'field')
table.column_rename('group_id', 'group')
@@ -182,14 +176,13 @@ class RuleGroupGroup(ModelSQL):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
# Migration from 1.0 table name change
- TableHandler.table_rename(cursor, 'group_rule_group_rel', cls._table)
- TableHandler.sequence_rename(cursor, 'group_rule_group_rel_id_seq',
+ TableHandler.table_rename('group_rule_group_rel', cls._table)
+ TableHandler.sequence_rename('group_rule_group_rel_id_seq',
cls._table + '_id_seq')
# Migration from 2.0 rule_group_id and group_id renamed into rule_group
# and group
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.column_rename('rule_group_id', 'rule_group')
table.column_rename('group_id', 'group')
super(RuleGroupGroup, cls).__register__(module_name)
@@ -206,20 +199,20 @@ class RuleGroupUser(ModelSQL):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
# Migration from 1.0 table name change
- TableHandler.table_rename(cursor, 'user_rule_group_rel', cls._table)
- TableHandler.sequence_rename(cursor, 'user_rule_group_rel_id_seq',
+ TableHandler.table_rename('user_rule_group_rel', cls._table)
+ TableHandler.sequence_rename('user_rule_group_rel_id_seq',
cls._table + '_id_seq')
# Migration from 2.0 rule_group_id and user_id renamed into rule_group
# and user
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.column_rename('rule_group_id', 'rule_group')
table.column_rename('user_id', 'user')
super(RuleGroupUser, cls).__register__(module_name)
class Lang:
+ __metaclass__ = PoolMeta
__name__ = 'ir.lang'
@classmethod
@@ -230,6 +223,7 @@ class Lang:
class SequenceType:
+ __metaclass__ = PoolMeta
__name__ = 'ir.sequence.type'
groups = fields.Many2Many('ir.sequence.type-res.group', 'sequence_type',
'group', 'User Groups',
@@ -268,6 +262,7 @@ class SequenceTypeGroup(ModelSQL):
class Sequence:
+ __metaclass__ = PoolMeta
__name__ = 'ir.sequence'
groups = fields.Function(fields.Many2Many('res.group', None, None,
'User Groups'), 'get_groups', searcher='search_groups')
@@ -306,6 +301,7 @@ class SequenceStrict(Sequence):
class ModuleConfigWizardItem:
+ __metaclass__ = PoolMeta
__name__ = 'ir.module.config_wizard.item'
@classmethod
diff --git a/trytond/res/locale/es_EC.po b/trytond/res/locale/es_EC.po
index fca7257..df859ea 100644
--- a/trytond/res/locale/es_EC.po
+++ b/trytond/res/locale/es_EC.po
@@ -328,7 +328,7 @@ msgstr "Creado por usuario"
msgctxt "field:res.user,email:"
msgid "Email"
-msgstr "Correo electrónico"
+msgstr "Email"
msgctxt "field:res.user,groups:"
msgid "Groups"
diff --git a/trytond/res/locale/es_MX.po b/trytond/res/locale/es_MX.po
index 1e3328e..29c1306 100644
--- a/trytond/res/locale/es_MX.po
+++ b/trytond/res/locale/es_MX.po
@@ -640,13 +640,15 @@ msgctxt "view:res.group:"
msgid "Access Permissions"
msgstr ""
+#, fuzzy
msgctxt "view:res.group:"
msgid "Group"
-msgstr ""
+msgstr "Grupo"
+#, fuzzy
msgctxt "view:res.group:"
msgid "Groups"
-msgstr ""
+msgstr "Grupos"
msgctxt "view:res.group:"
msgid "Members"
@@ -664,21 +666,24 @@ msgctxt "view:res.user.config.start:"
msgid "You can now add some users into the system."
msgstr ""
+#, fuzzy
msgctxt "view:res.user.warning:"
msgid "Warning"
-msgstr ""
+msgstr "Avisos"
+#, fuzzy
msgctxt "view:res.user.warning:"
msgid "Warnings"
-msgstr ""
+msgstr "Avisos"
msgctxt "view:res.user:"
msgid "Access Permissions"
msgstr ""
+#, fuzzy
msgctxt "view:res.user:"
msgid "Actions"
-msgstr ""
+msgstr "Acciones"
msgctxt "view:res.user:"
msgid "Group Membership"
@@ -688,13 +693,15 @@ msgctxt "view:res.user:"
msgid "Preferences"
msgstr ""
+#, fuzzy
msgctxt "view:res.user:"
msgid "User"
-msgstr ""
+msgstr "Usuario"
+#, fuzzy
msgctxt "view:res.user:"
msgid "Users"
-msgstr ""
+msgstr "Usuarios"
msgctxt "wizard_button:res.user.config,start,end:"
msgid "Cancel"
diff --git a/trytond/res/locale/hu_HU.po b/trytond/res/locale/hu_HU.po
index 8ee0ed4..6eaf59b 100644
--- a/trytond/res/locale/hu_HU.po
+++ b/trytond/res/locale/hu_HU.po
@@ -642,11 +642,11 @@ msgstr ""
msgctxt "view:res.group:"
msgid "Group"
-msgstr ""
+msgstr "Csoport"
msgctxt "view:res.group:"
msgid "Groups"
-msgstr ""
+msgstr "Csoportok"
msgctxt "view:res.group:"
msgid "Members"
@@ -664,13 +664,14 @@ msgctxt "view:res.user.config.start:"
msgid "You can now add some users into the system."
msgstr ""
+#, fuzzy
msgctxt "view:res.user.warning:"
msgid "Warning"
-msgstr ""
+msgstr "Figyelmeztetések"
msgctxt "view:res.user.warning:"
msgid "Warnings"
-msgstr ""
+msgstr "Figyelmeztetések"
msgctxt "view:res.user:"
msgid "Access Permissions"
@@ -678,7 +679,7 @@ msgstr ""
msgctxt "view:res.user:"
msgid "Actions"
-msgstr ""
+msgstr "Műveletek"
msgctxt "view:res.user:"
msgid "Group Membership"
@@ -690,11 +691,11 @@ msgstr ""
msgctxt "view:res.user:"
msgid "User"
-msgstr ""
+msgstr "Felhasználó"
msgctxt "view:res.user:"
msgid "Users"
-msgstr ""
+msgstr "Felhasználók"
msgctxt "wizard_button:res.user.config,start,end:"
msgid "Cancel"
diff --git a/trytond/res/locale/nl_NL.po b/trytond/res/locale/lo_LA.po
similarity index 81%
copy from trytond/res/locale/nl_NL.po
copy to trytond/res/locale/lo_LA.po
index 8e5a2b2..d2cc6c0 100644
--- a/trytond/res/locale/nl_NL.po
+++ b/trytond/res/locale/lo_LA.po
@@ -20,10 +20,9 @@ msgctxt "error:res.user:"
msgid "You can not have two users with the same login!"
msgstr ""
-#, fuzzy
msgctxt "field:ir.action-res.group,action:"
msgid "Action"
-msgstr "Actie"
+msgstr ""
msgctxt "field:ir.action-res.group,create_date:"
msgid "Create Date"
@@ -36,29 +35,31 @@ msgstr ""
#, fuzzy
msgctxt "field:ir.action-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "ກຸ່ມ"
+#, fuzzy
msgctxt "field:ir.action-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
#, fuzzy
msgctxt "field:ir.action-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
+#, fuzzy
msgctxt "field:ir.action-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:ir.action-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
-#, fuzzy
msgctxt "field:ir.model.button-res.group,active:"
msgid "Active"
-msgstr "Actief"
+msgstr ""
msgctxt "field:ir.model.button-res.group,button:"
msgid "Button"
@@ -75,24 +76,27 @@ msgstr ""
#, fuzzy
msgctxt "field:ir.model.button-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "ກຸ່ມ"
+#, fuzzy
msgctxt "field:ir.model.button-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
#, fuzzy
msgctxt "field:ir.model.button-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
+#, fuzzy
msgctxt "field:ir.model.button-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:ir.model.button-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "field:ir.model.field-res.group,create_date:"
msgid "Create Date"
@@ -109,24 +113,27 @@ msgstr ""
#, fuzzy
msgctxt "field:ir.model.field-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "ກຸ່ມ"
+#, fuzzy
msgctxt "field:ir.model.field-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
#, fuzzy
msgctxt "field:ir.model.field-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
+#, fuzzy
msgctxt "field:ir.model.field-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:ir.model.field-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "field:ir.rule.group-res.group,create_date:"
msgid "Create Date"
@@ -136,19 +143,17 @@ msgctxt "field:ir.rule.group-res.group,create_uid:"
msgid "Create User"
msgstr ""
-#, fuzzy
msgctxt "field:ir.rule.group-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "ກຸ່ມ"
msgctxt "field:ir.rule.group-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
-#, fuzzy
msgctxt "field:ir.rule.group-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
msgctxt "field:ir.rule.group-res.group,rule_group:"
msgid "Rule Group"
@@ -156,11 +161,11 @@ msgstr ""
msgctxt "field:ir.rule.group-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
msgctxt "field:ir.rule.group-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "field:ir.rule.group-res.user,create_date:"
msgid "Create Date"
@@ -170,43 +175,43 @@ msgctxt "field:ir.rule.group-res.user,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:ir.rule.group-res.user,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
-#, fuzzy
msgctxt "field:ir.rule.group-res.user,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
msgctxt "field:ir.rule.group-res.user,rule_group:"
msgid "Rule Group"
msgstr ""
-#, fuzzy
msgctxt "field:ir.rule.group-res.user,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "ຜູ້ໃຊ້"
msgctxt "field:ir.rule.group-res.user,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
msgctxt "field:ir.rule.group-res.user,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "field:ir.sequence,groups:"
msgid "User Groups"
-msgstr ""
+msgstr "ກຸ່ມຜູ້ໃຊ້ງານ"
msgctxt "field:ir.sequence.strict,groups:"
msgid "User Groups"
-msgstr ""
+msgstr "ກຸ່ມຜູ້ໃຊ້ງານ"
+#, fuzzy
msgctxt "field:ir.sequence.type,groups:"
msgid "User Groups"
-msgstr ""
+msgstr "ກຸ່ມຜູ້ໃຊ້ງານ"
msgctxt "field:ir.sequence.type-res.group,create_date:"
msgid "Create Date"
@@ -218,29 +223,28 @@ msgstr ""
msgctxt "field:ir.sequence.type-res.group,group:"
msgid "User Groups"
-msgstr ""
+msgstr "ກຸ່ມຜູ້ໃຊ້ງານ"
msgctxt "field:ir.sequence.type-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
#, fuzzy
msgctxt "field:ir.sequence.type-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
-#, fuzzy
msgctxt "field:ir.sequence.type-res.group,sequence_type:"
msgid "Sequence Type"
-msgstr "Reeks type"
+msgstr ""
msgctxt "field:ir.sequence.type-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
msgctxt "field:ir.sequence.type-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "field:ir.ui.menu-res.group,create_date:"
msgid "Create Date"
@@ -253,29 +257,31 @@ msgstr ""
#, fuzzy
msgctxt "field:ir.ui.menu-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "ກຸ່ມ"
+#, fuzzy
msgctxt "field:ir.ui.menu-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
-#, fuzzy
msgctxt "field:ir.ui.menu-res.group,menu:"
msgid "Menu"
-msgstr "Menu"
+msgstr ""
#, fuzzy
msgctxt "field:ir.ui.menu-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
+#, fuzzy
msgctxt "field:ir.ui.menu-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:ir.ui.menu-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "field:res.group,create_date:"
msgid "Create Date"
@@ -289,9 +295,10 @@ msgctxt "field:res.group,field_access:"
msgid "Access Field"
msgstr ""
+#, fuzzy
msgctxt "field:res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
msgctxt "field:res.group,menu_access:"
msgid "Access Menu"
@@ -304,12 +311,12 @@ msgstr ""
#, fuzzy
msgctxt "field:res.group,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
#, fuzzy
msgctxt "field:res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
msgctxt "field:res.group,rule_groups:"
msgid "Rules"
@@ -318,25 +325,25 @@ msgstr ""
#, fuzzy
msgctxt "field:res.group,users:"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "ຜູ້ໃຊ້"
+#, fuzzy
msgctxt "field:res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
-#, fuzzy
msgctxt "field:res.user,actions:"
msgid "Actions"
-msgstr "Acties"
+msgstr ""
-#, fuzzy
msgctxt "field:res.user,active:"
msgid "Active"
-msgstr "Actief"
+msgstr ""
msgctxt "field:res.user,create_date:"
msgid "Create Date"
@@ -346,24 +353,23 @@ msgctxt "field:res.user,create_uid:"
msgid "Create User"
msgstr ""
-#, fuzzy
msgctxt "field:res.user,email:"
msgid "Email"
-msgstr "E-mail"
+msgstr ""
#, fuzzy
msgctxt "field:res.user,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "ໝວດ"
+#, fuzzy
msgctxt "field:res.user,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
-#, fuzzy
msgctxt "field:res.user,language:"
msgid "Language"
-msgstr "Taal"
+msgstr ""
msgctxt "field:res.user,language_direction:"
msgid "Language Direction"
@@ -380,7 +386,7 @@ msgstr ""
#, fuzzy
msgctxt "field:res.user,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
msgctxt "field:res.user,password:"
msgid "Password"
@@ -397,7 +403,7 @@ msgstr ""
#, fuzzy
msgctxt "field:res.user,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
msgctxt "field:res.user,rule_groups:"
msgid "Rules"
@@ -419,18 +425,19 @@ msgctxt "field:res.user,warnings:"
msgid "Warnings"
msgstr ""
+#, fuzzy
msgctxt "field:res.user,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:res.user,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
-#, fuzzy
msgctxt "field:res.user-ir.action,action:"
msgid "Action"
-msgstr "Actie"
+msgstr ""
msgctxt "field:res.user-ir.action,create_date:"
msgid "Create Date"
@@ -440,27 +447,30 @@ msgctxt "field:res.user-ir.action,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:res.user-ir.action,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
#, fuzzy
msgctxt "field:res.user-ir.action,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
#, fuzzy
msgctxt "field:res.user-ir.action,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "ຜູ້ໃຊ້"
+#, fuzzy
msgctxt "field:res.user-ir.action,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:res.user-ir.action,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "field:res.user-res.group,create_date:"
msgid "Create Date"
@@ -473,33 +483,37 @@ msgstr ""
#, fuzzy
msgctxt "field:res.user-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "ກຸ່ມ"
+#, fuzzy
msgctxt "field:res.user-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
#, fuzzy
msgctxt "field:res.user-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
#, fuzzy
msgctxt "field:res.user-res.group,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "ຜູ້ໃຊ້"
+#, fuzzy
msgctxt "field:res.user-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:res.user-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
+#, fuzzy
msgctxt "field:res.user.config.start,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
msgctxt "field:res.user.login.attempt,create_date:"
msgid "Create Date"
@@ -509,9 +523,10 @@ msgctxt "field:res.user.login.attempt,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:res.user.login.attempt,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
msgctxt "field:res.user.login.attempt,login:"
msgid "Login"
@@ -520,15 +535,17 @@ msgstr ""
#, fuzzy
msgctxt "field:res.user.login.attempt,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
+#, fuzzy
msgctxt "field:res.user.login.attempt,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:res.user.login.attempt,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "field:res.user.warning,always:"
msgid "Always"
@@ -542,32 +559,35 @@ msgctxt "field:res.user.warning,create_uid:"
msgid "Create User"
msgstr ""
+#, fuzzy
msgctxt "field:res.user.warning,id:"
msgid "ID"
-msgstr ""
+msgstr "ເລກປະຈຳໂຕ"
#, fuzzy
msgctxt "field:res.user.warning,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
#, fuzzy
msgctxt "field:res.user.warning,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "ຊື່"
#, fuzzy
msgctxt "field:res.user.warning,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "ຜູ້ໃຊ້"
+#, fuzzy
msgctxt "field:res.user.warning,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "ຂຽນວັນທີ"
+#, fuzzy
msgctxt "field:res.user.warning,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "ຂຽນຜູ້ໃຊ້ງານ"
msgctxt "help:ir.sequence.type,groups:"
msgid "Groups allowed to edit the sequences of this type"
@@ -580,7 +600,7 @@ msgstr ""
#, fuzzy
msgctxt "model:ir.action,name:act_group_form"
msgid "Groups"
-msgstr "Groepen"
+msgstr "ໝວດ"
msgctxt "model:ir.action,name:act_user_config"
msgid "Configure Users"
@@ -589,7 +609,7 @@ msgstr ""
#, fuzzy
msgctxt "model:ir.action,name:act_user_form"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "ຜູ້ໃຊ້"
msgctxt "model:ir.action-res.group,name:"
msgid "Action - Group"
@@ -622,17 +642,17 @@ msgstr ""
#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_group_form"
msgid "Groups"
-msgstr "Groepen"
+msgstr "ໝວດ"
#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_res"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "ຜູ້ໃຊ້"
#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_user_form"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "ຜູ້ໃຊ້"
msgctxt "model:ir.ui.menu-res.group,name:"
msgid "UI Menu - Group"
@@ -641,21 +661,21 @@ msgstr ""
#, fuzzy
msgctxt "model:res.group,name:"
msgid "Group"
-msgstr "Groep"
+msgstr "ກຸ່ມ"
#, fuzzy
msgctxt "model:res.group,name:group_admin"
msgid "Administration"
-msgstr "Systeembeheer"
+msgstr "ຜູ້ບໍລິຫານ"
#, fuzzy
msgctxt "model:res.user,name:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "ຜູ້ໃຊ້"
msgctxt "model:res.user,name:user_admin"
msgid "Administrator"
-msgstr ""
+msgstr "ຜູ້ບໍລິຫານ"
msgctxt "model:res.user,name:user_trigger"
msgid "Cron Trigger"
@@ -665,9 +685,10 @@ msgctxt "model:res.user-ir.action,name:"
msgid "User - Action"
msgstr ""
+#, fuzzy
msgctxt "model:res.user-res.group,name:"
msgid "User - Group"
-msgstr ""
+msgstr "ກຸ່ມຜູ້ໃຊ້ງານ"
msgctxt "model:res.user.config.start,name:"
msgid "User Config Init"
@@ -688,12 +709,11 @@ msgstr ""
#, fuzzy
msgctxt "view:res.group:"
msgid "Group"
-msgstr "Groep"
+msgstr "ກຸ່ມ"
-#, fuzzy
msgctxt "view:res.group:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "ໝວດ"
msgctxt "view:res.group:"
msgid "Members"
@@ -711,10 +731,9 @@ msgctxt "view:res.user.config.start:"
msgid "You can now add some users into the system."
msgstr ""
-#, fuzzy
msgctxt "view:res.user.warning:"
msgid "Warning"
-msgstr "Waarschuwing"
+msgstr ""
msgctxt "view:res.user.warning:"
msgid "Warnings"
@@ -724,10 +743,9 @@ msgctxt "view:res.user:"
msgid "Access Permissions"
msgstr ""
-#, fuzzy
msgctxt "view:res.user:"
msgid "Actions"
-msgstr "Acties"
+msgstr ""
msgctxt "view:res.user:"
msgid "Group Membership"
@@ -737,31 +755,27 @@ msgctxt "view:res.user:"
msgid "Preferences"
msgstr ""
-#, fuzzy
msgctxt "view:res.user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "ຜູ້ໃຊ້"
#, fuzzy
msgctxt "view:res.user:"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "ຜູ້ໃຊ້"
-#, fuzzy
msgctxt "wizard_button:res.user.config,start,end:"
msgid "Cancel"
-msgstr "Annuleren"
+msgstr "ຍົກເລີກ"
-#, fuzzy
msgctxt "wizard_button:res.user.config,start,user:"
msgid "OK"
-msgstr "Oké"
+msgstr ""
-#, fuzzy
msgctxt "wizard_button:res.user.config,user,add:"
msgid "Add"
-msgstr "Toevoegen"
+msgstr ""
msgctxt "wizard_button:res.user.config,user,end:"
msgid "End"
-msgstr ""
+msgstr "ສິ້ນສຸດ"
diff --git a/trytond/res/locale/nl_NL.po b/trytond/res/locale/nl_NL.po
index 8e5a2b2..75fbc7f 100644
--- a/trytond/res/locale/nl_NL.po
+++ b/trytond/res/locale/nl_NL.po
@@ -415,9 +415,10 @@ msgctxt "field:res.user,status_bar:"
msgid "Status Bar"
msgstr ""
+#, fuzzy
msgctxt "field:res.user,warnings:"
msgid "Warnings"
-msgstr ""
+msgstr "Waarschuwing"
msgctxt "field:res.user,write_date:"
msgid "Write Date"
@@ -653,9 +654,10 @@ msgctxt "model:res.user,name:"
msgid "User"
msgstr "Gebruiker"
+#, fuzzy
msgctxt "model:res.user,name:user_admin"
msgid "Administrator"
-msgstr ""
+msgstr "Systeembeheer"
msgctxt "model:res.user,name:user_trigger"
msgid "Cron Trigger"
@@ -716,9 +718,10 @@ msgctxt "view:res.user.warning:"
msgid "Warning"
msgstr "Waarschuwing"
+#, fuzzy
msgctxt "view:res.user.warning:"
msgid "Warnings"
-msgstr ""
+msgstr "Waarschuwing"
msgctxt "view:res.user:"
msgid "Access Permissions"
diff --git a/trytond/res/locale/nl_NL.po b/trytond/res/locale/zh_CN.po
similarity index 75%
copy from trytond/res/locale/nl_NL.po
copy to trytond/res/locale/zh_CN.po
index 8e5a2b2..d3b16f8 100644
--- a/trytond/res/locale/nl_NL.po
+++ b/trytond/res/locale/zh_CN.po
@@ -4,764 +4,710 @@ msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "error:res.group:"
msgid "The name of the group must be unique!"
-msgstr ""
+msgstr "用户组标识必须唯一!"
msgctxt "error:res.user:"
msgid ""
"Users can not be deleted for logging purpose.\n"
"Instead you must inactivate them."
-msgstr ""
+msgstr "禁止用户登录请封闭帐号而非删除用户."
msgctxt "error:res.user:"
msgid "Wrong password!"
-msgstr ""
+msgstr "密码错误!"
msgctxt "error:res.user:"
msgid "You can not have two users with the same login!"
-msgstr ""
+msgstr "用户的登录名不能相同!"
-#, fuzzy
msgctxt "field:ir.action-res.group,action:"
msgid "Action"
-msgstr "Actie"
+msgstr "操作"
msgctxt "field:ir.action-res.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.action-res.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.action-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:ir.action-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.action-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.action-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.action-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
-#, fuzzy
msgctxt "field:ir.model.button-res.group,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:ir.model.button-res.group,button:"
msgid "Button"
-msgstr ""
+msgstr "按钮"
msgctxt "field:ir.model.button-res.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.model.button-res.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.model.button-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:ir.model.button-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.model.button-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.model.button-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.model.button-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.model.field-res.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期:"
msgctxt "field:ir.model.field-res.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.model.field-res.group,field:"
msgid "Model Field"
-msgstr ""
+msgstr "模型数据"
-#, fuzzy
msgctxt "field:ir.model.field-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:ir.model.field-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.model.field-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.model.field-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.model.field-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.rule.group-res.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.rule.group-res.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.rule.group-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:ir.rule.group-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.rule.group-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.rule.group-res.group,rule_group:"
msgid "Rule Group"
-msgstr ""
+msgstr "规则组"
msgctxt "field:ir.rule.group-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.rule.group-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.rule.group-res.user,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.rule.group-res.user,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.rule.group-res.user,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.rule.group-res.user,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.rule.group-res.user,rule_group:"
msgid "Rule Group"
-msgstr ""
+msgstr "规则组"
-#, fuzzy
msgctxt "field:ir.rule.group-res.user,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "field:ir.rule.group-res.user,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.rule.group-res.user,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.sequence,groups:"
msgid "User Groups"
-msgstr ""
+msgstr "用户组"
msgctxt "field:ir.sequence.strict,groups:"
msgid "User Groups"
-msgstr ""
+msgstr "用户组"
msgctxt "field:ir.sequence.type,groups:"
msgid "User Groups"
-msgstr ""
+msgstr "用户组"
msgctxt "field:ir.sequence.type-res.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.sequence.type-res.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:ir.sequence.type-res.group,group:"
msgid "User Groups"
-msgstr ""
+msgstr "用户组"
msgctxt "field:ir.sequence.type-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.sequence.type-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:ir.sequence.type-res.group,sequence_type:"
msgid "Sequence Type"
-msgstr "Reeks type"
+msgstr "序列类型"
msgctxt "field:ir.sequence.type-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.sequence.type-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:ir.ui.menu-res.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:ir.ui.menu-res.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:ir.ui.menu-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:ir.ui.menu-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:ir.ui.menu-res.group,menu:"
msgid "Menu"
-msgstr "Menu"
+msgstr "菜单"
-#, fuzzy
msgctxt "field:ir.ui.menu-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:ir.ui.menu-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:ir.ui.menu-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:res.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:res.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:res.group,field_access:"
msgid "Access Field"
-msgstr ""
+msgstr "数据权限"
msgctxt "field:res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:res.group,menu_access:"
msgid "Access Menu"
-msgstr ""
+msgstr "菜单权限"
msgctxt "field:res.group,model_access:"
msgid "Access Model"
-msgstr ""
+msgstr "模型权限"
-#, fuzzy
msgctxt "field:res.group,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:res.group,rule_groups:"
msgid "Rules"
-msgstr ""
+msgstr "规则"
-#, fuzzy
msgctxt "field:res.group,users:"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "用户"
msgctxt "field:res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
-#, fuzzy
msgctxt "field:res.user,actions:"
msgid "Actions"
-msgstr "Acties"
+msgstr "操作"
-#, fuzzy
msgctxt "field:res.user,active:"
msgid "Active"
-msgstr "Actief"
+msgstr "启用"
msgctxt "field:res.user,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:res.user,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:res.user,email:"
msgid "Email"
-msgstr "E-mail"
+msgstr "电子邮件"
-#, fuzzy
msgctxt "field:res.user,groups:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
msgctxt "field:res.user,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:res.user,language:"
msgid "Language"
-msgstr "Taal"
+msgstr "语言"
msgctxt "field:res.user,language_direction:"
msgid "Language Direction"
-msgstr ""
+msgstr "语言方向"
msgctxt "field:res.user,login:"
msgid "Login"
-msgstr ""
+msgstr "登录"
msgctxt "field:res.user,menu:"
msgid "Menu Action"
-msgstr ""
+msgstr "菜单动作"
-#, fuzzy
msgctxt "field:res.user,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:res.user,password:"
msgid "Password"
-msgstr ""
+msgstr "密码"
msgctxt "field:res.user,password_hash:"
msgid "Password Hash"
-msgstr ""
+msgstr "密码HASH"
msgctxt "field:res.user,pyson_menu:"
msgid "PySON Menu"
-msgstr ""
+msgstr "PYSON 菜单"
-#, fuzzy
msgctxt "field:res.user,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:res.user,rule_groups:"
msgid "Rules"
-msgstr ""
+msgstr "规则"
msgctxt "field:res.user,sessions:"
msgid "Sessions"
-msgstr ""
+msgstr "进程"
msgctxt "field:res.user,signature:"
msgid "Signature"
-msgstr ""
+msgstr "印签"
msgctxt "field:res.user,status_bar:"
msgid "Status Bar"
-msgstr ""
+msgstr "状态烂"
msgctxt "field:res.user,warnings:"
msgid "Warnings"
-msgstr ""
+msgstr "警告"
msgctxt "field:res.user,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:res.user,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
-#, fuzzy
msgctxt "field:res.user-ir.action,action:"
msgid "Action"
-msgstr "Actie"
+msgstr "操作"
msgctxt "field:res.user-ir.action,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:res.user-ir.action,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:res.user-ir.action,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:res.user-ir.action,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:res.user-ir.action,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "field:res.user-ir.action,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:res.user-ir.action,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:res.user-res.group,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:res.user-res.group,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
-#, fuzzy
msgctxt "field:res.user-res.group,group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
msgctxt "field:res.user-res.group,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:res.user-res.group,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:res.user-res.group,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "field:res.user-res.group,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:res.user-res.group,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:res.user.config.start,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:res.user.login.attempt,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:res.user.login.attempt,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:res.user.login.attempt,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
msgctxt "field:res.user.login.attempt,login:"
msgid "Login"
-msgstr ""
+msgstr "登录"
-#, fuzzy
msgctxt "field:res.user.login.attempt,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
msgctxt "field:res.user.login.attempt,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:res.user.login.attempt,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "field:res.user.warning,always:"
msgid "Always"
-msgstr ""
+msgstr "总是"
msgctxt "field:res.user.warning,create_date:"
msgid "Create Date"
-msgstr ""
+msgstr "创建日期"
msgctxt "field:res.user.warning,create_uid:"
msgid "Create User"
-msgstr ""
+msgstr "创建者"
msgctxt "field:res.user.warning,id:"
msgid "ID"
-msgstr ""
+msgstr "标识"
-#, fuzzy
msgctxt "field:res.user.warning,name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:res.user.warning,rec_name:"
msgid "Name"
-msgstr "Naam bijlage"
+msgstr "名称"
-#, fuzzy
msgctxt "field:res.user.warning,user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "field:res.user.warning,write_date:"
msgid "Write Date"
-msgstr ""
+msgstr "写入日期"
msgctxt "field:res.user.warning,write_uid:"
msgid "Write User"
-msgstr ""
+msgstr "写入帐号"
msgctxt "help:ir.sequence.type,groups:"
msgid "Groups allowed to edit the sequences of this type"
-msgstr ""
+msgstr "允许编辑此类序列的用户组"
msgctxt "help:res.user,actions:"
msgid "Actions that will be run at login"
-msgstr ""
+msgstr "用户登录自动运行的操作"
-#, fuzzy
msgctxt "model:ir.action,name:act_group_form"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
msgctxt "model:ir.action,name:act_user_config"
msgid "Configure Users"
-msgstr ""
+msgstr "配置用户"
-#, fuzzy
msgctxt "model:ir.action,name:act_user_form"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "用户"
msgctxt "model:ir.action-res.group,name:"
msgid "Action - Group"
-msgstr ""
+msgstr "操作-用户组"
msgctxt "model:ir.cron,name:cron_trigger_time"
msgid "Run On Time Triggers"
-msgstr ""
+msgstr "时基触发器"
msgctxt "model:ir.model.button-res.group,name:"
msgid "Model Button - Group"
-msgstr ""
+msgstr "模型按钮-用户组"
msgctxt "model:ir.model.field-res.group,name:"
msgid "Model Field Group Rel"
-msgstr ""
+msgstr "模型数据组 参考"
msgctxt "model:ir.rule.group-res.group,name:"
msgid "Rule Group - Group"
-msgstr ""
+msgstr "规则组 - 用户组"
msgctxt "model:ir.rule.group-res.user,name:"
msgid "Rule Group - User"
-msgstr ""
+msgstr "规则组 - 用户"
msgctxt "model:ir.sequence.type-res.group,name:"
msgid "Sequence Type - Group"
-msgstr ""
+msgstr "序列类别 - 用户组"
-#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_group_form"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
-#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_res"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "用户"
-#, fuzzy
msgctxt "model:ir.ui.menu,name:menu_user_form"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "用户"
msgctxt "model:ir.ui.menu-res.group,name:"
msgid "UI Menu - Group"
-msgstr ""
+msgstr "菜单 - 用户组"
-#, fuzzy
msgctxt "model:res.group,name:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
-#, fuzzy
msgctxt "model:res.group,name:group_admin"
msgid "Administration"
-msgstr "Systeembeheer"
+msgstr "管理员"
-#, fuzzy
msgctxt "model:res.user,name:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "用户"
msgctxt "model:res.user,name:user_admin"
msgid "Administrator"
-msgstr ""
+msgstr "Administrator"
msgctxt "model:res.user,name:user_trigger"
msgid "Cron Trigger"
-msgstr ""
+msgstr "Cron 触发"
msgctxt "model:res.user-ir.action,name:"
msgid "User - Action"
-msgstr ""
+msgstr "用户 - 操作"
msgctxt "model:res.user-res.group,name:"
msgid "User - Group"
-msgstr ""
+msgstr "用户 - 用户组"
msgctxt "model:res.user.config.start,name:"
msgid "User Config Init"
-msgstr ""
+msgstr "用户init配置"
msgctxt "model:res.user.login.attempt,name:"
msgid "Login Attempt"
-msgstr ""
+msgstr "登录次数"
msgctxt "model:res.user.warning,name:"
msgid "User Warning"
-msgstr ""
+msgstr "用户警告"
msgctxt "view:res.group:"
msgid "Access Permissions"
-msgstr ""
+msgstr "操作权限"
-#, fuzzy
msgctxt "view:res.group:"
msgid "Group"
-msgstr "Groep"
+msgstr "用户组"
-#, fuzzy
msgctxt "view:res.group:"
msgid "Groups"
-msgstr "Groepen"
+msgstr "用户组"
msgctxt "view:res.group:"
msgid "Members"
-msgstr ""
+msgstr "成员"
msgctxt "view:res.user.config.start:"
msgid "Add Users"
-msgstr ""
+msgstr "添加用户"
msgctxt "view:res.user.config.start:"
msgid "Be careful that the login must be unique!"
-msgstr ""
+msgstr "用户组标识必须唯一!"
msgctxt "view:res.user.config.start:"
msgid "You can now add some users into the system."
-msgstr ""
+msgstr "现在可以给系统添加用户了."
-#, fuzzy
msgctxt "view:res.user.warning:"
msgid "Warning"
-msgstr "Waarschuwing"
+msgstr "警告"
msgctxt "view:res.user.warning:"
msgid "Warnings"
-msgstr ""
+msgstr "警告"
msgctxt "view:res.user:"
msgid "Access Permissions"
-msgstr ""
+msgstr "操作权限"
-#, fuzzy
msgctxt "view:res.user:"
msgid "Actions"
-msgstr "Acties"
+msgstr "动作"
msgctxt "view:res.user:"
msgid "Group Membership"
-msgstr ""
+msgstr "用户组"
msgctxt "view:res.user:"
msgid "Preferences"
-msgstr ""
+msgstr "偏好"
-#, fuzzy
msgctxt "view:res.user:"
msgid "User"
-msgstr "Gebruiker"
+msgstr "基本信息"
-#, fuzzy
msgctxt "view:res.user:"
msgid "Users"
-msgstr "Gebruikers"
+msgstr "用户"
-#, fuzzy
msgctxt "wizard_button:res.user.config,start,end:"
msgid "Cancel"
-msgstr "Annuleren"
+msgstr "取消"
-#, fuzzy
msgctxt "wizard_button:res.user.config,start,user:"
msgid "OK"
-msgstr "Oké"
+msgstr "确定"
-#, fuzzy
msgctxt "wizard_button:res.user.config,user,add:"
msgid "Add"
-msgstr "Toevoegen"
+msgstr "添加"
msgctxt "wizard_button:res.user.config,user,end:"
msgid "End"
-msgstr ""
+msgstr "完成"
diff --git a/trytond/res/user.py b/trytond/res/user.py
index 7fd6f60..64d500b 100644
--- a/trytond/res/user.py
+++ b/trytond/res/user.py
@@ -113,9 +113,9 @@ class User(ModelSQL, ModelView):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
super(User, cls).__register__(module_name)
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 1.6
@@ -244,7 +244,7 @@ class User(ModelSQL, ModelView):
args.extend((users, cls._convert_vals(values)))
super(User, cls).write(*args)
# Clean cursor cache as it could be filled by domain_get
- for cache in Transaction().cursor.cache.itervalues():
+ for cache in Transaction().cache.itervalues():
if cls.__name__ in cache:
for user in all_users:
if user.id in cache[cls.__name__]:
@@ -451,7 +451,7 @@ class User(ModelSQL, ModelView):
result = cls._get_login_cache.get(login)
if result:
return result
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
table = cls.__table__()
cursor.execute(*table.select(table.id, table.password_hash,
where=(table.login == login) & (table.active == True)))
@@ -495,20 +495,21 @@ class User(ModelSQL, ModelView):
@classmethod
def hash_sha1(cls, password):
- if isinstance(password, unicode):
- password = password.encode('utf-8')
salt = ''.join(random.sample(string.ascii_letters + string.digits, 8))
- hash_ = hashlib.sha1(password + salt).hexdigest()
+ salted_password = password + salt
+ if isinstance(salted_password, unicode):
+ salted_password = salted_password.encode('utf-8')
+ hash_ = hashlib.sha1(salted_password).hexdigest()
return '$'.join(['sha1', hash_, salt])
@classmethod
def check_sha1(cls, password, hash_):
if isinstance(password, unicode):
password = password.encode('utf-8')
- if isinstance(hash_, unicode):
- hash_ = hash_.encode('utf-8')
hash_method, hash_, salt = hash_.split('$', 2)
salt = salt or ''
+ if isinstance(salt, unicode):
+ salt = salt.encode('utf-8')
assert hash_method == 'sha1'
return hash_ == hashlib.sha1(password + salt).hexdigest()
@@ -516,16 +517,16 @@ class User(ModelSQL, ModelView):
def hash_bcrypt(cls, password):
if isinstance(password, unicode):
password = password.encode('utf-8')
- hash_ = bcrypt.hashpw(password, bcrypt.gensalt())
+ hash_ = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8')
return '$'.join(['bcrypt', hash_])
@classmethod
def check_bcrypt(cls, password, hash_):
if isinstance(password, unicode):
password = password.encode('utf-8')
+ hash_method, hash_ = hash_.split('$', 1)
if isinstance(hash_, unicode):
hash_ = hash_.encode('utf-8')
- hash_method, hash_ = hash_.split('$', 1)
assert hash_method == 'bcrypt'
return hash_ == bcrypt.hashpw(password, hash_)
@@ -543,7 +544,7 @@ class LoginAttempt(ModelSQL):
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
super(LoginAttempt, cls).__register__(module_name)
- table = TableHandler(Transaction().cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
# Migration from 2.8: remove user
table.drop_column('user')
@@ -562,22 +563,23 @@ class LoginAttempt(ModelSQL):
@classmethod
@_login_size
def add(cls, login):
- cls.delete(cls.search([
- ('create_date', '<', cls.delay()),
- ]))
+ cursor = Transaction().connection.cursor()
+ table = cls.__table__()
+ cursor.execute(*table.delete(where=table.create_date < cls.delay()))
+
cls.create([{'login': login}])
@classmethod
@_login_size
def remove(cls, login):
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
table = cls.__table__()
cursor.execute(*table.delete(where=table.login == login))
@classmethod
@_login_size
def count(cls, login):
- cursor = Transaction().cursor
+ cursor = Transaction().connection.cursor()
table = cls.__table__()
cursor.execute(*table.select(Count(Literal(1)),
where=(table.login == login)
@@ -629,13 +631,12 @@ class UserGroup(ModelSQL):
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
- cursor = Transaction().cursor
# Migration from 1.0 table name change
- TableHandler.table_rename(cursor, 'res_group_user_rel', cls._table)
- TableHandler.sequence_rename(cursor, 'res_group_user_rel_id_seq',
+ TableHandler.table_rename('res_group_user_rel', cls._table)
+ TableHandler.sequence_rename('res_group_user_rel_id_seq',
cls._table + '_id_seq')
# Migration from 2.0 uid and gid rename into user and group
- table = TableHandler(cursor, cls, module_name)
+ table = TableHandler(cls, module_name)
table.column_rename('uid', 'user')
table.column_rename('gid', 'group')
super(UserGroup, cls).__register__(module_name)
diff --git a/trytond/security.py b/trytond/security.py
index 70d594e..c874c7c 100644
--- a/trytond/security.py
+++ b/trytond/security.py
@@ -8,7 +8,7 @@ except ImportError:
from trytond.pool import Pool
from trytond.config import config
from trytond.transaction import Transaction
-from trytond.exceptions import NotLogged
+from trytond import backend
def _get_pool(dbname):
@@ -20,24 +20,22 @@ def _get_pool(dbname):
def login(dbname, loginname, password, cache=True):
- with Transaction().start(dbname, 0) as transaction:
+ with Transaction().start(dbname, 0):
pool = _get_pool(dbname)
User = pool.get('res.user')
user_id = User.get_login(loginname, password)
- transaction.cursor.commit()
if user_id:
if not cache:
return user_id
- with Transaction().start(dbname, user_id) as transaction:
+ with Transaction().start(dbname, user_id):
Session = pool.get('ir.session')
session, = Session.create([{}])
- transaction.cursor.commit()
return user_id, session.key
- return False
+ return
def logout(dbname, user, session):
- with Transaction().start(dbname, 0) as transaction:
+ with Transaction().start(dbname, 0):
pool = _get_pool(dbname)
Session = pool.get('ir.session')
sessions = Session.search([
@@ -48,7 +46,6 @@ def logout(dbname, user, session):
session, = sessions
name = session.create_uid.login
Session.delete(sessions)
- transaction.cursor.commit()
return name
@@ -60,17 +57,19 @@ def check_super(passwd):
def check(dbname, user, session):
- if user == 0:
- raise Exception('AccessDenied')
- if not user:
- raise NotLogged()
- with Transaction().start(dbname, user) as transaction:
- pool = _get_pool(dbname)
- Session = pool.get('ir.session')
- try:
- if not Session.check(user, session):
- raise NotLogged()
- else:
- return user
- finally:
- transaction.cursor.commit()
+ DatabaseOperationalError = backend.get('DatabaseOperationalError')
+ for count in range(config.getint('database', 'retry'), -1, -1):
+ with Transaction().start(dbname, user) as transaction:
+ pool = _get_pool(dbname)
+ Session = pool.get('ir.session')
+ try:
+ if not Session.check(user, session):
+ return
+ else:
+ return user
+ except DatabaseOperationalError:
+ if count:
+ continue
+ raise
+ finally:
+ transaction.commit()
diff --git a/trytond/sendmail.py b/trytond/sendmail.py
new file mode 100644
index 0000000..cdc5d6e
--- /dev/null
+++ b/trytond/sendmail.py
@@ -0,0 +1,104 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import logging
+import smtplib
+import urllib
+from email.message import Message
+
+from .config import config, parse_uri
+from .transaction import Transaction
+
+__all__ = ['sendmail_transactional', 'sendmail', 'SMTPDataManager']
+logger = logging.getLogger(__name__)
+
+
+def sendmail_transactional(
+ from_addr, to_addrs, msg, transaction=None, datamanager=None):
+ if transaction is None:
+ transaction = Transaction()
+ assert isinstance(transaction, Transaction), transaction
+ if datamanager is None:
+ datamanager = SMTPDataManager()
+ datamanager = transaction.join(datamanager)
+ datamanager.put(from_addr, to_addrs, msg)
+
+
+def sendmail(from_addr, to_addrs, msg, server=None):
+ if server is None:
+ server = get_smtp_server()
+ quit = True
+ else:
+ quit = False
+ try:
+ senderrs = server.sendmail(from_addr, to_addrs, msg.as_string())
+ except smtplib.SMTPException:
+ logger.error('fail to send email', exc_info=True)
+ if senderrs:
+ logger.warn('fail to send email to %s', senderrs)
+ if quit:
+ server.quit()
+
+
+def get_smtp_server(uri=None):
+ if uri is None:
+ uri = config.get('email', 'uri')
+ uri = parse_uri(uri)
+ if uri.scheme.startswith('smtps'):
+ server = smtplib.SMTP_SSL(uri.hostname, uri.port)
+ else:
+ server = smtplib.SMTP(uri.hostname, uri.port)
+
+ if 'tls' in uri.scheme:
+ server.starttls()
+
+ if uri.username and uri.password:
+ server.login(
+ urllib.unquote_plus(uri.username),
+ urllib.unquote_plus(uri.password))
+ return server
+
+
+class SMTPDataManager(object):
+
+ def __init__(self, uri=None):
+ self.uri = uri
+ self.queue = []
+ self._server = None
+
+ def put(self, from_addr, to_addrs, msg):
+ assert isinstance(msg, Message), msg
+ self.queue.append((from_addr, to_addrs, msg))
+
+ def __eq__(self, other):
+ if not isinstance(other, SMTPDataManager):
+ return NotImplemented
+ return self.uri == other.uri
+
+ def abort(self, trans):
+ self._finish()
+
+ def tpc_begin(self, trans):
+ pass
+
+ def commit(self, trans):
+ pass
+
+ def tpc_vote(self, trans):
+ if self._server is None:
+ self._server = get_smtp_server(self.uri)
+
+ def tpc_finish(self, trans):
+ if self._server is not None:
+ for from_addr, to_addrs, msg in self.queue:
+ sendmail(from_addr, to_addrs, msg, server=self._server)
+ self._server.quit()
+ self._finish()
+
+ def tpc_abort(self, trans):
+ if self._server:
+ self._server.close()
+ self._finish()
+
+ def _finish(self):
+ self._server = None
+ self.queue = []
diff --git a/trytond/server.py b/trytond/server.py
deleted file mode 100644
index e75f6f3..0000000
--- a/trytond/server.py
+++ /dev/null
@@ -1,235 +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.
-"""
-%prog [options]
-"""
-import logging
-import logging.config
-import logging.handlers
-import sys
-import os
-import signal
-import time
-from getpass import getpass
-import threading
-
-from sql import Table
-
-from trytond.config import config, parse_listen
-from trytond import backend
-from trytond.pool import Pool
-from trytond.monitor import monitor
-from .transaction import Transaction
-
-
-class TrytonServer(object):
-
- def __init__(self, options):
-
- 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 = ('%(process)s %(thread)s [%(asctime)s] '
- '%(levelname)s %(name)s %(message)s')
- if options.verbose:
- if options.dev:
- level = logging.DEBUG
- else:
- level = logging.INFO
- else:
- level = logging.ERROR
- logging.basicConfig(level=level, format=logformat)
-
- self.logger = logging.getLogger(__name__)
-
- if options.configfile:
- self.logger.info('using %s as configuration file',
- options.configfile)
- 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[0] != 'UTC':
- self.logger.error('timezone is not set to UTC')
-
- def run(self):
- "Run the server and never return"
- init = {}
-
- signal.signal(signal.SIGINT, lambda *a: self.stop())
- signal.signal(signal.SIGTERM, lambda *a: self.stop())
- if hasattr(signal, 'SIGQUIT'):
- signal.signal(signal.SIGQUIT, lambda *a: self.stop())
- if hasattr(signal, 'SIGUSR1'):
- signal.signal(signal.SIGUSR1, lambda *a: self.restart())
-
- if self.options.pidfile:
- with open(self.options.pidfile, 'w') as fd_pid:
- fd_pid.write("%d" % (os.getpid()))
-
- if not self.options.update:
- self.start_servers()
-
- for db_name in self.options.database_names:
- init[db_name] = False
- try:
- with Transaction().start(db_name, 0) as transaction:
- cursor = transaction.cursor
- if self.options.update:
- if not cursor.test():
- self.logger.info("init db")
- backend.get('Database').init(cursor)
- init[db_name] = True
- cursor.commit()
- elif not cursor.test():
- raise Exception("'%s' is not a Tryton database!" %
- db_name)
- except Exception:
- self.stop(False)
- raise
-
- for db_name in self.options.database_names:
- if self.options.update:
- with Transaction().start(db_name, 0) as transaction:
- cursor = transaction.cursor
- if not cursor.test():
- raise Exception("'%s' is not a Tryton database!"
- % db_name)
- lang = Table('ir_lang')
- cursor.execute(*lang.select(lang.code,
- where=lang.translatable == True))
- lang = [x[0] for x in cursor.fetchall()]
- else:
- lang = None
- Pool(db_name).init(update=self.options.update, lang=lang)
-
- for db_name in self.options.database_names:
- if init[db_name]:
- # try to read password from environment variable
- # TRYTONPASSFILE, empty TRYTONPASSFILE ignored
- passpath = os.getenv('TRYTONPASSFILE')
- password = ''
- if passpath:
- try:
- with open(passpath) as passfile:
- password = passfile.readline()[:-1]
- except Exception, err:
- sys.stderr.write('Can not read password '
- 'from "%s": "%s"\n' % (passpath, err))
-
- if not password:
- while True:
- password = getpass('Admin Password for %s: ' % db_name)
- password2 = getpass('Admin Password Confirmation: ')
- if password != password2:
- sys.stderr.write('Admin Password Confirmation '
- 'doesn\'t match Admin Password!\n')
- continue
- if not password:
- sys.stderr.write('Admin Password is required!\n')
- continue
- break
-
- with Transaction().start(db_name, 0) as transaction:
- pool = Pool()
- User = pool.get('res.user')
- admin, = User.search([('login', '=', 'admin')])
- User.write([admin], {
- 'password': password,
- })
- transaction.cursor.commit()
-
- if self.options.update:
- self.logger.info('Update/Init succeed!')
- logging.shutdown()
- sys.exit(0)
-
- threads = {}
- while True:
- if self.options.cron:
- for dbname in Pool.database_list():
- thread = threads.get(dbname)
- if thread and thread.is_alive():
- continue
- pool = Pool(dbname)
- if not pool.lock.acquire(0):
- continue
- try:
- try:
- Cron = pool.get('ir.cron')
- except KeyError:
- continue
- finally:
- pool.lock.release()
- thread = threading.Thread(
- target=Cron.run,
- args=(dbname,), kwargs={})
- thread.start()
- threads[dbname] = thread
- if self.options.dev:
- for _ in range(60):
- 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.get('jsonrpc', 'listen'):
- from trytond.protocols.jsonrpc import JSONRPCDaemon
- for hostname, port in parse_listen(
- config.get('jsonrpc', 'listen')):
- self.jsonrpcd.append(JSONRPCDaemon(hostname, port, ssl))
- self.logger.info("starting JSON-RPC%s protocol on %s:%d",
- ssl and ' SSL' or '', hostname or '*', port)
-
- if config.get('xmlrpc', 'listen'):
- from trytond.protocols.xmlrpc import XMLRPCDaemon
- for hostname, port in parse_listen(
- config.get('xmlrpc', 'listen')):
- self.xmlrpcd.append(XMLRPCDaemon(hostname, port, ssl))
- self.logger.info("starting XML-RPC%s protocol on %s:%d",
- ssl and ' SSL' or '', hostname or '*', port)
-
- if config.get('webdav', 'listen'):
- from trytond.protocols.webdav import WebDAVServerThread
- for hostname, port in parse_listen(
- config.get('webdav', 'listen')):
- self.webdavd.append(WebDAVServerThread(hostname, port, ssl))
- self.logger.info("starting WebDAV%s protocol on %s:%d",
- ssl and ' SSL' or '', hostname or '*', port)
-
- for servers in (self.xmlrpcd, self.jsonrpcd, self.webdavd):
- for server in servers:
- server.start()
-
- def stop(self, exit=True):
- for servers in (self.xmlrpcd, self.jsonrpcd, self.webdavd):
- for server in servers:
- server.stop()
- server.join()
- if exit:
- if self.options.pidfile:
- os.unlink(self.options.pidfile)
- logging.getLogger('server').info('stopped')
- logging.shutdown()
- sys.exit(0)
-
- def restart(self):
- self.stop(False)
- args = ([sys.executable] + ['-W%s' % o for o in sys.warnoptions]
- + sys.argv)
- if sys.platform == "win32":
- args = ['"%s"' % arg for arg in args]
- os.execv(sys.executable, args)
diff --git a/trytond/tests/__init__.py b/trytond/tests/__init__.py
index f841378..a16f4c2 100644
--- a/trytond/tests/__init__.py
+++ b/trytond/tests/__init__.py
@@ -86,6 +86,8 @@ def register():
Many2ManySize,
Many2ManySizeTarget,
Many2ManySizeRelation,
+ Many2ManyTree,
+ Many2ManyTreeRelation,
Reference,
ReferenceTarget,
ReferenceRequired,
@@ -104,6 +106,7 @@ def register():
ModelStorage,
ModelSQLRequiredField,
ModelSQLTimestamp,
+ ModelSQLFieldSet,
Model4Union1,
Model4Union2,
Model4Union3,
@@ -160,6 +163,8 @@ def register():
Many2OneDomainValidation,
Many2OneOrderBy,
Many2OneSearch,
+ Many2OneTree,
+ Many2OneMPTT,
TestHistory,
TestHistoryLine,
FieldContextChild,
diff --git a/trytond/tests/export_data.py b/trytond/tests/export_data.py
index e084c2b..422e6b2 100644
--- a/trytond/tests/export_data.py
+++ b/trytond/tests/export_data.py
@@ -7,7 +7,6 @@ from trytond.pool import PoolMeta
__all__ = [
'ExportDataTarget', 'ExportData', 'ExportDataTarget2',
'ExportDataRelation']
-__metaclass__ = PoolMeta
class ExportDataTarget(ModelSQL):
@@ -46,6 +45,7 @@ class ExportData(ModelSQL):
class ExportDataTarget2:
'Export Date Target'
+ __metaclass__ = PoolMeta
__name__ = 'test.export_data.target'
one2many = fields.Many2One('test.export_data', 'Export Data')
diff --git a/trytond/tests/history.py b/trytond/tests/history.py
index 6421ad8..f94bd01 100644
--- a/trytond/tests/history.py
+++ b/trytond/tests/history.py
@@ -11,6 +11,10 @@ class TestHistory(ModelSQL):
_history = True
value = fields.Integer('Value')
lines = fields.One2Many('test.history.line', 'history', 'Lines')
+ lines_at_stamp = fields.One2Many(
+ 'test.history.line', 'history', 'Lines at Stamp',
+ datetime_field='stamp')
+ stamp = fields.Timestamp('Stamp')
class TestHistoryLine(ModelSQL):
diff --git a/trytond/tests/model.py b/trytond/tests/model.py
index 835eb99..c0d1730 100644
--- a/trytond/tests/model.py
+++ b/trytond/tests/model.py
@@ -5,7 +5,7 @@ from trytond.model import ModelSingleton, ModelSQL, UnionMixin, fields
__all__ = [
'Singleton', 'URLObject',
'ModelStorage',
- 'ModelSQLRequiredField', 'ModelSQLTimestamp',
+ 'ModelSQLRequiredField', 'ModelSQLTimestamp', 'ModelSQLFieldSet',
'Model4Union1', 'Model4Union2', 'Model4Union3', 'Model4Union4',
'Union', 'UnionUnion',
'Model4UnionTree1', 'Model4UnionTree2', 'UnionTree',
@@ -47,6 +47,21 @@ class ModelSQLTimestamp(ModelSQL):
__name__ = 'test.modelsql.timestamp'
+class ModelSQLFieldSet(ModelSQL):
+ 'Model to test field set'
+ __name__ = 'test.modelsql.field_set'
+
+ field = fields.Function(fields.Integer('Field'),
+ 'get_field', setter='set_field')
+
+ def get_field(self, name=None):
+ return
+
+ @classmethod
+ def set_field(cls, records, name, value):
+ pass
+
+
class Model4Union1(ModelSQL):
'Model for union 1'
__name__ = 'test.model.union1'
diff --git a/trytond/tests/run-tests.py b/trytond/tests/run-tests.py
index 1bbc3a9..cac56a2 100755
--- a/trytond/tests/run-tests.py
+++ b/trytond/tests/run-tests.py
@@ -18,6 +18,8 @@ logging.basicConfig(level=logging.ERROR)
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--config", dest="config",
help="specify config file")
+parser.add_argument("-f", "--failfast", action="store_true", dest="failfast",
+ help="Stop the test run on the first error or failure")
parser.add_argument("-m", "--modules", action="store_true", dest="modules",
default=False, help="Run also modules tests")
parser.add_argument("--no-doctest", action="store_false", dest="doctest",
@@ -42,5 +44,6 @@ if not opt.modules:
suite = all_suite(opt.tests)
else:
suite = modules_suite(opt.tests, doc=opt.doctest)
-result = unittest.TextTestRunner(verbosity=opt.verbosity).run(suite)
+result = unittest.TextTestRunner(
+ verbosity=opt.verbosity, failfast=opt.failfast).run(suite)
sys.exit(not result.wasSuccessful())
diff --git a/trytond/tests/test.py b/trytond/tests/test.py
index b493a27..62a03a6 100644
--- a/trytond/tests/test.py
+++ b/trytond/tests/test.py
@@ -30,13 +30,14 @@ __all__ = [
'Many2ManyReference', 'Many2ManyReferenceTarget',
'Many2ManyReferenceRelation',
'Many2ManySize', 'Many2ManySizeTarget', 'Many2ManySizeRelation',
+ 'Many2ManyTree', 'Many2ManyTreeRelation',
'Reference', 'ReferenceTarget', 'ReferenceRequired',
'Property',
'Selection', 'SelectionRequired',
'DictSchema', 'Dict', 'DictDefault', 'DictRequired',
'Binary', 'BinaryDefault', 'BinaryRequired',
'Many2OneDomainValidation', 'Many2OneTarget', 'Many2OneOrderBy',
- 'Many2OneSearch',
+ 'Many2OneSearch', 'Many2OneTree', 'Many2OneMPTT',
]
@@ -588,6 +589,22 @@ class Many2ManySizeRelation(ModelSQL):
target = fields.Many2One('test.many2many_size.target', 'Target')
+class Many2ManyTree(ModelSQL):
+ 'Many2Many Tree'
+ __name__ = 'test.many2many_tree'
+ parents = fields.Many2Many('test.many2many_tree.relation',
+ 'child', 'parent', 'Parents')
+ children = fields.Many2Many('test.many2many_tree.relation',
+ 'parent', 'child', 'Children')
+
+
+class Many2ManyTreeRelation(ModelSQL):
+ 'Many2Many Tree Relation'
+ __name__ = 'test.many2many_tree.relation'
+ parent = fields.Many2One('test.many2many_tree', 'Parent')
+ child = fields.Many2One('test.many2many_tree', 'Child')
+
+
class Reference(ModelSQL):
'Reference'
__name__ = 'test.reference'
@@ -744,3 +761,26 @@ class Many2OneSearch(ModelSQL):
"Many2One Search"
__name__ = 'test.many2one_search'
many2one = fields.Many2One('test.many2one_target', 'many2one')
+
+
+class Many2OneTree(ModelSQL):
+ 'Many2One Tree'
+ __name__ = 'test.many2one_tree'
+ many2one = fields.Many2One('test.many2one_tree', 'many2one')
+
+
+class Many2OneMPTT(ModelSQL):
+ 'Many2One MPTT'
+ __name__ = 'test.many2one_mptt'
+ many2one = fields.Many2One('test.many2one_mptt', 'many2one',
+ left='left', right='right')
+ left = fields.Integer('Left', required=True)
+ right = fields.Integer('Right', required=True)
+
+ @classmethod
+ def default_left(cls):
+ return 0
+
+ @classmethod
+ def default_right(cls):
+ return 0
diff --git a/trytond/tests/test_access.py b/trytond/tests/test_access.py
index fc780c5..be67436 100644
--- a/trytond/tests/test_access.py
+++ b/trytond/tests/test_access.py
@@ -2,775 +2,780 @@
# 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.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
+from trytond.pool import Pool
from trytond.exceptions import UserError
-CONTEXT = CONTEXT.copy()
-CONTEXT['_check_access'] = True
+_context = {'_check_access': True}
class ModelAccessTestCase(unittest.TestCase):
'Test Model Access'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.model_access = POOL.get('ir.model.access')
- self.test_access = POOL.get('test.access')
- self.model = POOL.get('ir.model')
- self.group = POOL.get('res.group')
- def test0010perm_read(self):
+ @with_transaction(context=_context)
+ def test_perm_read(self):
'Test Read Access'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- model, = self.model.search([('model', '=', 'test.access')])
-
- test, = self.test_access.create([{}])
-
- # Without model access
- self.test_access.read([test.id])
-
- # With model access
-
- # One access allowed for any group
- model_access_wo_group, = self.model_access.create([{
- 'model': model.id,
- 'group': None,
- 'perm_read': True,
- }])
- self.test_access.read([test.id])
-
- # One access disallowed for any group
- self.model_access.write([model_access_wo_group], {
- 'perm_read': False,
- })
- self.assertRaises(UserError, self.test_access.read, [test.id])
-
- # Two access rules with one group allowed
- group, = self.group.search([('users', '=', USER)])
- model_access_w_group, = self.model_access.create([{
- 'model': model.id,
- 'group': group.id,
- 'perm_read': True,
- }])
-
- self.test_access.read([test.id])
-
- # Two access rules with both allowed
- self.model_access.write([model_access_wo_group], {
- 'perm_read': True,
- })
- self.test_access.read([test.id])
-
- # Two access rules with any group allowed
- self.model_access.write([model_access_w_group], {
- 'perm_read': False,
- })
- self.test_access.read([test.id])
-
- # Two access rules with both disallowed
- self.model_access.write([model_access_wo_group], {
- 'perm_read': False,
- })
- 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(UserError, self.test_access.read, [test.id])
-
- # One access allowed for one group
- self.model_access.write([model_access_w_group], {
+ pool = Pool()
+ ModelAccess = pool.get('ir.model.access')
+ TestAccess = pool.get('test.access')
+ Model = pool.get('ir.model')
+ Group = pool.get('res.group')
+
+ model, = Model.search([('model', '=', 'test.access')])
+
+ test, = TestAccess.create([{}])
+
+ # Without model access
+ TestAccess.read([test.id])
+
+ # With model access
+
+ # One access allowed for any group
+ model_access_wo_group, = ModelAccess.create([{
+ 'model': model.id,
+ 'group': None,
'perm_read': True,
- })
- self.test_access.read([test.id])
+ }])
+ TestAccess.read([test.id])
- # One access allowed for one other group
- group, = self.group.create([{'name': 'Test'}])
- self.model_access.write([model_access_w_group], {
+ # One access disallowed for any group
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_read': False,
+ })
+ self.assertRaises(UserError, TestAccess.read, [test.id])
+
+ # Two access rules with one group allowed
+ group, = Group.search([('users', '=', Transaction().user)])
+ model_access_w_group, = ModelAccess.create([{
+ 'model': model.id,
'group': group.id,
- })
- self.test_access.read([test.id])
+ 'perm_read': True,
+ }])
- # One access disallowed for one other group
- self.model_access.write([model_access_w_group], {
- 'perm_read': False,
- })
- self.test_access.read([test.id])
+ TestAccess.read([test.id])
- transaction.cursor.rollback()
- self.model_access._get_access_cache.clear()
+ # Two access rules with both allowed
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_read': True,
+ })
+ TestAccess.read([test.id])
+
+ # Two access rules with any group allowed
+ ModelAccess.write([model_access_w_group], {
+ 'perm_read': False,
+ })
+ TestAccess.read([test.id])
+
+ # Two access rules with both disallowed
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_read': False,
+ })
+ self.assertRaises(UserError, TestAccess.read, [test.id])
+
+ # One access disallowed for one group
+ ModelAccess.delete([model_access_wo_group])
+ self.assertRaises(UserError, TestAccess.read, [test.id])
+
+ # One access allowed for one group
+ ModelAccess.write([model_access_w_group], {
+ 'perm_read': True,
+ })
+ TestAccess.read([test.id])
- def test0020perm_write(self):
+ # One access allowed for one other group
+ group, = Group.create([{'name': 'Test'}])
+ ModelAccess.write([model_access_w_group], {
+ 'group': group.id,
+ })
+ TestAccess.read([test.id])
+
+ # One access disallowed for one other group
+ ModelAccess.write([model_access_w_group], {
+ 'perm_read': False,
+ })
+ TestAccess.read([test.id])
+
+ @with_transaction(context=_context)
+ def test_perm_write(self):
'Test Write Access'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- model, = self.model.search([('model', '=', 'test.access')])
-
- test, = self.test_access.create([{}])
-
- # Without model access
- self.test_access.write([test], {})
-
- # With model access
-
- # One access allowed for any group
- model_access_wo_group, = self.model_access.create([{
- 'model': model.id,
- 'group': None,
- 'perm_write': True,
- }])
- self.test_access.write([test], {})
-
- # One access disallowed for any group
- self.model_access.write([model_access_wo_group], {
- 'perm_write': False,
- })
- self.assertRaises(UserError, self.test_access.write, [test], {})
-
- # Two access rules with one group allowed
- group, = self.group.search([('users', '=', USER)])
- model_access_w_group, = self.model_access.create([{
- 'model': model.id,
- 'group': group.id,
- 'perm_write': True,
- }])
- self.test_access.write([test], {})
-
- # Two access rules with both allowed
- self.model_access.write([model_access_wo_group], {
- 'perm_write': True,
- })
- self.test_access.write([test], {})
-
- # Two access rules with any group allowed
- self.model_access.write([model_access_w_group], {
- 'perm_write': False,
- })
- self.test_access.write([test], {})
-
- # Two access rules with both disallowed
- self.model_access.write([model_access_wo_group], {
- 'perm_write': False,
- })
- self.assertRaises(UserError, self.test_access.write, [test], {})
-
- # One access disallowed for one group
- self.model_access.delete([model_access_wo_group])
- self.assertRaises(UserError, self.test_access.write, [test], {})
-
- # One access allowed for one group
- self.model_access.write([model_access_w_group], {
+ pool = Pool()
+ ModelAccess = pool.get('ir.model.access')
+ TestAccess = pool.get('test.access')
+ Model = pool.get('ir.model')
+ Group = pool.get('res.group')
+
+ model, = Model.search([('model', '=', 'test.access')])
+
+ test, = TestAccess.create([{}])
+
+ # Without model access
+ TestAccess.write([test], {})
+
+ # With model access
+
+ # One access allowed for any group
+ model_access_wo_group, = ModelAccess.create([{
+ 'model': model.id,
+ 'group': None,
'perm_write': True,
- })
- self.test_access.write([test], {})
+ }])
+ TestAccess.write([test], {})
+
+ # One access disallowed for any group
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_write': False,
+ })
+ self.assertRaises(UserError, TestAccess.write, [test], {})
- # One access allowed for one other group
- group, = self.group.create([{'name': 'Test'}])
- self.model_access.write([model_access_w_group], {
+ # Two access rules with one group allowed
+ group, = Group.search([('users', '=', Transaction().user)])
+ model_access_w_group, = ModelAccess.create([{
+ 'model': model.id,
'group': group.id,
- })
- self.test_access.write([test], {})
+ 'perm_write': True,
+ }])
+ TestAccess.write([test], {})
+
+ # Two access rules with both allowed
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_write': True,
+ })
+ TestAccess.write([test], {})
+
+ # Two access rules with any group allowed
+ ModelAccess.write([model_access_w_group], {
+ 'perm_write': False,
+ })
+ TestAccess.write([test], {})
+
+ # Two access rules with both disallowed
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_write': False,
+ })
+ self.assertRaises(UserError, TestAccess.write, [test], {})
- # One access disallowed for one other group
- self.model_access.write([model_access_w_group], {
- 'perm_write': False,
- })
- self.test_access.write([test], {})
+ # One access disallowed for one group
+ ModelAccess.delete([model_access_wo_group])
+ self.assertRaises(UserError, TestAccess.write, [test], {})
- transaction.cursor.rollback()
- self.model_access._get_access_cache.clear()
+ # One access allowed for one group
+ ModelAccess.write([model_access_w_group], {
+ 'perm_write': True,
+ })
+ TestAccess.write([test], {})
+
+ # One access allowed for one other group
+ group, = Group.create([{'name': 'Test'}])
+ ModelAccess.write([model_access_w_group], {
+ 'group': group.id,
+ })
+ TestAccess.write([test], {})
- def test0030perm_create(self):
+ # One access disallowed for one other group
+ ModelAccess.write([model_access_w_group], {
+ 'perm_write': False,
+ })
+ TestAccess.write([test], {})
+
+ @with_transaction(context=_context)
+ def test_perm_create(self):
'Test Create Access'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- model, = self.model.search([('model', '=', 'test.access')])
-
- # Without model access
- self.test_access.create([{}])
-
- # With model access
-
- # One access allowed for any group
- model_access_wo_group, = self.model_access.create([{
- 'model': model.id,
- 'group': None,
- 'perm_create': True,
- }])
- self.test_access.create([{}])
-
- # One access disallowed for any group
- self.model_access.write([model_access_wo_group], {
- 'perm_create': False,
- })
- self.assertRaises(UserError, self.test_access.create, {})
-
- # Two access rules with one group allowed
- group, = self.group.search([('users', '=', USER)])
- model_access_w_group, = self.model_access.create([{
- 'model': model.id,
- 'group': group.id,
- 'perm_create': True,
- }])
-
- self.test_access.create([{}])
-
- # Two access rules with both allowed
- self.model_access.write([model_access_wo_group], {
- 'perm_create': True,
- })
- self.test_access.create([{}])
-
- # Two access rules with any group allowed
- self.model_access.write([model_access_w_group], {
- 'perm_create': False,
- })
- self.test_access.create([{}])
-
- # Two access rules with both disallowed
- self.model_access.write([model_access_wo_group], {
- 'perm_create': False,
- })
- self.assertRaises(UserError, self.test_access.create, [{}])
-
- # One access disallowed for one group
- self.model_access.delete([model_access_wo_group])
- self.assertRaises(UserError, self.test_access.create, [{}])
-
- # One access allowed for one group
- self.model_access.write([model_access_w_group], {
+ pool = Pool()
+ ModelAccess = pool.get('ir.model.access')
+ TestAccess = pool.get('test.access')
+ Model = pool.get('ir.model')
+ Group = pool.get('res.group')
+
+ model, = Model.search([('model', '=', 'test.access')])
+
+ # Without model access
+ TestAccess.create([{}])
+
+ # With model access
+
+ # One access allowed for any group
+ model_access_wo_group, = ModelAccess.create([{
+ 'model': model.id,
+ 'group': None,
'perm_create': True,
- })
- self.test_access.create([{}])
+ }])
+ TestAccess.create([{}])
- # One access allowed for one other group
- group, = self.group.create([{'name': 'Test'}])
- self.model_access.write([model_access_w_group], {
+ # One access disallowed for any group
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_create': False,
+ })
+ self.assertRaises(UserError, TestAccess.create, {})
+
+ # Two access rules with one group allowed
+ group, = Group.search([('users', '=', Transaction().user)])
+ model_access_w_group, = ModelAccess.create([{
+ 'model': model.id,
'group': group.id,
- })
- self.test_access.create([{}])
+ 'perm_create': True,
+ }])
+
+ TestAccess.create([{}])
+
+ # Two access rules with both allowed
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_create': True,
+ })
+ TestAccess.create([{}])
- # One access disallowed for one other group
- self.model_access.write([model_access_w_group], {
- 'perm_create': False,
- })
- self.test_access.create([{}])
+ # Two access rules with any group allowed
+ ModelAccess.write([model_access_w_group], {
+ 'perm_create': False,
+ })
+ TestAccess.create([{}])
+
+ # Two access rules with both disallowed
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_create': False,
+ })
+ self.assertRaises(UserError, TestAccess.create, [{}])
- transaction.cursor.rollback()
- self.model_access._get_access_cache.clear()
+ # One access disallowed for one group
+ ModelAccess.delete([model_access_wo_group])
+ self.assertRaises(UserError, TestAccess.create, [{}])
+
+ # One access allowed for one group
+ ModelAccess.write([model_access_w_group], {
+ 'perm_create': True,
+ })
+ TestAccess.create([{}])
- def test0040perm_delete(self):
+ # One access allowed for one other group
+ group, = Group.create([{'name': 'Test'}])
+ ModelAccess.write([model_access_w_group], {
+ 'group': group.id,
+ })
+ TestAccess.create([{}])
+
+ # One access disallowed for one other group
+ ModelAccess.write([model_access_w_group], {
+ 'perm_create': False,
+ })
+ TestAccess.create([{}])
+
+ @with_transaction(context=_context)
+ def test_perm_delete(self):
'Test Delete Access'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- model, = self.model.search([('model', '=', 'test.access')])
-
- tests = [self.test_access.create([{}])[0] for x in range(11)]
-
- # Without model access
- self.test_access.delete([tests.pop()])
-
- # With model access
-
- # One access allowed for any group
- model_access_wo_group, = self.model_access.create([{
- 'model': model.id,
- 'group': None,
- 'perm_delete': True,
- }])
- self.test_access.delete([tests.pop()])
-
- # One access disallowed for any group
- self.model_access.write([model_access_wo_group], {
- 'perm_delete': False,
- })
- self.assertRaises(UserError, self.test_access.delete,
- [tests.pop()])
-
- # Two access rules with one group allowed
- group = self.group.search([('users', '=', USER)])[0]
- model_access_w_group, = self.model_access.create([{
- 'model': model.id,
- 'group': group.id,
- 'perm_delete': True,
- }])
-
- self.test_access.delete([tests.pop()])
-
- # Two access rules with both allowed
- self.model_access.write([model_access_wo_group], {
- 'perm_delete': True,
- })
- self.test_access.delete([tests.pop()])
-
- # Two access rules with any group allowed
- self.model_access.write([model_access_w_group], {
- 'perm_delete': False,
- })
- self.test_access.delete([tests.pop()])
-
- # Two access rules with both disallowed
- self.model_access.write([model_access_wo_group], {
- 'perm_delete': False,
- })
- 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(UserError, self.test_access.delete,
- [tests.pop()])
-
- # One access allowed for one group
- self.model_access.write([model_access_w_group], {
+ pool = Pool()
+ ModelAccess = pool.get('ir.model.access')
+ TestAccess = pool.get('test.access')
+ Model = pool.get('ir.model')
+ Group = pool.get('res.group')
+
+ model, = Model.search([('model', '=', 'test.access')])
+
+ tests = [TestAccess.create([{}])[0] for x in range(11)]
+
+ # Without model access
+ TestAccess.delete([tests.pop()])
+
+ # With model access
+
+ # One access allowed for any group
+ model_access_wo_group, = ModelAccess.create([{
+ 'model': model.id,
+ 'group': None,
'perm_delete': True,
- })
- self.test_access.delete([tests.pop()])
+ }])
+ TestAccess.delete([tests.pop()])
- # One access allowed for one other group
- group, = self.group.create([{'name': 'Test'}])
- self.model_access.write([model_access_w_group], {
+ # One access disallowed for any group
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_delete': False,
+ })
+ self.assertRaises(UserError, TestAccess.delete, [tests.pop()])
+
+ # Two access rules with one group allowed
+ group = Group.search([('users', '=', Transaction().user)])[0]
+ model_access_w_group, = ModelAccess.create([{
+ 'model': model.id,
'group': group.id,
- })
- self.test_access.delete([tests.pop()])
+ 'perm_delete': True,
+ }])
+
+ TestAccess.delete([tests.pop()])
+
+ # Two access rules with both allowed
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_delete': True,
+ })
+ TestAccess.delete([tests.pop()])
+
+ # Two access rules with any group allowed
+ ModelAccess.write([model_access_w_group], {
+ 'perm_delete': False,
+ })
+ TestAccess.delete([tests.pop()])
+
+ # Two access rules with both disallowed
+ ModelAccess.write([model_access_wo_group], {
+ 'perm_delete': False,
+ })
+ self.assertRaises(UserError, TestAccess.delete, [tests.pop()])
- # One access disallowed for one other group
- self.model_access.write([model_access_w_group], {
- 'perm_delete': False,
- })
- self.test_access.delete([tests.pop()])
+ # One access disallowed for one group
+ ModelAccess.delete([model_access_wo_group])
+ self.assertRaises(UserError, TestAccess.delete, [tests.pop()])
- transaction.cursor.rollback()
- self.model_access._get_access_cache.clear()
+ # One access allowed for one group
+ ModelAccess.write([model_access_w_group], {
+ 'perm_delete': True,
+ })
+ TestAccess.delete([tests.pop()])
+
+ # One access allowed for one other group
+ group, = Group.create([{'name': 'Test'}])
+ ModelAccess.write([model_access_w_group], {
+ 'group': group.id,
+ })
+ TestAccess.delete([tests.pop()])
+
+ # One access disallowed for one other group
+ ModelAccess.write([model_access_w_group], {
+ 'perm_delete': False,
+ })
+ TestAccess.delete([tests.pop()])
class ModelFieldAccessTestCase(unittest.TestCase):
'Test Model Field Access'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.field_access = POOL.get('ir.model.field.access')
- self.test_access = POOL.get('test.access')
- self.field = POOL.get('ir.model.field')
- self.group = POOL.get('res.group')
+ @with_transaction(context=_context)
def test0010perm_read(self):
'Test Read Access'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- field1, = self.field.search([
- ('model.model', '=', 'test.access'),
- ('name', '=', 'field1'),
- ])
- field2, = self.field.search([
- ('model.model', '=', 'test.access'),
- ('name', '=', 'field2'),
- ])
-
- test, = self.test_access.create([{
- 'field1': 'ham',
- 'field2': 'spam',
- }])
-
- # Without field access
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # With field access
-
- # One access allowed for any group
- field_access_wo_group, = self.field_access.create([{
- 'field': field1.id,
- 'group': None,
- 'perm_read': True,
- }])
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # One access disallowed for any group
- self.field_access.write([field_access_wo_group], {
- 'perm_read': False,
- })
-
- self.assertRaises(UserError, self.test_access.read, [test.id],
- ['field1'])
- self.test_access.read([test.id], ['field2'])
- 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)
-
- # Two access rules with one group allowed
- group = self.group.search([('users', '=', USER)])[0]
- field_access_w_group, = self.field_access.create([{
- 'field': field1.id,
- 'group': group.id,
- 'perm_read': True,
- }])
-
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # Two access rules with both allowed
- self.field_access.write([field_access_wo_group], {
+ pool = Pool()
+ FieldAccess = pool.get('ir.model.field.access')
+ TestAccess = pool.get('test.access')
+ Field = pool.get('ir.model.field')
+ Group = pool.get('res.group')
+ transaction = Transaction()
+
+ field1, = Field.search([
+ ('model.model', '=', 'test.access'),
+ ('name', '=', 'field1'),
+ ])
+ field2, = Field.search([
+ ('model.model', '=', 'test.access'),
+ ('name', '=', 'field2'),
+ ])
+
+ test, = TestAccess.create([{
+ 'field1': 'ham',
+ 'field2': 'spam',
+ }])
+
+ # Without field access
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # With field access
+
+ # One access allowed for any group
+ field_access_wo_group, = FieldAccess.create([{
+ 'field': field1.id,
+ 'group': None,
'perm_read': True,
- })
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # Two access rules with any group allowed
- self.field_access.write([field_access_w_group], {
- 'perm_read': False,
- })
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # Two access rules with both disallowed
- self.field_access.write([field_access_wo_group], {
+ }])
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # One access disallowed for any group
+ FieldAccess.write([field_access_wo_group], {
'perm_read': False,
})
- self.assertRaises(UserError, self.test_access.read, [test.id],
- ['field1'])
- self.test_access.read([test.id], ['field2'])
- 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(UserError, self.test_access.read, [test.id],
- ['field1'])
- self.test_access.read([test.id], ['field2'])
- 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 allowed for one group
- self.field_access.write([field_access_w_group], {
- 'perm_read': True,
- })
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # One access allowed for one other group
- group, = self.group.create([{'name': 'Test'}])
- self.field_access.write([field_access_w_group], {
+
+ self.assertRaises(UserError, TestAccess.read, [test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ self.assertRaises(UserError, TestAccess.read, [test.id])
+ self.assertRaises(UserError, getattr, test, 'field1')
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # Two access rules with one group allowed
+ group = Group.search([('users', '=', transaction.user)])[0]
+ field_access_w_group, = FieldAccess.create([{
+ 'field': field1.id,
'group': group.id,
- })
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # One access disallowed for one other group
- self.field_access.write([field_access_w_group], {
- 'perm_read': False,
- })
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # Two access rules on both fields allowed
- self.field_access.delete([field_access_w_group])
-
- field_access1, = self.field_access.create([{
- 'field': field1.id,
- 'group': None,
- 'perm_read': True,
- }])
- field_access2, = self.field_access.create([{
- 'field': field2.id,
- 'group': None,
- 'perm_read': True,
- }])
-
- self.test_access.read([test.id], ['field1'])
- self.test_access.read([test.id], ['field2'])
- self.test_access.read([test.id])
- test.field1
- test.field2
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # Two access rules on both fields one allowed and one disallowed
- self.field_access.write([field_access2], {
+ 'perm_read': True,
+ }])
+
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # Two access rules with both allowed
+ FieldAccess.write([field_access_wo_group], {
+ 'perm_read': True,
+ })
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # Two access rules with any group allowed
+ FieldAccess.write([field_access_w_group], {
'perm_read': False,
})
- self.test_access.read([test.id], ['field1'])
- self.assertRaises(UserError, self.test_access.read, [test.id],
- ['field2'])
- self.assertRaises(UserError, self.test_access.read, [test.id])
- test.field1
- self.assertRaises(UserError, getattr, test, 'field2')
- transaction.cursor.cache.clear()
- test = self.test_access(test.id)
-
- # Two access rules on both fields disallowed
- self.field_access.write([field_access1], {
- 'perm_read': False,
- })
- self.assertRaises(UserError, self.test_access.read, [test.id],
- ['field1'])
- self.assertRaises(UserError, self.test_access.read, [test.id],
- ['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)
-
- transaction.cursor.rollback()
- self.field_access._get_access_cache.clear()
-
- def test0010perm_write(self):
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # Two access rules with both disallowed
+ FieldAccess.write([field_access_wo_group], {
+ 'perm_read': False,
+ })
+ self.assertRaises(UserError, TestAccess.read, [test.id],
+ ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ self.assertRaises(UserError, TestAccess.read, [test.id])
+ self.assertRaises(UserError, getattr, test, 'field1')
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # One access disallowed for one group
+ FieldAccess.delete([field_access_wo_group])
+ self.assertRaises(UserError, TestAccess.read, [test.id],
+ ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ self.assertRaises(UserError, TestAccess.read, [test.id])
+ self.assertRaises(UserError, getattr, test, 'field1')
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # One access allowed for one group
+ FieldAccess.write([field_access_w_group], {
+ 'perm_read': True,
+ })
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # One access allowed for one other group
+ group, = Group.create([{'name': 'Test'}])
+ FieldAccess.write([field_access_w_group], {
+ 'group': group.id,
+ })
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # One access disallowed for one other group
+ FieldAccess.write([field_access_w_group], {
+ 'perm_read': False,
+ })
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # Two access rules on both fields allowed
+ FieldAccess.delete([field_access_w_group])
+
+ field_access1, = FieldAccess.create([{
+ 'field': field1.id,
+ 'group': None,
+ 'perm_read': True,
+ }])
+ field_access2, = FieldAccess.create([{
+ 'field': field2.id,
+ 'group': None,
+ 'perm_read': True,
+ }])
+
+ TestAccess.read([test.id], ['field1'])
+ TestAccess.read([test.id], ['field2'])
+ TestAccess.read([test.id])
+ test.field1
+ test.field2
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # Two access rules on both fields one allowed and one disallowed
+ FieldAccess.write([field_access2], {
+ 'perm_read': False,
+ })
+ TestAccess.read([test.id], ['field1'])
+ self.assertRaises(UserError, TestAccess.read, [test.id],
+ ['field2'])
+ self.assertRaises(UserError, TestAccess.read, [test.id])
+ test.field1
+ self.assertRaises(UserError, getattr, test, 'field2')
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ # Two access rules on both fields disallowed
+ FieldAccess.write([field_access1], {
+ 'perm_read': False,
+ })
+ self.assertRaises(UserError, TestAccess.read, [test.id],
+ ['field1'])
+ self.assertRaises(UserError, TestAccess.read, [test.id],
+ ['field2'])
+ self.assertRaises(UserError, TestAccess.read, [test.id])
+ self.assertRaises(UserError, getattr, test, 'field1')
+ self.assertRaises(UserError, getattr, test, 'field2')
+ transaction.cache.clear()
+ test = TestAccess(test.id)
+
+ @with_transaction(context=_context)
+ def test_perm_write(self):
'Test Write Access'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- field1, = self.field.search([
- ('model.model', '=', 'test.access'),
- ('name', '=', 'field1'),
- ])
- field2, = self.field.search([
- ('model.model', '=', 'test.access'),
- ('name', '=', 'field2'),
- ])
-
- test, = self.test_access.create([{
- 'field1': 'ham',
- 'field2': 'spam',
- }])
-
- # Without field access
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
-
- # With field access
-
- # One access allowed for any group
- field_access_wo_group, = self.field_access.create([{
- 'field': field1.id,
- 'group': None,
- 'perm_write': True,
- }])
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.test_access.write([test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
-
- # One access disallowed for any group
- self.field_access.write([field_access_wo_group], {
- 'perm_write': False,
- })
-
- self.test_access.write([test], {})
- self.assertRaises(UserError, self.test_access.write, [test],
- {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.assertRaises(UserError, self.test_access.write, [test], {
+ pool = Pool()
+ FieldAccess = pool.get('ir.model.field.access')
+ TestAccess = pool.get('test.access')
+ Field = pool.get('ir.model.field')
+ Group = pool.get('res.group')
+ transaction = Transaction()
+
+ field1, = Field.search([
+ ('model.model', '=', 'test.access'),
+ ('name', '=', 'field1'),
+ ])
+ field2, = Field.search([
+ ('model.model', '=', 'test.access'),
+ ('name', '=', 'field2'),
+ ])
+
+ test, = TestAccess.create([{
'field1': 'ham',
'field2': 'spam',
- })
-
- # Two access rules with one group allowed
- group = self.group.search([('users', '=', USER)])[0]
- field_access_w_group, = self.field_access.create([{
- 'field': field1.id,
- 'group': group.id,
- 'perm_write': True,
- }])
-
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.test_access.write([test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
+ }])
- # Two access rules with both allowed
- self.field_access.write([field_access_wo_group], {
- 'perm_write': True,
- })
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.test_access.write([test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
-
- # Two access rules with any group allowed
- self.field_access.write([field_access_w_group], {
- 'perm_write': False,
- })
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.test_access.write([test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
-
- # Two access rules with both disallowed
- self.field_access.write([field_access_wo_group], {
- 'perm_write': False,
- })
- self.test_access.write([test], {})
- self.assertRaises(UserError, self.test_access.write, [test],
- {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.assertRaises(UserError, self.test_access.write, [test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
-
- # One access disallowed for one group
- self.field_access.delete([field_access_wo_group])
- self.test_access.write([test], {})
- self.assertRaises(UserError, self.test_access.write, [test],
- {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'ham'})
- self.assertRaises(UserError, self.test_access.write, [test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
+ # Without field access
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+
+ # With field access
- # One access allowed for one group
- self.field_access.write([field_access_w_group], {
+ # One access allowed for any group
+ field_access_wo_group, = FieldAccess.create([{
+ 'field': field1.id,
+ 'group': None,
'perm_write': True,
- })
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.test_access.write([test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
+ }])
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ TestAccess.write([test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
- # One access allowed for one other group
- group, = self.group.create([{'name': 'Test'}])
- self.field_access.write([field_access_w_group], {
+ # One access disallowed for any group
+ FieldAccess.write([field_access_wo_group], {
+ 'perm_write': False,
+ })
+
+ TestAccess.write([test], {})
+ self.assertRaises(UserError, TestAccess.write, [test],
+ {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ self.assertRaises(UserError, TestAccess.write, [test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+ self.assertRaises(UserError, TestAccess.write,
+ [test], {'field2': 'spam'}, [test], {'field1': 'ham'})
+
+ # Two access rules with one group allowed
+ group = Group.search([('users', '=', transaction.user)])[0]
+ field_access_w_group, = FieldAccess.create([{
+ 'field': field1.id,
'group': group.id,
- })
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.test_access.write([test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
-
- # One access disallowed for one other group
- self.field_access.write([field_access_w_group], {
- 'perm_write': False,
- })
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.test_access.write([test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
-
- # Two access rules on both fields allowed
- self.field_access.delete([field_access_w_group])
-
- field_access1, = self.field_access.create([{
- 'field': field1.id,
- 'group': None,
- 'perm_write': True,
- }])
- field_access2, = self.field_access.create([{
- 'field': field2.id,
- 'group': None,
- 'perm_write': True,
- }])
-
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.test_access.write([test], {'field2': 'spam'})
- self.test_access.write([test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
-
- # Two access rules on both fields one allowed and one disallowed
- self.field_access.write([field_access2], {
- 'perm_write': False,
- })
- self.test_access.write([test], {})
- self.test_access.write([test], {'field1': 'ham'})
- self.assertRaises(UserError, self.test_access.write, [test], {
- 'field2': 'spam'})
- self.assertRaises(UserError, self.test_access.write, [test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
-
- # Two access rules on both fields disallowed
- self.field_access.write([field_access1], {
- 'perm_write': False,
- })
- self.test_access.write([test], {})
- self.assertRaises(UserError, self.test_access.write, [test], {
- 'field1': 'ham'})
- self.assertRaises(UserError, self.test_access.write, [test], {
- 'field2': 'spam'})
- self.assertRaises(UserError, self.test_access.write, [test], {
- 'field1': 'ham',
- 'field2': 'spam',
- })
+ 'perm_write': True,
+ }])
+
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ TestAccess.write([test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
- transaction.cursor.rollback()
- self.field_access._get_access_cache.clear()
+ # Two access rules with both allowed
+ FieldAccess.write([field_access_wo_group], {
+ 'perm_write': True,
+ })
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ TestAccess.write([test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # Two access rules with any group allowed
+ FieldAccess.write([field_access_w_group], {
+ 'perm_write': False,
+ })
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ TestAccess.write([test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # Two access rules with both disallowed
+ FieldAccess.write([field_access_wo_group], {
+ 'perm_write': False,
+ })
+ TestAccess.write([test], {})
+ self.assertRaises(UserError, TestAccess.write, [test],
+ {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ self.assertRaises(UserError, TestAccess.write, [test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # One access disallowed for one group
+ FieldAccess.delete([field_access_wo_group])
+ TestAccess.write([test], {})
+ self.assertRaises(UserError, TestAccess.write, [test],
+ {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'ham'})
+ self.assertRaises(UserError, TestAccess.write, [test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # One access allowed for one group
+ FieldAccess.write([field_access_w_group], {
+ 'perm_write': True,
+ })
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ TestAccess.write([test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # One access allowed for one other group
+ group, = Group.create([{'name': 'Test'}])
+ FieldAccess.write([field_access_w_group], {
+ 'group': group.id,
+ })
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ TestAccess.write([test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # One access disallowed for one other group
+ FieldAccess.write([field_access_w_group], {
+ 'perm_write': False,
+ })
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ TestAccess.write([test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # Two access rules on both fields allowed
+ FieldAccess.delete([field_access_w_group])
+
+ field_access1, = FieldAccess.create([{
+ 'field': field1.id,
+ 'group': None,
+ 'perm_write': True,
+ }])
+ field_access2, = FieldAccess.create([{
+ 'field': field2.id,
+ 'group': None,
+ 'perm_write': True,
+ }])
+
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ TestAccess.write([test], {'field2': 'spam'})
+ TestAccess.write([test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # Two access rules on both fields one allowed and one disallowed
+ FieldAccess.write([field_access2], {
+ 'perm_write': False,
+ })
+ TestAccess.write([test], {})
+ TestAccess.write([test], {'field1': 'ham'})
+ self.assertRaises(UserError, TestAccess.write, [test], {
+ 'field2': 'spam'})
+ self.assertRaises(UserError, TestAccess.write, [test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
+
+ # Two access rules on both fields disallowed
+ FieldAccess.write([field_access1], {
+ 'perm_write': False,
+ })
+ TestAccess.write([test], {})
+ self.assertRaises(UserError, TestAccess.write, [test], {
+ 'field1': 'ham'})
+ self.assertRaises(UserError, TestAccess.write, [test], {
+ 'field2': 'spam'})
+ self.assertRaises(UserError, TestAccess.write, [test], {
+ 'field1': 'ham',
+ 'field2': 'spam',
+ })
def suite():
diff --git a/trytond/tests/test_copy.py b/trytond/tests/test_copy.py
index ceb642c..9e8c55f 100644
--- a/trytond/tests/test_copy.py
+++ b/trytond/tests/test_copy.py
@@ -2,75 +2,77 @@
# 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
+
+from trytond.tests.test_tryton import install_module, with_transaction
+from trytond.pool import Pool
class CopyTestCase(unittest.TestCase):
'Test copy'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.one2many = POOL.get('test.copy.one2many')
- self.one2many_target = POOL.get('test.copy.one2many.target')
- self.one2many_reference = POOL.get('test.copy.one2many_reference')
- self.one2many_reference_target = \
- POOL.get('test.copy.one2many_reference.target')
- self.many2many = POOL.get('test.copy.many2many')
- self.many2many_target = POOL.get('test.copy.many2many.target')
- self.many2many_reference = POOL.get('test.copy.many2many_reference')
- self.many2many_reference_target = \
- POOL.get('test.copy.many2many_reference.target')
- def test0130one2many(self):
- 'Test one2many'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT):
- for One2many, Target in (
- (self.one2many, self.one2many_target),
- (self.one2many_reference, self.one2many_reference_target),
- ):
- one2many = One2many(name='Test')
- one2many.one2many = [
- Target(name='Target 1'),
- Target(name='Target 2'),
- ]
- one2many.save()
+ @with_transaction()
+ def test_one2many(self):
+ 'Test copy one2many'
+ pool = Pool()
+ One2many_ = pool.get('test.copy.one2many')
+ One2manyTarget = pool.get('test.copy.one2many.target')
+ One2manyReference = pool.get('test.copy.one2many_reference')
+ One2manyReferenceTarget = \
+ pool.get('test.copy.one2many_reference.target')
+
+ for One2many, Target in (
+ (One2many_, One2manyTarget),
+ (One2manyReference, One2manyReferenceTarget),
+ ):
+ one2many = One2many(name='Test')
+ one2many.one2many = [
+ Target(name='Target 1'),
+ Target(name='Target 2'),
+ ]
+ one2many.save()
+
+ one2many_copy, = One2many.copy([one2many])
- one2many_copy, = One2many.copy([one2many])
+ self.assertNotEqual(one2many, one2many_copy)
+ self.assertEqual(len(one2many.one2many),
+ len(one2many_copy.one2many))
+ self.assertNotEqual(one2many.one2many, one2many_copy.one2many)
+ self.assertEqual([x.name for x in one2many.one2many],
+ [x.name for x in one2many_copy.one2many])
- self.assertNotEqual(one2many, one2many_copy)
- self.assertEqual(len(one2many.one2many),
- len(one2many_copy.one2many))
- self.assertNotEqual(one2many.one2many, one2many_copy.one2many)
- self.assertEqual([x.name for x in one2many.one2many],
- [x.name for x in one2many_copy.one2many])
+ @with_transaction()
+ def test_many2many(self):
+ 'Test copy many2many'
+ pool = Pool()
+ Many2many_ = pool.get('test.copy.many2many')
+ Many2manyTarget = pool.get('test.copy.many2many.target')
+ Many2manyReference = pool.get('test.copy.many2many_reference')
+ Many2manyReferenceTarget = \
+ pool.get('test.copy.many2many_reference.target')
- def test0140many2many(self):
- 'Test many2many'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT):
- for Many2many, Target in (
- (self.many2many, self.many2many_target),
- (self.many2many_reference,
- self.many2many_reference_target),
- ):
- many2many = Many2many(name='Test')
- many2many.many2many = [
- Target(name='Target 1'),
- Target(name='Target 2'),
- ]
- many2many.save()
+ for Many2many, Target in (
+ (Many2many_, Many2manyTarget),
+ (Many2manyReference, Many2manyReferenceTarget),
+ ):
+ many2many = Many2many(name='Test')
+ many2many.many2many = [
+ Target(name='Target 1'),
+ Target(name='Target 2'),
+ ]
+ many2many.save()
- many2many_copy, = Many2many.copy([many2many])
+ many2many_copy, = Many2many.copy([many2many])
- self.assertNotEqual(many2many, many2many_copy)
- self.assertEqual(len(many2many.many2many),
- len(many2many_copy.many2many))
- self.assertEqual(many2many.many2many, many2many_copy.many2many)
- self.assertEqual([x.name for x in many2many.many2many],
- [x.name for x in many2many_copy.many2many])
+ self.assertNotEqual(many2many, many2many_copy)
+ self.assertEqual(len(many2many.many2many),
+ len(many2many_copy.many2many))
+ self.assertEqual(many2many.many2many, many2many_copy.many2many)
+ self.assertEqual([x.name for x in many2many.many2many],
+ [x.name for x in many2many_copy.many2many])
def suite():
diff --git a/trytond/tests/test_exportdata.py b/trytond/tests/test_exportdata.py
index d95921c..6ae8bb1 100644
--- a/trytond/tests/test_exportdata.py
+++ b/trytond/tests/test_exportdata.py
@@ -12,352 +12,353 @@ except ImportError:
import unittest
from decimal import Decimal
import datetime
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
-from trytond.transaction import Transaction
+from trytond.tests.test_tryton import install_module, with_transaction
+from trytond.pool import Pool
class ExportDataTestCase(unittest.TestCase):
'Test export_data'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.export_data = POOL.get('test.export_data')
- self.export_data_target = POOL.get('test.export_data.target')
- self.export_data_relation = POOL.get('test.export_data.relation')
-
- def test0010boolean(self):
- 'Test boolean'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'boolean': True,
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['boolean']), [[True]])
-
- export2, = self.export_data.create([{
- 'boolean': False,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['boolean']),
- [[False]])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2],
- ['boolean']),
- [[True], [False]])
-
- transaction.cursor.rollback()
-
- def test0020integer(self):
- 'Test integer'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'integer': 2,
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['integer']), [[2]])
-
- export2, = self.export_data.create([{
- 'integer': 0,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['integer']), [[0]])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2], ['integer']),
- [[2], [0]])
-
- transaction.cursor.rollback()
-
- def test0030float(self):
- 'Test float'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'float': 1.1,
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['float']), [[1.1]])
-
- export2, = self.export_data.create([{
- 'float': 0,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['float']), [[0]])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2], ['float']),
- [[1.1], [0]])
-
- transaction.cursor.rollback()
-
- def test0040numeric(self):
- 'Test numeric'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'numeric': Decimal('1.1'),
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['numeric']),
- [[Decimal('1.1')]])
-
- export2, = self.export_data.create([{
- 'numeric': Decimal('0'),
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['numeric']),
- [[Decimal('0')]])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2], ['numeric']),
- [[Decimal('1.1')], [Decimal('0')]])
-
- transaction.cursor.rollback()
-
- def test0050char(self):
- 'Test char'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'char': 'test',
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['char']), [['test']])
-
- export2, = self.export_data.create([{
- 'char': None,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['char']), [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2], ['char']),
- [['test'], ['']])
-
- transaction.cursor.rollback()
-
- def test0060text(self):
- 'Test text'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'text': 'test',
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['text']), [['test']])
-
- export2, = self.export_data.create([{
- 'text': None,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['text']), [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2], ['text']),
- [['test'], ['']])
-
- transaction.cursor.rollback()
-
- def test0080date(self):
- 'Test date'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'date': datetime.date(2010, 1, 1),
- }])
- self.assert_(self.export_data.export_data([export1],
- ['date']) == [[datetime.date(2010, 1, 1)]])
-
- export2, = self.export_data.create([{
- 'date': None,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['date']), [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2], ['date']),
- [[datetime.date(2010, 1, 1)], ['']])
-
- transaction.cursor.rollback()
-
- def test0090datetime(self):
- 'Test datetime'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'datetime': datetime.datetime(2010, 1, 1, 12, 0, 0),
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['datetime']),
- [[datetime.datetime(2010, 1, 1, 12, 0, 0)]])
-
- export2, = self.export_data.create([{
- 'datetime': None,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['datetime']),
- [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2], ['datetime']),
- [[datetime.datetime(2010, 1, 1, 12, 0, 0)], ['']])
-
- transaction.cursor.rollback()
-
- def test0100selection(self):
- 'Test selection'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{
- 'selection': 'select1',
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['selection',
- 'selection.translated']),
- [['select1', 'Select 1']])
-
- export2, = self.export_data.create([{
- 'selection': None,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['selection']), [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2],
- ['selection']),
- [['select1'], ['']])
-
- transaction.cursor.rollback()
-
- def test0110many2one(self):
- 'Test many2one'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- target, = self.export_data_target.create([{
- 'name': 'Target Test',
- }])
- export1, = self.export_data.create([{
- 'many2one': target.id,
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['many2one/name']),
- [['Target Test']])
-
- export2, = self.export_data.create([{
- 'many2one': None,
+
+ @with_transaction()
+ def test_boolean(self):
+ 'Test export_data boolean'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'boolean': True,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['boolean']), [[True]])
+
+ export2, = ExportData.create([{
+ 'boolean': False,
}])
- self.assertEqual(
- self.export_data.export_data([export2], ['many2one/name']),
- [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2],
- ['many2one/name']),
- [['Target Test'], ['']])
-
- transaction.cursor.rollback()
-
- def test0120many2many(self):
- 'Test many2many'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- target1, = self.export_data_target.create([{
- 'name': 'Target 1',
- }])
- export1, = self.export_data.create([{
- 'many2many': [('add', [target1])],
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['many2many/name']),
- [['Target 1']])
-
- target2, = self.export_data_target.create([{
- 'name': 'Target 2',
- }])
- self.export_data.write([export1], {
- 'many2many': [('add', [target1.id, target2.id])],
- })
- self.assertEqual(
- self.export_data.export_data([export1], ['id',
- 'many2many/name']),
- [[export1.id, 'Target 1'], ['', 'Target 2']])
-
- export2, = self.export_data.create([{
- 'many2many': None,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['many2many/name']),
- [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2],
- ['id', 'many2many/name']),
- [[export1.id, 'Target 1'], ['', 'Target 2'], [export2.id, '']])
-
- transaction.cursor.rollback()
-
- def test0130one2many(self):
- 'Test one2many'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- export1, = self.export_data.create([{}])
- self.export_data_target.create([{
- 'name': 'Target 1',
- 'one2many': export1.id,
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['one2many/name']),
- [['Target 1']])
-
- self.export_data_target.create([{
- 'name': 'Target 2',
- 'one2many': export1.id,
- }])
- self.assertEqual(
- self.export_data.export_data([export1],
- ['id', 'one2many/name']),
- [[export1.id, 'Target 1'], ['', 'Target 2']])
-
- export2, = self.export_data.create([{}])
- self.assertEqual(
- self.export_data.export_data([export2], ['one2many/name']),
- [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2], ['id',
- 'one2many/name']),
- [[export1.id, 'Target 1'], ['', 'Target 2'], [export2.id, '']])
-
- transaction.cursor.rollback()
-
- def test0140reference(self):
- 'Test reference'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- target1, = self.export_data_target.create([{}])
- export1, = self.export_data.create([{
- 'reference': str(target1),
- }])
- self.assertEqual(
- self.export_data.export_data([export1], ['reference']),
- [[str(target1)]])
-
- export2, = self.export_data.create([{
- 'reference': None,
- }])
- self.assertEqual(
- self.export_data.export_data([export2], ['reference']), [['']])
-
- self.assertEqual(
- self.export_data.export_data([export1, export2],
- ['reference']),
- [[str(target1)], ['']])
-
- transaction.cursor.rollback()
+ self.assertEqual(
+ ExportData.export_data([export2], ['boolean']),
+ [[False]])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2],
+ ['boolean']),
+ [[True], [False]])
+
+ @with_transaction()
+ def test_integer(self):
+ 'Test export_data integer'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'integer': 2,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['integer']), [[2]])
+
+ export2, = ExportData.create([{
+ 'integer': 0,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['integer']), [[0]])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2], ['integer']),
+ [[2], [0]])
+
+ @with_transaction()
+ def test_float(self):
+ 'Test export_data float'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'float': 1.1,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['float']), [[1.1]])
+
+ export2, = ExportData.create([{
+ 'float': 0,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['float']), [[0]])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2], ['float']),
+ [[1.1], [0]])
+
+ @with_transaction()
+ def test_numeric(self):
+ 'Test export_data numeric'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'numeric': Decimal('1.1'),
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['numeric']),
+ [[Decimal('1.1')]])
+
+ export2, = ExportData.create([{
+ 'numeric': Decimal('0'),
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['numeric']),
+ [[Decimal('0')]])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2], ['numeric']),
+ [[Decimal('1.1')], [Decimal('0')]])
+
+ @with_transaction()
+ def test_char(self):
+ 'Test export_data char'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'char': 'test',
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['char']), [['test']])
+
+ export2, = ExportData.create([{
+ 'char': None,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['char']), [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2], ['char']),
+ [['test'], ['']])
+
+ @with_transaction()
+ def test_text(self):
+ 'Test export_data text'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'text': 'test',
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['text']), [['test']])
+
+ export2, = ExportData.create([{
+ 'text': None,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['text']), [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2], ['text']),
+ [['test'], ['']])
+
+ @with_transaction()
+ def test_date(self):
+ 'Test export_data date'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'date': datetime.date(2010, 1, 1),
+ }])
+ self.assert_(ExportData.export_data([export1],
+ ['date']) == [[datetime.date(2010, 1, 1)]])
+
+ export2, = ExportData.create([{
+ 'date': None,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['date']), [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2], ['date']),
+ [[datetime.date(2010, 1, 1)], ['']])
+
+ @with_transaction()
+ def test_datetime(self):
+ 'Test export_data datetime'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'datetime': datetime.datetime(2010, 1, 1, 12, 0, 0),
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['datetime']),
+ [[datetime.datetime(2010, 1, 1, 12, 0, 0)]])
+
+ export2, = ExportData.create([{
+ 'datetime': None,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['datetime']),
+ [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2], ['datetime']),
+ [[datetime.datetime(2010, 1, 1, 12, 0, 0)], ['']])
+
+ @with_transaction()
+ def test_selection(self):
+ 'Test export_data selection'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'selection': 'select1',
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['selection',
+ 'selection.translated']),
+ [['select1', 'Select 1']])
+
+ export2, = ExportData.create([{
+ 'selection': None,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['selection']), [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2],
+ ['selection']),
+ [['select1'], ['']])
+
+ @with_transaction()
+ def test_many2one(self):
+ 'Test export_data many2one'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+ ExportDataTarget = pool.get('test.export_data.target')
+
+ target, = ExportDataTarget.create([{
+ 'name': 'Target Test',
+ }])
+ export1, = ExportData.create([{
+ 'many2one': target.id,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['many2one/name']),
+ [['Target Test']])
+
+ export2, = ExportData.create([{
+ 'many2one': None,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['many2one/name']),
+ [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2],
+ ['many2one/name']),
+ [['Target Test'], ['']])
+
+ @with_transaction()
+ def test_many2many(self):
+ 'Test export_data many2many'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+ ExportDataTarget = pool.get('test.export_data.target')
+
+ target1, = ExportDataTarget.create([{
+ 'name': 'Target 1',
+ }])
+ export1, = ExportData.create([{
+ 'many2many': [('add', [target1])],
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['many2many/name']),
+ [['Target 1']])
+
+ target2, = ExportDataTarget.create([{
+ 'name': 'Target 2',
+ }])
+ ExportData.write([export1], {
+ 'many2many': [('add', [target1.id, target2.id])],
+ })
+ self.assertEqual(
+ ExportData.export_data([export1], ['id',
+ 'many2many/name']),
+ [[export1.id, 'Target 1'], ['', 'Target 2']])
+
+ export2, = ExportData.create([{
+ 'many2many': None,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['many2many/name']),
+ [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2],
+ ['id', 'many2many/name']),
+ [[export1.id, 'Target 1'], ['', 'Target 2'], [export2.id, '']])
+
+ @with_transaction()
+ def test_one2many(self):
+ 'Test export_data one2many'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+ ExportDataTarget = pool.get('test.export_data.target')
+
+ export1, = ExportData.create([{}])
+ ExportDataTarget.create([{
+ 'name': 'Target 1',
+ 'one2many': export1.id,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['one2many/name']),
+ [['Target 1']])
+
+ ExportDataTarget.create([{
+ 'name': 'Target 2',
+ 'one2many': export1.id,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1],
+ ['id', 'one2many/name']),
+ [[export1.id, 'Target 1'], ['', 'Target 2']])
+
+ export2, = ExportData.create([{}])
+ self.assertEqual(
+ ExportData.export_data([export2], ['one2many/name']),
+ [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2], ['id',
+ 'one2many/name']),
+ [[export1.id, 'Target 1'], ['', 'Target 2'], [export2.id, '']])
+
+ @with_transaction()
+ def test_reference(self):
+ 'Test export_data reference'
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+ ExportDataTarget = pool.get('test.export_data.target')
+
+ target1, = ExportDataTarget.create([{}])
+ export1, = ExportData.create([{
+ 'reference': str(target1),
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['reference']),
+ [[str(target1)]])
+
+ export2, = ExportData.create([{
+ 'reference': None,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export2], ['reference']), [['']])
+
+ self.assertEqual(
+ ExportData.export_data([export1, export2],
+ ['reference']),
+ [[str(target1)], ['']])
def suite():
diff --git a/trytond/tests/test_field_context.py b/trytond/tests/test_field_context.py
index 99a9cbc..6a71a05 100644
--- a/trytond/tests/test_field_context.py
+++ b/trytond/tests/test_field_context.py
@@ -3,30 +3,31 @@
import unittest
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
-from trytond.transaction import Transaction
+from trytond.tests.test_tryton import install_module, with_transaction
+from trytond.pool import Pool
class FieldContextTestCase(unittest.TestCase):
"Test context on field"
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
+ @with_transaction()
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')
+ pool = Pool()
+ Parent = pool.get('test.field_context.parent')
+ Child = pool.get('test.field_context.child')
+ 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():
diff --git a/trytond/tests/test_fields.py b/trytond/tests/test_fields.py
index c6c64cf..10b0b78 100644
--- a/trytond/tests/test_fields.py
+++ b/trytond/tests/test_fields.py
@@ -12,3499 +12,3626 @@ except ImportError:
import unittest
import datetime
from decimal import Decimal
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.model import fields
+from trytond.pool import Pool
class FieldsTestCase(unittest.TestCase):
'Test Fields'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.boolean = POOL.get('test.boolean')
- self.boolean_default = POOL.get('test.boolean_default')
-
- self.integer = POOL.get('test.integer')
- self.integer_default = POOL.get('test.integer_default')
- self.integer_required = POOL.get('test.integer_required')
- self.integer_domain = POOL.get('test.integer_domain')
-
- self.float = POOL.get('test.float')
- self.float_default = POOL.get('test.float_default')
- self.float_required = POOL.get('test.float_required')
- self.float_digits = POOL.get('test.float_digits')
-
- self.numeric = POOL.get('test.numeric')
- self.numeric_default = POOL.get('test.numeric_default')
- self.numeric_required = POOL.get('test.numeric_required')
- self.numeric_digits = POOL.get('test.numeric_digits')
-
- self.char = POOL.get('test.char')
- self.char_default = POOL.get('test.char_default')
- self.char_required = POOL.get('test.char_required')
- self.char_size = POOL.get('test.char_size')
- self.char_translate = POOL.get('test.char_translate')
-
- self.text = POOL.get('test.text')
- self.text_default = POOL.get('test.text_default')
- self.text_required = POOL.get('test.text_required')
- self.text_size = POOL.get('test.text_size')
- self.text_translate = POOL.get('test.text_translate')
-
- self.date = POOL.get('test.date')
- self.date_default = POOL.get('test.date_default')
- self.date_required = POOL.get('test.date_required')
-
- self.datetime = POOL.get('test.datetime')
- self.datetime_default = POOL.get('test.datetime_default')
- self.datetime_required = POOL.get('test.datetime_required')
- self.datetime_format = POOL.get('test.datetime_format')
-
- self.time = POOL.get('test.time')
- self.time_default = POOL.get('test.time_default')
- self.time_required = POOL.get('test.time_required')
- self.time_format = POOL.get('test.time_format')
-
- self.timedelta = POOL.get('test.timedelta')
- self.timedelta_default = POOL.get('test.timedelta_default')
- self.timedelta_required = POOL.get('test.timedelta_required')
-
- self.one2one = POOL.get('test.one2one')
- self.one2one_target = POOL.get('test.one2one.target')
- self.one2one_required = POOL.get('test.one2one_required')
- self.one2one_domain = POOL.get('test.one2one_domain')
-
- self.one2many = POOL.get('test.one2many')
- self.one2many_target = POOL.get('test.one2many.target')
- self.one2many_required = POOL.get('test.one2many_required')
- self.one2many_reference = POOL.get('test.one2many_reference')
- self.one2many_reference_target = POOL.get(
- 'test.one2many_reference.target')
- self.one2many_size = POOL.get('test.one2many_size')
- self.one2many_size_pyson = POOL.get('test.one2many_size_pyson')
-
- self.many2many = POOL.get('test.many2many')
- self.many2many_target = POOL.get('test.many2many.target')
- self.many2many_required = POOL.get('test.many2many_required')
- self.many2many_reference = POOL.get('test.many2many_reference')
- self.many2many_reference_target = POOL.get(
- 'test.many2many_reference.target')
- self.many2many_size = POOL.get('test.many2many_size')
- self.many2many_size_target = POOL.get('test.many2many_size.target')
-
- self.reference = POOL.get('test.reference')
- self.reference_target = POOL.get('test.reference.target')
- self.reference_required = POOL.get('test.reference_required')
-
- self.property_ = POOL.get('test.property')
- self.ir_property = POOL.get('ir.property')
- self.model_field = POOL.get('ir.model.field')
-
- self.selection = POOL.get('test.selection')
- self.selection_required = POOL.get('test.selection_required')
-
- self.dict_ = POOL.get('test.dict')
- self.dict_schema = POOL.get('test.dict.schema')
- self.dict_default = POOL.get('test.dict_default')
- self.dict_required = POOL.get('test.dict_required')
-
- self.binary = POOL.get('test.binary')
- self.binary_default = POOL.get('test.binary_default')
- self.binary_required = POOL.get('test.binary_required')
-
- self.m2o_domain_validation = POOL.get('test.many2one_domainvalidation')
- self.m2o_orderby = POOL.get('test.many2one_orderby')
- self.m2o_target = POOL.get('test.many2one_target')
- self.m2o_search = POOL.get('test.many2one_search')
-
- def test0010boolean(self):
+
+ @with_transaction()
+ def test_boolean(self):
'Test Boolean'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- boolean1, = self.boolean.create([{
- 'boolean': True,
- }])
- self.assert_(boolean1)
- self.assertEqual(boolean1.boolean, True)
+ pool = Pool()
+ Boolean = pool.get('test.boolean')
+ BooleanDefault = pool.get('test.boolean_default')
- booleans = self.boolean.search([
- ('boolean', '=', True),
- ])
- self.assertEqual(booleans, [boolean1])
+ boolean1, = Boolean.create([{
+ 'boolean': True,
+ }])
+ self.assert_(boolean1)
+ self.assertEqual(boolean1.boolean, True)
+
+ booleans = Boolean.search([
+ ('boolean', '=', True),
+ ])
+ self.assertEqual(booleans, [boolean1])
+
+ booleans = Boolean.search([
+ ('boolean', '!=', True),
+ ])
+ self.assertEqual(booleans, [])
+
+ booleans = Boolean.search([
+ ('boolean', 'in', [True]),
+ ])
+ self.assertEqual(booleans, [boolean1])
+
+ booleans = Boolean.search([
+ ('boolean', 'in', [False]),
+ ])
+ self.assertEqual(booleans, [])
+
+ booleans = Boolean.search([
+ ('boolean', 'not in', [True]),
+ ])
+ self.assertEqual(booleans, [])
+
+ booleans = Boolean.search([
+ ('boolean', 'not in', [False]),
+ ])
+ self.assertEqual(booleans, [boolean1])
+
+ boolean2, = Boolean.create([{
+ 'boolean': False,
+ }])
+ self.assert_(boolean2)
+ self.assertEqual(boolean2.boolean, False)
+
+ booleans = Boolean.search([
+ ('boolean', '=', False),
+ ])
+ self.assertEqual(booleans, [boolean2])
+
+ booleans = Boolean.search([
+ ('boolean', 'in', [True, False]),
+ ])
+ self.assertEqual(booleans, [boolean1, boolean2])
+
+ booleans = Boolean.search([
+ ('boolean', 'not in', [True, False]),
+ ])
+ self.assertEqual(booleans, [])
+
+ boolean3, = Boolean.create([{}])
+ self.assert_(boolean3)
+ self.assertEqual(boolean3.boolean, False)
+
+ # Test search with NULL value
+ boolean4, = Boolean.create([{
+ 'boolean': None,
+ }])
+ self.assert_(boolean4)
+
+ booleans = Boolean.search([
+ ('boolean', '=', False),
+ ])
+ self.assertEqual(booleans,
+ [boolean2, boolean3, boolean4])
+
+ booleans = Boolean.search([
+ ('boolean', '!=', False),
+ ])
+ self.assertEqual(booleans, [boolean1])
+
+ boolean4, = BooleanDefault.create([{}])
+ self.assert_(boolean4)
+ self.assertTrue(boolean4.boolean)
+
+ Boolean.write([boolean1], {
+ 'boolean': False,
+ })
+ self.assertEqual(boolean1.boolean, False)
+
+ Boolean.write([boolean2], {
+ 'boolean': True,
+ })
+ self.assertEqual(boolean2.boolean, True)
+
+ @with_transaction()
+ def test_integer(self):
+ 'Test Integer'
+ pool = Pool()
+ Integer = pool.get('test.integer')
+ IntegerDefault = pool.get('test.integer_default')
+ IntegerRequired = pool.get('test.integer_required')
+ transaction = Transaction()
- booleans = self.boolean.search([
- ('boolean', '!=', True),
- ])
- self.assertEqual(booleans, [])
+ integer1, = Integer.create([{
+ 'integer': 1,
+ }])
+ self.assert_(integer1)
+ self.assertEqual(integer1.integer, 1)
+
+ integers = Integer.search([
+ ('integer', '=', 1),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', '=', 0),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', '!=', 1),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', '!=', 0),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', 'in', [1]),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', 'in', [0]),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', 'in', []),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', 'not in', [1]),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', 'not in', [0]),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', 'not in', []),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', '<', 5),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', '<', -5),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', '<', 1),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', '<=', 5),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', '<=', -5),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', '<=', 1),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', '>', 5),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', '>', -5),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', '>', 1),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', '>=', 5),
+ ])
+ self.assertEqual(integers, [])
+
+ integers = Integer.search([
+ ('integer', '>=', -5),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integers = Integer.search([
+ ('integer', '>=', 1),
+ ])
+ self.assertEqual(integers, [integer1])
+
+ integer2, = Integer.create([{
+ 'integer': 0,
+ }])
+ self.assert_(integer2)
+ self.assertEqual(integer2.integer, 0)
+
+ integers = Integer.search([
+ ('integer', '=', 0),
+ ])
+ self.assertEqual(integers, [integer2])
+
+ integers = Integer.search([
+ ('integer', 'in', [0, 1]),
+ ])
+ self.assertEqual(integers, [integer1, integer2])
+
+ integers = Integer.search([
+ ('integer', 'not in', [0, 1]),
+ ])
+ self.assertEqual(integers, [])
+
+ integer3, = Integer.create([{}])
+ self.assert_(integer3)
+ self.assertEqual(integer3.integer, None)
+
+ integer4, = IntegerDefault.create([{}])
+ self.assert_(integer4)
+ self.assertEqual(integer4.integer, 5)
+
+ Integer.write([integer1], {
+ 'integer': 0,
+ })
+ self.assertEqual(integer1.integer, 0)
+
+ Integer.write([integer2], {
+ 'integer': 1,
+ })
+ self.assertEqual(integer2.integer, 1)
+
+ self.assertRaises(Exception, Integer.create, [{
+ 'integer': 'test',
+ }])
- booleans = self.boolean.search([
- ('boolean', 'in', [True]),
- ])
- self.assertEqual(booleans, [boolean1])
+ self.assertRaises(Exception, Integer.write, [integer1], {
+ 'integer': 'test',
+ })
- booleans = self.boolean.search([
- ('boolean', 'in', [False]),
- ])
- self.assertEqual(booleans, [])
+ # We should catch UserError but mysql does not raise an
+ # IntegrityError but an OperationalError
+ self.assertRaises(Exception, IntegerRequired.create, [{}])
+ transaction.rollback()
- booleans = self.boolean.search([
- ('boolean', 'not in', [True]),
- ])
- self.assertEqual(booleans, [])
+ integer5, = IntegerRequired.create([{
+ 'integer': 0,
+ }])
+ self.assert_(integer5)
+ self.assertEqual(integer5.integer, 0)
- booleans = self.boolean.search([
- ('boolean', 'not in', [False]),
- ])
- self.assertEqual(booleans, [boolean1])
+ transaction.rollback()
- boolean2, = self.boolean.create([{
- 'boolean': False,
- }])
- self.assert_(boolean2)
- self.assertEqual(boolean2.boolean, False)
+ @with_transaction()
+ def test_integer_with_domain(self):
+ 'Test Integer with domain'
+ pool = Pool()
+ IntegerDomain = pool.get('test.integer_domain')
+ IntegerDomain.create([{
+ 'integer': 100,
+ }])
+ self.assertRaises(UserError, IntegerDomain.create, [{
+ 'integer': 10,
+ }])
- booleans = self.boolean.search([
- ('boolean', '=', False),
- ])
- self.assertEqual(booleans, [boolean2])
+ @with_transaction()
+ def test_float(self):
+ 'Test Float'
+ pool = Pool()
+ Float = pool.get('test.float')
+ FloatDefault = pool.get('test.float_default')
+ FloatRequired = pool.get('test.float_required')
+ FloatDigits = pool.get('test.float_digits')
+ transaction = Transaction()
+
+ float1, = Float.create([{
+ 'float': 1.1,
+ }])
+ self.assert_(float1)
+ self.assertEqual(float1.float, 1.1)
+
+ floats = Float.search([
+ ('float', '=', 1.1),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', '=', 0),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', '!=', 1.1),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', '!=', 0),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', 'in', [1.1]),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', 'in', [0]),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', 'in', []),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', 'not in', [1.1]),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', 'not in', [0]),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', 'not in', []),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', '<', 5),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', '<', -5),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', '<', 1.1),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', '<=', 5),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', '<=', -5),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', '<=', 1.1),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', '>', 5),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', '>', -5),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', '>', 1.1),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', '>=', 5),
+ ])
+ self.assertEqual(floats, [])
+
+ floats = Float.search([
+ ('float', '>=', -5),
+ ])
+ self.assertEqual(floats, [float1])
+
+ floats = Float.search([
+ ('float', '>=', 1.1),
+ ])
+ self.assertEqual(floats, [float1])
+
+ float2, = Float.create([{
+ 'float': 0,
+ }])
+ self.assert_(float2)
+ self.assertEqual(float2.float, 0)
+
+ floats = Float.search([
+ ('float', '=', 0),
+ ])
+ self.assertEqual(floats, [float2])
+
+ floats = Float.search([
+ ('float', 'in', [0, 1.1]),
+ ])
+ self.assertEqual(floats, [float1, float2])
+
+ floats = Float.search([
+ ('float', 'not in', [0, 1.1]),
+ ])
+ self.assertEqual(floats, [])
+
+ float3, = Float.create([{}])
+ self.assert_(float3)
+ self.assertEqual(float3.float, None)
+
+ float4, = FloatDefault.create([{}])
+ self.assert_(float4)
+ self.assertEqual(float4.float, 5.5)
+
+ Float.write([float1], {
+ 'float': 0,
+ })
+ self.assertEqual(float1.float, 0)
+
+ Float.write([float2], {
+ 'float': 1.1,
+ })
+ self.assertEqual(float2.float, 1.1)
+
+ self.assertRaises(Exception, Float.create, [{
+ 'float': 'test',
+ }])
- booleans = self.boolean.search([
- ('boolean', 'in', [True, False]),
- ])
- self.assertEqual(booleans, [boolean1, boolean2])
+ self.assertRaises(Exception, Float.write, [float1], {
+ 'float': 'test',
+ })
- booleans = self.boolean.search([
- ('boolean', 'not in', [True, False]),
- ])
- self.assertEqual(booleans, [])
+ self.assertRaises(UserError, FloatRequired.create, [{}])
+ transaction.rollback()
- boolean3, = self.boolean.create([{}])
- self.assert_(boolean3)
- self.assertEqual(boolean3.boolean, False)
+ float5, = FloatRequired.create([{
+ 'float': 0.0,
+ }])
+ self.assertEqual(float5.float, 0.0)
- # Test search with NULL value
- boolean4, = self.boolean.create([{
- 'boolean': None,
- }])
- self.assert_(boolean4)
+ float6, = FloatDigits.create([{
+ 'digits': 1,
+ 'float': 1.1,
+ }])
+ self.assert_(float6)
- booleans = self.boolean.search([
- ('boolean', '=', False),
- ])
- self.assertEqual(booleans,
- [boolean2, boolean3, boolean4])
+ self.assertRaises(UserError, FloatDigits.create, [{
+ 'digits': 1,
+ 'float': 1.11,
+ }])
- booleans = self.boolean.search([
- ('boolean', '!=', False),
- ])
- self.assertEqual(booleans, [boolean1])
+ self.assertRaises(UserError, FloatDigits.write,
+ [float6], {
+ 'float': 1.11,
+ })
- boolean4, = self.boolean_default.create([{}])
- self.assert_(boolean4)
- self.assertTrue(boolean4.boolean)
+ self.assertRaises(UserError, FloatDigits.write,
+ [float6], {
+ 'digits': 0,
+ })
- self.boolean.write([boolean1], {
- 'boolean': False,
- })
- self.assertEqual(boolean1.boolean, False)
+ float7, = Float.create([{
+ 'float': 0.123456789012345,
+ }])
+ self.assertEqual(float7.float, 0.123456789012345)
- self.boolean.write([boolean2], {
- 'boolean': True,
- })
- self.assertEqual(boolean2.boolean, True)
+ @with_transaction()
+ def test_float_search_none(self):
+ 'Test float search with None'
+ pool = Pool()
+ Float = pool.get('test.float')
- transaction.cursor.rollback()
+ float_none, float0, float1 = Float.create([{
+ 'float': None,
+ }, {
+ 'float': 0,
+ }, {
+ 'float': 1,
+ }])
+ self.assertEqual([float_none], Float.search([
+ ('float', '=', None),
+ ]))
+ self.assertEqual([float0], Float.search([
+ ('float', '=', 0),
+ ]))
+ self.assertEqual([float1], Float.search([
+ ('float', '>', 0),
+ ]))
+
+ self.assertEqual([float0, float1], Float.search([
+ ('float', '!=', None),
+ ]))
+ self.assertEqual([float1], Float.search([
+ ('float', '!=', 0),
+ ]))
+ self.assertEqual([float0], Float.search([
+ ('float', '<', 1),
+ ]))
+
+ self.assertEqual([float_none, float1], Float.search([
+ 'OR',
+ ('float', '>', 0),
+ ('float', '=', None),
+ ]))
+
+ @with_transaction()
+ def test_numeric(self):
+ 'Test Numeric'
+ pool = Pool()
+ Numeric = pool.get('test.numeric')
+ NumericDefault = pool.get('test.numeric_default')
+ NumericRequired = pool.get('test.numeric_required')
+ NumericDigits = pool.get('test.numeric_digits')
+ transaction = Transaction()
+
+ numeric1, = Numeric.create([{
+ 'numeric': Decimal('1.1'),
+ }])
+ self.assert_(numeric1)
+ self.assertEqual(numeric1.numeric, Decimal('1.1'))
+
+ numerics = Numeric.search([
+ ('numeric', '=', Decimal('1.1')),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', '=', Decimal('0')),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', '!=', Decimal('1.1')),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', '!=', Decimal('0')),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', 'in', [Decimal('1.1')]),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', 'in', [Decimal('0')]),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', 'in', []),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', 'not in', [Decimal('1.1')]),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', 'not in', [Decimal('0')]),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', 'not in', []),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', '<', Decimal('5')),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', '<', Decimal('-5')),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', '<', Decimal('1.1')),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', '<=', Decimal('5')),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', '<=', Decimal('-5')),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', '<=', Decimal('1.1')),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', '>', Decimal('5')),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', '>', Decimal('-5')),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', '>', Decimal('1.1')),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', '>=', Decimal('5')),
+ ])
+ self.assertEqual(numerics, [])
+
+ numerics = Numeric.search([
+ ('numeric', '>=', Decimal('-5')),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numerics = Numeric.search([
+ ('numeric', '>=', Decimal('1.1')),
+ ])
+ self.assertEqual(numerics, [numeric1])
+
+ numeric2, = Numeric.create([{
+ 'numeric': Decimal('0'),
+ }])
+ self.assert_(numeric2)
+ self.assertEqual(numeric2.numeric, Decimal('0'))
+
+ numerics = Numeric.search([
+ ('numeric', '=', Decimal('0')),
+ ])
+ self.assertEqual(numerics, [numeric2])
+
+ numerics = Numeric.search([
+ ('numeric', 'in', [Decimal('0'), Decimal('1.1')]),
+ ])
+ self.assertEqual(numerics, [numeric1, numeric2])
+
+ numerics = Numeric.search([
+ ('numeric', 'not in', [Decimal('0'), Decimal('1.1')]),
+ ])
+ self.assertEqual(numerics, [])
+
+ numeric3, = Numeric.create([{}])
+ self.assert_(numeric3)
+ self.assertEqual(numeric3.numeric, None)
+
+ numeric4, = NumericDefault.create([{}])
+ self.assert_(numeric4)
+ self.assertEqual(numeric4.numeric, Decimal('5.5'))
+
+ Numeric.write([numeric1], {
+ 'numeric': Decimal('0'),
+ })
+ self.assertEqual(numeric1.numeric, Decimal('0'))
+
+ Numeric.write([numeric2], {
+ 'numeric': Decimal('1.1'),
+ })
+ self.assertEqual(numeric2.numeric, Decimal('1.1'))
+
+ self.assertRaises(Exception, Numeric.create, [{
+ 'numeric': 'test',
+ }])
- def test0020integer(self):
- 'Test Integer'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- integer1, = self.integer.create([{
- 'integer': 1,
- }])
- self.assert_(integer1)
- self.assertEqual(integer1.integer, 1)
+ self.assertRaises(Exception, Numeric.write, [numeric1], {
+ 'numeric': 'test',
+ })
- integers = self.integer.search([
- ('integer', '=', 1),
- ])
- self.assertEqual(integers, [integer1])
+ self.assertRaises(UserError, NumericRequired.create, [{}])
+ transaction.rollback()
- integers = self.integer.search([
- ('integer', '=', 0),
- ])
- self.assertEqual(integers, [])
+ numeric5, = NumericRequired.create([{
+ 'numeric': Decimal(0),
+ }])
+ self.assertEqual(numeric5.numeric, 0)
- integers = self.integer.search([
- ('integer', '!=', 1),
- ])
- self.assertEqual(integers, [])
+ numeric6, = NumericDigits.create([{
+ 'digits': 1,
+ 'numeric': Decimal('1.1'),
+ }])
+ self.assert_(numeric6)
- integers = self.integer.search([
- ('integer', '!=', 0),
- ])
- self.assertEqual(integers, [integer1])
+ self.assertRaises(UserError, NumericDigits.create, [{
+ 'digits': 1,
+ 'numeric': Decimal('1.11'),
+ }])
- integers = self.integer.search([
- ('integer', 'in', [1]),
- ])
- self.assertEqual(integers, [integer1])
+ self.assertRaises(UserError, NumericDigits.write,
+ [numeric6], {
+ 'numeric': Decimal('1.11'),
+ })
- integers = self.integer.search([
- ('integer', 'in', [0]),
- ])
- self.assertEqual(integers, [])
+ self.assertRaises(UserError, NumericDigits.write,
+ [numeric6], {
+ 'numeric': Decimal('0.10000000000000001'),
+ })
- integers = self.integer.search([
- ('integer', 'in', []),
- ])
- self.assertEqual(integers, [])
+ self.assertRaises(UserError, NumericDigits.write,
+ [numeric6], {
+ 'digits': 0,
+ })
- integers = self.integer.search([
- ('integer', 'not in', [1]),
- ])
- self.assertEqual(integers, [])
+ numeric7, = Numeric.create([{
+ 'numeric': Decimal('0.1234567890123456789'),
+ }])
+ self.assertEqual(numeric7.numeric,
+ Decimal('0.1234567890123456789'))
- integers = self.integer.search([
- ('integer', 'not in', [0]),
- ])
- self.assertEqual(integers, [integer1])
+ @with_transaction()
+ def test_numeric_search_cast(self):
+ 'Test numeric search cast'
+ pool = Pool()
+ Numeric = pool.get('test.numeric')
- integers = self.integer.search([
- ('integer', 'not in', []),
- ])
- self.assertEqual(integers, [integer1])
+ numeric1, numeric2 = Numeric.create([{
+ 'numeric': Decimal('1.1'),
+ }, {
+ 'numeric': Decimal('100.0'),
+ }])
+ numerics = Numeric.search([
+ ('numeric', '<', Decimal('5')),
+ ])
+ self.assertEqual(numerics, [numeric1])
- integers = self.integer.search([
- ('integer', '<', 5),
- ])
- self.assertEqual(integers, [integer1])
+ @with_transaction()
+ def test_numeric_search_none(self):
+ 'Test numeric search with None'
+ pool = Pool()
+ Numeric = pool.get('test.numeric')
+
+ numeric_none, numeric0, numeric1 = Numeric.create([{
+ 'numeric': None,
+ }, {
+ 'numeric': 0,
+ }, {
+ 'numeric': 1,
+ }])
+ self.assertEqual([numeric_none], Numeric.search([
+ ('numeric', '=', None),
+ ]))
+ self.assertEqual([numeric0], Numeric.search([
+ ('numeric', '=', 0),
+ ]))
+ self.assertEqual([numeric1], Numeric.search([
+ ('numeric', '>', 0),
+ ]))
+
+ self.assertEqual([numeric0, numeric1], Numeric.search([
+ ('numeric', '!=', None),
+ ]))
+ self.assertEqual([numeric1], Numeric.search([
+ ('numeric', '!=', 0),
+ ]))
+ self.assertEqual([numeric0], Numeric.search([
+ ('numeric', '<', 1),
+ ]))
+
+ self.assertEqual([numeric_none, numeric1], Numeric.search([
+ 'OR',
+ ('numeric', '>', 0),
+ ('numeric', '=', None),
+ ]))
+
+ @with_transaction()
+ def test_char(self):
+ 'Test Char'
+ pool = Pool()
+ Char = pool.get('test.char')
+ CharDefault = pool.get('test.char_default')
+ CharRequired = pool.get('test.char_required')
+ CharSize = pool.get('test.char_size')
+ CharTranslate = pool.get('test.char_translate')
+ transaction = Transaction()
+
+ for char in (CharTranslate, Char):
+ char1, = char.create([{
+ 'char': 'Test',
+ }])
+ self.assert_(char1)
+ self.assertEqual(char1.char, 'Test')
- integers = self.integer.search([
- ('integer', '<', -5),
+ chars = char.search([
+ ('char', '=', 'Test'),
])
- self.assertEqual(integers, [])
+ self.assertEqual(chars, [char1])
- integers = self.integer.search([
- ('integer', '<', 1),
+ chars = char.search([
+ ('char', '=', 'Foo'),
])
- self.assertEqual(integers, [])
+ self.assertEqual(chars, [])
- integers = self.integer.search([
- ('integer', '<=', 5),
+ chars = char.search([
+ ('char', '=', None),
])
- self.assertEqual(integers, [integer1])
+ self.assertEqual(chars, [])
- integers = self.integer.search([
- ('integer', '<=', -5),
+ chars = char.search([
+ ('char', '!=', 'Test'),
])
- self.assertEqual(integers, [])
+ self.assertEqual(chars, [])
- integers = self.integer.search([
- ('integer', '<=', 1),
+ chars = char.search([
+ ('char', '!=', 'Foo'),
])
- self.assertEqual(integers, [integer1])
+ self.assertEqual(chars, [char1])
- integers = self.integer.search([
- ('integer', '>', 5),
+ chars = char.search([
+ ('char', '!=', None),
])
- self.assertEqual(integers, [])
+ self.assertEqual(chars, [char1])
- integers = self.integer.search([
- ('integer', '>', -5),
+ chars = char.search([
+ ('char', 'in', ['Test']),
])
- self.assertEqual(integers, [integer1])
+ self.assertEqual(chars, [char1])
- integers = self.integer.search([
- ('integer', '>', 1),
+ chars = char.search([
+ ('char', 'in', ['Foo']),
])
- self.assertEqual(integers, [])
+ self.assertEqual(chars, [])
- integers = self.integer.search([
- ('integer', '>=', 5),
+ chars = char.search([
+ ('char', 'in', [None]),
])
- self.assertEqual(integers, [])
+ self.assertEqual(chars, [])
- integers = self.integer.search([
- ('integer', '>=', -5),
+ chars = char.search([
+ ('char', 'in', []),
])
- self.assertEqual(integers, [integer1])
+ self.assertEqual(chars, [])
- integers = self.integer.search([
- ('integer', '>=', 1),
+ chars = char.search([
+ ('char', 'not in', ['Test']),
])
- self.assertEqual(integers, [integer1])
-
- integer2, = self.integer.create([{
- 'integer': 0,
- }])
- self.assert_(integer2)
- self.assertEqual(integer2.integer, 0)
+ self.assertEqual(chars, [])
- integers = self.integer.search([
- ('integer', '=', 0),
+ chars = char.search([
+ ('char', 'not in', ['Foo']),
])
- self.assertEqual(integers, [integer2])
+ self.assertEqual(chars, [char1])
- integers = self.integer.search([
- ('integer', 'in', [0, 1]),
+ chars = char.search([
+ ('char', 'not in', [None]),
])
- self.assertEqual(integers, [integer1, integer2])
+ self.assertEqual(chars, [char1])
- integers = self.integer.search([
- ('integer', 'not in', [0, 1]),
+ chars = char.search([
+ ('char', 'not in', []),
])
- self.assertEqual(integers, [])
-
- integer3, = self.integer.create([{}])
- self.assert_(integer3)
- self.assertEqual(integer3.integer, None)
-
- integer4, = self.integer_default.create([{}])
- self.assert_(integer4)
- self.assertEqual(integer4.integer, 5)
-
- self.integer.write([integer1], {
- 'integer': 0,
- })
- self.assertEqual(integer1.integer, 0)
-
- self.integer.write([integer2], {
- 'integer': 1,
- })
- self.assertEqual(integer2.integer, 1)
-
- self.assertRaises(Exception, self.integer.create, [{
- 'integer': 'test',
- }])
-
- self.assertRaises(Exception, self.integer.write, [integer1], {
- 'integer': 'test',
- })
-
- # We should catch UserError but mysql does not raise an
- # IntegrityError but an OperationalError
- self.assertRaises(Exception, self.integer_required.create, [{}])
- transaction.cursor.rollback()
-
- integer5, = self.integer_required.create([{
- 'integer': 0,
- }])
- self.assert_(integer5)
- self.assertEqual(integer5.integer, 0)
-
- transaction.cursor.rollback()
-
- def test0021integer(self):
- 'Test Integer with domain'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.integer_domain.create([{
- 'integer': 100,
- }])
- self.assertRaises(UserError, self.integer_domain.create, [{
- 'integer': 10,
- }])
-
- def test0030float(self):
- 'Test Float'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- float1, = self.float.create([{
- 'float': 1.1,
- }])
- self.assert_(float1)
- self.assertEqual(float1.float, 1.1)
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', '=', 1.1),
+ chars = char.search([
+ ('char', 'like', 'Test'),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', '=', 0),
+ chars = char.search([
+ ('char', 'like', 'T%'),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', '!=', 1.1),
+ chars = char.search([
+ ('char', 'like', 'Foo'),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', '!=', 0),
+ chars = char.search([
+ ('char', 'like', 'F%'),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', 'in', [1.1]),
+ chars = char.search([
+ ('char', 'ilike', 'test'),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', 'in', [0]),
+ chars = char.search([
+ ('char', 'ilike', 't%'),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', 'in', []),
+ chars = char.search([
+ ('char', 'ilike', 'foo'),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', 'not in', [1.1]),
+ chars = char.search([
+ ('char', 'ilike', 'f%'),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', 'not in', [0]),
+ chars = char.search([
+ ('char', 'not like', 'Test'),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', 'not in', []),
+ chars = char.search([
+ ('char', 'not like', 'T%'),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', '<', 5),
+ chars = char.search([
+ ('char', 'not like', 'Foo'),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', '<', -5),
+ chars = char.search([
+ ('char', 'not like', 'F%'),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', '<', 1.1),
+ chars = char.search([
+ ('char', 'not ilike', 'test'),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', '<=', 5),
+ chars = char.search([
+ ('char', 'not ilike', 't%'),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', '<=', -5),
+ chars = char.search([
+ ('char', 'not ilike', 'foo'),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', '<=', 1.1),
+ chars = char.search([
+ ('char', 'not ilike', 'f%'),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [char1])
- floats = self.float.search([
- ('float', '>', 5),
- ])
- self.assertEqual(floats, [])
+ char2, = char.create([{
+ 'char': None,
+ }])
+ self.assert_(char2)
+ self.assertEqual(char2.char, None)
- floats = self.float.search([
- ('float', '>', -5),
+ chars = char.search([
+ ('char', '=', None),
])
- self.assertEqual(floats, [float1])
+ self.assertEqual(chars, [char2])
- floats = self.float.search([
- ('float', '>', 1.1),
+ chars = char.search([
+ ('char', 'in', [None, 'Test']),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [char1, char2])
- floats = self.float.search([
- ('float', '>=', 5),
+ chars = char.search([
+ ('char', 'not in', [None, 'Test']),
])
- self.assertEqual(floats, [])
+ self.assertEqual(chars, [])
- floats = self.float.search([
- ('float', '>=', -5),
- ])
- self.assertEqual(floats, [float1])
+ char3, = Char.create([{}])
+ self.assert_(char3)
+ self.assertEqual(char3.char, None)
- floats = self.float.search([
- ('float', '>=', 1.1),
- ])
- self.assertEqual(floats, [float1])
+ char4, = CharDefault.create([{}])
+ self.assert_(char4)
+ self.assertEqual(char4.char, 'Test')
- float2, = self.float.create([{
- 'float': 0,
- }])
- self.assert_(float2)
- self.assertEqual(float2.float, 0)
+ Char.write([char1], {
+ 'char': None,
+ })
+ self.assertEqual(char1.char, None)
- floats = self.float.search([
- ('float', '=', 0),
- ])
- self.assertEqual(floats, [float2])
+ Char.write([char2], {
+ 'char': 'Test',
+ })
+ self.assertEqual(char2.char, 'Test')
- floats = self.float.search([
- ('float', 'in', [0, 1.1]),
- ])
- self.assertEqual(floats, [float1, float2])
+ self.assertRaises(UserError, CharRequired.create, [{}])
+ transaction.rollback()
- floats = self.float.search([
- ('float', 'not in', [0, 1.1]),
- ])
- self.assertEqual(floats, [])
+ self.assertRaises(UserError, CharRequired.create, [{
+ 'char': '',
+ }])
+ transaction.rollback()
- float3, = self.float.create([{}])
- self.assert_(float3)
- self.assertEqual(float3.float, None)
+ char5, = CharRequired.create([{
+ 'char': 'Test',
+ }])
+ self.assert_(char5)
- float4, = self.float_default.create([{}])
- self.assert_(float4)
- self.assertEqual(float4.float, 5.5)
+ char6, = CharSize.create([{
+ 'char': 'Test',
+ }])
+ self.assert_(char6)
- self.float.write([float1], {
- 'float': 0,
- })
- self.assertEqual(float1.float, 0)
+ self.assertRaises(Exception, CharSize.create, [{
+ 'char': 'foobar',
+ }])
- self.float.write([float2], {
- 'float': 1.1,
- })
- self.assertEqual(float2.float, 1.1)
+ self.assertRaises(Exception, CharSize.write, [char6], {
+ 'char': 'foobar',
+ })
+ transaction.rollback()
- self.assertRaises(Exception, self.float.create, [{
- 'float': 'test',
+ char7, = Char.create([{
+ 'char': u'é',
+ }])
+ self.assert_(char7)
+ self.assertEqual(char7.char, u'é')
+
+ chars = Char.search([
+ ('char', '=', u'é'),
+ ])
+ self.assertEqual(chars, [char7])
+
+ Char.write([char7], {
+ 'char': 'é',
+ })
+ self.assertEqual(char7.char, u'é')
+
+ chars = Char.search([
+ ('char', '=', 'é'),
+ ])
+ self.assertEqual(chars, [char7])
+
+ @with_transaction()
+ def test_text(self):
+ 'Test Text'
+ pool = Pool()
+ Text = pool.get('test.text')
+ TextDefault = pool.get('test.text_default')
+ TextRequired = pool.get('test.text_required')
+ TextSize = pool.get('test.text_size')
+ TextTranslate = pool.get('test.text_translate')
+ transaction = Transaction()
+
+ for text in (TextTranslate, Text):
+ text1, = text.create([{
+ 'text': 'Test',
}])
+ self.assert_(text1)
+ self.assertEqual(text1.text, 'Test')
- self.assertRaises(Exception, self.float.write, [float1], {
- 'float': 'test',
- })
-
- self.assertRaises(UserError, self.float_required.create, [{}])
- transaction.cursor.rollback()
-
- float5, = self.float_required.create([{
- 'float': 0.0,
- }])
- self.assertEqual(float5.float, 0.0)
+ texts = text.search([
+ ('text', '=', 'Test'),
+ ])
+ self.assertEqual(texts, [text1])
- float6, = self.float_digits.create([{
- 'digits': 1,
- 'float': 1.1,
- }])
- self.assert_(float6)
+ texts = text.search([
+ ('text', '=', 'Foo'),
+ ])
+ self.assertEqual(texts, [])
- self.assertRaises(UserError, self.float_digits.create, [{
- 'digits': 1,
- 'float': 1.11,
- }])
+ texts = text.search([
+ ('text', '=', None),
+ ])
+ self.assertEqual(texts, [])
- self.assertRaises(UserError, self.float_digits.write,
- [float6], {
- 'float': 1.11,
- })
+ texts = text.search([
+ ('text', '!=', 'Test'),
+ ])
+ self.assertEqual(texts, [])
- self.assertRaises(UserError, self.float_digits.write,
- [float6], {
- 'digits': 0,
- })
+ texts = text.search([
+ ('text', '!=', 'Foo'),
+ ])
+ self.assertEqual(texts, [text1])
- float7, = self.float.create([{
- 'float': 0.123456789012345,
- }])
- self.assertEqual(float7.float, 0.123456789012345)
+ texts = text.search([
+ ('text', '!=', None),
+ ])
+ self.assertEqual(texts, [text1])
- transaction.cursor.rollback()
+ texts = text.search([
+ ('text', 'in', ['Test']),
+ ])
+ self.assertEqual(texts, [text1])
- 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,
- context=CONTEXT) as transaction:
- numeric1, = self.numeric.create([{
- 'numeric': Decimal('1.1'),
- }])
- self.assert_(numeric1)
- self.assertEqual(numeric1.numeric, Decimal('1.1'))
+ texts = text.search([
+ ('text', 'in', ['Foo']),
+ ])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '=', Decimal('1.1')),
+ texts = text.search([
+ ('text', 'in', [None]),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '=', Decimal('0')),
+ texts = text.search([
+ ('text', 'in', []),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '!=', Decimal('1.1')),
+ texts = text.search([
+ ('text', 'not in', ['Test']),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '!=', Decimal('0')),
+ texts = text.search([
+ ('text', 'not in', ['Foo']),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', 'in', [Decimal('1.1')]),
+ texts = text.search([
+ ('text', 'not in', [None]),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', 'in', [Decimal('0')]),
+ texts = text.search([
+ ('text', 'not in', []),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', 'in', []),
+ texts = text.search([
+ ('text', 'like', 'Test'),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', 'not in', [Decimal('1.1')]),
+ texts = text.search([
+ ('text', 'like', 'T%'),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', 'not in', [Decimal('0')]),
+ texts = text.search([
+ ('text', 'like', 'Foo'),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', 'not in', []),
+ texts = text.search([
+ ('text', 'like', 'F%'),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '<', Decimal('5')),
+ texts = text.search([
+ ('text', 'ilike', 'test'),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', '<', Decimal('-5')),
+ texts = text.search([
+ ('text', 'ilike', 't%'),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', '<', Decimal('1.1')),
+ texts = text.search([
+ ('text', 'ilike', 'foo'),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '<=', Decimal('5')),
+ texts = text.search([
+ ('text', 'ilike', 'f%'),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '<=', Decimal('-5')),
+ texts = text.search([
+ ('text', 'not like', 'Test'),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '<=', Decimal('1.1')),
+ texts = text.search([
+ ('text', 'not like', 'T%'),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '>', Decimal('5')),
+ texts = text.search([
+ ('text', 'not like', 'Foo'),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', '>', Decimal('-5')),
+ texts = text.search([
+ ('text', 'not like', 'F%'),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', '>', Decimal('1.1')),
+ texts = text.search([
+ ('text', 'not ilike', 'test'),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '>=', Decimal('5')),
+ texts = text.search([
+ ('text', 'not ilike', 't%'),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [])
- numerics = self.numeric.search([
- ('numeric', '>=', Decimal('-5')),
+ texts = text.search([
+ ('text', 'not ilike', 'foo'),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [text1])
- numerics = self.numeric.search([
- ('numeric', '>=', Decimal('1.1')),
+ texts = text.search([
+ ('text', 'not ilike', 'f%'),
])
- self.assertEqual(numerics, [numeric1])
+ self.assertEqual(texts, [text1])
- numeric2, = self.numeric.create([{
- 'numeric': Decimal('0'),
+ text2, = text.create([{
+ 'text': None,
}])
- self.assert_(numeric2)
- self.assertEqual(numeric2.numeric, Decimal('0'))
+ self.assert_(text2)
+ self.assertEqual(text2.text, None)
- numerics = self.numeric.search([
- ('numeric', '=', Decimal('0')),
+ texts = text.search([
+ ('text', '=', None),
])
- self.assertEqual(numerics, [numeric2])
+ self.assertEqual(texts, [text2])
- numerics = self.numeric.search([
- ('numeric', 'in', [Decimal('0'), Decimal('1.1')]),
+ texts = text.search([
+ ('text', 'in', [None, 'Test']),
])
- self.assertEqual(numerics, [numeric1, numeric2])
+ self.assertEqual(texts, [text1, text2])
- numerics = self.numeric.search([
- ('numeric', 'not in', [Decimal('0'), Decimal('1.1')]),
+ texts = text.search([
+ ('text', 'not in', [None, 'Test']),
])
- self.assertEqual(numerics, [])
+ self.assertEqual(texts, [])
- numeric3, = self.numeric.create([{}])
- self.assert_(numeric3)
- self.assertEqual(numeric3.numeric, None)
+ text3, = Text.create([{}])
+ self.assert_(text3)
+ self.assertEqual(text3.text, None)
- numeric4, = self.numeric_default.create([{}])
- self.assert_(numeric4)
- self.assertEqual(numeric4.numeric, Decimal('5.5'))
+ text4, = TextDefault.create([{}])
+ self.assert_(text4)
+ self.assertEqual(text4.text, 'Test')
- self.numeric.write([numeric1], {
- 'numeric': Decimal('0'),
- })
- self.assertEqual(numeric1.numeric, Decimal('0'))
+ Text.write([text1], {
+ 'text': None,
+ })
+ self.assertEqual(text1.text, None)
- self.numeric.write([numeric2], {
- 'numeric': Decimal('1.1'),
- })
- self.assertEqual(numeric2.numeric, Decimal('1.1'))
+ Text.write([text2], {
+ 'text': 'Test',
+ })
+ self.assertEqual(text2.text, 'Test')
- self.assertRaises(Exception, self.numeric.create, [{
- 'numeric': 'test',
- }])
+ self.assertRaises(UserError, TextRequired.create, [{}])
+ transaction.rollback()
- self.assertRaises(Exception, self.numeric.write, [numeric1], {
- 'numeric': 'test',
- })
+ text5, = TextRequired.create([{
+ 'text': 'Test',
+ }])
+ self.assert_(text5)
- self.assertRaises(UserError, self.numeric_required.create, [{}])
- transaction.cursor.rollback()
+ text6, = TextSize.create([{
+ 'text': 'Test',
+ }])
+ self.assert_(text6)
- numeric5, = self.numeric_required.create([{
- 'numeric': Decimal(0),
+ self.assertRaises(UserError, TextSize.create, [{
+ 'text': 'foobar',
}])
- self.assertEqual(numeric5.numeric, 0)
- numeric6, = self.numeric_digits.create([{
- 'digits': 1,
- 'numeric': Decimal('1.1'),
- }])
- self.assert_(numeric6)
+ self.assertRaises(UserError, TextSize.write, [text6], {
+ 'text': 'foobar',
+ })
- self.assertRaises(UserError, self.numeric_digits.create, [{
- 'digits': 1,
- 'numeric': Decimal('1.11'),
- }])
+ text7, = Text.create([{
+ 'text': 'Foo\nBar',
+ }])
+ self.assert_(text7)
- self.assertRaises(UserError, self.numeric_digits.write,
- [numeric6], {
- 'numeric': Decimal('1.11'),
- })
+ text8, = Text.create([{
+ 'text': u'é',
+ }])
+ self.assert_(text8)
+ self.assertEqual(text8.text, u'é')
+
+ texts = Text.search([
+ ('text', '=', u'é'),
+ ])
+ self.assertEqual(texts, [text8])
+
+ Text.write([text8], {
+ 'text': 'é',
+ })
+ self.assertEqual(text8.text, u'é')
+
+ texts = Text.search([
+ ('text', '=', 'é'),
+ ])
+ self.assertEqual(texts, [text8])
+
+ @with_transaction()
+ def test_date(self):
+ 'Test Date'
+ pool = Pool()
+ Date = pool.get('test.date')
+ DateDefault = pool.get('test.date_default')
+ DateRequired = pool.get('test.date_required')
+ transaction = Transaction()
+
+ today = datetime.date(2009, 1, 1)
+ tomorrow = today + datetime.timedelta(1)
+ yesterday = today - datetime.timedelta(1)
+ default_date = datetime.date(2000, 1, 1)
+
+ date1, = Date.create([{
+ 'date': today,
+ }])
+ self.assert_(date1)
+ self.assertEqual(date1.date, today)
+
+ dates = Date.search([
+ ('date', '=', today),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', '=', tomorrow),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '=', None),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '!=', today),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '!=', tomorrow),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', '!=', None),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', 'in', [today]),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', 'in', [tomorrow]),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', 'in', [None]),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', 'in', []),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', 'not in', [today]),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', 'not in', [tomorrow]),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', 'not in', [None]),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', 'not in', []),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', '<', tomorrow),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', '<', yesterday),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '<', today),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '<=', today),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', '<=', yesterday),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '<=', tomorrow),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', '>', tomorrow),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '>', yesterday),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', '>', today),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '>=', tomorrow),
+ ])
+ self.assertEqual(dates, [])
+
+ dates = Date.search([
+ ('date', '>=', yesterday),
+ ])
+ self.assertEqual(dates, [date1])
+
+ dates = Date.search([
+ ('date', '>=', today),
+ ])
+ self.assertEqual(dates, [date1])
+
+ date2, = Date.create([{
+ 'date': yesterday,
+ }])
+ self.assert_(date2)
+ self.assertEqual(date2.date, yesterday)
+
+ dates = Date.search([
+ ('date', '=', yesterday),
+ ])
+ self.assertEqual(dates, [date2])
+
+ dates = Date.search([
+ ('date', 'in', [yesterday, today]),
+ ])
+ self.assertEqual(dates, [date1, date2])
+
+ dates = Date.search([
+ ('date', 'not in', [yesterday, today]),
+ ])
+ self.assertEqual(dates, [])
+
+ date3, = Date.create([{}])
+ self.assert_(date3)
+ self.assertEqual(date3.date, None)
+
+ date4, = DateDefault.create([{}])
+ self.assert_(date4)
+ self.assertEqual(date4.date, default_date)
+
+ Date.write([date1], {
+ 'date': yesterday,
+ })
+ self.assertEqual(date1.date, yesterday)
+
+ Date.write([date2], {
+ 'date': today,
+ })
+ self.assertEqual(date2.date, today)
+
+ self.assertRaises(Exception, Date.create, [{
+ 'date': 'test',
+ }])
- self.assertRaises(UserError, self.numeric_digits.write,
- [numeric6], {
- 'numeric': Decimal('0.10000000000000001'),
- })
+ self.assertRaises(Exception, Date.write, [date1], {
+ 'date': 'test',
+ })
- self.assertRaises(UserError, self.numeric_digits.write,
- [numeric6], {
- 'digits': 0,
- })
+ self.assertRaises(Exception, Date.create, [{
+ 'date': 1,
+ }])
- numeric7, = self.numeric.create([{
- 'numeric': Decimal('0.1234567890123456789'),
- }])
- self.assertEqual(numeric7.numeric,
- Decimal('0.1234567890123456789'))
+ self.assertRaises(Exception, Date.write, [date1], {
+ 'date': 1,
+ })
- transaction.cursor.rollback()
+ self.assertRaises(Exception, Date.create, [{
+ 'date': datetime.datetime.now(),
+ }])
- def test0041numeric(self):
- 'Test numeric search cast'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- numeric1, numeric2 = self.numeric.create([{
- 'numeric': Decimal('1.1'),
- }, {
- 'numeric': Decimal('100.0'),
- }])
- numerics = self.numeric.search([
- ('numeric', '<', Decimal('5')),
- ])
- self.assertEqual(numerics, [numeric1])
+ self.assertRaises(Exception, Date.write, [date1], {
+ 'date': datetime.datetime.now(),
+ })
- 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,
- context=CONTEXT) as transaction:
- for char in (self.char_translate, self.char):
- char1, = char.create([{
- 'char': 'Test',
- }])
- self.assert_(char1)
- self.assertEqual(char1.char, 'Test')
-
- chars = char.search([
- ('char', '=', 'Test'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', '=', 'Foo'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', '=', None),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', '!=', 'Test'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', '!=', 'Foo'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', '!=', None),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'in', ['Test']),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'in', ['Foo']),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'in', [None]),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'in', []),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'not in', ['Test']),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'not in', ['Foo']),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'not in', [None]),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'not in', []),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'like', 'Test'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'like', 'T%'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'like', 'Foo'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'like', 'F%'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'ilike', 'test'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'ilike', 't%'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'ilike', 'foo'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'ilike', 'f%'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'not like', 'Test'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'not like', 'T%'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'not like', 'Foo'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'not like', 'F%'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'not ilike', 'test'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'not ilike', 't%'),
- ])
- self.assertEqual(chars, [])
-
- chars = char.search([
- ('char', 'not ilike', 'foo'),
- ])
- self.assertEqual(chars, [char1])
-
- chars = char.search([
- ('char', 'not ilike', 'f%'),
- ])
- self.assertEqual(chars, [char1])
-
- char2, = char.create([{
- 'char': None,
- }])
- self.assert_(char2)
- self.assertEqual(char2.char, None)
-
- chars = char.search([
- ('char', '=', None),
- ])
- self.assertEqual(chars, [char2])
-
- chars = char.search([
- ('char', 'in', [None, 'Test']),
- ])
- self.assertEqual(chars, [char1, char2])
-
- chars = char.search([
- ('char', 'not in', [None, 'Test']),
- ])
- self.assertEqual(chars, [])
-
- char3, = self.char.create([{}])
- self.assert_(char3)
- self.assertEqual(char3.char, None)
-
- char4, = self.char_default.create([{}])
- self.assert_(char4)
- self.assertEqual(char4.char, 'Test')
-
- self.char.write([char1], {
- 'char': None,
- })
- self.assertEqual(char1.char, None)
+ self.assertRaises(Exception, Date.create, [{
+ 'date': '2009-13-01',
+ }])
- self.char.write([char2], {
- 'char': 'Test',
- })
- self.assertEqual(char2.char, 'Test')
+ self.assertRaises(Exception, Date.write, [date1], {
+ 'date': '2009-02-29',
+ })
- self.assertRaises(UserError, self.char_required.create, [{}])
- transaction.cursor.rollback()
+ date5, = Date.create([{
+ 'date': '2009-01-01',
+ }])
+ self.assert_(date5)
+ self.assertEqual(date5.date, datetime.date(2009, 1, 1))
+
+ self.assertRaises(UserError, DateRequired.create, [{}])
+ transaction.rollback()
- self.assertRaises(UserError, self.char_required.create, [{
- 'char': '',
+ date6, = DateRequired.create([{
+ 'date': today,
}])
- transaction.cursor.rollback()
+ self.assert_(date6)
- char5, = self.char_required.create([{
- 'char': 'Test',
- }])
- self.assert_(char5)
+ date7, = Date.create([{
+ 'date': None,
+ }])
+ self.assert_(date7)
- char6, = self.char_size.create([{
- 'char': 'Test',
- }])
- self.assert_(char6)
+ date8, = Date.create([{
+ 'date': None,
+ }])
+ self.assert_(date8)
- self.assertRaises(Exception, self.char_size.create, [{
- 'char': 'foobar',
+ @with_transaction()
+ def test_datetime(self):
+ 'Test DateTime'
+ pool = Pool()
+ Datetime = pool.get('test.datetime')
+ DatetimeDefault = pool.get('test.datetime_default')
+ DatetimeRequired = pool.get('test.datetime_required')
+ DatetimeFormat = pool.get('test.datetime_format')
+ transaction = Transaction()
+
+ today = datetime.datetime(2009, 1, 1, 12, 0, 0)
+ tomorrow = today + datetime.timedelta(1)
+ yesterday = today - datetime.timedelta(1)
+ default_datetime = datetime.datetime(2000, 1, 1, 12, 0, 0)
+
+ datetime1, = Datetime.create([{
+ 'datetime': today,
+ }])
+ self.assert_(datetime1)
+ self.assertEqual(datetime1.datetime, today)
+
+ datetimes = Datetime.search([
+ ('datetime', '=', today),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', '=', tomorrow),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '=', None),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '!=', today),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '!=', tomorrow),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', '!=', None),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', 'in', [today]),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', 'in', [tomorrow]),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', 'in', [None]),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', 'in', []),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', 'not in', [today]),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', 'not in', [tomorrow]),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', 'not in', [None]),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', 'not in', []),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', '<', tomorrow),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', '<', yesterday),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '<', today),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '<=', today),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', '<=', yesterday),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '<=', tomorrow),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', '>', tomorrow),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '>', yesterday),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', '>', today),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '>=', tomorrow),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetimes = Datetime.search([
+ ('datetime', '>=', yesterday),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetimes = Datetime.search([
+ ('datetime', '>=', today),
+ ])
+ self.assertEqual(datetimes, [datetime1])
+
+ datetime2, = Datetime.create([{
+ 'datetime': yesterday,
+ }])
+ self.assert_(datetime2)
+ self.assertEqual(datetime2.datetime, yesterday)
+
+ datetimes = Datetime.search([
+ ('datetime', '=', yesterday),
+ ])
+ self.assertEqual(datetimes, [datetime2])
+
+ datetimes = Datetime.search([
+ ('datetime', 'in', [yesterday, today]),
+ ])
+ self.assertEqual(datetimes, [datetime1, datetime2])
+
+ datetimes = Datetime.search([
+ ('datetime', 'not in', [yesterday, today]),
+ ])
+ self.assertEqual(datetimes, [])
+
+ datetime3, = Datetime.create([{}])
+ self.assert_(datetime3)
+ self.assertEqual(datetime3.datetime, None)
+
+ datetime4, = DatetimeDefault.create([{}])
+ self.assert_(datetime4)
+ self.assertEqual(datetime4.datetime, default_datetime)
+
+ Datetime.write([datetime1], {
+ 'datetime': yesterday,
+ })
+ self.assertEqual(datetime1.datetime, yesterday)
+
+ Datetime.write([datetime2], {
+ 'datetime': today,
+ })
+ self.assertEqual(datetime2.datetime, today)
+
+ self.assertRaises(Exception, Datetime.create, [{
+ 'datetime': 'test',
}])
- self.assertRaises(Exception, self.char_size.write, [char6], {
- 'char': 'foobar',
- })
- transaction.cursor.rollback()
+ self.assertRaises(Exception, Datetime.write, [datetime1],
+ {
+ 'datetime': 'test',
+ })
- char7, = self.char.create([{
- 'char': u'é',
- }])
- self.assert_(char7)
- self.assertEqual(char7.char, u'é')
+ self.assertRaises(Exception, Datetime.create, [{
+ 'datetime': 1,
+ }])
- chars = self.char.search([
- ('char', '=', u'é'),
- ])
- self.assertEqual(chars, [char7])
+ self.assertRaises(Exception, Datetime.write, [datetime1],
+ {
+ 'datetime': 1,
+ })
- self.char.write([char7], {
- 'char': 'é',
- })
- self.assertEqual(char7.char, u'é')
+ self.assertRaises(Exception, Datetime.create, [{
+ 'datetime': datetime.date.today(),
+ }])
- chars = self.char.search([
- ('char', '=', 'é'),
- ])
- self.assertEqual(chars, [char7])
+ self.assertRaises(Exception, Datetime.write, [datetime1],
+ {
+ 'datetime': datetime.date.today(),
+ })
- transaction.cursor.rollback()
+ self.assertRaises(Exception, Datetime.create, [{
+ 'datetime': '2009-13-01 12:30:00',
+ }])
- def test0060text(self):
- 'Test Text'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- for text in (self.text_translate, self.text):
- text1, = text.create([{
- 'text': 'Test',
- }])
- self.assert_(text1)
- self.assertEqual(text1.text, 'Test')
-
- texts = text.search([
- ('text', '=', 'Test'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', '=', 'Foo'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', '=', None),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', '!=', 'Test'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', '!=', 'Foo'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', '!=', None),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'in', ['Test']),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'in', ['Foo']),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'in', [None]),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'in', []),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'not in', ['Test']),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'not in', ['Foo']),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'not in', [None]),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'not in', []),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'like', 'Test'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'like', 'T%'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'like', 'Foo'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'like', 'F%'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'ilike', 'test'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'ilike', 't%'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'ilike', 'foo'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'ilike', 'f%'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'not like', 'Test'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'not like', 'T%'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'not like', 'Foo'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'not like', 'F%'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'not ilike', 'test'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'not ilike', 't%'),
- ])
- self.assertEqual(texts, [])
-
- texts = text.search([
- ('text', 'not ilike', 'foo'),
- ])
- self.assertEqual(texts, [text1])
-
- texts = text.search([
- ('text', 'not ilike', 'f%'),
- ])
- self.assertEqual(texts, [text1])
-
- text2, = text.create([{
- 'text': None,
- }])
- self.assert_(text2)
- self.assertEqual(text2.text, None)
-
- texts = text.search([
- ('text', '=', None),
- ])
- self.assertEqual(texts, [text2])
-
- texts = text.search([
- ('text', 'in', [None, 'Test']),
- ])
- self.assertEqual(texts, [text1, text2])
-
- texts = text.search([
- ('text', 'not in', [None, 'Test']),
- ])
- self.assertEqual(texts, [])
-
- text3, = self.text.create([{}])
- self.assert_(text3)
- self.assertEqual(text3.text, None)
-
- text4, = self.text_default.create([{}])
- self.assert_(text4)
- self.assertEqual(text4.text, 'Test')
-
- self.text.write([text1], {
- 'text': None,
- })
- self.assertEqual(text1.text, None)
+ self.assertRaises(Exception, Datetime.write, [datetime1],
+ {
+ 'datetime': '2009-02-29 12:30:00',
+ })
- self.text.write([text2], {
- 'text': 'Test',
- })
- self.assertEqual(text2.text, 'Test')
+ self.assertRaises(Exception, Datetime.write, [datetime1],
+ {
+ 'datetime': '2009-01-01 25:00:00',
+ })
- self.assertRaises(UserError, self.text_required.create, [{}])
- transaction.cursor.rollback()
+ datetime5, = Datetime.create([{
+ 'datetime': '2009-01-01 12:00:00',
+ }])
+ self.assert_(datetime5)
+ self.assertEqual(datetime5.datetime,
+ datetime.datetime(2009, 1, 1, 12, 0, 0))
- text5, = self.text_required.create([{
- 'text': 'Test',
- }])
- self.assert_(text5)
+ self.assertRaises(UserError, DatetimeRequired.create, [{}])
+ transaction.rollback()
- text6, = self.text_size.create([{
- 'text': 'Test',
- }])
- self.assert_(text6)
+ datetime6, = DatetimeRequired.create([{
+ 'datetime': today,
+ }])
+ self.assert_(datetime6)
- self.assertRaises(UserError, self.text_size.create, [{
- 'text': 'foobar',
- }])
+ datetime7, = Datetime.create([{
+ 'datetime': None,
+ }])
+ self.assert_(datetime7)
- self.assertRaises(UserError, self.text_size.write, [text6], {
- 'text': 'foobar',
- })
+ datetime8, = Datetime.create([{
+ 'datetime': None,
+ }])
+ self.assert_(datetime8)
- text7, = self.text.create([{
- 'text': 'Foo\nBar',
- }])
- self.assert_(text7)
+ datetime9, = Datetime.create([{
+ 'datetime': today.replace(microsecond=1),
+ }])
+ self.assert_(datetime9)
+ self.assertEqual(datetime9.datetime, today)
- text8, = self.text.create([{
- 'text': u'é',
- }])
- self.assert_(text8)
- self.assertEqual(text8.text, u'é')
+ # Test format
+ self.assert_(DatetimeFormat.create([{
+ 'datetime': datetime.datetime(2009, 1, 1, 12, 30),
+ }]))
+ self.assertRaises(UserError, DatetimeFormat.create, [{
+ 'datetime': datetime.datetime(2009, 1, 1, 12, 30, 25),
+ }])
- texts = self.text.search([
- ('text', '=', u'é'),
- ])
- self.assertEqual(texts, [text8])
+ @with_transaction()
+ def test_time(self):
+ 'Test Time'
+ pool = Pool()
+ Time = pool.get('test.time')
+ TimeDefault = pool.get('test.time_default')
+ TimeRequired = pool.get('test.time_required')
+ TimeFormat = pool.get('test.time_format')
+ transaction = Transaction()
+
+ pre_evening = datetime.time(16, 30)
+ evening = datetime.time(18, 45, 3)
+ night = datetime.time(20, 00)
+ default_time = datetime.time(16, 30)
+
+ time1, = Time.create([{
+ 'time': evening,
+ }])
+ self.assert_(time1)
+ self.assertEqual(time1.time, evening)
+
+ times = Time.search([
+ ('time', '=', evening),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', '=', night),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '=', None),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '!=', evening),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '!=', night),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', '!=', None),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', 'in', [evening]),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', 'in', [night]),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', 'in', [None]),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', 'in', []),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', 'not in', [evening]),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', 'not in', [night]),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', 'not in', [None]),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', 'not in', []),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', '<', night),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', '<', pre_evening),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '<', evening),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '<=', evening),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', '<=', pre_evening),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '<=', night),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', '>', night),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '>', pre_evening),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', '>', evening),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '>=', night),
+ ])
+ self.assertEqual(times, [])
+
+ times = Time.search([
+ ('time', '>=', pre_evening),
+ ])
+ self.assertEqual(times, [time1])
+
+ times = Time.search([
+ ('time', '>=', evening),
+ ])
+ self.assertEqual(times, [time1])
+
+ time2, = Time.create([{
+ 'time': pre_evening,
+ }])
+ self.assert_(time2)
+ self.assertEqual(time2.time, pre_evening)
+
+ times = Time.search([
+ ('time', '=', pre_evening),
+ ])
+ self.assertEqual(times, [time2])
+
+ times = Time.search([
+ ('time', 'in', [pre_evening, evening]),
+ ])
+ self.assertEqual(times, [time1, time2])
+
+ times = Time.search([
+ ('time', 'not in', [pre_evening, evening]),
+ ])
+ self.assertEqual(times, [])
+
+ time3, = Time.create([{}])
+ self.assert_(time3)
+ self.assertEqual(time3.time, None)
+
+ time4, = TimeDefault.create([{}])
+ self.assert_(time4)
+ self.assertEqual(time4.time, default_time)
+
+ Time.write([time1], {
+ 'time': pre_evening,
+ })
+ self.assertEqual(time1.time, pre_evening)
+
+ Time.write([time2], {
+ 'time': evening,
+ })
+ self.assertEqual(time2.time, evening)
+
+ self.assertRaises(Exception, Time.create, [{
+ 'time': 'test',
+ }])
- self.text.write([text8], {
- 'text': 'é',
- })
- self.assertEqual(text8.text, u'é')
+ self.assertRaises(Exception, Time.write, [time1],
+ {
+ 'time': 'test',
+ })
- texts = self.text.search([
- ('text', '=', 'é'),
- ])
- self.assertEqual(texts, [text8])
+ self.assertRaises(Exception, Time.create, [{
+ 'time': 1,
+ }])
- transaction.cursor.rollback()
+ self.assertRaises(Exception, Time.write, [time1],
+ {
+ 'time': 1,
+ })
- def test0080date(self):
- 'Test Date'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- today = datetime.date(2009, 1, 1)
- tomorrow = today + datetime.timedelta(1)
- yesterday = today - datetime.timedelta(1)
- default_date = datetime.date(2000, 1, 1)
-
- date1, = self.date.create([{
- 'date': today,
- }])
- self.assert_(date1)
- self.assertEqual(date1.date, today)
+ self.assertRaises(Exception, Time.write, [time1],
+ {
+ 'time': '25:00:00',
+ })
- dates = self.date.search([
- ('date', '=', today),
- ])
- self.assertEqual(dates, [date1])
+ time5, = Time.create([{
+ 'time': '12:00:00',
+ }])
+ self.assert_(time5)
+ self.assertEqual(time5.time, datetime.time(12, 0))
- dates = self.date.search([
- ('date', '=', tomorrow),
- ])
- self.assertEqual(dates, [])
+ self.assertRaises(UserError, TimeRequired.create, [{}])
+ transaction.rollback()
- dates = self.date.search([
- ('date', '=', None),
- ])
- self.assertEqual(dates, [])
+ time6, = TimeRequired.create([{
+ 'time': evening,
+ }])
+ self.assert_(time6)
- dates = self.date.search([
- ('date', '!=', today),
- ])
- self.assertEqual(dates, [])
+ time7, = Time.create([{
+ 'time': None,
+ }])
+ self.assert_(time7)
- dates = self.date.search([
- ('date', '!=', tomorrow),
- ])
- self.assertEqual(dates, [date1])
+ time9, = Time.create([{
+ 'time': evening.replace(microsecond=1),
+ }])
+ self.assert_(time9)
+ self.assertEqual(time9.time, evening)
+
+ # Test format
+ self.assert_(TimeFormat.create([{
+ 'time': datetime.time(12, 30),
+ }]))
+ self.assertRaises(UserError, TimeFormat.create, [{
+ 'time': datetime.time(12, 30, 25),
+ }])
+
+ @with_transaction()
+ def test_one2one(self):
+ 'Test One2One'
+ pool = Pool()
+ One2one = pool.get('test.one2one')
+ One2oneTarget = pool.get('test.one2one.target')
+ One2oneRequired = pool.get('test.one2one_required')
+ One2oneDomain = pool.get('test.one2one_domain')
+ transaction = Transaction()
+
+ target1, = One2oneTarget.create([{
+ 'name': 'target1',
+ }])
+ one2one1, = One2one.create([{
+ 'name': 'origin1',
+ 'one2one': target1.id,
+ }])
+ self.assert_(one2one1)
+ self.assertEqual(one2one1.one2one, target1)
+
+ self.assertEqual(One2one.read([one2one1.id],
+ ['one2one.name'])[0]['one2one.name'], 'target1')
+
+ one2ones = One2one.search([
+ ('one2one', '=', 'target1'),
+ ])
+ self.assertEqual(one2ones, [one2one1])
+
+ one2ones = One2one.search([
+ ('one2one', '!=', 'target1'),
+ ])
+ self.assertEqual(one2ones, [])
+
+ one2ones = One2one.search([
+ ('one2one', 'in', [target1.id]),
+ ])
+ self.assertEqual(one2ones, [one2one1])
+
+ one2ones = One2one.search([
+ ('one2one', 'in', [0]),
+ ])
+ self.assertEqual(one2ones, [])
+
+ one2ones = One2one.search([
+ ('one2one', 'not in', [target1.id]),
+ ])
+ self.assertEqual(one2ones, [])
+
+ one2ones = One2one.search([
+ ('one2one', 'not in', [0]),
+ ])
+ self.assertEqual(one2ones, [one2one1])
+
+ one2ones = One2one.search([
+ ('one2one.name', '=', 'target1'),
+ ])
+ self.assertEqual(one2ones, [one2one1])
+
+ one2ones = One2one.search([
+ ('one2one.name', '!=', 'target1'),
+ ])
+ self.assertEqual(one2ones, [])
+
+ one2one2, = One2one.create([{
+ 'name': 'origin2',
+ }])
+ self.assert_(one2one2)
+ self.assertEqual(one2one2.one2one, None)
- dates = self.date.search([
- ('date', '!=', None),
- ])
- self.assertEqual(dates, [date1])
+ one2ones = One2one.search([
+ ('one2one', '=', None),
+ ])
+ self.assertEqual(one2ones, [one2one2])
- dates = self.date.search([
- ('date', 'in', [today]),
- ])
- self.assertEqual(dates, [date1])
+ target2, = One2oneTarget.create([{
+ 'name': 'target2',
+ }])
+ One2one.write([one2one2], {
+ 'one2one': target2.id,
+ })
+ self.assertEqual(one2one2.one2one, target2)
+
+ One2one.write([one2one2], {
+ 'one2one': None,
+ })
+ self.assertEqual(one2one2.one2one, None)
+
+ self.assertRaises(UserError, One2one.create, [{
+ 'name': 'one2one3',
+ 'one2one': target1.id,
+ }])
+ transaction.rollback()
- dates = self.date.search([
- ('date', 'in', [tomorrow]),
- ])
- self.assertEqual(dates, [])
+ self.assertRaises(UserError, One2one.write, [one2one2], {
+ 'one2one': target1.id,
+ })
+ transaction.rollback()
- dates = self.date.search([
- ('date', 'in', [None]),
- ])
- self.assertEqual(dates, [])
+ self.assertRaises(UserError, One2oneRequired.create, [{
+ 'name': 'one2one3',
+ }])
+ transaction.rollback()
- dates = self.date.search([
- ('date', 'in', []),
- ])
- self.assertEqual(dates, [])
+ target3, = One2oneTarget.create([{
+ 'name': 'target3',
+ }])
- dates = self.date.search([
- ('date', 'not in', [today]),
- ])
- self.assertEqual(dates, [])
+ one2one3, = One2oneRequired.create([{
+ 'name': 'one2one3',
+ 'one2one': target3.id,
+ }])
+ self.assert_(one2one3)
- dates = self.date.search([
- ('date', 'not in', [tomorrow]),
- ])
- self.assertEqual(dates, [date1])
+ target4, = One2oneTarget.create([{
+ 'name': 'target4',
+ }])
+ self.assertRaises(UserError, One2oneDomain.create, [{
+ 'name': 'one2one4',
+ 'one2one': target4.id,
+ }])
+ transaction.rollback()
- dates = self.date.search([
- ('date', 'not in', [None]),
- ])
- self.assertEqual(dates, [date1])
+ target5, = One2oneTarget.create([{
+ 'name': 'domain',
+ }])
+ one2one5, = One2oneDomain.create([{
+ 'name': 'one2one5',
+ 'one2one': target5.id,
+ }])
+ targets = One2oneTarget.create([{
+ 'name': 'multiple1',
+ }, {
+ 'name': 'multiple2',
+ }])
+ one2ones = One2one.create([{
+ 'name': 'origin6',
+ 'one2one': targets[0].id,
+ }, {
+ 'name': 'origin7',
+ 'one2one': targets[1].id,
+ }])
+ for one2one, target in zip(one2ones, targets):
+ self.assert_(one2one)
+ self.assertEqual(one2one.one2one, target)
- dates = self.date.search([
- ('date', 'not in', []),
- ])
- self.assertEqual(dates, [date1])
+ @with_transaction()
+ def test_one2many(self):
+ 'Test One2Many'
+ pool = Pool()
+ One2many = pool.get('test.one2many')
+ One2manyTarget = pool.get('test.one2many.target')
+ One2manyRequired = pool.get('test.one2many_required')
+ One2manyReference = pool.get('test.one2many_reference')
+ One2manyReferenceTarget = pool.get('test.one2many_reference.target')
+ One2manySize = pool.get('test.one2many_size')
+ One2manySizePyson = pool.get('test.one2many_size_pyson')
+ transaction = Transaction()
+
+ for one2many, one2many_target in (
+ (One2many, One2manyTarget),
+ (One2manyReference, One2manyReferenceTarget),
+ ):
+ one2many1, = one2many.create([{
+ 'name': 'origin1',
+ 'targets': [
+ ('create', [{
+ 'name': 'target1',
+ }]),
+ ],
+ }])
+ self.assert_(one2many1)
- dates = self.date.search([
- ('date', '<', tomorrow),
- ])
- self.assertEqual(dates, [date1])
+ self.assertEqual(len(one2many1.targets), 1)
+ target1, = one2many1.targets
- dates = self.date.search([
- ('date', '<', yesterday),
- ])
- self.assertEqual(dates, [])
+ # Try with target1 stored in cache
+ target1 = one2many_target(target1.id)
+ target1.origin
+ one2many1 = one2many(one2many1)
+ self.assertEqual(one2many1.targets, (target1,))
- dates = self.date.search([
- ('date', '<', today),
+ one2manys = one2many.search([
+ ('targets', '=', 'target1'),
])
- self.assertEqual(dates, [])
+ self.assertEqual(one2manys, [one2many1])
- dates = self.date.search([
- ('date', '<=', today),
+ one2manys = one2many.search([
+ ('targets', '!=', 'target1'),
])
- self.assertEqual(dates, [date1])
+ self.assertEqual(one2manys, [])
- dates = self.date.search([
- ('date', '<=', yesterday),
+ one2manys = one2many.search([
+ ('targets', 'in', [target1.id]),
])
- self.assertEqual(dates, [])
+ self.assertEqual(one2manys, [one2many1])
- dates = self.date.search([
- ('date', '<=', tomorrow),
+ one2manys = one2many.search([
+ ('targets', 'in', [0]),
])
- self.assertEqual(dates, [date1])
+ self.assertEqual(one2manys, [])
- dates = self.date.search([
- ('date', '>', tomorrow),
+ one2manys = one2many.search([
+ ('targets', 'not in', (target1.id,)),
])
- self.assertEqual(dates, [])
+ self.assertEqual(one2manys, [])
- dates = self.date.search([
- ('date', '>', yesterday),
+ one2manys = one2many.search([
+ ('targets', 'not in', [0]),
])
- self.assertEqual(dates, [date1])
+ self.assertEqual(one2manys, [one2many1])
- dates = self.date.search([
- ('date', '>', today),
+ one2manys = one2many.search([
+ ('targets.name', '=', 'target1'),
])
- self.assertEqual(dates, [])
+ self.assertEqual(one2manys, [one2many1])
- dates = self.date.search([
- ('date', '>=', tomorrow),
+ one2manys = one2many.search([
+ ('targets.name', '!=', 'target1'),
])
- self.assertEqual(dates, [])
+ self.assertEqual(one2manys, [])
- dates = self.date.search([
- ('date', '>=', yesterday),
+ one2manys = one2many.search([
+ ('targets', 'where', [('name', '=', 'target1')]),
])
- self.assertEqual(dates, [date1])
-
- dates = self.date.search([
- ('date', '>=', today),
+ self.assertEqual(one2manys, [one2many1])
+ one2manys = one2many.search([
+ ('targets', 'not where', [('name', '=', 'target1')]),
])
- self.assertEqual(dates, [date1])
+ self.assertEqual(one2manys, [])
- date2, = self.date.create([{
- 'date': yesterday,
+ one2many2, = one2many.create([{
+ 'name': 'origin2',
}])
- self.assert_(date2)
- self.assertEqual(date2.date, yesterday)
+ self.assert_(one2many2)
- dates = self.date.search([
- ('date', '=', yesterday),
- ])
- self.assertEqual(dates, [date2])
-
- dates = self.date.search([
- ('date', 'in', [yesterday, today]),
- ])
- self.assertEqual(dates, [date1, date2])
+ self.assertEqual(one2many2.targets, ())
- dates = self.date.search([
- ('date', 'not in', [yesterday, today]),
+ one2manys = one2many.search([
+ ('targets', '=', None),
])
- self.assertEqual(dates, [])
-
- date3, = self.date.create([{}])
- self.assert_(date3)
- self.assertEqual(date3.date, None)
-
- date4, = self.date_default.create([{}])
- self.assert_(date4)
- self.assertEqual(date4.date, default_date)
-
- self.date.write([date1], {
- 'date': yesterday,
- })
- self.assertEqual(date1.date, yesterday)
+ self.assertEqual(one2manys, [one2many2])
- self.date.write([date2], {
- 'date': today,
+ one2many.write([one2many1], {
+ 'targets': [
+ ('write', [target1.id], {
+ 'name': 'target1bis',
+ }),
+ ],
})
- self.assertEqual(date2.date, today)
+ self.assertEqual(target1.name, 'target1bis')
- self.assertRaises(Exception, self.date.create, [{
- 'date': 'test',
+ target2, = one2many_target.create([{
+ 'name': 'target2',
}])
-
- self.assertRaises(Exception, self.date.write, [date1], {
- 'date': 'test',
+ one2many.write([one2many1], {
+ 'targets': [
+ ('add', [target2.id]),
+ ],
})
+ self.assertEqual(one2many1.targets,
+ (target1, target2))
- self.assertRaises(Exception, self.date.create, [{
- 'date': 1,
- }])
-
- self.assertRaises(Exception, self.date.write, [date1], {
- 'date': 1,
+ one2many.write([one2many1], {
+ 'targets': [
+ ('remove', [target2.id]),
+ ],
})
+ self.assertEqual(one2many1.targets, (target1,))
+ target2, = one2many_target.search([
+ ('id', '=', target2.id),
+ ])
+ self.assert_(target2)
- self.assertRaises(Exception, self.date.create, [{
- 'date': datetime.datetime.now(),
- }])
-
- self.assertRaises(Exception, self.date.write, [date1], {
- 'date': datetime.datetime.now(),
+ one2many.write([one2many1], {
+ 'targets': [
+ ('remove', [target1.id]),
+ ],
})
+ self.assertEqual(one2many1.targets, ())
+ targets = one2many_target.search([
+ ('id', 'in', [target1.id, target2.id]),
+ ])
+ self.assertEqual(targets, [target1, target2])
- self.assertRaises(Exception, self.date.create, [{
- 'date': '2009-13-01',
- }])
+ one2many.write([one2many1], {
+ 'targets': [
+ ('add', [target1.id, target2.id]),
+ ],
+ })
+ self.assertEqual(one2many1.targets,
+ (target1, target2))
- self.assertRaises(Exception, self.date.write, [date1], {
- 'date': '2009-02-29',
+ one2many.write([one2many1], {
+ 'targets': [
+ ('copy', [target1.id], {'name': 'copy1'}),
+ ],
})
+ targets = one2many_target.search([
+ ('id', 'not in', [target1.id, target2.id]),
+ ])
+ self.assertEqual(len(targets), 1)
+ self.assertEqual(targets[0].name, 'copy1')
- date5, = self.date.create([{
- 'date': '2009-01-01',
- }])
- self.assert_(date5)
- self.assertEqual(date5.date, datetime.date(2009, 1, 1))
+ one2many.write([one2many1], {
+ 'targets': [
+ ('copy', [target2.id]),
+ ],
+ })
+ self.assertEqual(len(one2many1.targets), 4)
+ targets = one2many_target.search([
+ ('id', 'not in', [target1.id, target2.id]),
+ ])
+ self.assertEqual(len(targets), 2)
+ names = set([target.name for target in targets])
+ self.assertEqual(names, set(('copy1', 'target2')))
+
+ copy_ids = [target.id for target in targets]
+ one2many.write([one2many1], {
+ 'targets': [
+ ('delete', [target2.id] + copy_ids),
+ ],
+ })
+ self.assertEqual(one2many1.targets, (target1,))
+ targets = one2many_target.search([
+ ('id', '=', target2.id),
+ ])
+ self.assertEqual(targets, [])
- self.assertRaises(UserError, self.date_required.create, [{}])
- transaction.cursor.rollback()
+ transaction.rollback()
- date6, = self.date_required.create([{
- 'date': today,
- }])
- self.assert_(date6)
+ self.assertRaises(UserError, One2manyRequired.create, [{
+ 'name': 'origin3',
+ }])
+ transaction.rollback()
+
+ origin3_id, = One2manyRequired.create([{
+ 'name': 'origin3',
+ 'targets': [
+ ('create', [{
+ 'name': 'target3',
+ }]),
+ ],
+ }])
+ self.assert_(origin3_id)
- date7, = self.date.create([{
- 'date': None,
- }])
- self.assert_(date7)
+ One2manySize.create([{
+ 'targets': [('create', [{}])] * 3,
+ }])
+ self.assertRaises(UserError, One2manySize.create, [{
+ 'targets': [('create', [{}])] * 4,
+ }])
+ One2manySizePyson.create([{
+ 'limit': 4,
+ 'targets': [('create', [{}])] * 4,
+ }])
+ self.assertRaises(UserError, One2manySizePyson.create, [{
+ 'limit': 2,
+ 'targets': [('create', [{}])] * 4,
+ }])
- date8, = self.date.create([{
- 'date': None,
+ @with_transaction()
+ def test_many2many(self):
+ 'Test Many2Many'
+ pool = Pool()
+ Many2many = pool.get('test.many2many')
+ Many2manyTarget = pool.get('test.many2many.target')
+ Many2manyRequired = pool.get('test.many2many_required')
+ Many2manyReference = pool.get('test.many2many_reference')
+ Many2manyReferenceTarget = pool.get('test.many2many_reference.target')
+ Many2manySizeTarget = pool.get('test.many2many_size.target')
+ transaction = Transaction()
+
+ for many2many, many2many_target in (
+ (Many2many, Many2manyTarget),
+ (Many2manyReference, Many2manyReferenceTarget),
+ ):
+ many2many1, = many2many.create([{
+ 'name': 'origin1',
+ 'targets': [
+ ('create', [{
+ 'name': 'target1',
+ }]),
+ ],
}])
- self.assert_(date8)
-
- transaction.cursor.rollback()
+ self.assert_(many2many1)
- def test0090datetime(self):
- 'Test DateTime'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- today = datetime.datetime(2009, 1, 1, 12, 0, 0)
- tomorrow = today + datetime.timedelta(1)
- yesterday = today - datetime.timedelta(1)
- default_datetime = datetime.datetime(2000, 1, 1, 12, 0, 0)
-
- datetime1, = self.datetime.create([{
- 'datetime': today,
- }])
- self.assert_(datetime1)
- self.assertEqual(datetime1.datetime, today)
+ self.assertEqual(len(many2many1.targets), 1)
+ target1, = many2many1.targets
- datetimes = self.datetime.search([
- ('datetime', '=', today),
+ many2manys = many2many.search([
+ ('targets', '=', 'target1'),
])
- self.assertEqual(datetimes, [datetime1])
+ self.assertEqual(many2manys, [many2many1])
- datetimes = self.datetime.search([
- ('datetime', '=', tomorrow),
+ many2manys = many2many.search([
+ ('targets', '!=', 'target1'),
])
- self.assertEqual(datetimes, [])
+ self.assertEqual(many2manys, [])
- datetimes = self.datetime.search([
- ('datetime', '=', None),
+ many2manys = many2many.search([
+ ('targets', 'in', [target1.id]),
])
- self.assertEqual(datetimes, [])
+ self.assertEqual(many2manys, [many2many1])
- datetimes = self.datetime.search([
- ('datetime', '!=', today),
+ many2manys = many2many.search([
+ ('targets', 'in', [0]),
])
- self.assertEqual(datetimes, [])
+ self.assertEqual(many2manys, [])
- datetimes = self.datetime.search([
- ('datetime', '!=', tomorrow),
+ many2manys = many2many.search([
+ ('targets', 'not in', [target1.id]),
])
- self.assertEqual(datetimes, [datetime1])
+ self.assertEqual(many2manys, [])
- datetimes = self.datetime.search([
- ('datetime', '!=', None),
+ many2manys = many2many.search([
+ ('targets', 'not in', [0]),
])
- self.assertEqual(datetimes, [datetime1])
+ self.assertEqual(many2manys, [many2many1])
- datetimes = self.datetime.search([
- ('datetime', 'in', [today]),
+ many2manys = many2many.search([
+ ('targets.name', '=', 'target1'),
])
- self.assertEqual(datetimes, [datetime1])
+ self.assertEqual(many2manys, [many2many1])
- datetimes = self.datetime.search([
- ('datetime', 'in', [tomorrow]),
+ many2manys = many2many.search([
+ ('targets.name', '!=', 'target1'),
])
- self.assertEqual(datetimes, [])
+ self.assertEqual(many2manys, [])
- datetimes = self.datetime.search([
- ('datetime', 'in', [None]),
+ many2manys = many2many.search([
+ ('targets', 'where', [('name', '=', 'target1')]),
])
- self.assertEqual(datetimes, [])
-
- datetimes = self.datetime.search([
- ('datetime', 'in', []),
+ self.assertEqual(many2manys, [many2many1])
+ many2manys = many2many.search([
+ ('targets', 'not where', [('name', '=', 'target1')]),
])
- self.assertEqual(datetimes, [])
+ self.assertEqual(many2manys, [])
- datetimes = self.datetime.search([
- ('datetime', 'not in', [today]),
- ])
- self.assertEqual(datetimes, [])
+ many2many2, = many2many.create([{
+ 'name': 'origin2',
+ }])
+ self.assert_(many2many2)
- datetimes = self.datetime.search([
- ('datetime', 'not in', [tomorrow]),
- ])
- self.assertEqual(datetimes, [datetime1])
+ self.assertEqual(many2many2.targets, ())
- datetimes = self.datetime.search([
- ('datetime', 'not in', [None]),
+ many2manys = many2many.search([
+ ('targets', '=', None),
])
- self.assertEqual(datetimes, [datetime1])
+ self.assertEqual(many2manys, [many2many2])
- datetimes = self.datetime.search([
- ('datetime', 'not in', []),
- ])
- self.assertEqual(datetimes, [datetime1])
+ many2many.write([many2many1], {
+ 'targets': [
+ ('write', [target1.id], {
+ 'name': 'target1bis',
+ }),
+ ],
+ })
+ self.assertEqual(target1.name, 'target1bis')
- datetimes = self.datetime.search([
- ('datetime', '<', tomorrow),
- ])
- self.assertEqual(datetimes, [datetime1])
+ target2, = many2many_target.create([{
+ 'name': 'target2',
+ }])
+ many2many.write([many2many1], {
+ 'targets': [
+ ('add', [target2.id]),
+ ],
+ })
+ self.assertEqual(many2many1.targets,
+ (target1, target2))
- datetimes = self.datetime.search([
- ('datetime', '<', yesterday),
+ many2many.write([many2many1], {
+ 'targets': [
+ ('remove', [target2.id]),
+ ],
+ })
+ self.assertEqual(many2many1.targets, (target1,))
+ target2, = many2many_target.search([
+ ('id', '=', target2.id),
])
- self.assertEqual(datetimes, [])
+ self.assert_(target2)
- datetimes = self.datetime.search([
- ('datetime', '<', today),
+ many2many.write([many2many1], {
+ 'targets': [
+ ('remove', [target1.id]),
+ ],
+ })
+ self.assertEqual(many2many1.targets, ())
+ targets = many2many_target.search([
+ ('id', 'in', [target1.id, target2.id]),
])
- self.assertEqual(datetimes, [])
+ self.assertEqual(targets, [target1, target2])
- datetimes = self.datetime.search([
- ('datetime', '<=', today),
- ])
- self.assertEqual(datetimes, [datetime1])
+ many2many.write([many2many1], {
+ 'targets': [
+ ('add', [target1.id, target2.id]),
+ ],
+ })
+ self.assertEqual(many2many1.targets,
+ (target1, target2))
- datetimes = self.datetime.search([
- ('datetime', '<=', yesterday),
+ many2many.write([many2many1], {
+ 'targets': [
+ ('copy', [target1.id], {'name': 'copy1'}),
+ ],
+ })
+ targets = many2many_target.search([
+ ('id', 'not in', [target1.id, target2.id]),
])
- self.assertEqual(datetimes, [])
+ self.assertEqual(len(targets), 1)
+ self.assertEqual(targets[0].name, 'copy1')
- datetimes = self.datetime.search([
- ('datetime', '<=', tomorrow),
+ many2many.write([many2many1], {
+ 'targets': [
+ ('copy', [target2.id]),
+ ],
+ })
+ self.assertEqual(len(many2many1.targets), 4)
+ targets = many2many_target.search([
+ ('id', 'not in', [target1.id, target2.id]),
+ ])
+ self.assertEqual(len(targets), 2)
+ names = set([target.name for target in targets])
+ self.assertEqual(names, set(('copy1', 'target2')))
+
+ copy_ids = [target.id for target in targets]
+ many2many.write([many2many1], {
+ 'targets': [
+ ('delete', [target2.id] + copy_ids),
+ ],
+ })
+ self.assertEqual(many2many1.targets, (target1,))
+ targets = many2many_target.search([
+ ('id', '=', target2.id),
])
- self.assertEqual(datetimes, [datetime1])
+ self.assertEqual(targets, [])
- datetimes = self.datetime.search([
- ('datetime', '>', tomorrow),
- ])
- self.assertEqual(datetimes, [])
+ transaction.rollback()
- datetimes = self.datetime.search([
- ('datetime', '>', yesterday),
- ])
- self.assertEqual(datetimes, [datetime1])
+ self.assertRaises(UserError, Many2manyRequired.create, [{
+ 'name': 'origin3',
+ }])
+ transaction.rollback()
+
+ origin3_id, = Many2manyRequired.create([{
+ 'name': 'origin3',
+ 'targets': [
+ ('create', [{
+ 'name': 'target3',
+ }]),
+ ],
+ }])
+ self.assert_(origin3_id)
+
+ Many2manySizeTarget.create([{
+ 'name': str(i),
+ } for i in range(6)])
+
+ transaction.rollback()
+
+ @with_transaction()
+ def test_many2many_tree(self):
+ pool = Pool()
+ Many2Many = pool.get('test.many2many_tree')
+
+ second1, second2, second3, second4 = Many2Many.create([
+ {},
+ {},
+ {},
+ {},
+ ])
+ first1, first2, first3, first4 = Many2Many.create([
+ {'children': [('add', [second1.id, second2.id])]},
+ {'children': [('add', [second1.id, second2.id])]},
+ {'children': [('add', [second3.id, second4.id])]},
+ {'children': [('add', [second4.id])]},
+ ])
+ root1, root2 = Many2Many.create([
+ {'children': [
+ ('add', [first1.id, first2.id, second1.id])]},
+ {'children': [('add', [first3.id, first4.id])]},
+ ])
+
+ all_ = Many2Many.search([])
+
+ def not_(l):
+ return [r for r in all_ if r not in l]
+
+ for clause, test in [
+ ([root1.id], [second1, second2, first1, first2, root1]),
+ ([second1.id], [second1]),
+ ([root2.id], [second3, second4, first3, first4, root2]),
+ ([], []),
+ ]:
+ result = Many2Many.search(
+ [('parents', 'child_of', clause)])
+ self.assertEqual(result, test)
+
+ result = Many2Many.search(
+ [('parents', 'not child_of', clause)])
+ self.assertEqual(result, not_(test))
+
+ for clause, test in [
+ ([root1.id], [root1]),
+ ([first3.id], [first3, root2]),
+ ([second4.id], [second4, first3, first4, root2]),
+ ([second4.id, first4.id], [second4, first3, first4,
+ root2]),
+ ([], []),
+ ]:
+ result = Many2Many.search(
+ [('parents', 'parent_of', clause)])
+ self.assertEqual(result, test)
+
+ result = Many2Many.search(
+ [('parents', 'not parent_of', clause)])
+ self.assertEqual(result, not_(test))
+
+ @with_transaction()
+ def test_reference(self):
+ 'Test Reference'
+ pool = Pool()
+ Reference = pool.get('test.reference')
+ ReferenceTarget = pool.get('test.reference.target')
+ ReferenceRequired = pool.get('test.reference_required')
+ transaction = Transaction()
+
+ target1, = ReferenceTarget.create([{
+ 'name': 'target1',
+ }])
+ reference1, = Reference.create([{
+ 'name': 'reference1',
+ 'reference': str(target1),
+ }])
+ self.assert_(reference1)
+
+ self.assertEqual(reference1.reference, target1)
+
+ references = Reference.search([
+ ('reference', '=', str(target1)),
+ ])
+ self.assertEqual(references, [reference1])
+
+ references = Reference.search([
+ ('reference', '=', (target1.__name__, target1.id)),
+ ])
+ self.assertEqual(references, [reference1])
+
+ references = Reference.search([
+ ('reference', '=', [target1.__name__, target1.id]),
+ ])
+ self.assertEqual(references, [reference1])
+
+ references = Reference.search([
+ ('reference.name', '=', 'target1',
+ 'test.reference.target'),
+ ])
+ self.assertEqual(references, [reference1])
+
+ references = Reference.search([
+ ('reference', '!=', str(target1)),
+ ])
+ self.assertEqual(references, [])
+
+ references = Reference.search([
+ ('reference', '!=', str(target1)),
+ ])
+ self.assertEqual(references, [])
+
+ references = Reference.search([
+ ('reference', 'in', [str(target1)]),
+ ])
+ self.assertEqual(references, [reference1])
+
+ references = Reference.search([
+ ('reference', 'in',
+ [('test.reference.target', target1.id)]),
+ ])
+ self.assertEqual(references, [reference1])
+
+ references = Reference.search([
+ ('reference', 'in', [None]),
+ ])
+ self.assertEqual(references, [])
+
+ references = Reference.search([
+ ('reference', 'not in', [str(target1)]),
+ ])
+ self.assertEqual(references, [])
+
+ references = Reference.search([
+ ('reference', 'not in',
+ [('test.reference.target', target1.id)]),
+ ])
+ self.assertEqual(references, [])
+
+ references = Reference.search([
+ ('reference', 'not in', [None]),
+ ])
+ self.assertEqual(references, [reference1])
+
+ reference2, = Reference.create([{
+ 'name': 'reference2',
+ }])
+ self.assert_(reference2)
- datetimes = self.datetime.search([
- ('datetime', '>', today),
- ])
- self.assertEqual(datetimes, [])
+ self.assertEqual(reference2.reference, None)
- datetimes = self.datetime.search([
- ('datetime', '>=', tomorrow),
- ])
- self.assertEqual(datetimes, [])
+ references = Reference.search([
+ ('reference', '=', None),
+ ])
+ self.assertEqual(references, [reference2])
- datetimes = self.datetime.search([
- ('datetime', '>=', yesterday),
- ])
- self.assertEqual(datetimes, [datetime1])
+ target2, = ReferenceTarget.create([{
+ 'name': 'target2',
+ }])
- datetimes = self.datetime.search([
- ('datetime', '>=', today),
- ])
- self.assertEqual(datetimes, [datetime1])
+ Reference.write([reference2], {
+ 'reference': str(target2),
+ })
+ self.assertEqual(reference2.reference, target2)
- datetime2, = self.datetime.create([{
- 'datetime': yesterday,
- }])
- self.assert_(datetime2)
- self.assertEqual(datetime2.datetime, yesterday)
+ Reference.write([reference2], {
+ 'reference': None,
+ })
+ self.assertEqual(reference2.reference, None)
- datetimes = self.datetime.search([
- ('datetime', '=', yesterday),
- ])
- self.assertEqual(datetimes, [datetime2])
+ Reference.write([reference2], {
+ 'reference': ('test.reference.target', target2.id),
+ })
+ self.assertEqual(reference2.reference, target2)
- datetimes = self.datetime.search([
- ('datetime', 'in', [yesterday, today]),
- ])
- self.assertEqual(datetimes, [datetime1, datetime2])
+ reference3, = Reference.create([{
+ 'name': 'reference3',
+ 'reference': ('test.reference.target', target1.id),
+ }])
+ self.assert_(reference3)
- datetimes = self.datetime.search([
- ('datetime', 'not in', [yesterday, today]),
- ])
- self.assertEqual(datetimes, [])
+ self.assertRaises(UserError, ReferenceRequired.create, [{
+ 'name': 'reference4',
+ }])
+ transaction.rollback()
- datetime3, = self.datetime.create([{}])
- self.assert_(datetime3)
- self.assertEqual(datetime3.datetime, None)
+ target4, = ReferenceTarget.create([{
+ 'name': 'target4_id',
+ }])
- datetime4, = self.datetime_default.create([{}])
- self.assert_(datetime4)
- self.assertEqual(datetime4.datetime, default_datetime)
+ reference4, = ReferenceRequired.create([{
+ 'name': 'reference4',
+ 'reference': str(target4),
+ }])
+ self.assert_(reference4)
- self.datetime.write([datetime1], {
- 'datetime': yesterday,
- })
- self.assertEqual(datetime1.datetime, yesterday)
+ @with_transaction()
+ def test_property(self):
+ 'Test Property with supported field types'
+ pool = Pool()
+ Property = pool.get('test.property')
+ IrProperty = pool.get('ir.property')
+ ModelField = pool.get('ir.model.field')
+ Char = pool.get('test.char')
+ transaction = Transaction()
- self.datetime.write([datetime2], {
- 'datetime': today,
- })
- self.assertEqual(datetime2.datetime, today)
+ # Test Char
+ prop_a, = Property.create([{'char': 'Test'}])
+ self.assert_(prop_a)
+ self.assertEqual(prop_a.char, 'Test')
- self.assertRaises(Exception, self.datetime.create, [{
- 'datetime': 'test',
- }])
+ prop_b, = Property.create([{}])
+ self.assert_(prop_b)
+ self.assertEqual(prop_b.char, None)
- self.assertRaises(Exception, self.datetime.write, [datetime1],
- {
- 'datetime': 'test',
- })
+ prop_c, = Property.create([{'char': 'FooBar'}])
+ self.assert_(prop_c)
+ self.assertEqual(prop_c.char, 'FooBar')
- self.assertRaises(Exception, self.datetime.create, [{
- 'datetime': 1,
- }])
+ props = Property.search([('char', '=', 'Test')])
+ self.assertEqual(props, [prop_a])
- self.assertRaises(Exception, self.datetime.write, [datetime1],
- {
- 'datetime': 1,
- })
+ props = Property.search([('char', '=', None)])
+ self.assertEqual(props, [prop_b])
- self.assertRaises(Exception, self.datetime.create, [{
- 'datetime': datetime.date.today(),
- }])
+ props = Property.search([('char', '!=', None)])
+ self.assertEqual(props, [prop_a, prop_c])
- self.assertRaises(Exception, self.datetime.write, [datetime1],
- {
- 'datetime': datetime.date.today(),
- })
+ props = Property.search([('char', 'like', 'Tes%')])
+ self.assertEqual(props, [prop_a])
- self.assertRaises(Exception, self.datetime.create, [{
- 'datetime': '2009-13-01 12:30:00',
- }])
+ props = Property.search([('char', 'like', '%Bar')])
+ self.assertEqual(props, [prop_c])
- self.assertRaises(Exception, self.datetime.write, [datetime1],
- {
- 'datetime': '2009-02-29 12:30:00',
- })
+ props = Property.search([('char', 'not like', 'Tes%')])
+ self.assertEqual(props, [prop_b, prop_c])
- self.assertRaises(Exception, self.datetime.write, [datetime1],
- {
- 'datetime': '2009-01-01 25:00:00',
- })
+ props = Property.search([('char', 'ilike', 'tes%')])
+ self.assert_(props, [prop_a])
- datetime5, = self.datetime.create([{
- 'datetime': '2009-01-01 12:00:00',
- }])
- self.assert_(datetime5)
- self.assertEqual(datetime5.datetime,
- datetime.datetime(2009, 1, 1, 12, 0, 0))
+ props = Property.search([('char', 'ilike', '%bar')])
+ self.assertEqual(props, [prop_c])
- self.assertRaises(UserError, self.datetime_required.create, [{}])
- transaction.cursor.rollback()
+ props = Property.search([('char', 'not ilike', 'tes%')])
+ self.assertEqual(props, [prop_b, prop_c])
- datetime6, = self.datetime_required.create([{
- 'datetime': today,
- }])
- self.assert_(datetime6)
+ props = Property.search([('char', 'in', ['Test'])])
+ self.assertEqual(props, [prop_a])
- datetime7, = self.datetime.create([{
- 'datetime': None,
- }])
- self.assert_(datetime7)
+ props = Property.search([
+ ('char', 'in', ['Test', 'FooBar'])])
+ self.assertEqual(props, [prop_a, prop_c])
- datetime8, = self.datetime.create([{
- 'datetime': None,
- }])
- self.assert_(datetime8)
+ props = Property.search([
+ ('char', 'not in', ['Test', 'FooBar'])])
+ self.assertEqual(props, [prop_b])
- datetime9, = self.datetime.create([{
- 'datetime': today.replace(microsecond=1),
- }])
- self.assert_(datetime9)
- self.assertEqual(datetime9.datetime, today)
-
- # Test format
- self.assert_(self.datetime_format.create([{
- 'datetime': datetime.datetime(2009, 1, 1, 12, 30),
- }]))
- self.assertRaises(UserError, self.datetime_format.create, [{
- 'datetime': datetime.datetime(2009, 1, 1, 12, 30, 25),
- }])
+ # Test default value
+ property_field, = ModelField.search([
+ ('model.model', '=', 'test.property'),
+ ('name', '=', 'char'),
+ ], limit=1)
+ IrProperty.create([{
+ 'field': property_field.id,
+ 'value': ',DEFAULT_VALUE',
+ }])
- transaction.cursor.rollback()
+ prop_d, = Property.create([{}])
+ self.assert_(prop_d)
+ self.assertEqual(prop_d.char, 'DEFAULT_VALUE')
- def test0100time(self):
- 'Test Time'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- pre_evening = datetime.time(16, 30)
- evening = datetime.time(18, 45, 3)
- night = datetime.time(20, 00)
- default_time = datetime.time(16, 30)
-
- time1, = self.time.create([{
- 'time': evening,
- }])
- self.assert_(time1)
- self.assertEqual(time1.time, evening)
+ props = Property.search([('char', '!=', None)])
+ self.assertEqual(props, [prop_a, prop_c, prop_d])
- times = self.time.search([
- ('time', '=', evening),
- ])
- self.assertEqual(times, [time1])
+ Property.write([prop_a], {'char': None})
+ self.assertEqual(prop_a.char, None)
- times = self.time.search([
- ('time', '=', night),
- ])
- self.assertEqual(times, [])
+ Property.write([prop_b], {'char': 'Test'})
+ self.assertEqual(prop_b.char, 'Test')
- times = self.time.search([
- ('time', '=', None),
- ])
- self.assertEqual(times, [])
+ transaction.rollback()
- times = self.time.search([
- ('time', '!=', evening),
- ])
- self.assertEqual(times, [])
+ # Test Many2One
+ char_a, = Char.create([{'char': 'Test'}])
+ self.assert_(char_a)
- times = self.time.search([
- ('time', '!=', night),
- ])
- self.assertEqual(times, [time1])
+ char_b, = Char.create([{'char': 'FooBar'}])
+ self.assert_(char_b)
- times = self.time.search([
- ('time', '!=', None),
- ])
- self.assertEqual(times, [time1])
+ prop_a, = Property.create([{'many2one': char_a.id}])
+ self.assert_(prop_a)
+ self.assertEqual(prop_a.many2one, char_a)
- times = self.time.search([
- ('time', 'in', [evening]),
- ])
- self.assertEqual(times, [time1])
+ prop_b, = Property.create([{'many2one': char_b.id}])
+ self.assert_(prop_b)
+ self.assertEqual(prop_b.many2one, char_b)
- times = self.time.search([
- ('time', 'in', [night]),
- ])
- self.assertEqual(times, [])
+ prop_c, = Property.create([{}])
+ self.assert_(prop_c)
+ self.assertEqual(prop_c.many2one, None)
- times = self.time.search([
- ('time', 'in', [None]),
- ])
- self.assertEqual(times, [])
+ props = Property.search([('many2one', '=', char_a.id)])
+ self.assertEqual(props, [prop_a])
- times = self.time.search([
- ('time', 'in', []),
- ])
- self.assertEqual(times, [])
+ props = Property.search([('many2one', '!=', None)])
+ self.assertEqual(props, [prop_a, prop_b])
- times = self.time.search([
- ('time', 'not in', [evening]),
- ])
- self.assertEqual(times, [])
+ props = Property.search([('many2one', '=', None)])
+ self.assertEqual(props, [prop_c])
- times = self.time.search([
- ('time', 'not in', [night]),
- ])
- self.assertEqual(times, [time1])
+ self.assertEqual(prop_a.many2one, char_a)
- times = self.time.search([
- ('time', 'not in', [None]),
- ])
- self.assertEqual(times, [time1])
+ props = Property.search([
+ ('many2one', 'in', [char_a.id, char_b.id])])
+ self.assertEqual(props, [prop_a, prop_b])
- times = self.time.search([
- ('time', 'not in', []),
- ])
- self.assertEqual(times, [time1])
+ props = Property.search([
+ ('many2one', 'not in', [char_a.id, char_b.id])])
+ self.assertEqual(props, [prop_c])
- times = self.time.search([
- ('time', '<', night),
- ])
- self.assertEqual(times, [time1])
+ Property.write([prop_b], {'many2one': char_a.id})
+ self.assertEqual(prop_b.many2one, char_a)
- times = self.time.search([
- ('time', '<', pre_evening),
- ])
- self.assertEqual(times, [])
+ transaction.rollback()
- times = self.time.search([
- ('time', '<', evening),
- ])
- self.assertEqual(times, [])
+ # Test Numeric
+ prop_a, = Property.create([{'numeric': Decimal('1.1')}])
+ self.assert_(prop_a)
+ self.assertEqual(prop_a.numeric, Decimal('1.1'))
- times = self.time.search([
- ('time', '<=', evening),
- ])
- self.assertEqual(times, [time1])
+ prop_b, = Property.create([{'numeric': Decimal('2.6')}])
+ self.assert_(prop_b)
+ self.assertEqual(prop_b.numeric, Decimal('2.6'))
- times = self.time.search([
- ('time', '<=', pre_evening),
- ])
- self.assertEqual(times, [])
+ prop_c, = Property.create([{}])
+ self.assert_(prop_c)
+ self.assertEqual(prop_c.numeric, None)
- times = self.time.search([
- ('time', '<=', night),
- ])
- self.assertEqual(times, [time1])
+ props = Property.search([('numeric', '!=', None)])
+ self.assertEqual(props, [prop_a, prop_b])
- times = self.time.search([
- ('time', '>', night),
- ])
- self.assertEqual(times, [])
+ props = Property.search([('numeric', '=', None)])
+ self.assertEqual(props, [prop_c])
- times = self.time.search([
- ('time', '>', pre_evening),
- ])
- self.assertEqual(times, [time1])
+ props = Property.search([
+ ('numeric', '=', Decimal('1.1')),
+ ])
+ self.assertEqual(props, [prop_a])
- times = self.time.search([
- ('time', '>', evening),
- ])
- self.assertEqual(times, [])
+ props = Property.search([
+ ('numeric', '!=', Decimal('1.1'))])
+ self.assertEqual(props, [prop_b, prop_c])
- times = self.time.search([
- ('time', '>=', night),
- ])
- self.assertEqual(times, [])
+ props = Property.search([
+ ('numeric', '<', Decimal('2.6')),
+ ])
+ self.assertEqual(props, [prop_a])
- times = self.time.search([
- ('time', '>=', pre_evening),
- ])
- self.assertEqual(times, [time1])
+ props = Property.search([
+ ('numeric', '<=', Decimal('2.6'))])
+ self.assertEqual(props, [prop_a, prop_b])
- times = self.time.search([
- ('time', '>=', evening),
- ])
- self.assertEqual(times, [time1])
+ props = Property.search([
+ ('numeric', '>', Decimal('1.1')),
+ ])
+ self.assertEqual(props, [prop_b])
- time2, = self.time.create([{
- 'time': pre_evening,
- }])
- self.assert_(time2)
- self.assertEqual(time2.time, pre_evening)
+ props = Property.search([
+ ('numeric', '>=', Decimal('1.1'))])
+ self.assertEqual(props, [prop_a, prop_b])
- times = self.time.search([
- ('time', '=', pre_evening),
- ])
- self.assertEqual(times, [time2])
+ props = Property.search([
+ ('numeric', 'in', [Decimal('1.1')])])
+ self.assertEqual(props, [prop_a])
- times = self.time.search([
- ('time', 'in', [pre_evening, evening]),
- ])
- self.assertEqual(times, [time1, time2])
+ props = Property.search([
+ ('numeric', 'in', [Decimal('1.1'), Decimal('2.6')])])
+ self.assertEqual(props, [prop_a, prop_b])
- times = self.time.search([
- ('time', 'not in', [pre_evening, evening]),
- ])
- self.assertEqual(times, [])
+ props = Property.search([
+ ('numeric', 'not in', [Decimal('1.1')])])
+ self.assertEqual(props, [prop_b, prop_c])
- time3, = self.time.create([{}])
- self.assert_(time3)
- self.assertEqual(time3.time, None)
+ props = Property.search([
+ ('numeric', 'not in', [Decimal('1.1'), Decimal('2.6')])])
+ self.assertEqual(props, [prop_c])
- time4, = self.time_default.create([{}])
- self.assert_(time4)
- self.assertEqual(time4.time, default_time)
+ # Test default value
+ property_field, = ModelField.search([
+ ('model.model', '=', 'test.property'),
+ ('name', '=', 'numeric'),
+ ], limit=1)
+ IrProperty.create([{
+ 'field': property_field.id,
+ 'value': ',3.7',
+ }])
- self.time.write([time1], {
- 'time': pre_evening,
- })
- self.assertEqual(time1.time, pre_evening)
+ prop_d, = Property.create([{}])
+ self.assert_(prop_d)
+ self.assertEqual(prop_d.numeric, Decimal('3.7'))
- self.time.write([time2], {
- 'time': evening,
- })
- self.assertEqual(time2.time, evening)
+ Property.write([prop_a], {'numeric': None})
+ self.assertEqual(prop_a.numeric, None)
- self.assertRaises(Exception, self.time.create, [{
- 'time': 'test',
- }])
+ Property.write([prop_b], {'numeric': Decimal('3.11')})
+ self.assertEqual(prop_b.numeric, Decimal('3.11'))
- self.assertRaises(Exception, self.time.write, [time1],
- {
- 'time': 'test',
- })
+ transaction.rollback()
- self.assertRaises(Exception, self.time.create, [{
- 'time': 1,
- }])
+ # Test Selection
+ prop_a, = Property.create([{'selection': 'option_a'}])
+ self.assert_(prop_a)
+ self.assertEqual(prop_a.selection, 'option_a')
- self.assertRaises(Exception, self.time.write, [time1],
- {
- 'time': 1,
- })
+ prop_b, = Property.create([{'selection': 'option_b'}])
+ self.assert_(prop_b)
+ self.assertEqual(prop_b.selection, 'option_b')
- self.assertRaises(Exception, self.time.write, [time1],
- {
- 'time': '25:00:00',
- })
+ prop_c, = Property.create([{}])
+ self.assert_(prop_c)
+ self.assertEqual(prop_c.selection, None)
- time5, = self.time.create([{
- 'time': '12:00:00',
- }])
- self.assert_(time5)
- self.assertEqual(time5.time, datetime.time(12, 0))
+ props = Property.search([('selection', '=', 'option_a')])
+ self.assertEqual(props, [prop_a])
- self.assertRaises(UserError, self.time_required.create, [{}])
- transaction.cursor.rollback()
+ props = Property.search([('selection', '!=', None)])
+ self.assertEqual(props, [prop_a, prop_b])
- time6, = self.time_required.create([{
- 'time': evening,
- }])
- self.assert_(time6)
+ props = Property.search([('selection', '=', None)])
+ self.assertEqual(props, [prop_c])
- time7, = self.time.create([{
- 'time': None,
- }])
- self.assert_(time7)
+ props = Property.search([('selection', '!=', 'option_a')])
+ self.assertEqual(props, [prop_b, prop_c])
- time9, = self.time.create([{
- 'time': evening.replace(microsecond=1),
- }])
- self.assert_(time9)
- self.assertEqual(time9.time, evening)
+ props = Property.search([
+ ('selection', 'in', ['option_a'])])
+ self.assertEqual(props, [prop_a])
- # Test format
- self.assert_(self.time_format.create([{
- 'time': datetime.time(12, 30),
- }]))
- self.assertRaises(UserError, self.time_format.create, [{
- 'time': datetime.time(12, 30, 25),
- }])
+ props = Property.search([
+ ('selection', 'in', ['option_a', 'option_b'])])
+ self.assertEqual(props, [prop_a, prop_b])
- transaction.cursor.rollback()
+ props = Property.search([
+ ('selection', 'not in', ['option_a'])])
+ self.assertEqual(props, [prop_b, prop_c])
- def test0110one2one(self):
- 'Test One2One'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- target1, = self.one2one_target.create([{
- 'name': 'target1',
- }])
- one2one1, = self.one2one.create([{
- 'name': 'origin1',
- 'one2one': target1.id,
- }])
- self.assert_(one2one1)
- self.assertEqual(one2one1.one2one, target1)
+ # Test default value
+ property_field, = ModelField.search([
+ ('model.model', '=', 'test.property'),
+ ('name', '=', 'selection'),
+ ], limit=1)
+ IrProperty.create([{
+ 'field': property_field.id,
+ 'value': ',option_a',
+ }])
- self.assertEqual(self.one2one.read([one2one1.id],
- ['one2one.name'])[0]['one2one.name'], 'target1')
+ prop_d, = Property.create([{}])
+ self.assert_(prop_d)
+ self.assertEqual(prop_d.selection, 'option_a')
- one2ones = self.one2one.search([
- ('one2one', '=', 'target1'),
- ])
- self.assertEqual(one2ones, [one2one1])
+ Property.write([prop_a], {'selection': None})
+ self.assertEqual(prop_a.selection, None)
- one2ones = self.one2one.search([
- ('one2one', '!=', 'target1'),
- ])
- self.assertEqual(one2ones, [])
+ Property.write([prop_c], {'selection': 'option_b'})
+ self.assertEqual(prop_c.selection, 'option_b')
- one2ones = self.one2one.search([
- ('one2one', 'in', [target1.id]),
- ])
- self.assertEqual(one2ones, [one2one1])
+ @with_transaction()
+ def test_selection(self):
+ 'Test Selection'
+ pool = Pool()
+ Selection = pool.get('test.selection')
+ SelectionRequired = pool.get('test.selection_required')
+ transaction = Transaction()
+
+ selection1, = Selection.create([{'select': 'arabic'}])
+ self.assert_(selection1)
+ self.assertEqual(selection1.select, 'arabic')
+ self.assertEqual(selection1.select_string, 'Arabic')
+
+ selection2, = Selection.create([{'select': None}])
+ self.assert_(selection2)
+ self.assertEqual(selection2.select, None)
+ self.assertEqual(selection2.select_string, '')
+
+ self.assertRaises(UserError, Selection.create,
+ [{'select': 'chinese'}])
+
+ selection3, = Selection.create(
+ [{'select': 'arabic', 'dyn_select': '1'}])
+ self.assert_(selection3)
+ self.assertEqual(selection3.select, 'arabic')
+ self.assertEqual(selection3.dyn_select, '1')
+
+ selection4, = Selection.create(
+ [{'select': 'hexa', 'dyn_select': '0x3'}])
+ self.assert_(selection4)
+ self.assertEqual(selection4.select, 'hexa')
+ self.assertEqual(selection4.dyn_select, '0x3')
+
+ selection5, = Selection.create(
+ [{'select': 'hexa', 'dyn_select': None}])
+ self.assert_(selection5)
+ self.assertEqual(selection5.select, 'hexa')
+ self.assertEqual(selection5.dyn_select, None)
+
+ self.assertRaises(UserError, Selection.create,
+ [{'select': 'arabic', 'dyn_select': '0x3'}])
+ self.assertRaises(UserError, Selection.create,
+ [{'select': 'hexa', 'dyn_select': '3'}])
+
+ self.assertRaises(UserError, SelectionRequired.create, [{}])
+ transaction.rollback()
+
+ self.assertRaises(UserError, SelectionRequired.create,
+ [{'select': None}])
+ transaction.rollback()
+
+ selection6, = SelectionRequired.create([{'select': 'latin'}])
+ self.assert_(selection6)
+ self.assertEqual(selection6.select, 'latin')
+
+ @with_transaction()
+ def test_dict(self):
+ 'Test Dict'
+ pool = Pool()
+ Dict = pool.get('test.dict')
+ DictSchema = pool.get('test.dict.schema')
+ DictDefault = pool.get('test.dict_default')
+ DictRequired = pool.get('test.dict_required')
+ transaction = Transaction()
+
+ DictSchema.create([{
+ 'name': 'a',
+ 'string': 'A',
+ 'type_': 'integer',
+ }, {
+ 'name': 'b',
+ 'string': 'B',
+ 'type_': 'integer',
+ }, {
+ 'name': 'type',
+ 'string': 'Type',
+ 'type_': 'selection',
+ 'selection': ('arabic: Arabic\n'
+ 'hexa: Hexadecimal'),
+ }])
- one2ones = self.one2one.search([
- ('one2one', 'in', [0]),
- ])
- self.assertEqual(one2ones, [])
+ dict1, = Dict.create([{
+ 'dico': {'a': 1, 'b': 2},
+ }])
+ self.assert_(dict1.dico == {'a': 1, 'b': 2})
+
+ Dict.write([dict1], {'dico': {'z': 26}})
+ self.assert_(dict1.dico == {'z': 26})
+
+ dict1.dico = {
+ 'a': 1,
+ 'type': 'arabic',
+ }
+ dict1.save()
+ self.assertEqual(dict1.dico, {'a': 1, 'type': 'arabic'})
+ self.assertEqual(dict1.dico_string, {
+ 'a': 1,
+ 'type': 'Arabic',
+ })
+ self.assertEqual(dict1.dico_string_keys, {
+ 'a': 'A',
+ 'type': 'Type',
+ })
- one2ones = self.one2one.search([
- ('one2one', 'not in', [target1.id]),
- ])
- self.assertEqual(one2ones, [])
+ dict2, = Dict.create([{}])
+ self.assert_(dict2.dico is None)
- one2ones = self.one2one.search([
- ('one2one', 'not in', [0]),
- ])
- self.assertEqual(one2ones, [one2one1])
+ dict3, = DictDefault.create([{}])
+ self.assert_(dict3.dico == {'a': 1})
- one2ones = self.one2one.search([
- ('one2one.name', '=', 'target1'),
- ])
- self.assertEqual(one2ones, [one2one1])
+ self.assertRaises(UserError, DictRequired.create, [{}])
+ transaction.rollback()
- one2ones = self.one2one.search([
- ('one2one.name', '!=', 'target1'),
- ])
- self.assertEqual(one2ones, [])
+ dict4, = DictRequired.create([{'dico': dict(a=1)}])
+ self.assert_(dict4.dico == {'a': 1})
- one2one2, = self.one2one.create([{
- 'name': 'origin2',
- }])
- self.assert_(one2one2)
- self.assertEqual(one2one2.one2one, None)
+ self.assertRaises(UserError, DictRequired.create,
+ [{'dico': {}}])
- one2ones = self.one2one.search([
- ('one2one', '=', None),
- ])
- self.assertEqual(one2ones, [one2one2])
+ @with_transaction()
+ def test_binary(self):
+ 'Test Binary'
+ pool = Pool()
+ Binary = pool.get('test.binary')
+ BinaryDefault = pool.get('test.binary_default')
+ BinaryRequired = pool.get('test.binary_required')
+ transaction = Transaction()
+
+ bin1, = Binary.create([{
+ 'binary': fields.Binary.cast(b'foo'),
+ }])
+ self.assert_(bin1.binary == fields.Binary.cast(b'foo'))
- target2, = self.one2one_target.create([{
- 'name': 'target2',
- }])
- self.one2one.write([one2one2], {
- 'one2one': target2.id,
- })
- self.assertEqual(one2one2.one2one, target2)
+ Binary.write([bin1], {'binary': fields.Binary.cast(b'bar')})
+ self.assert_(bin1.binary == fields.Binary.cast(b'bar'))
- self.one2one.write([one2one2], {
- 'one2one': None,
- })
- self.assertEqual(one2one2.one2one, None)
+ with transaction.set_context({'test.binary.binary': 'size'}):
+ bin1_size = Binary(bin1.id)
+ self.assert_(bin1_size.binary == len(b'bar'))
+ self.assert_(bin1_size.binary != fields.Binary.cast(b'bar'))
- self.assertRaises(UserError, self.one2one.create, [{
- 'name': 'one2one3',
- 'one2one': target1.id,
- }])
- transaction.cursor.rollback()
+ bin2, = Binary.create([{}])
+ self.assert_(bin2.binary is None)
- self.assertRaises(UserError, self.one2one.write, [one2one2], {
- 'one2one': target1.id,
- })
- transaction.cursor.rollback()
+ bin3, = BinaryDefault.create([{}])
+ self.assert_(bin3.binary == fields.Binary.cast(b'default'))
- self.assertRaises(UserError, self.one2one_required.create, [{
- 'name': 'one2one3',
- }])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, BinaryRequired.create, [{}])
+ transaction.rollback()
- target3, = self.one2one_target.create([{
- 'name': 'target3',
- }])
+ bin4, = BinaryRequired.create([{
+ 'binary': fields.Binary.cast(b'baz'),
+ }])
+ self.assert_(bin4.binary == fields.Binary.cast(b'baz'))
- one2one3, = self.one2one_required.create([{
- 'name': 'one2one3',
- 'one2one': target3.id,
- }])
- self.assert_(one2one3)
+ self.assertRaises(UserError, BinaryRequired.create,
+ [{'binary': fields.Binary.cast(b'')}])
- target4, = self.one2one_target.create([{
- 'name': 'target4',
- }])
- self.assertRaises(UserError, self.one2one_domain.create, [{
- 'name': 'one2one4',
- 'one2one': target4.id,
- }])
- transaction.cursor.rollback()
+ @with_transaction()
+ def test_many2one(self):
+ 'Test Many2One'
+ pool = Pool()
+ Many2oneDomainValidation = pool.get('test.many2one_domainvalidation')
+ Many2oneOrderby = pool.get('test.many2one_orderby')
+ Many2oneTarget = pool.get('test.many2one_target')
+ Many2oneSearch = pool.get('test.many2one_search')
+ transaction = Transaction()
+
+ # Not respecting the domain raise an Error
+ m2o_1, = Many2oneTarget.create([{'value': 1}])
+ self.assertRaises(UserError, Many2oneDomainValidation.create,
+ [{'many2one': m2o_1}])
+
+ # Respecting the domain works
+ m2o_6, = Many2oneTarget.create([{'value': 6}])
+ domain, = Many2oneDomainValidation.create([{'many2one': m2o_6}])
+ self.assert_(domain)
+ self.assertEqual(domain.many2one.value, 6)
+
+ # Inactive records are taken into account
+ m2o_6.active = False
+ m2o_6.save()
+ domain.dummy = 'Dummy'
+ domain.save()
+
+ # Testing order_by
+ for value in (5, 3, 2):
+ m2o, = Many2oneTarget.create([{'value': value}])
+ Many2oneOrderby.create([{'many2one': m2o}])
+
+ search = Many2oneOrderby.search([], order=[('many2one', 'ASC')])
+ self.assertTrue(all(x.many2one.value <= y.many2one.value
+ for x, y in zip(search, search[1:])))
+
+ search = Many2oneOrderby.search([],
+ order=[('many2one.id', 'ASC')])
+ self.assertTrue(all(x.many2one.id <= y.many2one.id
+ for x, y in zip(search, search[1:])))
+
+ search = Many2oneOrderby.search([],
+ order=[('many2one.value', 'ASC')])
+ self.assertTrue(all(x.many2one.value <= y.many2one.value
+ for x, y in zip(search, search[1:])))
+
+ transaction.rollback()
+
+ target1, target2 = Many2oneTarget.create([
+ {'value': 1},
+ {'value': 2},
+ ])
+
+ search1, search2 = Many2oneSearch.create([
+ {'many2one': target1.id},
+ {'many2one': target2.id},
+ ])
+
+ # Search join
+ Many2oneSearch.target_search = 'join'
+ self.assertEqual(Many2oneSearch.search([
+ ('many2one.value', '=', 1),
+ ]), [search1])
+
+ # Search subquery
+ Many2oneSearch.target_search = 'subquery'
+ self.assertEqual(Many2oneSearch.search([
+ ('many2one.value', '=', 1),
+ ]), [search1])
+
+ transaction.rollback()
+
+ for model in ['test.many2one_tree', 'test.many2one_mptt']:
+ pool = Pool()
+ Many2One = pool.get(model)
+
+ root1, root2 = Many2One.create([{}, {}])
+ first1, first2, first3, first4 = Many2One.create([
+ {'many2one': root1.id},
+ {'many2one': root1.id},
+ {'many2one': root2.id},
+ {'many2one': root2.id},
+ ])
+ second1, second2, second3, second4 = Many2One.create([
+ {'many2one': first1.id},
+ {'many2one': first1.id},
+ {'many2one': first2.id},
+ {'many2one': first2.id},
+ ])
+ all_ = Many2One.search([])
+
+ def not_(l):
+ return [r for r in all_ if r not in l]
+
+ for clause, test in [
+ ([root1.id], [root1, first1, first2,
+ second1, second2, second3, second4]),
+ ([second1.id], [second1]),
+ ([root2.id], [root2, first3, first4]),
+ ([first2.id, first3.id], [first2, first3,
+ second3, second4]),
+ ([], []),
+ ]:
+ result = Many2One.search(
+ [('many2one', 'child_of', clause)])
+ self.assertEqual(result, test)
+
+ result = Many2One.search(
+ [('many2one', 'not child_of', clause)])
+ self.assertEqual(result, not_(test))
+
+ for clause, test in [
+ ([root1.id], [root1]),
+ ([first3.id], [root2, first3]),
+ ([second4.id], [root1, first2, second4]),
+ ([second4.id, first4.id], [root1, root2,
+ first2, first4, second4]),
+ ([], []),
+ ]:
+ result = Many2One.search(
+ [('many2one', 'parent_of', clause)])
+ self.assertEqual(result, test)
+
+ result = Many2One.search(
+ [('many2one', 'not parent_of', clause)])
+ self.assertEqual(result, not_(test))
+
+ transaction.rollback()
+
+ @with_transaction()
+ def test_timedelta(self):
+ 'Test timedelta'
+ pool = Pool()
+ Timedelta = pool.get('test.timedelta')
+ TimedeltaDefault = pool.get('test.timedelta_default')
+ TimedeltaRequired = pool.get('test.timedelta_required')
+ transaction = Transaction()
+
+ minute = datetime.timedelta(minutes=1)
+ hour = datetime.timedelta(hours=1)
+ day = datetime.timedelta(days=1)
+ default_timedelta = datetime.timedelta(seconds=3600)
+
+ timedelta1, = Timedelta.create([{
+ 'timedelta': hour,
+ }])
+ self.assert_(timedelta1)
+ self.assertEqual(timedelta1.timedelta, hour)
+
+ timedelta = Timedelta.search([
+ ('timedelta', '=', hour),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '=', day),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '=', None),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '!=', day),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '!=', None),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'in', [hour]),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'in', [day]),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'in', [minute]),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'in', [None]),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'in', []),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'not in', [hour]),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'not in', [day]),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'not in', [None]),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'not in', []),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '<', day),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '<', minute),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '<', hour),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '<=', hour),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '<=', minute),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '<=', day),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '>', day),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '>', minute),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '>', hour),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '>=', day),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '>=', minute),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta = Timedelta.search([
+ ('timedelta', '>=', hour),
+ ])
+ self.assertEqual(timedelta, [timedelta1])
+
+ timedelta2, = Timedelta.create([{
+ 'timedelta': minute,
+ }])
+ self.assert_(timedelta2)
+ self.assertEqual(timedelta2.timedelta, minute)
+
+ timedelta = Timedelta.search([
+ ('timedelta', '=', minute),
+ ])
+ self.assertEqual(timedelta, [timedelta2])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'in', [minute, hour]),
+ ])
+ self.assertEqual(timedelta, [timedelta1, timedelta2])
+
+ timedelta = Timedelta.search([
+ ('timedelta', 'not in', [minute, hour]),
+ ])
+ self.assertEqual(timedelta, [])
+
+ timedelta3, = Timedelta.create([{}])
+ self.assert_(timedelta3)
+ self.assertEqual(timedelta3.timedelta, None)
+
+ timedelta4, = TimedeltaDefault.create([{}])
+ self.assert_(timedelta4)
+ self.assertEqual(timedelta4.timedelta, default_timedelta)
+
+ Timedelta.write([timedelta1], {
+ 'timedelta': minute,
+ })
+ self.assertEqual(timedelta1.timedelta, minute)
+
+ Timedelta.write([timedelta2], {
+ 'timedelta': day,
+ })
+ self.assertEqual(timedelta2.timedelta, day)
+
+ self.assertRaises(Exception, Timedelta.create, [{
+ 'timedelta': 'test',
+ }])
- target5, = self.one2one_target.create([{
- 'name': 'domain',
- }])
- one2one5, = self.one2one_domain.create([{
- 'name': 'one2one5',
- 'one2one': target5.id,
- }])
- targets = self.one2one_target.create([{
- 'name': 'multiple1',
- }, {
- 'name': 'multiple2',
- }])
- one2ones = self.one2one.create([{
- 'name': 'origin6',
- 'one2one': targets[0].id,
- }, {
- 'name': 'origin7',
- 'one2one': targets[1].id,
- }])
- for one2one, target in zip(one2ones, targets):
- self.assert_(one2one)
- self.assertEqual(one2one.one2one, target)
+ self.assertRaises(Exception, Timedelta.write, [timedelta1], {
+ 'timedelta': 'test',
+ })
- transaction.cursor.rollback()
+ self.assertRaises(Exception, Timedelta.create, [{
+ 'timedelta': 1,
+ }])
- def test0120one2many(self):
- 'Test One2Many'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- for one2many, one2many_target in (
- (self.one2many, self.one2many_target),
- (self.one2many_reference, self.one2many_reference_target),
- ):
- one2many1, = one2many.create([{
- 'name': 'origin1',
- 'targets': [
- ('create', [{
- 'name': 'target1',
- }]),
- ],
- }])
- self.assert_(one2many1)
-
- self.assertEqual(len(one2many1.targets), 1)
- target1, = one2many1.targets
-
- # Try with target1 stored in cache
- target1 = one2many_target(target1.id)
- target1.origin
- one2many1 = one2many(one2many1)
- self.assertEqual(one2many1.targets, (target1,))
-
- one2manys = one2many.search([
- ('targets', '=', 'target1'),
- ])
- self.assertEqual(one2manys, [one2many1])
-
- one2manys = one2many.search([
- ('targets', '!=', 'target1'),
- ])
- self.assertEqual(one2manys, [])
-
- one2manys = one2many.search([
- ('targets', 'in', [target1.id]),
- ])
- self.assertEqual(one2manys, [one2many1])
-
- one2manys = one2many.search([
- ('targets', 'in', [0]),
- ])
- self.assertEqual(one2manys, [])
-
- one2manys = one2many.search([
- ('targets', 'not in', (target1.id,)),
- ])
- self.assertEqual(one2manys, [])
-
- one2manys = one2many.search([
- ('targets', 'not in', [0]),
- ])
- self.assertEqual(one2manys, [one2many1])
-
- one2manys = one2many.search([
- ('targets.name', '=', 'target1'),
- ])
- self.assertEqual(one2manys, [one2many1])
-
- one2manys = one2many.search([
- ('targets.name', '!=', 'target1'),
- ])
- self.assertEqual(one2manys, [])
-
- one2many2, = one2many.create([{
- 'name': 'origin2',
- }])
- self.assert_(one2many2)
-
- self.assertEqual(one2many2.targets, ())
-
- one2manys = one2many.search([
- ('targets', '=', None),
- ])
- self.assertEqual(one2manys, [one2many2])
-
- one2many.write([one2many1], {
- 'targets': [
- ('write', [target1.id], {
- 'name': 'target1bis',
- }),
- ],
- })
- self.assertEqual(target1.name, 'target1bis')
+ self.assertRaises(Exception, Timedelta.write, [timedelta1], {
+ 'timedelta': 1,
+ })
- target2, = one2many_target.create([{
- 'name': 'target2',
- }])
- one2many.write([one2many1], {
- 'targets': [
- ('add', [target2.id]),
- ],
- })
- self.assertEqual(one2many1.targets,
- (target1, target2))
-
- one2many.write([one2many1], {
- 'targets': [
- ('remove', [target2.id]),
- ],
- })
- self.assertEqual(one2many1.targets, (target1,))
- target2, = one2many_target.search([
- ('id', '=', target2.id),
- ])
- self.assert_(target2)
-
- one2many.write([one2many1], {
- 'targets': [
- ('remove', [target1.id]),
- ],
- })
- self.assertEqual(one2many1.targets, ())
- targets = one2many_target.search([
- ('id', 'in', [target1.id, target2.id]),
- ])
- self.assertEqual(targets, [target1, target2])
-
- one2many.write([one2many1], {
- 'targets': [
- ('add', [target1.id, target2.id]),
- ],
- })
- self.assertEqual(one2many1.targets,
- (target1, target2))
-
- one2many.write([one2many1], {
- 'targets': [
- ('copy', [target1.id], {'name': 'copy1'}),
- ],
- })
- targets = one2many_target.search([
- ('id', 'not in', [target1.id, target2.id]),
- ])
- self.assertEqual(len(targets), 1)
- self.assertEqual(targets[0].name, 'copy1')
-
- one2many.write([one2many1], {
- 'targets': [
- ('copy', [target2.id]),
- ],
- })
- self.assertEqual(len(one2many1.targets), 4)
- targets = one2many_target.search([
- ('id', 'not in', [target1.id, target2.id]),
- ])
- self.assertEqual(len(targets), 2)
- names = set([target.name for target in targets])
- self.assertEqual(names, set(('copy1', 'target2')))
-
- copy_ids = [target.id for target in targets]
- one2many.write([one2many1], {
- 'targets': [
- ('delete', [target2.id] + copy_ids),
- ],
- })
- self.assertEqual(one2many1.targets, (target1,))
- targets = one2many_target.search([
- ('id', '=', target2.id),
- ])
- self.assertEqual(targets, [])
-
- transaction.cursor.rollback()
-
- self.assertRaises(UserError, self.one2many_required.create, [{
- 'name': 'origin3',
- }])
- transaction.cursor.rollback()
-
- origin3_id, = self.one2many_required.create([{
- 'name': 'origin3',
- 'targets': [
- ('create', [{
- 'name': 'target3',
- }]),
- ],
- }])
- self.assert_(origin3_id)
-
- self.one2many_size.create([{
- 'targets': [('create', [{}])] * 3,
- }])
- self.assertRaises(UserError, self.one2many_size.create, [{
- 'targets': [('create', [{}])] * 4,
- }])
- self.one2many_size_pyson.create([{
- 'limit': 4,
- 'targets': [('create', [{}])] * 4,
- }])
- self.assertRaises(UserError, self.one2many_size_pyson.create, [{
- 'limit': 2,
- 'targets': [('create', [{}])] * 4,
- }])
-
- transaction.cursor.rollback()
-
- def test0130many2many(self):
- 'Test Many2Many'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- for many2many, many2many_target in (
- (self.many2many, self.many2many_target),
- (self.many2many_reference,
- self.many2many_reference_target),
- ):
- many2many1, = many2many.create([{
- 'name': 'origin1',
- 'targets': [
- ('create', [{
- 'name': 'target1',
- }]),
- ],
- }])
- self.assert_(many2many1)
-
- self.assertEqual(len(many2many1.targets), 1)
- target1, = many2many1.targets
-
- many2manys = many2many.search([
- ('targets', '=', 'target1'),
- ])
- self.assertEqual(many2manys, [many2many1])
-
- many2manys = many2many.search([
- ('targets', '!=', 'target1'),
- ])
- self.assertEqual(many2manys, [])
-
- many2manys = many2many.search([
- ('targets', 'in', [target1.id]),
- ])
- self.assertEqual(many2manys, [many2many1])
-
- many2manys = many2many.search([
- ('targets', 'in', [0]),
- ])
- self.assertEqual(many2manys, [])
-
- many2manys = many2many.search([
- ('targets', 'not in', [target1.id]),
- ])
- self.assertEqual(many2manys, [])
-
- many2manys = many2many.search([
- ('targets', 'not in', [0]),
- ])
- self.assertEqual(many2manys, [many2many1])
-
- many2manys = many2many.search([
- ('targets.name', '=', 'target1'),
- ])
- self.assertEqual(many2manys, [many2many1])
-
- many2manys = many2many.search([
- ('targets.name', '!=', 'target1'),
- ])
- self.assertEqual(many2manys, [])
-
- many2many2, = many2many.create([{
- 'name': 'origin2',
- }])
- self.assert_(many2many2)
-
- self.assertEqual(many2many2.targets, ())
-
- many2manys = many2many.search([
- ('targets', '=', None),
- ])
- self.assertEqual(many2manys, [many2many2])
-
- many2many.write([many2many1], {
- 'targets': [
- ('write', [target1.id], {
- 'name': 'target1bis',
- }),
- ],
- })
- self.assertEqual(target1.name, 'target1bis')
-
- target2, = many2many_target.create([{
- 'name': 'target2',
- }])
- many2many.write([many2many1], {
- 'targets': [
- ('add', [target2.id]),
- ],
- })
- self.assertEqual(many2many1.targets,
- (target1, target2))
-
- many2many.write([many2many1], {
- 'targets': [
- ('remove', [target2.id]),
- ],
- })
- self.assertEqual(many2many1.targets, (target1,))
- target2, = many2many_target.search([
- ('id', '=', target2.id),
- ])
- self.assert_(target2)
-
- many2many.write([many2many1], {
- 'targets': [
- ('remove', [target1.id]),
- ],
- })
- self.assertEqual(many2many1.targets, ())
- targets = many2many_target.search([
- ('id', 'in', [target1.id, target2.id]),
- ])
- self.assertEqual(targets, [target1, target2])
-
- many2many.write([many2many1], {
- 'targets': [
- ('add', [target1.id, target2.id]),
- ],
- })
- self.assertEqual(many2many1.targets,
- (target1, target2))
-
- many2many.write([many2many1], {
- 'targets': [
- ('copy', [target1.id], {'name': 'copy1'}),
- ],
- })
- targets = many2many_target.search([
- ('id', 'not in', [target1.id, target2.id]),
- ])
- self.assertEqual(len(targets), 1)
- self.assertEqual(targets[0].name, 'copy1')
-
- many2many.write([many2many1], {
- 'targets': [
- ('copy', [target2.id]),
- ],
- })
- self.assertEqual(len(many2many1.targets), 4)
- targets = many2many_target.search([
- ('id', 'not in', [target1.id, target2.id]),
- ])
- self.assertEqual(len(targets), 2)
- names = set([target.name for target in targets])
- self.assertEqual(names, set(('copy1', 'target2')))
-
- copy_ids = [target.id for target in targets]
- many2many.write([many2many1], {
- 'targets': [
- ('delete', [target2.id] + copy_ids),
- ],
- })
- self.assertEqual(many2many1.targets, (target1,))
- targets = many2many_target.search([
- ('id', '=', target2.id),
- ])
- self.assertEqual(targets, [])
-
- transaction.cursor.rollback()
-
- self.assertRaises(UserError, self.many2many_required.create, [{
- 'name': 'origin3',
- }])
- transaction.cursor.rollback()
-
- origin3_id, = self.many2many_required.create([{
- 'name': 'origin3',
- 'targets': [
- ('create', [{
- 'name': 'target3',
- }]),
- ],
- }])
- self.assert_(origin3_id)
-
- self.many2many_size_target.create([{
- 'name': str(i),
- } for i in range(6)])
-
- transaction.cursor.rollback()
-
- def test0140reference(self):
- 'Test Reference'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- target1, = self.reference_target.create([{
- 'name': 'target1',
- }])
- reference1, = self.reference.create([{
- 'name': 'reference1',
- 'reference': str(target1),
- }])
- self.assert_(reference1)
-
- self.assertEqual(reference1.reference, target1)
-
- references = self.reference.search([
- ('reference', '=', str(target1)),
- ])
- self.assertEqual(references, [reference1])
-
- references = self.reference.search([
- ('reference', '=', (target1.__name__, target1.id)),
- ])
- self.assertEqual(references, [reference1])
-
- references = self.reference.search([
- ('reference', '=', [target1.__name__, target1.id]),
- ])
- self.assertEqual(references, [reference1])
-
- references = self.reference.search([
- ('reference.name', '=', 'target1',
- 'test.reference.target'),
- ])
- self.assertEqual(references, [reference1])
-
- references = self.reference.search([
- ('reference', '!=', str(target1)),
- ])
- self.assertEqual(references, [])
-
- references = self.reference.search([
- ('reference', '!=', str(target1)),
- ])
- self.assertEqual(references, [])
-
- references = self.reference.search([
- ('reference', 'in', [str(target1)]),
- ])
- self.assertEqual(references, [reference1])
-
- references = self.reference.search([
- ('reference', 'in',
- [('test.reference.target', target1.id)]),
- ])
- self.assertEqual(references, [reference1])
-
- references = self.reference.search([
- ('reference', 'in', [None]),
- ])
- self.assertEqual(references, [])
-
- references = self.reference.search([
- ('reference', 'not in', [str(target1)]),
- ])
- self.assertEqual(references, [])
-
- references = self.reference.search([
- ('reference', 'not in',
- [('test.reference.target', target1.id)]),
- ])
- self.assertEqual(references, [])
-
- references = self.reference.search([
- ('reference', 'not in', [None]),
- ])
- self.assertEqual(references, [reference1])
-
- reference2, = self.reference.create([{
- 'name': 'reference2',
- }])
- self.assert_(reference2)
-
- self.assertEqual(reference2.reference, None)
-
- references = self.reference.search([
- ('reference', '=', None),
- ])
- self.assertEqual(references, [reference2])
-
- target2, = self.reference_target.create([{
- 'name': 'target2',
- }])
-
- self.reference.write([reference2], {
- 'reference': str(target2),
- })
- self.assertEqual(reference2.reference, target2)
-
- self.reference.write([reference2], {
- 'reference': None,
- })
- self.assertEqual(reference2.reference, None)
-
- self.reference.write([reference2], {
- 'reference': ('test.reference.target', target2.id),
- })
- self.assertEqual(reference2.reference, target2)
-
- reference3, = self.reference.create([{
- 'name': 'reference3',
- 'reference': ('test.reference.target', target1.id),
- }])
- self.assert_(reference3)
-
- self.assertRaises(UserError, self.reference_required.create, [{
- 'name': 'reference4',
- }])
- transaction.cursor.rollback()
-
- target4, = self.reference_target.create([{
- 'name': 'target4_id',
- }])
-
- reference4, = self.reference_required.create([{
- 'name': 'reference4',
- 'reference': str(target4),
- }])
- self.assert_(reference4)
-
- transaction.cursor.rollback()
-
- def test0150property(self):
- 'Test Property with supported field types'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
-
- # Test Char
- prop_a, = self.property_.create([{'char': 'Test'}])
- self.assert_(prop_a)
- self.assertEqual(prop_a.char, 'Test')
-
- prop_b, = self.property_.create([{}])
- self.assert_(prop_b)
- self.assertEqual(prop_b.char, None)
-
- prop_c, = self.property_.create([{'char': 'FooBar'}])
- self.assert_(prop_c)
- self.assertEqual(prop_c.char, 'FooBar')
-
- props = self.property_.search([('char', '=', 'Test')])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([('char', '=', None)])
- self.assertEqual(props, [prop_b])
-
- props = self.property_.search([('char', '!=', None)])
- self.assertEqual(props, [prop_a, prop_c])
-
- props = self.property_.search([('char', 'like', 'Tes%')])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([('char', 'like', '%Bar')])
- self.assertEqual(props, [prop_c])
-
- props = self.property_.search([('char', 'not like', 'Tes%')])
- self.assertEqual(props, [prop_b, prop_c])
-
- props = self.property_.search([('char', 'ilike', 'tes%')])
- self.assert_(props, [prop_a])
-
- props = self.property_.search([('char', 'ilike', '%bar')])
- self.assertEqual(props, [prop_c])
-
- props = self.property_.search([('char', 'not ilike', 'tes%')])
- self.assertEqual(props, [prop_b, prop_c])
-
- props = self.property_.search([('char', 'in', ['Test'])])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([
- ('char', 'in', ['Test', 'FooBar'])])
- self.assertEqual(props, [prop_a, prop_c])
-
- props = self.property_.search([
- ('char', 'not in', ['Test', 'FooBar'])])
- self.assertEqual(props, [prop_b])
-
- # Test default value
- property_field, = self.model_field.search([
- ('model.model', '=', 'test.property'),
- ('name', '=', 'char'),
- ], limit=1)
- self.ir_property.create([{
- 'field': property_field.id,
- 'value': ',DEFAULT_VALUE',
- }])
-
- prop_d, = self.property_.create([{}])
- self.assert_(prop_d)
- self.assertEqual(prop_d.char, 'DEFAULT_VALUE')
-
- props = self.property_.search([('char', '!=', None)])
- self.assertEqual(props, [prop_a, prop_c, prop_d])
-
- self.property_.write([prop_a], {'char': None})
- self.assertEqual(prop_a.char, None)
-
- self.property_.write([prop_b], {'char': 'Test'})
- self.assertEqual(prop_b.char, 'Test')
-
- transaction.cursor.rollback()
-
- # Test Many2One
- char_a, = self.char.create([{'char': 'Test'}])
- self.assert_(char_a)
-
- char_b, = self.char.create([{'char': 'FooBar'}])
- self.assert_(char_b)
-
- prop_a, = self.property_.create([{'many2one': char_a.id}])
- self.assert_(prop_a)
- self.assertEqual(prop_a.many2one, char_a)
-
- prop_b, = self.property_.create([{'many2one': char_b.id}])
- self.assert_(prop_b)
- self.assertEqual(prop_b.many2one, char_b)
-
- prop_c, = self.property_.create([{}])
- self.assert_(prop_c)
- self.assertEqual(prop_c.many2one, None)
-
- props = self.property_.search([('many2one', '=', char_a.id)])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([('many2one', '!=', None)])
- self.assertEqual(props, [prop_a, prop_b])
-
- props = self.property_.search([('many2one', '=', None)])
- self.assertEqual(props, [prop_c])
-
- self.assertEqual(prop_a.many2one, char_a)
-
- props = self.property_.search([
- ('many2one', 'in', [char_a.id, char_b.id])])
- self.assertEqual(props, [prop_a, prop_b])
-
- props = self.property_.search([
- ('many2one', 'not in', [char_a.id, char_b.id])])
- self.assertEqual(props, [prop_c])
-
- self.property_.write([prop_b], {'many2one': char_a.id})
- self.assertEqual(prop_b.many2one, char_a)
-
- transaction.cursor.rollback()
-
- # Test Numeric
- prop_a, = self.property_.create([{'numeric': Decimal('1.1')}])
- self.assert_(prop_a)
- self.assertEqual(prop_a.numeric, Decimal('1.1'))
-
- prop_b, = self.property_.create([{'numeric': Decimal('2.6')}])
- self.assert_(prop_b)
- self.assertEqual(prop_b.numeric, Decimal('2.6'))
-
- prop_c, = self.property_.create([{}])
- self.assert_(prop_c)
- self.assertEqual(prop_c.numeric, None)
-
- props = self.property_.search([('numeric', '!=', None)])
- self.assertEqual(props, [prop_a, prop_b])
-
- props = self.property_.search([('numeric', '=', None)])
- self.assertEqual(props, [prop_c])
-
- props = self.property_.search([
- ('numeric', '=', Decimal('1.1')),
- ])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([
- ('numeric', '!=', Decimal('1.1'))])
- self.assertEqual(props, [prop_b, prop_c])
-
- props = self.property_.search([
- ('numeric', '<', Decimal('2.6')),
- ])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([
- ('numeric', '<=', Decimal('2.6'))])
- self.assertEqual(props, [prop_a, prop_b])
+ self.assertRaises(UserError, TimedeltaRequired.create, [{}])
+ transaction.rollback()
- props = self.property_.search([
- ('numeric', '>', Decimal('1.1')),
- ])
- self.assertEqual(props, [prop_b])
-
- props = self.property_.search([
- ('numeric', '>=', Decimal('1.1'))])
- self.assertEqual(props, [prop_a, prop_b])
-
- props = self.property_.search([
- ('numeric', 'in', [Decimal('1.1')])])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([
- ('numeric', 'in', [Decimal('1.1'), Decimal('2.6')])])
- self.assertEqual(props, [prop_a, prop_b])
-
- props = self.property_.search([
- ('numeric', 'not in', [Decimal('1.1')])])
- self.assertEqual(props, [prop_b, prop_c])
-
- props = self.property_.search([
- ('numeric', 'not in', [Decimal('1.1'), Decimal('2.6')])])
- self.assertEqual(props, [prop_c])
-
- # Test default value
- property_field, = self.model_field.search([
- ('model.model', '=', 'test.property'),
- ('name', '=', 'numeric'),
- ], limit=1)
- self.ir_property.create([{
- 'field': property_field.id,
- 'value': ',3.7',
- }])
-
- prop_d, = self.property_.create([{}])
- self.assert_(prop_d)
- self.assertEqual(prop_d.numeric, Decimal('3.7'))
-
- self.property_.write([prop_a], {'numeric': None})
- self.assertEqual(prop_a.numeric, None)
-
- self.property_.write([prop_b], {'numeric': Decimal('3.11')})
- self.assertEqual(prop_b.numeric, Decimal('3.11'))
-
- transaction.cursor.rollback()
-
- # Test Selection
- prop_a, = self.property_.create([{'selection': 'option_a'}])
- self.assert_(prop_a)
- self.assertEqual(prop_a.selection, 'option_a')
-
- prop_b, = self.property_.create([{'selection': 'option_b'}])
- self.assert_(prop_b)
- self.assertEqual(prop_b.selection, 'option_b')
-
- prop_c, = self.property_.create([{}])
- self.assert_(prop_c)
- self.assertEqual(prop_c.selection, None)
-
- props = self.property_.search([('selection', '=', 'option_a')])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([('selection', '!=', None)])
- self.assertEqual(props, [prop_a, prop_b])
-
- props = self.property_.search([('selection', '=', None)])
- self.assertEqual(props, [prop_c])
-
- props = self.property_.search([('selection', '!=', 'option_a')])
- self.assertEqual(props, [prop_b, prop_c])
-
- props = self.property_.search([
- ('selection', 'in', ['option_a'])])
- self.assertEqual(props, [prop_a])
-
- props = self.property_.search([
- ('selection', 'in', ['option_a', 'option_b'])])
- self.assertEqual(props, [prop_a, prop_b])
-
- props = self.property_.search([
- ('selection', 'not in', ['option_a'])])
- self.assertEqual(props, [prop_b, prop_c])
-
- # Test default value
- property_field, = self.model_field.search([
- ('model.model', '=', 'test.property'),
- ('name', '=', 'selection'),
- ], limit=1)
- self.ir_property.create([{
- 'field': property_field.id,
- 'value': ',option_a',
- }])
-
- prop_d, = self.property_.create([{}])
- self.assert_(prop_d)
- self.assertEqual(prop_d.selection, 'option_a')
-
- self.property_.write([prop_a], {'selection': None})
- self.assertEqual(prop_a.selection, None)
-
- self.property_.write([prop_c], {'selection': 'option_b'})
- self.assertEqual(prop_c.selection, 'option_b')
-
- transaction.cursor.rollback()
-
- def test0160selection(self):
- 'Test Selection'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- 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(UserError, self.selection.create,
- [{'select': 'chinese'}])
-
- selection3, = self.selection.create(
- [{'select': 'arabic', 'dyn_select': '1'}])
- self.assert_(selection3)
- self.assertEqual(selection3.select, 'arabic')
- self.assertEqual(selection3.dyn_select, '1')
-
- selection4, = self.selection.create(
- [{'select': 'hexa', 'dyn_select': '0x3'}])
- self.assert_(selection4)
- self.assertEqual(selection4.select, 'hexa')
- self.assertEqual(selection4.dyn_select, '0x3')
-
- selection5, = self.selection.create(
- [{'select': 'hexa', 'dyn_select': None}])
- self.assert_(selection5)
- self.assertEqual(selection5.select, 'hexa')
- self.assertEqual(selection5.dyn_select, None)
-
- self.assertRaises(UserError, self.selection.create,
- [{'select': 'arabic', 'dyn_select': '0x3'}])
- self.assertRaises(UserError, self.selection.create,
- [{'select': 'hexa', 'dyn_select': '3'}])
-
- self.assertRaises(UserError, self.selection_required.create, [{}])
- transaction.cursor.rollback()
-
- self.assertRaises(UserError, self.selection_required.create,
- [{'select': None}])
- transaction.cursor.rollback()
-
- selection6, = self.selection_required.create([{'select': 'latin'}])
- self.assert_(selection6)
- self.assertEqual(selection6.select, 'latin')
-
- def test0170dict(self):
- 'Test Dict'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
-
- self.dict_schema.create([{
- 'name': 'a',
- 'string': 'A',
- 'type_': 'integer',
- }, {
- 'name': 'b',
- 'string': 'B',
- 'type_': 'integer',
- }, {
- 'name': 'type',
- 'string': 'Type',
- 'type_': 'selection',
- 'selection': ('arabic: Arabic\n'
- 'hexa: Hexadecimal'),
- }])
-
- dict1, = self.dict_.create([{
- 'dico': {'a': 1, 'b': 2},
- }])
- self.assert_(dict1.dico == {'a': 1, 'b': 2})
-
- self.dict_.write([dict1], {'dico': {'z': 26}})
- self.assert_(dict1.dico == {'z': 26})
-
- dict1.dico = {
- 'a': 1,
- 'type': 'arabic',
- }
- dict1.save()
- self.assertEqual(dict1.dico, {'a': 1, 'type': 'arabic'})
- self.assertEqual(dict1.dico_string, {
- 'a': 1,
- 'type': 'Arabic',
- })
- self.assertEqual(dict1.dico_string_keys, {
- 'a': 'A',
- 'type': 'Type',
- })
-
- dict2, = self.dict_.create([{}])
- self.assert_(dict2.dico is None)
-
- dict3, = self.dict_default.create([{}])
- self.assert_(dict3.dico == {'a': 1})
-
- 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(UserError, self.dict_required.create,
- [{'dico': {}}])
- transaction.cursor.rollback()
-
- def test0180binary(self):
- 'Test Binary'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- bin1, = self.binary.create([{
- 'binary': fields.Binary.cast(b'foo'),
- }])
- self.assert_(bin1.binary == fields.Binary.cast(b'foo'))
-
- self.binary.write([bin1], {'binary': fields.Binary.cast(b'bar')})
- self.assert_(bin1.binary == fields.Binary.cast(b'bar'))
-
- with transaction.set_context({'test.binary.binary': 'size'}):
- bin1_size = self.binary(bin1.id)
- self.assert_(bin1_size.binary == len(b'bar'))
- self.assert_(bin1_size.binary != fields.Binary.cast(b'bar'))
-
- bin2, = self.binary.create([{}])
- self.assert_(bin2.binary is None)
-
- bin3, = self.binary_default.create([{}])
- self.assert_(bin3.binary == fields.Binary.cast(b'default'))
-
- self.assertRaises(UserError, self.binary_required.create, [{}])
- transaction.cursor.rollback()
-
- bin4, = self.binary_required.create([{
- 'binary': fields.Binary.cast(b'baz'),
- }])
- self.assert_(bin4.binary == fields.Binary.cast(b'baz'))
-
- self.assertRaises(UserError, self.binary_required.create,
- [{'binary': fields.Binary.cast(b'')}])
-
- transaction.cursor.rollback()
-
- def test0190many2one(self):
- 'Test Many2One'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
-
- # Not respecting the domain raise an Error
- m2o_1, = self.m2o_target.create([{'value': 1}])
- self.assertRaises(UserError, self.m2o_domain_validation.create,
- [{'many2one': m2o_1}])
-
- # Respecting the domain works
- m2o_6, = self.m2o_target.create([{'value': 6}])
- domain, = self.m2o_domain_validation.create([{'many2one': m2o_6}])
- self.assert_(domain)
- self.assertEqual(domain.many2one.value, 6)
-
- # Inactive records are taken into account
- m2o_6.active = False
- m2o_6.save()
- domain.dummy = 'Dummy'
- domain.save()
-
- # Testing order_by
- for value in (5, 3, 2):
- m2o, = self.m2o_target.create([{'value': value}])
- self.m2o_orderby.create([{'many2one': m2o}])
-
- search = self.m2o_orderby.search([], order=[('many2one', 'ASC')])
- self.assertTrue(all(x.many2one.value <= y.many2one.value
- for x, y in zip(search, search[1:])))
-
- search = self.m2o_orderby.search([],
- order=[('many2one.id', 'ASC')])
- self.assertTrue(all(x.many2one.id <= y.many2one.id
- for x, y in zip(search, search[1:])))
-
- search = self.m2o_orderby.search([],
- order=[('many2one.value', 'ASC')])
- self.assertTrue(all(x.many2one.value <= y.many2one.value
- for x, y in zip(search, search[1:])))
-
- transaction.cursor.rollback()
-
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
-
- target1, target2 = self.m2o_target.create([
- {'value': 1},
- {'value': 2},
- ])
-
- search1, search2 = self.m2o_search.create([
- {'many2one': target1.id},
- {'many2one': target2.id},
- ])
-
- # Search join
- self.m2o_search.target_search = 'join'
- self.assertEqual(self.m2o_search.search([
- ('many2one.value', '=', 1),
- ]), [search1])
-
- # Search subquery
- self.m2o_search.target_search = 'subquery'
- self.assertEqual(self.m2o_search.search([
- ('many2one.value', '=', 1),
- ]), [search1])
-
- def test0200timedelta(self):
- 'Test timedelta'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
-
- minute = datetime.timedelta(minutes=1)
- hour = datetime.timedelta(hours=1)
- day = datetime.timedelta(days=1)
- default_timedelta = datetime.timedelta(seconds=3600)
-
- timedelta1, = self.timedelta.create([{
- 'timedelta': hour,
- }])
- self.assert_(timedelta1)
- self.assertEqual(timedelta1.timedelta, hour)
-
- timedelta = self.timedelta.search([
- ('timedelta', '=', hour),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', '=', day),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', '=', None),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', '!=', day),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', '!=', None),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'in', [hour]),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'in', [day]),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'in', [minute]),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'in', [None]),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'in', []),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'not in', [hour]),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'not in', [day]),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'not in', [None]),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'not in', []),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', '<', day),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', '<', minute),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', '<', hour),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', '<=', hour),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', '<=', minute),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', '<=', day),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', '>', day),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', '>', minute),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', '>', hour),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', '>=', day),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta = self.timedelta.search([
- ('timedelta', '>=', minute),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta = self.timedelta.search([
- ('timedelta', '>=', hour),
- ])
- self.assertEqual(timedelta, [timedelta1])
-
- timedelta2, = self.timedelta.create([{
- 'timedelta': minute,
- }])
- self.assert_(timedelta2)
- self.assertEqual(timedelta2.timedelta, minute)
-
- timedelta = self.timedelta.search([
- ('timedelta', '=', minute),
- ])
- self.assertEqual(timedelta, [timedelta2])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'in', [minute, hour]),
- ])
- self.assertEqual(timedelta, [timedelta1, timedelta2])
-
- timedelta = self.timedelta.search([
- ('timedelta', 'not in', [minute, hour]),
- ])
- self.assertEqual(timedelta, [])
-
- timedelta3, = self.timedelta.create([{}])
- self.assert_(timedelta3)
- self.assertEqual(timedelta3.timedelta, None)
-
- timedelta4, = self.timedelta_default.create([{}])
- self.assert_(timedelta4)
- self.assertEqual(timedelta4.timedelta, default_timedelta)
-
- self.timedelta.write([timedelta1], {
- 'timedelta': minute,
- })
- self.assertEqual(timedelta1.timedelta, minute)
-
- self.timedelta.write([timedelta2], {
+ timedelta6, = TimedeltaRequired.create([{
'timedelta': day,
- })
- self.assertEqual(timedelta2.timedelta, day)
-
- self.assertRaises(Exception, self.timedelta.create, [{
- 'timedelta': 'test',
- }])
-
- self.assertRaises(Exception, self.timedelta.write, [timedelta1], {
- 'timedelta': 'test',
- })
-
- self.assertRaises(Exception, self.timedelta.create, [{
- 'timedelta': 1,
- }])
-
- self.assertRaises(Exception, self.timedelta.write, [timedelta1], {
- 'timedelta': 1,
- })
-
- self.assertRaises(UserError, self.timedelta_required.create, [{}])
- transaction.cursor.rollback()
-
- timedelta6, = self.timedelta_required.create([{
- 'timedelta': day,
- }])
- self.assert_(timedelta6)
-
- timedelta7, = self.timedelta.create([{
- 'timedelta': None,
- }])
- self.assert_(timedelta7)
+ }])
+ self.assert_(timedelta6)
- transaction.cursor.rollback()
+ timedelta7, = Timedelta.create([{
+ 'timedelta': None,
+ }])
+ self.assert_(timedelta7)
def suite():
diff --git a/trytond/tests/test_history.py b/trytond/tests/test_history.py
index 0131dac..a8cd77b 100644
--- a/trytond/tests/test_history.py
+++ b/trytond/tests/test_history.py
@@ -3,9 +3,9 @@
import unittest
import datetime
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
+from trytond.pool import Pool
from trytond.exceptions import UserError
from trytond import backend
@@ -13,286 +13,267 @@ from trytond import backend
class HistoryTestCase(unittest.TestCase):
'Test History'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
+ @with_transaction()
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):
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
+ table = History.__table__()
+ history_table = History.__table_history__()
+ cursor.execute(*table.delete())
+ cursor.execute(*history_table.delete())
+ transaction.commit()
+
+ @with_transaction()
+ def test_read(self):
'Test read history'
- History = POOL.get('test.history')
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
# Create some history entry
# It is needed to commit to have different timestamps
+ history = History(value=1)
+ history.save()
+ history_id = history.id
+ first = history.create_date
+
+ transaction.commit()
+
+ history = History(history_id)
+ history.value = 2
+ history.save()
+ second = history.write_date
+
+ transaction.commit()
+
+ history = History(history_id)
+ history.value = 3
+ history.save()
+ third = history.write_date
+
+ transaction.commit()
+
+ for timestamp, value in [
+ (first, 1),
+ (second, 2),
+ (third, 3),
+ (datetime.datetime.now(), 3),
+ (datetime.datetime.max, 3),
+ ]:
+ with Transaction().set_context(_datetime=timestamp):
+ history = History(history_id)
+ self.assertEqual(history.value, value)
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(value=1)
- history.save()
- history_id = history.id
- first = history.create_date
-
- transaction.cursor.commit()
-
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 2
- history.save()
- second = history.write_date
-
- transaction.cursor.commit()
-
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 3
- history.save()
- third = history.write_date
-
- transaction.cursor.commit()
-
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- for timestamp, value in [
- (first, 1),
- (second, 2),
- (third, 3),
- (datetime.datetime.now(), 3),
- (datetime.datetime.max, 3),
- ]:
- with Transaction().set_context(_datetime=timestamp):
- history = History(history_id)
- self.assertEqual(history.value, value)
-
- with Transaction().set_context(_datetime=datetime.datetime.min):
- self.assertRaises(UserError, History.read, [history_id])
+ with Transaction().set_context(_datetime=datetime.datetime.min):
+ self.assertRaises(UserError, History.read, [history_id])
@unittest.skipUnless(backend.name() == 'postgresql',
'CURRENT_TIMESTAMP as transaction_timestamp is specific to postgresql')
- def test0020read_same_timestamp(self):
+ @with_transaction()
+ def test_read_same_timestamp(self):
'Test read history with same timestamp'
- History = POOL.get('test.history')
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
- 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 = History(value=1)
+ history.save()
+ history_id = history.id
+ first = history.create_date
- history.value = 2
- history.save()
- second = history.write_date
+ history.value = 2
+ history.save()
+ second = history.write_date
- self.assertEqual(first, second)
+ self.assertEqual(first, second)
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 3
- history.save()
- third = history.write_date
+ history = History(history_id)
+ history.value = 3
+ history.save()
+ third = history.write_date
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- for timestamp, value in [
- (first, 2),
- (third, 3),
- ]:
- with Transaction().set_context(_datetime=timestamp):
- history = History(history_id)
- self.assertEqual(history.value, value)
+ for timestamp, value in [
+ (first, 2),
+ (third, 3),
+ ]:
+ with Transaction().set_context(_datetime=timestamp):
+ history = History(history_id)
+ self.assertEqual(history.value, value)
- def test0030history_revisions(self):
+ @with_transaction()
+ def test_history_revisions(self):
'Test history revisions'
- History = POOL.get('test.history')
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
- 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 = History(value=1)
+ history.save()
+ history_id = history.id
+ first = history.create_date
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 2
- history.save()
- second = history.write_date
+ history = History(history_id)
+ history.value = 2
+ history.save()
+ second = history.write_date
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 3
- history.save()
- third = history.write_date
+ history = History(history_id)
+ history.value = 3
+ history.save()
+ third = history.write_date
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- revisions = History.history_revisions([history_id])
- self.assertEqual(revisions, [
- (third, history_id, u'Administrator'),
- (second, history_id, u'Administrator'),
- (first, history_id, u'Administrator'),
- ])
+ revisions = History.history_revisions([history_id])
+ self.assertEqual(revisions, [
+ (third, history_id, u'Administrator'),
+ (second, history_id, u'Administrator'),
+ (first, history_id, u'Administrator'),
+ ])
- def test0040restore_history(self):
+ @with_transaction()
+ def test_restore_history(self):
'Test restore history'
- History = POOL.get('test.history')
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
- 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 = History(value=1)
+ history.save()
+ history_id = history.id
+ first = history.create_date
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 2
- history.save()
+ history = History(history_id)
+ history.value = 2
+ history.save()
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- History.restore_history([history_id], first)
- history = History(history_id)
- self.assertEqual(history.value, 1)
+ History.restore_history([history_id], first)
+ history = History(history_id)
+ self.assertEqual(history.value, 1)
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- History.restore_history([history_id], datetime.datetime.min)
- self.assertRaises(UserError, History.read, [history_id])
+ transaction.rollback()
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- History.delete([History(history_id)])
+ History.restore_history([history_id], datetime.datetime.min)
+ self.assertRaises(UserError, History.read, [history_id])
- transaction.cursor.commit()
+ transaction.rollback()
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- History.restore_history([history_id], datetime.datetime.max)
- self.assertRaises(UserError, History.read, [history_id])
+ History.delete([History(history_id)])
+
+ transaction.commit()
+
+ History.restore_history([history_id], datetime.datetime.max)
+ self.assertRaises(UserError, History.read, [history_id])
- def test0041restore_history_before(self):
+ @with_transaction()
+ def test_restore_history_before(self):
'Test restore history before'
- History = POOL.get('test.history')
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(value=1)
- history.save()
- history_id = history.id
+ history = History(value=1)
+ history.save()
+ history_id = history.id
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 2
- history.save()
- second = history.write_date
+ history = History(history_id)
+ history.value = 2
+ history.save()
+ second = history.write_date
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 3
- history.save()
+ history = History(history_id)
+ history.value = 3
+ history.save()
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- History.restore_history_before([history_id], second)
- history = History(history_id)
- self.assertEqual(history.value, 1)
+ History.restore_history_before([history_id], second)
+ history = History(history_id)
+ self.assertEqual(history.value, 1)
@unittest.skipUnless(backend.name() == 'postgresql',
'CURRENT_TIMESTAMP as transaction_timestamp is specific to postgresql')
- def test0045restore_history_same_timestamp(self):
+ @with_transaction()
+ def test_restore_history_same_timestamp(self):
'Test restore history with same timestamp'
- History = POOL.get('test.history')
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
- 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
+ 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)
+ self.assertEqual(first, second)
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 3
- history.save()
+ history = History(history_id)
+ history.value = 3
+ history.save()
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- History.restore_history([history_id], first)
- history = History(history_id)
- self.assertEqual(history.value, 2)
+ History.restore_history([history_id], first)
+ history = History(history_id)
+ self.assertEqual(history.value, 2)
- def test0050ordered_search(self):
+ @with_transaction()
+ def test_ordered_search(self):
'Test ordered search of history models'
- History = POOL.get('test.history')
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
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()
+ history = History(value=1)
+ history.save()
+ first_id = history.id
+ first_stamp = history.create_date
+ transaction.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
+ history = History(value=2)
+ history.save()
+ second_id = history.id
+ second_stamp = history.create_date
- transaction.cursor.commit()
+ transaction.commit()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- first, second = History.search([], order=order)
+ first, second = History.search([], order=order)
- self.assertEqual(first.id, first_id)
- self.assertEqual(second.id, second_id)
+ 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()
+ first.value = 3
+ first.save()
+ third_stamp = first.write_date
+ transaction.commit()
results = [
(first_stamp, [first]),
@@ -301,20 +282,18 @@ class HistoryTestCase(unittest.TestCase):
(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)
+ for timestamp, instances in results:
+ with Transaction().set_context(_datetime=timestamp):
+ records = History.search([], order=order)
+ self.assertEqual(records, instances)
+ transaction.rollback()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- to_delete, _ = History.search([], order=order)
+ to_delete, _ = History.search([], order=order)
- self.assertEqual(to_delete.id, second.id)
+ self.assertEqual(to_delete.id, second.id)
- History.delete([to_delete])
- transaction.cursor.commit()
+ History.delete([to_delete])
+ transaction.commit()
results = [
(first_stamp, [first]),
@@ -323,31 +302,32 @@ class HistoryTestCase(unittest.TestCase):
(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)
+ for timestamp, instances in results:
+ with Transaction().set_context(_datetime=timestamp,
+ from_test=True):
+ records = History.search([], order=order)
+ self.assertEqual(records, instances)
+ transaction.rollback()
@unittest.skipUnless(backend.name() == 'postgresql',
'CURRENT_TIMESTAMP as transaction_timestamp is specific to postgresql')
- def test0060_ordered_search_same_timestamp(self):
+ @with_transaction()
+ def test_ordered_search_same_timestamp(self):
'Test ordered search with same timestamp'
- History = POOL.get('test.history')
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
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
+ 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()
+ self.assertEqual(first_stamp, second_stamp)
+ transaction.commit()
results = [
(second_stamp, [history], [4]),
@@ -355,123 +335,129 @@ class HistoryTestCase(unittest.TestCase):
(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)
+ 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)
+ transaction.rollback()
- def test0070_browse(self):
+ @with_transaction()
+ def test_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
+ pool = Pool()
+ History = pool.get('test.history')
+ Line = pool.get('test.history.line')
+ transaction = Transaction()
- transaction.cursor.commit()
+ 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
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- history = History(history_id)
- history.value = 2
- history.save()
+ first_stamp = line_b.create_date
- Line.delete([Line(line_b_id)])
+ history.stamp = first_stamp
+ history.save()
- line_a = Line(line_a_id)
- line_a.name = 'c'
- line_a.save()
+ transaction.commit()
- second_stamp = line_a.write_date
+ history = History(history_id)
+ history.value = 2
+ history.save()
- transaction.cursor.commit()
+ Line.delete([Line(line_b_id)])
- 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'])
+ line_a = Line(line_a_id)
+ line_a.name = 'c'
+ line_a.save()
- 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'])
+ second_stamp = line_a.write_date
- 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'])
+ transaction.commit()
- def test0080_search_cursor_max(self):
- 'Test search with number of history entries at cursor.IN_MAX'
- History = POOL.get('test.history')
+ history = History(history_id)
+ self.assertEqual(history.value, 2)
+ self.assertEqual([l.name for l in history.lines], ['c'])
+ self.assertEqual(history.stamp, first_stamp)
+ self.assertEqual(
+ [l.name for l in history.lines_at_stamp], ['a', 'b'])
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- cursor = transaction.cursor
+ 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'])
- history = History(value=-1)
+ 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'])
+ self.assertEqual(history.stamp, first_stamp)
+ self.assertEqual(
+ [l.name for l in history.lines_at_stamp], ['a', 'b'])
+
+ @with_transaction()
+ def test_search_cursor_max(self):
+ 'Test search with number of history entries at database.IN_MAX'
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
+ database = transaction.database
+
+ history = History(value=-1)
+ history.save()
+
+ for history.value in range(database.IN_MAX + 1):
history.save()
- for history.value in range(cursor.IN_MAX + 1):
- history.save()
-
- with transaction.set_context(_datetime=datetime.datetime.max):
- record, = History.search([])
+ with transaction.set_context(_datetime=datetime.datetime.max):
+ record, = History.search([])
- self.assertEqual(record.value, cursor.IN_MAX)
+ self.assertEqual(record.value, database.IN_MAX)
- def test0090_search_cursor_max_entries(self):
- 'Test search for skipping first history entries at cursor.IN_MAX'
- History = POOL.get('test.history')
+ @with_transaction()
+ def test_search_cursor_max_entries(self):
+ 'Test search for skipping first history entries at database.IN_MAX'
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
+ database = transaction.database
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- cursor = transaction.cursor
+ for i in xrange(0, 2):
+ history = History(value=-1)
+ history.save()
- for i in xrange(0, 2):
- history = History(value=-1)
+ for history.value in range(database.IN_MAX + 1):
history.save()
- for history.value in range(cursor.IN_MAX + 1):
- history.save()
-
- with transaction.set_context(_datetime=datetime.datetime.max):
- records = History.search([])
-
- self.assertEqual({r.value for r in records}, {cursor.IN_MAX})
- self.assertEqual(len(records), 2)
+ with transaction.set_context(_datetime=datetime.datetime.max):
+ records = History.search([])
- def test0100_search_cursor_max_histories(self):
- 'Test search with number of histories at cursor.IN_MAX'
- History = POOL.get('test.history')
+ self.assertEqual({r.value for r in records}, {database.IN_MAX})
+ self.assertEqual(len(records), 2)
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- cursor = transaction.cursor
+ @with_transaction()
+ def test_search_cursor_max_histories(self):
+ 'Test search with number of histories at database.IN_MAX'
+ pool = Pool()
+ History = pool.get('test.history')
+ transaction = Transaction()
+ database = transaction.database
- n = cursor.IN_MAX + 1
- History.create([{'value': 1}] * n)
+ n = database.IN_MAX + 1
+ History.create([{'value': 1}] * n)
- with transaction.set_context(_datetime=datetime.datetime.max):
- records = History.search([])
+ with transaction.set_context(_datetime=datetime.datetime.max):
+ records = History.search([])
- self.assertEqual({r.value for r in records}, {1})
- self.assertEqual(len(records), n)
+ self.assertEqual({r.value for r in records}, {1})
+ self.assertEqual(len(records), n)
def suite():
diff --git a/trytond/tests/test_importdata.py b/trytond/tests/test_importdata.py
index 594b34f..748ffcc 100644
--- a/trytond/tests/test_importdata.py
+++ b/trytond/tests/test_importdata.py
@@ -4,442 +4,473 @@
import unittest
from decimal import InvalidOperation
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
+from trytond.pool import Pool
from trytond.exceptions import UserError
class ImportDataTestCase(unittest.TestCase):
'Test import_data'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.boolean = POOL.get('test.import_data.boolean')
- self.integer = POOL.get('test.import_data.integer')
- self.integer_required = POOL.get('test.import_data.integer_required')
- self.float = POOL.get('test.import_data.float')
- self.float_required = POOL.get('test.import_data.float_required')
- self.numeric = POOL.get('test.import_data.numeric')
- self.numeric_required = POOL.get('test.import_data.numeric_required')
- self.char = POOL.get('test.import_data.char')
- self.text = POOL.get('test.import_data.text')
- self.date = POOL.get('test.import_data.date')
- self.datetime = POOL.get('test.import_data.datetime')
- self.selection = POOL.get('test.import_data.selection')
- self.many2one = POOL.get('test.import_data.many2one')
- self.many2many = POOL.get('test.import_data.many2many')
- self.one2many = POOL.get('test.import_data.one2many')
- self.reference = POOL.get('test.import_data.reference')
-
- def test0010boolean(self):
+
+ @with_transaction()
+ def test_boolean(self):
'Test boolean'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.boolean.import_data(['boolean'],
- [['True']]), 1)
+ pool = Pool()
+ Boolean = pool.get('test.import_data.boolean')
+
+ self.assertEqual(Boolean.import_data(['boolean'],
+ [['True']]), 1)
- self.assertEqual(self.boolean.import_data(['boolean'],
- [['1']]), 1)
+ self.assertEqual(Boolean.import_data(['boolean'],
+ [['1']]), 1)
- self.assertEqual(self.boolean.import_data(['boolean'],
- [['False']]), 1)
+ self.assertEqual(Boolean.import_data(['boolean'],
+ [['False']]), 1)
- self.assertEqual(self.boolean.import_data(['boolean'],
- [['0']]), 1)
+ self.assertEqual(Boolean.import_data(['boolean'],
+ [['0']]), 1)
- self.assertEqual(self.boolean.import_data(['boolean'],
- [['']]), 1)
+ self.assertEqual(Boolean.import_data(['boolean'],
+ [['']]), 1)
- self.assertEqual(self.boolean.import_data(['boolean'],
- [['True'], ['False']]), 2)
+ self.assertEqual(Boolean.import_data(['boolean'],
+ [['True'], ['False']]), 2)
- self.assertRaises(ValueError, self.boolean.import_data,
- ['boolean'], [['foo']])
+ self.assertRaises(ValueError, Boolean.import_data,
+ ['boolean'], [['foo']])
- def test0020integer(self):
+ @with_transaction()
+ def test_integer(self):
'Test integer'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.integer.import_data(['integer'],
- [['1']]), 1)
+ pool = Pool()
+ Integer = pool.get('test.import_data.integer')
+
+ self.assertEqual(Integer.import_data(['integer'],
+ [['1']]), 1)
- self.assertEqual(self.integer.import_data(['integer'],
- [['-1']]), 1)
+ self.assertEqual(Integer.import_data(['integer'],
+ [['-1']]), 1)
- self.assertEqual(self.integer.import_data(['integer'],
- [['']]), 1)
+ self.assertEqual(Integer.import_data(['integer'],
+ [['']]), 1)
- self.assertEqual(self.integer.import_data(['integer'],
- [['1'], ['2']]), 2)
+ self.assertEqual(Integer.import_data(['integer'],
+ [['1'], ['2']]), 2)
- self.assertRaises(ValueError, self.integer.import_data,
- ['integer'], [['1.1']])
+ self.assertRaises(ValueError, Integer.import_data,
+ ['integer'], [['1.1']])
- self.assertRaises(ValueError, self.integer.import_data,
- ['integer'], [['-1.1']])
+ self.assertRaises(ValueError, Integer.import_data,
+ ['integer'], [['-1.1']])
- self.assertRaises(ValueError, self.integer.import_data,
- ['integer'], [['foo']])
+ self.assertRaises(ValueError, Integer.import_data,
+ ['integer'], [['foo']])
- self.assertEqual(self.integer.import_data(['integer'],
- [['0']]), 1)
+ self.assertEqual(Integer.import_data(['integer'],
+ [['0']]), 1)
- def test0021integer_required(self):
+ @with_transaction()
+ def test_integer_required(self):
'Test required integer'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- self.assertEqual(self.integer_required.import_data(['integer'],
- [['1']]), 1)
+ pool = Pool()
+ IntegerRequired = pool.get('test.import_data.integer_required')
+ transaction = Transaction()
- self.assertEqual(self.integer_required.import_data(['integer'],
- [['-1']]), 1)
+ self.assertEqual(IntegerRequired.import_data(['integer'],
+ [['1']]), 1)
- self.assertRaises(UserError, self.integer_required.import_data,
- ['integer'], [['']])
- transaction.cursor.rollback()
+ self.assertEqual(IntegerRequired.import_data(['integer'],
+ [['-1']]), 1)
- self.assertEqual(self.integer_required.import_data(['integer'],
- [['1'], ['2']]), 2)
+ self.assertRaises(UserError, IntegerRequired.import_data,
+ ['integer'], [['']])
+ transaction.rollback()
- self.assertRaises(ValueError, self.integer_required.import_data,
- ['integer'], [['1.1']])
+ self.assertEqual(IntegerRequired.import_data(['integer'],
+ [['1'], ['2']]), 2)
- self.assertRaises(ValueError, self.integer_required.import_data,
- ['integer'], [['-1.1']])
+ self.assertRaises(ValueError, IntegerRequired.import_data,
+ ['integer'], [['1.1']])
- self.assertRaises(ValueError, self.integer_required.import_data,
- ['integer'], [['foo']])
+ self.assertRaises(ValueError, IntegerRequired.import_data,
+ ['integer'], [['-1.1']])
- self.assertEqual(self.integer_required.import_data(['integer'],
- [['0']]), 1)
+ self.assertRaises(ValueError, IntegerRequired.import_data,
+ ['integer'], [['foo']])
- def test0030float(self):
+ self.assertEqual(IntegerRequired.import_data(['integer'],
+ [['0']]), 1)
+
+ @with_transaction()
+ def test_float(self):
'Test float'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.float.import_data(['float'],
- [['1.1']]), 1)
+ pool = Pool()
+ Float = pool.get('test.import_data.float')
+
+ self.assertEqual(Float.import_data(['float'],
+ [['1.1']]), 1)
- self.assertEqual(self.float.import_data(['float'],
- [['-1.1']]), 1)
+ self.assertEqual(Float.import_data(['float'],
+ [['-1.1']]), 1)
- self.assertEqual(self.float.import_data(['float'],
- [['1']]), 1)
+ self.assertEqual(Float.import_data(['float'],
+ [['1']]), 1)
- self.assertEqual(self.float.import_data(['float'],
- [['']]), 1)
+ self.assertEqual(Float.import_data(['float'],
+ [['']]), 1)
- self.assertEqual(self.float.import_data(['float'],
- [['1.1'], ['2.2']]), 2)
+ self.assertEqual(Float.import_data(['float'],
+ [['1.1'], ['2.2']]), 2)
- self.assertRaises(ValueError, self.float.import_data,
- ['float'], [['foo']])
+ self.assertRaises(ValueError, Float.import_data,
+ ['float'], [['foo']])
- self.assertEqual(self.float.import_data(['float'],
- [['0']]), 1)
+ self.assertEqual(Float.import_data(['float'],
+ [['0']]), 1)
- self.assertEqual(self.float.import_data(['float'],
- [['0.0']]), 1)
+ self.assertEqual(Float.import_data(['float'],
+ [['0.0']]), 1)
- def test0031float_required(self):
+ @with_transaction()
+ def test_float_required(self):
'Test required float'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- self.assertEqual(self.float_required.import_data(['float'],
- [['1.1']]), 1)
+ pool = Pool()
+ FloatRequired = pool.get('test.import_data.float_required')
+ transaction = Transaction()
+
+ self.assertEqual(FloatRequired.import_data(['float'],
+ [['1.1']]), 1)
- self.assertEqual(self.float_required.import_data(['float'],
- [['-1.1']]), 1)
+ self.assertEqual(FloatRequired.import_data(['float'],
+ [['-1.1']]), 1)
- self.assertEqual(self.float_required.import_data(['float'],
- [['1']]), 1)
+ self.assertEqual(FloatRequired.import_data(['float'],
+ [['1']]), 1)
- self.assertRaises(UserError, self.float_required.import_data,
- ['float'], [['']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, FloatRequired.import_data,
+ ['float'], [['']])
+ transaction.rollback()
- self.assertEqual(self.float_required.import_data(['float'],
- [['1.1'], ['2.2']]), 2)
+ self.assertEqual(FloatRequired.import_data(['float'],
+ [['1.1'], ['2.2']]), 2)
- self.assertRaises(ValueError, self.float_required.import_data,
- ['float'], [['foo']])
+ self.assertRaises(ValueError, FloatRequired.import_data,
+ ['float'], [['foo']])
- self.assertEqual(self.float_required.import_data(['float'],
- [['0']]), 1)
+ self.assertEqual(FloatRequired.import_data(['float'],
+ [['0']]), 1)
- self.assertEqual(self.float_required.import_data(['float'],
- [['0.0']]), 1)
+ self.assertEqual(FloatRequired.import_data(['float'],
+ [['0.0']]), 1)
- def test0040numeric(self):
+ @with_transaction()
+ def test_numeric(self):
'Test numeric'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.numeric.import_data(['numeric'],
- [['1.1']]), 1)
+ pool = Pool()
+ Numeric = pool.get('test.import_data.numeric')
- self.assertEqual(self.numeric.import_data(['numeric'],
- [['-1.1']]), 1)
+ self.assertEqual(Numeric.import_data(['numeric'],
+ [['1.1']]), 1)
- self.assertEqual(self.numeric.import_data(['numeric'],
- [['1']]), 1)
+ self.assertEqual(Numeric.import_data(['numeric'],
+ [['-1.1']]), 1)
- self.assertEqual(self.numeric.import_data(['numeric'],
- [['']]), 1)
+ self.assertEqual(Numeric.import_data(['numeric'],
+ [['1']]), 1)
- self.assertEqual(self.numeric.import_data(['numeric'],
- [['1.1'], ['2.2']]), 2)
+ self.assertEqual(Numeric.import_data(['numeric'],
+ [['']]), 1)
- self.assertRaises(InvalidOperation, self.numeric.import_data,
- ['numeric'], [['foo']])
+ self.assertEqual(Numeric.import_data(['numeric'],
+ [['1.1'], ['2.2']]), 2)
- self.assertEqual(self.numeric.import_data(['numeric'],
- [['0']]), 1)
+ self.assertRaises(InvalidOperation, Numeric.import_data,
+ ['numeric'], [['foo']])
- self.assertEqual(self.numeric.import_data(['numeric'],
- [['0.0']]), 1)
+ self.assertEqual(Numeric.import_data(['numeric'],
+ [['0']]), 1)
- def test0041numeric_required(self):
+ self.assertEqual(Numeric.import_data(['numeric'],
+ [['0.0']]), 1)
+
+ @with_transaction()
+ def test_numeric_required(self):
'Test required numeric'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- self.assertEqual(self.numeric_required.import_data(['numeric'],
- [['1.1']]), 1)
+ pool = Pool()
+ NumericRequired = pool.get('test.import_data.numeric_required')
+ transaction = Transaction()
+
+ self.assertEqual(NumericRequired.import_data(['numeric'],
+ [['1.1']]), 1)
- self.assertEqual(self.numeric_required.import_data(['numeric'],
- [['-1.1']]), 1)
+ self.assertEqual(NumericRequired.import_data(['numeric'],
+ [['-1.1']]), 1)
- self.assertEqual(self.numeric_required.import_data(['numeric'],
- [['1']]), 1)
+ self.assertEqual(NumericRequired.import_data(['numeric'],
+ [['1']]), 1)
- self.assertRaises(UserError, self.numeric_required.import_data,
- ['numeric'], [['']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, NumericRequired.import_data,
+ ['numeric'], [['']])
+ transaction.rollback()
- self.assertEqual(self.numeric_required.import_data(['numeric'],
- [['1.1'], ['2.2']]), 2)
+ self.assertEqual(NumericRequired.import_data(['numeric'],
+ [['1.1'], ['2.2']]), 2)
- self.assertRaises(InvalidOperation,
- self.numeric_required.import_data, ['numeric'], [['foo']])
+ self.assertRaises(InvalidOperation,
+ NumericRequired.import_data, ['numeric'], [['foo']])
- self.assertEqual(self.numeric_required.import_data(['numeric'],
- [['0']]), 1)
+ self.assertEqual(NumericRequired.import_data(['numeric'],
+ [['0']]), 1)
- self.assertEqual(self.numeric_required.import_data(['numeric'],
- [['0.0']]), 1)
+ self.assertEqual(NumericRequired.import_data(['numeric'],
+ [['0.0']]), 1)
- def test0050char(self):
+ @with_transaction()
+ def test_char(self):
'Test char'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.char.import_data(['char'],
- [['test']]), 1)
+ pool = Pool()
+ Char = pool.get('test.import_data.char')
+
+ self.assertEqual(Char.import_data(['char'],
+ [['test']]), 1)
- self.assertEqual(self.char.import_data(['char'],
- [['']]), 1)
+ self.assertEqual(Char.import_data(['char'],
+ [['']]), 1)
- self.assertEqual(self.char.import_data(['char'],
- [['test'], ['foo'], ['bar']]), 3)
+ self.assertEqual(Char.import_data(['char'],
+ [['test'], ['foo'], ['bar']]), 3)
- def test0060text(self):
+ @with_transaction()
+ def test_text(self):
'Test text'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.text.import_data(['text'],
- [['test']]), 1)
+ pool = Pool()
+ Text = pool.get('test.import_data.text')
- self.assertEqual(self.text.import_data(['text'],
- [['']]), 1)
+ self.assertEqual(Text.import_data(['text'],
+ [['test']]), 1)
- self.assertEqual(self.text.import_data(['text'],
- [['test'], ['foo'], ['bar']]), 3)
+ self.assertEqual(Text.import_data(['text'],
+ [['']]), 1)
- def test0080date(self):
+ self.assertEqual(Text.import_data(['text'],
+ [['test'], ['foo'], ['bar']]), 3)
+
+ @with_transaction()
+ def test_date(self):
'Test date'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.date.import_data(['date'],
- [['2010-01-01']]), 1)
+ pool = Pool()
+ Date = pool.get('test.import_data.date')
+
+ self.assertEqual(Date.import_data(['date'],
+ [['2010-01-01']]), 1)
- self.assertEqual(self.date.import_data(['date'],
- [['']]), 1)
+ self.assertEqual(Date.import_data(['date'],
+ [['']]), 1)
- self.assertEqual(self.date.import_data(['date'],
- [['2010-01-01'], ['2010-02-01']]), 2)
+ self.assertEqual(Date.import_data(['date'],
+ [['2010-01-01'], ['2010-02-01']]), 2)
- self.assertRaises(ValueError, self.date.import_data,
- ['date'], [['foo']])
+ self.assertRaises(ValueError, Date.import_data,
+ ['date'], [['foo']])
- def test0090datetime(self):
+ @with_transaction()
+ def test_datetime(self):
'Test datetime'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.datetime.import_data(['datetime'],
- [['2010-01-01 12:00:00']]), 1)
+ pool = Pool()
+ Datetime = pool.get('test.import_data.datetime')
- self.assertEqual(self.datetime.import_data(['datetime'],
- [['']]), 1)
+ self.assertEqual(Datetime.import_data(['datetime'],
+ [['2010-01-01 12:00:00']]), 1)
- self.assertEqual(self.datetime.import_data(['datetime'],
- [['2010-01-01 12:00:00'], ['2010-01-01 13:30:00']]), 2)
+ self.assertEqual(Datetime.import_data(['datetime'],
+ [['']]), 1)
- self.assertRaises(ValueError, self.datetime.import_data,
- ['datetime'], [['foo']])
+ self.assertEqual(Datetime.import_data(['datetime'],
+ [['2010-01-01 12:00:00'], ['2010-01-01 13:30:00']]), 2)
- def test0100selection(self):
+ self.assertRaises(ValueError, Datetime.import_data,
+ ['datetime'], [['foo']])
+
+ @with_transaction()
+ def test_selection(self):
'Test selection'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- self.assertEqual(self.selection.import_data(['selection'],
- [['select1']]), 1)
+ pool = Pool()
+ Selection = pool.get('test.import_data.selection')
+
+ self.assertEqual(Selection.import_data(['selection'],
+ [['select1']]), 1)
- self.assertEqual(self.selection.import_data(['selection'],
- [['']]), 1)
+ self.assertEqual(Selection.import_data(['selection'],
+ [['']]), 1)
- self.assertEqual(self.selection.import_data(['selection'],
- [['select1'], ['select2']]), 2)
+ self.assertEqual(Selection.import_data(['selection'],
+ [['select1'], ['select2']]), 2)
- self.assertRaises(UserError, self.selection.import_data,
- ['selection'], [['foo']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, Selection.import_data,
+ ['selection'], [['foo']])
- def test0110many2one(self):
+ @with_transaction()
+ def test_many2one(self):
'Test many2one'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- self.assertEqual(self.many2one.import_data(['many2one'],
- [['Test']]), 1)
+ pool = Pool()
+ Many2one = pool.get('test.import_data.many2one')
+ transaction = Transaction()
+
+ self.assertEqual(Many2one.import_data(['many2one'],
+ [['Test']]), 1)
- self.assertEqual(self.many2one.import_data(['many2one:id'],
- [['tests.import_data_many2one_target_test']]), 1)
+ self.assertEqual(Many2one.import_data(['many2one:id'],
+ [['tests.import_data_many2one_target_test']]), 1)
- self.assertEqual(self.many2one.import_data(['many2one'],
- [['']]), 1)
+ self.assertEqual(Many2one.import_data(['many2one'],
+ [['']]), 1)
- self.assertEqual(self.many2one.import_data(['many2one'],
- [['Test'], ['Test']]), 2)
+ self.assertEqual(Many2one.import_data(['many2one'],
+ [['Test'], ['Test']]), 2)
- self.assertRaises(UserError, self.many2one.import_data,
- ['many2one'], [['foo']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, Many2one.import_data,
+ ['many2one'], [['foo']])
+ transaction.rollback()
- self.assertRaises(UserError, self.many2one.import_data,
- ['many2one'], [['Duplicate']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, Many2one.import_data,
+ ['many2one'], [['Duplicate']])
+ transaction.rollback()
- self.assertRaises(UserError, self.many2one.import_data,
- ['many2one:id'], [['foo']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, Many2one.import_data,
+ ['many2one:id'], [['foo']])
+ transaction.rollback()
- self.assertRaises(Exception, self.many2one.import_data,
- ['many2one:id'], [['tests.foo']])
- transaction.cursor.rollback()
+ self.assertRaises(Exception, Many2one.import_data,
+ ['many2one:id'], [['tests.foo']])
+ transaction.rollback()
- def test0120many2many(self):
+ @with_transaction()
+ def test_many2many(self):
'Test many2many'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- self.assertEqual(self.many2many.import_data(['many2many'],
- [['Test 1']]), 1)
+ pool = Pool()
+ Many2many = pool.get('test.import_data.many2many')
+ transaction = Transaction()
- self.assertEqual(self.many2many.import_data(['many2many:id'],
- [['tests.import_data_many2many_target_test1']]), 1)
+ self.assertEqual(Many2many.import_data(['many2many'],
+ [['Test 1']]), 1)
- self.assertEqual(self.many2many.import_data(['many2many'],
- [['Test 1,Test 2']]), 1)
+ self.assertEqual(Many2many.import_data(['many2many:id'],
+ [['tests.import_data_many2many_target_test1']]), 1)
- self.assertEqual(self.many2many.import_data(['many2many:id'],
- [['tests.import_data_many2many_target_test1,'
- 'tests.import_data_many2many_target_test2']]), 1)
+ self.assertEqual(Many2many.import_data(['many2many'],
+ [['Test 1,Test 2']]), 1)
- self.assertEqual(self.many2many.import_data(['many2many'],
- [['Test\, comma']]), 1)
+ self.assertEqual(Many2many.import_data(['many2many:id'],
+ [['tests.import_data_many2many_target_test1,'
+ 'tests.import_data_many2many_target_test2']]), 1)
- self.assertEqual(self.many2many.import_data(['many2many'],
- [['Test\, comma,Test 1']]), 1)
+ self.assertEqual(Many2many.import_data(['many2many'],
+ [['Test\, comma']]), 1)
- self.assertEqual(self.many2many.import_data(['many2many'],
- [['']]), 1)
+ self.assertEqual(Many2many.import_data(['many2many'],
+ [['Test\, comma,Test 1']]), 1)
- self.assertEqual(self.many2many.import_data(['many2many'],
- [['Test 1'], ['Test 2']]), 2)
+ self.assertEqual(Many2many.import_data(['many2many'],
+ [['']]), 1)
- self.assertRaises(UserError, self.many2many.import_data,
- ['many2many'], [['foo']])
- transaction.cursor.rollback()
+ self.assertEqual(Many2many.import_data(['many2many'],
+ [['Test 1'], ['Test 2']]), 2)
- self.assertRaises(UserError, self.many2many.import_data,
- ['many2many'], [['Test 1,foo']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, Many2many.import_data,
+ ['many2many'], [['foo']])
+ transaction.rollback()
- self.assertRaises(UserError, self.many2many.import_data,
- ['many2many'], [['Duplicate']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, Many2many.import_data,
+ ['many2many'], [['Test 1,foo']])
+ transaction.rollback()
- self.assertRaises(UserError, self.many2many.import_data,
- ['many2many'], [['Test 1,Duplicate']])
- transaction.cursor.rollback()
+ self.assertRaises(UserError, Many2many.import_data,
+ ['many2many'], [['Duplicate']])
+ transaction.rollback()
- def test0130one2many(self):
+ self.assertRaises(UserError, Many2many.import_data,
+ ['many2many'], [['Test 1,Duplicate']])
+ transaction.rollback()
+
+ @with_transaction()
+ def test_one2many(self):
'Test one2many'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.one2many.import_data(
- ['name', 'one2many/name'], [['Test', 'Test 1']]), 1)
-
- self.assertEqual(self.one2many.import_data(
- ['name', 'one2many/name'],
- [['Test', 'Test 1'], ['', 'Test 2']]), 1)
-
- self.assertEqual(self.one2many.import_data(
- ['name', 'one2many/name'],
- [
- ['Test 1', 'Test 1'],
- ['', 'Test 2'],
- ['Test 2', 'Test 1']]), 2)
-
- def test0140reference(self):
- 'Test reference'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- self.assertEqual(self.reference.import_data(['reference'],
- [['test.import_data.reference.selection,Test']]), 1)
- reference, = self.reference.search([])
- self.assertEqual(reference.reference.__name__,
- 'test.import_data.reference.selection')
- transaction.cursor.rollback()
+ pool = Pool()
+ One2many = pool.get('test.import_data.one2many')
+
+ self.assertEqual(One2many.import_data(
+ ['name', 'one2many/name'], [['Test', 'Test 1']]), 1)
+
+ self.assertEqual(One2many.import_data(
+ ['name', 'one2many/name'],
+ [['Test', 'Test 1'], ['', 'Test 2']]), 1)
- self.assertEqual(self.reference.import_data(['reference:id'],
- [['test.import_data.reference.selection,'
- 'tests.import_data_reference_selection_test']]), 1)
- reference, = self.reference.search([])
+ self.assertEqual(One2many.import_data(
+ ['name', 'one2many/name'],
+ [
+ ['Test 1', 'Test 1'],
+ ['', 'Test 2'],
+ ['Test 2', 'Test 1']]), 2)
+
+ @with_transaction()
+ def test_reference(self):
+ 'Test reference'
+ pool = Pool()
+ Reference = pool.get('test.import_data.reference')
+ transaction = Transaction()
+
+ self.assertEqual(Reference.import_data(['reference'],
+ [['test.import_data.reference.selection,Test']]), 1)
+ reference, = Reference.search([])
+ self.assertEqual(reference.reference.__name__,
+ 'test.import_data.reference.selection')
+ transaction.rollback()
+
+ self.assertEqual(Reference.import_data(['reference:id'],
+ [['test.import_data.reference.selection,'
+ 'tests.import_data_reference_selection_test']]), 1)
+ reference, = Reference.search([])
+ self.assertEqual(reference.reference.__name__,
+ 'test.import_data.reference.selection')
+ transaction.rollback()
+
+ self.assertEqual(Reference.import_data(['reference'],
+ [['']]), 1)
+ reference, = Reference.search([])
+ self.assertEqual(reference.reference, None)
+ transaction.rollback()
+
+ self.assertEqual(Reference.import_data(['reference'],
+ [['test.import_data.reference.selection,Test'],
+ ['test.import_data.reference.selection,Test']]), 2)
+ for reference in Reference.search([]):
self.assertEqual(reference.reference.__name__,
'test.import_data.reference.selection')
- transaction.cursor.rollback()
-
- self.assertEqual(self.reference.import_data(['reference'],
- [['']]), 1)
- reference, = self.reference.search([])
- self.assertEqual(reference.reference, None)
- transaction.cursor.rollback()
-
- self.assertEqual(self.reference.import_data(['reference'],
- [['test.import_data.reference.selection,Test'],
- ['test.import_data.reference.selection,Test']]), 2)
- for reference in self.reference.search([]):
- self.assertEqual(reference.reference.__name__,
- 'test.import_data.reference.selection')
- transaction.cursor.rollback()
-
- self.assertRaises(UserError, self.reference.import_data,
- ['reference'], [['test.import_data.reference.selection,foo']])
- transaction.cursor.rollback()
-
- self.assertRaises(UserError, self.reference.import_data,
- ['reference'],
- [['test.import_data.reference.selection,Duplicate']])
- transaction.cursor.rollback()
-
- self.assertRaises(UserError, self.reference.import_data,
- ['reference:id'],
- [['test.import_data.reference.selection,foo']])
- transaction.cursor.rollback()
-
- self.assertRaises(Exception, self.reference.import_data,
- ['reference:id'],
- [['test.import_data.reference.selection,test.foo']])
- transaction.cursor.rollback()
+ transaction.rollback()
+
+ self.assertRaises(UserError, Reference.import_data,
+ ['reference'], [['test.import_data.reference.selection,foo']])
+ transaction.rollback()
+
+ self.assertRaises(UserError, Reference.import_data,
+ ['reference'],
+ [['test.import_data.reference.selection,Duplicate']])
+ transaction.rollback()
+
+ self.assertRaises(UserError, Reference.import_data,
+ ['reference:id'],
+ [['test.import_data.reference.selection,foo']])
+ transaction.rollback()
+
+ self.assertRaises(Exception, Reference.import_data,
+ ['reference:id'],
+ [['test.import_data.reference.selection,test.foo']])
+ transaction.rollback()
def suite():
diff --git a/trytond/tests/test_mixins.py b/trytond/tests/test_mixins.py
index 087e40d..a65f36a 100644
--- a/trytond/tests/test_mixins.py
+++ b/trytond/tests/test_mixins.py
@@ -5,38 +5,44 @@
import unittest
import urllib
-from trytond.tests.test_tryton import (POOL, DB_NAME, USER, CONTEXT,
- install_module)
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
+from trytond.pool import Pool
from trytond.url import HOSTNAME
class UrlTestCase(unittest.TestCase):
"Test URL generation"
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.urlmodel = POOL.get('test.urlobject')
- self.urlwizard = POOL.get('test.test_wizard', type='wizard')
- self.hostname = HOSTNAME
+ @with_transaction()
def testModelURL(self):
"Test model URLs"
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.urlmodel.__url__,
- 'tryton://%s/%s/model/test.urlobject' % (self.hostname,
- urllib.quote(DB_NAME)))
+ pool = Pool()
+ UrlObject = pool.get('test.urlobject')
+ db_name = Transaction().database.name
- self.assertEqual(self.urlmodel(1).__url__,
- 'tryton://%s/%s/model/test.urlobject/1' % (self.hostname,
- urllib.quote(DB_NAME)))
+ self.assertEqual(UrlObject.__url__,
+ 'tryton://%s/%s/model/test.urlobject' % (
+ HOSTNAME, urllib.quote(db_name)))
+ self.assertEqual(UrlObject(1).__url__,
+ 'tryton://%s/%s/model/test.urlobject/1' % (
+ HOSTNAME, urllib.quote(db_name)))
+
+ @with_transaction()
def testWizardURL(self):
"Test wizard URLs"
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- self.assertEqual(self.urlwizard.__url__,
- 'tryton://%s/%s/wizard/test.test_wizard' % (self.hostname,
- urllib.quote(DB_NAME)))
+ pool = Pool()
+ UrlWizard = pool.get('test.test_wizard', type='wizard')
+ db_name = Transaction().database.name
+
+ self.assertEqual(UrlWizard.__url__,
+ 'tryton://%s/%s/wizard/test.test_wizard' % (
+ HOSTNAME, urllib.quote(db_name)))
def suite():
diff --git a/trytond/tests/test_modelsingleton.py b/trytond/tests/test_modelsingleton.py
index 014c80e..bd581b6 100644
--- a/trytond/tests/test_modelsingleton.py
+++ b/trytond/tests/test_modelsingleton.py
@@ -3,130 +3,131 @@
# this repository contains the full copyright notices and license terms.
import unittest
from datetime import datetime
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
+from trytond.pool import Pool
class ModelSingletonTestCase(unittest.TestCase):
'Test ModelSingleton'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.singleton = POOL.get('test.singleton')
- def test0010read(self):
+ @with_transaction()
+ def test_read(self):
'Test read method'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- singleton, = self.singleton.read([1], ['name'])
- self.assert_(singleton['name'] == 'test')
- self.assert_(singleton['id'] == 1)
-
- singleton, = self.singleton.read([1], ['name'])
- self.assert_(singleton['name'] == 'test')
- self.assert_(singleton['id'] == 1)
-
- singleton, = self.singleton.read([1], [
- 'create_uid',
- 'create_uid.rec_name',
- 'create_date',
- 'write_uid',
- 'write_date',
- ])
- self.assertEqual(singleton['create_uid'], USER)
- self.assertEqual(singleton['create_uid.rec_name'], 'Administrator')
- self.assert_(isinstance(singleton['create_date'], datetime))
- self.assertEqual(singleton['write_uid'], None)
- self.assertEqual(singleton['write_date'], None)
-
- transaction.cursor.rollback()
-
- def test0020create(self):
+ pool = Pool()
+ Singleton = pool.get('test.singleton')
+
+ singleton, = Singleton.read([1], ['name'])
+ self.assert_(singleton['name'] == 'test')
+ self.assert_(singleton['id'] == 1)
+
+ singleton, = Singleton.read([1], ['name'])
+ self.assert_(singleton['name'] == 'test')
+ self.assert_(singleton['id'] == 1)
+
+ singleton, = Singleton.read([1], [
+ 'create_uid',
+ 'create_uid.rec_name',
+ 'create_date',
+ 'write_uid',
+ 'write_date',
+ ])
+ self.assertEqual(singleton['create_uid'], Transaction().user)
+ self.assertEqual(singleton['create_uid.rec_name'], 'Administrator')
+ self.assert_(isinstance(singleton['create_date'], datetime))
+ self.assertEqual(singleton['write_uid'], None)
+ self.assertEqual(singleton['write_date'], None)
+
+ @with_transaction()
+ def test_create(self):
'Test create method'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- singleton, = self.singleton.create([{'name': 'bar'}])
- self.assert_(singleton)
- self.assertEqual(singleton.name, 'bar')
+ pool = Pool()
+ Singleton = pool.get('test.singleton')
- singleton2, = self.singleton.create([{'name': 'foo'}])
- self.assertEqual(singleton2, singleton)
+ singleton, = Singleton.create([{'name': 'bar'}])
+ self.assert_(singleton)
+ self.assertEqual(singleton.name, 'bar')
- self.assertEqual(singleton.name, 'foo')
+ singleton2, = Singleton.create([{'name': 'foo'}])
+ self.assertEqual(singleton2, singleton)
- singletons = self.singleton.search([])
- self.assertEqual(singletons, [singleton])
+ self.assertEqual(singleton.name, 'foo')
- transaction.cursor.rollback()
+ singletons = Singleton.search([])
+ self.assertEqual(singletons, [singleton])
- def test0030copy(self):
+ @with_transaction()
+ def test_copy(self):
'Test copy method'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- singleton, = self.singleton.search([])
+ pool = Pool()
+ Singleton = pool.get('test.singleton')
- singleton2, = self.singleton.copy([singleton])
- self.assertEqual(singleton2, singleton)
+ singleton, = Singleton.search([])
- singletons = self.singleton.search([])
- self.assertEqual(len(singletons), 1)
+ singleton2, = Singleton.copy([singleton])
+ self.assertEqual(singleton2, singleton)
- singleton3, = self.singleton.copy([singleton], {'name': 'bar'})
- self.assertEqual(singleton3, singleton)
+ singletons = Singleton.search([])
+ self.assertEqual(len(singletons), 1)
- singletons = self.singleton.search([])
- self.assertEqual(len(singletons), 1)
+ singleton3, = Singleton.copy([singleton], {'name': 'bar'})
+ self.assertEqual(singleton3, singleton)
- transaction.cursor.rollback()
+ singletons = Singleton.search([])
+ self.assertEqual(len(singletons), 1)
- def test0040default_get(self):
+ @with_transaction()
+ def test_default_get(self):
'Test default_get method'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- default = self.singleton.default_get(['name'])
- self.assertEqual(default, {'name': 'test'})
+ pool = Pool()
+ Singleton = pool.get('test.singleton')
- default = self.singleton.default_get(['create_uid'])
- self.assertEqual(len(default), 2)
+ default = Singleton.default_get(['name'])
+ self.assertEqual(default, {'name': 'test'})
- default = self.singleton.default_get(['create_uid'],
- with_rec_name=False)
- self.assertEqual(len(default), 1)
+ default = Singleton.default_get(['create_uid'])
+ self.assertEqual(len(default), 2)
- self.singleton.create([{'name': 'bar'}])
+ default = Singleton.default_get(['create_uid'],
+ with_rec_name=False)
+ self.assertEqual(len(default), 1)
- default = self.singleton.default_get(['name'])
- self.assertEqual(default, {'name': 'bar'})
+ Singleton.create([{'name': 'bar'}])
- default = self.singleton.default_get(['create_uid'])
- self.assertEqual(len(default), 2)
+ default = Singleton.default_get(['name'])
+ self.assertEqual(default, {'name': 'bar'})
- default = self.singleton.default_get(['create_uid'],
- with_rec_name=False)
- self.assertEqual(len(default), 1)
+ default = Singleton.default_get(['create_uid'])
+ self.assertEqual(len(default), 2)
- transaction.cursor.rollback()
+ default = Singleton.default_get(['create_uid'],
+ with_rec_name=False)
+ self.assertEqual(len(default), 1)
- def test0050search(self):
+ @with_transaction()
+ def test_search(self):
'Test search method'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- singletons = self.singleton.search([])
- self.assertEqual(map(int, singletons), [1])
-
- singletons = self.singleton.search([])
- self.assertEqual(map(int, singletons), [1])
-
- count = self.singleton.search([], count=True)
- self.assertEqual(count, 1)
-
- self.singleton.create([{'name': 'foo'}])
- singleton, = self.singleton.search([('name', '=', 'foo')])
- self.assertEqual(singleton.name, 'foo')
- singletons = self.singleton.search([('name', '=', 'bar')])
- self.assertEqual(singletons, [])
- transaction.cursor.rollback()
+ pool = Pool()
+ Singleton = pool.get('test.singleton')
+
+ singletons = Singleton.search([])
+ self.assertEqual(map(int, singletons), [1])
+
+ singletons = Singleton.search([])
+ self.assertEqual(map(int, singletons), [1])
+
+ count = Singleton.search([], count=True)
+ self.assertEqual(count, 1)
+
+ Singleton.create([{'name': 'foo'}])
+ singleton, = Singleton.search([('name', '=', 'foo')])
+ self.assertEqual(singleton.name, 'foo')
+ singletons = Singleton.search([('name', '=', 'bar')])
+ self.assertEqual(singletons, [])
def suite():
diff --git a/trytond/tests/test_modelsql.py b/trytond/tests/test_modelsql.py
index e9ea26d..7c47414 100644
--- a/trytond/tests/test_modelsql.py
+++ b/trytond/tests/test_modelsql.py
@@ -5,76 +5,114 @@
import unittest
import time
+from mock import patch, call
+
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, \
- install_module
+from trytond.pool import Pool
+from trytond.tests.test_tryton import install_module, with_transaction
class ModelSQLTestCase(unittest.TestCase):
'Test ModelSQL'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- 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):
+ @with_transaction()
+ def test_required_field_missing(self):
'Test error message when a required field is missing'
+ pool = Pool()
+ Modelsql = pool.get('test.modelsql')
+ transaction = Transaction()
+
fields = {
'desc': '',
'integer': 0,
}
for key, value in fields.iteritems():
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- try:
- self.modelsql.create([{key: value}])
- except UserError, err:
- # message must not quote key
- msg = "'%s' not missing but quoted in error: '%s'" % (key,
- err.message)
- self.assertTrue(key not in err.message, msg)
- continue
+ try:
+ Modelsql.create([{key: value}])
+ except UserError, err:
+ # message must not quote key
+ msg = "'%s' not missing but quoted in error: '%s'" % (key,
+ err.message)
+ self.assertTrue(key not in err.message, msg)
+ else:
self.fail('UserError should be caught')
+ transaction.rollback()
- def test0020check_timestamp(self):
+ @with_transaction()
+ def test_check_timestamp(self):
'Test check timestamp'
- # cursor must be committed between each changes otherwise NOW() returns
- # always the same timestamp.
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- cursor = transaction.cursor
- record, = self.modelsql_timestamp.create([{}])
- cursor.commit()
-
- timestamp = self.modelsql_timestamp.read([record.id],
- ['_timestamp'])[0]['_timestamp']
-
- if backend.name() in ('sqlite', 'mysql'):
- # timestamp precision of sqlite is the second
- time.sleep(1)
-
- self.modelsql_timestamp.write([record], {})
- cursor.commit()
-
- transaction.timestamp[str(record)] = timestamp
- self.assertRaises(ConcurrencyException,
- self.modelsql_timestamp.write, [record], {})
-
- transaction.timestamp[str(record)] = timestamp
- self.assertRaises(ConcurrencyException,
- self.modelsql_timestamp.delete, [record])
-
- transaction.timestamp.pop(str(record), None)
- self.modelsql_timestamp.write([record], {})
- cursor.commit()
- self.modelsql_timestamp.delete([record])
- cursor.commit()
+ pool = Pool()
+ ModelsqlTimestamp = pool.get('test.modelsql.timestamp')
+ transaction = Transaction()
+ # transaction must be committed between each changes otherwise NOW()
+ # returns always the same timestamp.
+ record, = ModelsqlTimestamp.create([{}])
+ transaction.commit()
+
+ timestamp = ModelsqlTimestamp.read([record.id],
+ ['_timestamp'])[0]['_timestamp']
+
+ if backend.name() in ('sqlite', 'mysql'):
+ # timestamp precision of sqlite is the second
+ time.sleep(1)
+
+ ModelsqlTimestamp.write([record], {})
+ transaction.commit()
+
+ transaction.timestamp[str(record)] = timestamp
+ self.assertRaises(ConcurrencyException,
+ ModelsqlTimestamp.write, [record], {})
+
+ transaction.timestamp[str(record)] = timestamp
+ self.assertRaises(ConcurrencyException,
+ ModelsqlTimestamp.delete, [record])
+
+ transaction.timestamp.pop(str(record), None)
+ ModelsqlTimestamp.write([record], {})
+ transaction.commit()
+ ModelsqlTimestamp.delete([record])
+ transaction.commit()
+
+ @with_transaction()
+ def test_create_field_set(self):
+ 'Test field.set in create'
+ pool = Pool()
+ Model = pool.get('test.modelsql.field_set')
+
+ with patch.object(Model, 'set_field') as setter:
+ records = Model.create([{'field': 1}])
+ setter.assert_called_with(records, 'field', 1)
+
+ # Different values are not grouped
+ with patch.object(Model, 'set_field') as setter:
+ records = Model.create([{'field': 1}, {'field': 2}])
+ setter.assert_has_calls([
+ call([records[0]], 'field', 1),
+ call([records[1]], 'field', 2),
+ ])
+
+ # Same values are grouped in one call
+ with patch.object(Model, 'set_field') as setter:
+ records = Model.create([{'field': 1}, {'field': 1}])
+ setter.assert_called_with(records, 'field', 1)
+
+ # Mixed values are grouped per value
+ with patch.object(Model, 'set_field') as setter:
+ records = Model.create([{'field': 1}, {'field': 2}, {'field': 1}])
+ setter.assert_has_calls([
+ call([records[0], records[2]], 'field', 1),
+ call([records[1]], 'field', 2),
+ ])
def suite():
diff --git a/trytond/tests/test_modelstorage.py b/trytond/tests/test_modelstorage.py
index 1be6597..6ce7839 100644
--- a/trytond/tests/test_modelstorage.py
+++ b/trytond/tests/test_modelstorage.py
@@ -3,36 +3,36 @@
import unittest
-from trytond.transaction import Transaction
from trytond.pool import Pool
-from trytond.tests.test_tryton import DB_NAME, USER, CONTEXT, install_module
+from trytond.tests.test_tryton import install_module, with_transaction
class ModelStorageTestCase(unittest.TestCase):
'Test ModelStorage'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
+ @with_transaction()
def test_search_read_order(self):
'Test search_read order'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- pool = Pool()
- ModelStorage = pool.get('test.modelstorage')
+ pool = Pool()
+ ModelStorage = pool.get('test.modelstorage')
- ModelStorage.create([{'name': i} for i in ['foo', 'bar', 'test']])
+ ModelStorage.create([{'name': i} for i in ['foo', 'bar', 'test']])
- rows = ModelStorage.search_read([])
- self.assertTrue(
- all(x['id'] < y['id'] for x, y in zip(rows, rows[1:])))
+ rows = ModelStorage.search_read([])
+ self.assertTrue(
+ all(x['id'] < y['id'] for x, y in zip(rows, rows[1:])))
- rows = ModelStorage.search_read([], order=[('name', 'ASC')])
- self.assertTrue(
- all(x['name'] <= y['name'] for x, y in zip(rows, rows[1:])))
+ rows = ModelStorage.search_read([], order=[('name', 'ASC')])
+ self.assertTrue(
+ all(x['name'] <= y['name'] for x, y in zip(rows, rows[1:])))
- rows = ModelStorage.search_read([], order=[('name', 'DESC')])
- self.assertTrue(
- all(x['name'] >= y['name'] for x, y in zip(rows, rows[1:])))
+ rows = ModelStorage.search_read([], order=[('name', 'DESC')])
+ self.assertTrue(
+ all(x['name'] >= y['name'] for x, y in zip(rows, rows[1:])))
def suite():
diff --git a/trytond/tests/test_modelview.py b/trytond/tests/test_modelview.py
index 300978e..cd98369 100644
--- a/trytond/tests/test_modelview.py
+++ b/trytond/tests/test_modelview.py
@@ -3,74 +3,74 @@
import unittest
-from trytond.tests.test_tryton import DB_NAME, USER, CONTEXT, install_module
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.pool import Pool
-from trytond.transaction import Transaction
class ModelView(unittest.TestCase):
"Test ModelView"
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
+ @with_transaction()
def test_changed_values(self):
"Test ModelView._changed_values"
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- pool = Pool()
- Model = pool.get('test.modelview.changed_values')
- Target = pool.get('test.modelview.changed_values.target')
-
- record = Model()
-
- self.assertEqual(record._changed_values, {})
-
- record.name = 'foo'
- record.target = Target(1)
- record.ref_target = Target(2)
- record.targets = [Target(name='bar')]
- self.assertEqual(record._changed_values, {
- 'name': 'foo',
- 'target': 1,
- 'ref_target': 'test.modelview.changed_values.target,2',
- 'targets': {
- 'add': [
- (0, {'name': 'bar'}),
- ],
- },
- })
-
- record = Model(name='test', target=1, targets=[
- {'id': 1, 'name': 'foo'},
- {'id': 2},
- ], m2m_targets=[5, 6, 7])
-
- self.assertEqual(record._changed_values, {})
-
- target = record.targets[0]
- target.name = 'bar'
- record.targets = [target]
- record.m2m_targets = [Target(9), Target(10)]
- self.assertEqual(record._changed_values, {
- 'targets': {
- 'update': [{'id': 1, 'name': 'bar'}],
- 'remove': [2],
- },
- 'm2m_targets': [9, 10],
- })
-
- # change only one2many record
- record = Model(targets=[{'id': 1, 'name': 'foo'}])
- self.assertEqual(record._changed_values, {})
-
- target, = record.targets
- target.name = 'bar'
- record.targets = record.targets
- self.assertEqual(record._changed_values, {
- 'targets': {
- 'update': [{'id': 1, 'name': 'bar'}],
- },
- })
+ pool = Pool()
+ Model = pool.get('test.modelview.changed_values')
+ Target = pool.get('test.modelview.changed_values.target')
+
+ record = Model()
+
+ self.assertEqual(record._changed_values, {})
+
+ record.name = 'foo'
+ record.target = Target(1)
+ record.ref_target = Target(2)
+ record.targets = [Target(name='bar')]
+ self.assertEqual(record._changed_values, {
+ 'name': 'foo',
+ 'target': 1,
+ 'ref_target': 'test.modelview.changed_values.target,2',
+ 'targets': {
+ 'add': [
+ (0, {'name': 'bar'}),
+ ],
+ },
+ })
+
+ record = Model(name='test', target=1, targets=[
+ {'id': 1, 'name': 'foo'},
+ {'id': 2},
+ ], m2m_targets=[5, 6, 7])
+
+ self.assertEqual(record._changed_values, {})
+
+ target = record.targets[0]
+ target.name = 'bar'
+ record.targets = [target]
+ record.m2m_targets = [Target(9), Target(10)]
+ self.assertEqual(record._changed_values, {
+ 'targets': {
+ 'update': [{'id': 1, 'name': 'bar'}],
+ 'remove': [2],
+ },
+ 'm2m_targets': [9, 10],
+ })
+
+ # change only one2many record
+ record = Model(targets=[{'id': 1, 'name': 'foo'}])
+ self.assertEqual(record._changed_values, {})
+
+ target, = record.targets
+ target.name = 'bar'
+ record.targets = record.targets
+ self.assertEqual(record._changed_values, {
+ 'targets': {
+ 'update': [{'id': 1, 'name': 'bar'}],
+ },
+ })
def suite():
diff --git a/trytond/tests/test_mptt.py b/trytond/tests/test_mptt.py
index f4a73e6..5c92af0 100644
--- a/trytond/tests/test_mptt.py
+++ b/trytond/tests/test_mptt.py
@@ -4,21 +4,24 @@
import sys
import unittest
from mock import patch
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
+from trytond.pool import Pool
class MPTTTestCase(unittest.TestCase):
'Test Modified Preorder Tree Traversal'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.mptt = POOL.get('test.mptt')
- def CheckTree(self, parent_id=None, left=-1, right=sys.maxint):
+ def check_tree(self, parent_id=None, left=-1, right=sys.maxint):
+ pool = Pool()
+ Mptt = pool.get('test.mptt')
+
with Transaction().set_context(active_test=False):
- childs = self.mptt.search([
+ childs = Mptt.search([
('parent', '=', parent_id),
], order=[('left', 'ASC')])
for child in childs:
@@ -31,7 +34,7 @@ class MPTTTestCase(unittest.TestCase):
assert child.right < right, \
'%s: right %d >= parent right %d' % \
(child, child.right, right)
- self.CheckTree(child.id, left=child.left,
+ self.check_tree(child.id, left=child.left,
right=child.right)
next_left = -1
for child in childs:
@@ -47,8 +50,11 @@ class MPTTTestCase(unittest.TestCase):
% (child, child.right, previous_right)
previous_right = child.left
- def ReParent(self, parent=None):
- records = self.mptt.search([
+ def reparent(self, parent=None):
+ pool = Pool()
+ Mptt = pool.get('test.mptt')
+
+ records = Mptt.search([
('parent', '=', parent),
])
if not records:
@@ -61,135 +67,139 @@ class MPTTTestCase(unittest.TestCase):
record.parent = parent
record.save()
for record in records:
- self.ReParent(record)
-
- def test0010create(self):
+ self.reparent(record)
+
+ def create(self):
+ pool = Pool()
+ Mptt = pool.get('test.mptt')
+
+ new_records = [None, None, None]
+ for j in range(3):
+ parent_records = new_records
+ new_records = []
+ k = 0
+ to_create = []
+ for parent_record in parent_records:
+ to_create.extend([{
+ 'name': 'Test %d %d %d' % (j, k, i),
+ 'parent': (parent_record.id
+ if parent_record else None),
+ } for i in range(3)])
+ k += 1
+ new_records = Mptt.create(to_create)
+
+ @with_transaction()
+ def test_create(self):
'Test create tree'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- new_records = [None, None, None]
- for j in range(3):
- parent_records = new_records
- new_records = []
- k = 0
- for parent_record in parent_records:
- new_records += self.mptt.create([{
- 'name': 'Test %d %d %d' % (j, k, i),
- 'parent': (parent_record.id
- if parent_record else None),
- } for i in range(3)])
- k += 1
- self.CheckTree()
-
- transaction.cursor.commit()
-
- def test0030reparent(self):
+ self.create()
+ self.check_tree()
+
+ @with_transaction()
+ def test_reparent(self):
'Test re-parent'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- self.ReParent()
- transaction.cursor.rollback()
+ self.create()
+ self.reparent()
- def test0040active(self):
+ @with_transaction()
+ def test_active(self):
'Test active'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- records = self.mptt.search([])
- for record in records:
- if record.id % 2:
- record.active = False
- record.save()
- self.CheckTree()
- self.ReParent()
- self.CheckTree()
+ pool = Pool()
+ Mptt = pool.get('test.mptt')
- transaction.cursor.rollback()
+ self.create()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- records = self.mptt.search([])
- self.mptt.write(records[::2], {
- 'active': False
- })
- self.CheckTree()
-
- transaction.cursor.rollback()
-
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- records = self.mptt.search([])
- self.mptt.write(records[:len(records) // 2], {
- 'active': False
- })
- self.CheckTree()
+ records = Mptt.search([])
+ for record in records:
+ if record.id % 2:
+ record.active = False
+ record.save()
+ self.check_tree()
+ self.reparent()
+ self.check_tree()
+
+ records = Mptt.search([])
+ Mptt.write(records, {
+ 'active': True,
+ })
+ Mptt.write(records[::2], {
+ 'active': False
+ })
+ self.check_tree()
+
+ records = Mptt.search([])
+ Mptt.write(records, {
+ 'active': True,
+ })
+ Mptt.write(records[:len(records) // 2], {
+ 'active': False
+ })
+ self.check_tree()
+
+ records = Mptt.search([])
+ Mptt.write(records, {
+ 'active': False
+ })
+ self.reparent()
+ self.check_tree()
+
+ @with_transaction()
+ def test_delete(self):
+ 'Test delete'
+ pool = Pool()
+ Mptt = pool.get('test.mptt')
- transaction.cursor.rollback()
+ self.create()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- records = self.mptt.search([])
- self.mptt.write(records, {
- 'active': False
- })
- self.ReParent()
- self.CheckTree()
+ records = Mptt.search([])
+ for record in records:
+ if record.id % 2:
+ Mptt.delete([record])
+ self.check_tree()
- transaction.cursor.rollback()
+ records = Mptt.search([])
+ Mptt.delete(records[:len(records) // 2])
+ self.check_tree()
- def test0050delete(self):
- 'Test delete'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- records = self.mptt.search([])
- for record in records:
- if record.id % 2:
- self.mptt.delete([record])
- self.CheckTree()
+ records = Mptt.search([])
+ Mptt.delete(records)
+ self.check_tree()
- transaction.cursor.rollback()
+ @with_transaction()
+ def test_update_only_if_parent_is_modified(self):
+ 'The left and right fields must only be updated if parent is modified'
+ pool = Pool()
+ Mptt = pool.get('test.mptt')
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- records = self.mptt.search([])
- self.mptt.delete(records[:len(records) // 2])
- self.CheckTree()
+ self.create()
- transaction.cursor.rollback()
+ records = Mptt.search([
+ ('parent', '=', None),
+ ])
+ with patch.object(Mptt, '_update_tree') as update, \
+ patch.object(Mptt, '_rebuild_tree') as rebuild:
+ Mptt.write(records, {'name': 'Parent Records'})
+ self.assertFalse(update.called)
+ self.assertFalse(rebuild.called)
+
+ first_parent, second_parent = records[:2]
+ Mptt.write(list(first_parent.childs), {
+ 'parent': second_parent.id,
+ })
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- records = self.mptt.search([])
- self.mptt.delete(records)
- self.CheckTree()
+ self.assertTrue(update.called or rebuild.called)
- transaction.cursor.rollback()
+ @with_transaction()
+ def test_nested_create(self):
+ pool = Pool()
+ Mptt = pool.get('test.mptt')
- 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 test0070_nested_create(self):
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- record, = self.mptt.create([{
- 'name': 'Test nested create 1',
- 'childs': [('create', [{
- 'name': 'Test nested create 1 1',
- }])],
- }])
- self.CheckTree()
+ record, = Mptt.create([{
+ 'name': 'Test nested create 1',
+ 'childs': [('create', [{
+ 'name': 'Test nested create 1 1',
+ }])],
+ }])
+ self.check_tree()
def suite():
diff --git a/trytond/tests/test_protocols.py b/trytond/tests/test_protocols.py
index 28ae666..70df424 100644
--- a/trytond/tests/test_protocols.py
+++ b/trytond/tests/test_protocols.py
@@ -7,7 +7,7 @@ import datetime
from decimal import Decimal
from trytond.protocols.jsonrpc import JSONEncoder, JSONDecoder
-from trytond.protocols.xmlrpc import xmlrpclib
+from trytond.protocols.xmlrpc import client
class JSONTestCase(unittest.TestCase):
@@ -44,8 +44,8 @@ class XMLTestCase(unittest.TestCase):
'Test XML'
def dumps_loads(self, value):
- s = xmlrpclib.dumps((value,))
- result, _ = xmlrpclib.loads(s)
+ s = client.dumps((value,))
+ result, _ = client.loads(s)
result, = result
self.assertEqual(value, result)
diff --git a/trytond/tests/test_pyson.py b/trytond/tests/test_pyson.py
index 99cfe37..3426125 100644
--- a/trytond/tests/test_pyson.py
+++ b/trytond/tests/test_pyson.py
@@ -10,7 +10,7 @@ from trytond import pyson
class PYSONTestCase(unittest.TestCase):
'Test PySON'
- def test0010Eval(self):
+ def test_Eval(self):
'Test pyson.Eval'
self.assertEqual(pyson.Eval('test').pyson(), {
'__class__': 'Eval',
@@ -39,7 +39,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertIsInstance(pyson.Eval('test', False) | False, pyson.Or)
self.assertIsInstance(False | pyson.Eval('test', False), pyson.Or)
- def test0020Not(self):
+ def test_Not(self):
'Test pyson.Not'
self.assertEqual(pyson.Not(True).pyson(), {
'__class__': 'Not',
@@ -58,7 +58,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Not(True)), 'Not(True)')
- def test0030Bool(self):
+ def test_Bool(self):
'Test pyson.Bool'
self.assertEqual(pyson.Bool('test').pyson(), {
'__class__': 'Bool',
@@ -99,7 +99,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Bool('test')), "Bool('test')")
- def test0040And(self):
+ def test_And(self):
'Test pyson.And'
self.assertEqual(pyson.And(True, False).pyson(), {
'__class__': 'And',
@@ -144,7 +144,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.And(False, True, True)),
'And(False, True, True)')
- def test0050Or(self):
+ def test_Or(self):
'Test pyson.Or'
self.assertEqual(pyson.Or(True, False).pyson(), {
'__class__': 'Or',
@@ -189,7 +189,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Or(False, True, True)),
'Or(False, True, True)')
- def test0060Equal(self):
+ def test_Equal(self):
'Test pyson.Equal'
self.assertEqual(pyson.Equal('test', 'test').pyson(), {
'__class__': 'Equal',
@@ -210,7 +210,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Equal('foo', 'bar')),
"Equal('foo', 'bar')")
- def test0070Greater(self):
+ def test_Greater(self):
'Test pyson.Greater'
self.assertEqual(pyson.Greater(1, 0).pyson(), {
'__class__': 'Greater',
@@ -245,7 +245,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Greater(1, 0)), 'Greater(1, 0, False)')
- def test0080Less(self):
+ def test_Less(self):
'Test pyson.Less'
self.assertEqual(pyson.Less(0, 1).pyson(), {
'__class__': 'Less',
@@ -280,7 +280,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Less(0, 1)), 'Less(0, 1, False)')
- def test0090If(self):
+ def test_If(self):
'Test pyson.If'
self.assertEqual(pyson.If(True, 'foo', 'bar').pyson(), {
'__class__': 'If',
@@ -305,7 +305,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.If(True, 'foo', 'bar')),
"If(True, 'foo', 'bar')")
- def test0100Get(self):
+ def test_Get(self):
'Test pyson.Get'
self.assertEqual(pyson.Get({'foo': 'bar'}, 'foo', 'default').pyson(), {
'__class__': 'Get',
@@ -336,7 +336,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Get({'foo': 'bar'}, 'foo', 'default')),
"Get({'foo': 'bar'}, 'foo', 'default')")
- def test0110In(self):
+ def test_In(self):
'Test pyson.In'
self.assertEqual(pyson.In('foo', {'foo': 'bar'}).pyson(), {
'__class__': 'In',
@@ -382,7 +382,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.In('foo', ['foo', 'bar'])),
"In('foo', ['foo', 'bar'])")
- def test0120Date(self):
+ def test_Date(self):
'Test pyson.Date'
self.assertEqual(pyson.Date(2010, 1, 12, -1, 12, -7).pyson(), {
'__class__': 'Date',
@@ -437,7 +437,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Date(2010, 1, 12, -1, 12, -7)),
'Date(2010, 1, 12, -1, 12, -7)')
- def test0130DateTime(self):
+ def test_DateTime(self):
'Test pyson.DateTime'
self.assertEqual(pyson.DateTime(2010, 1, 12, 10, 30, 20, 0,
-1, 12, -7, 2, 15, 30, 1).pyson(), {
@@ -539,7 +539,7 @@ class PYSONTestCase(unittest.TestCase):
-1, 12, -7, 2, 15, 30, 1)),
'DateTime(2010, 1, 12, 10, 30, 20, 0, -1, 12, -7, 2, 15, 30, 1)')
- def test0140Len(self):
+ def test_Len(self):
'Test pyson.Len'
self.assertEqual(pyson.Len([1, 2, 3]).pyson(), {
'__class__': 'Len',
@@ -561,7 +561,7 @@ class PYSONTestCase(unittest.TestCase):
self.assertEqual(repr(pyson.Len([1, 2, 3])), 'Len([1, 2, 3])')
- def test0900Composite(self):
+ def test_Composite(self):
'Test Composite'
expr = pyson.If(pyson.Not(
pyson.In('company', pyson.Eval('context', {}))), '=', '!=')
diff --git a/trytond/tests/test_sendmail.py b/trytond/tests/test_sendmail.py
new file mode 100644
index 0000000..86b954c
--- /dev/null
+++ b/trytond/tests/test_sendmail.py
@@ -0,0 +1,106 @@
+# 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 smtplib
+from email.message import Message
+from mock import Mock, patch, call
+
+from trytond.sendmail import (
+ sendmail_transactional, sendmail, SMTPDataManager, get_smtp_server)
+from trytond.transaction import Transaction
+from .test_tryton import with_transaction, install_module
+
+
+class SendmailTestCase(unittest.TestCase):
+ 'Test sendmail'
+
+ @classmethod
+ def setUpClass(cls):
+ install_module('tests')
+
+ @with_transaction()
+ def test_sendmail_transactional(self):
+ 'Test sendmail_transactional'
+ message = Mock()
+ datamanager = Mock()
+ sendmail_transactional(
+ 'tryton at example.com', 'foo at example.com', message,
+ datamanager=datamanager)
+
+ datamanager.put.assert_called_once_with(
+ 'tryton at example.com', 'foo at example.com', message)
+
+ def test_sendmail(self):
+ 'Test sendmail'
+ message = Mock()
+ server = Mock()
+ sendmail(
+ 'tryton at example.com', 'foo at example.com', message, server=server)
+ server.sendmail.assert_called_with(
+ 'tryton at example.com', 'foo at example.com', message.as_string())
+ server.quit.assert_not_called()
+
+ def test_get_smtp_server(self):
+ 'Test get_smtp_server'
+ with patch.object(smtplib, 'SMTP') as SMTP:
+ SMTP.return_value = server = Mock()
+ self.assertEqual(get_smtp_server('smtp://localhost:25'), server)
+ SMTP.assert_called_once_with('localhost', 25)
+
+ with patch.object(smtplib, 'SMTP') as SMTP:
+ SMTP.return_value = server = Mock()
+ self.assertEqual(
+ get_smtp_server('smtp://foo:bar@localhost:25'), server)
+ SMTP.assert_called_once_with('localhost', 25)
+ server.login.assert_called_once_with('foo', 'bar')
+
+ with patch.object(smtplib, 'SMTP_SSL') as SMTP:
+ SMTP.return_value = server = Mock()
+ self.assertEqual(
+ get_smtp_server('smtps://localhost:25'), server)
+ SMTP.assert_called_once_with('localhost', 25)
+
+ with patch.object(smtplib, 'SMTP') as SMTP:
+ SMTP.return_value = server = Mock()
+ self.assertEqual(
+ get_smtp_server('smtp+tls://localhost:25'), server)
+ SMTP.assert_called_once_with('localhost', 25)
+ server.starttls.assert_called_once_with()
+
+ @patch('trytond.sendmail.get_smtp_server')
+ @with_transaction()
+ def test_SMTPDataManager(self, get_smtp_server):
+ 'Test SMTPDataManager'
+ transaction = Transaction()
+ get_smtp_server.return_value = server = Mock()
+
+ datamanager = transaction.join(SMTPDataManager())
+
+ # multiple join must return the same
+ self.assertEqual(transaction.join(SMTPDataManager()), datamanager)
+
+ msg1 = Mock(Message)
+ msg2 = Mock(Message)
+ datamanager.put('foo at example.com', 'bar at example.com', msg1)
+ datamanager.put('bar at example.com', 'foo at example.com', msg2)
+
+ transaction.commit()
+
+ server.sendmail.assert_has_calls([
+ call('foo at example.com', 'bar at example.com', msg1.as_string()),
+ call('bar at example.com', 'foo at example.com', msg2.as_string()),
+ ])
+ server.quit.assert_called_once_with()
+ self.assertFalse(datamanager.queue)
+
+ server.reset_mock()
+
+ datamanager.put('foo at example.com', 'bar at example.com', Mock(Message))
+ transaction.rollback()
+
+ server.sendmail.assert_not_called()
+ self.assertFalse(datamanager.queue)
+
+
+def suite():
+ return unittest.TestLoader().loadTestsFromTestCase(SendmailTestCase)
diff --git a/trytond/tests/test_sequence.py b/trytond/tests/test_sequence.py
index e4aa58f..6a3e0f1 100644
--- a/trytond/tests/test_sequence.py
+++ b/trytond/tests/test_sequence.py
@@ -3,112 +3,122 @@
# this repository contains the full copyright notices and license terms.
import unittest
import datetime
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
+from trytond.pool import Pool
from trytond.exceptions import UserError
class SequenceTestCase(unittest.TestCase):
'Test Sequence'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.sequence = POOL.get('ir.sequence')
- def test0010incremental(self):
+ @with_transaction()
+ def test_incremental(self):
'Test incremental'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- sequence, = self.sequence.create([{
- 'name': 'Test incremental',
- 'code': 'test',
- 'prefix': '',
- 'suffix': '',
- 'type': 'incremental',
- }])
- self.assertEqual(self.sequence.get_id(sequence), '1')
-
- self.sequence.write([sequence], {
- 'number_increment': 10,
- })
- self.assertEqual(self.sequence.get_id(sequence), '2')
- self.assertEqual(self.sequence.get_id(sequence), '12')
-
- self.sequence.write([sequence], {
- 'padding': 3,
- })
- self.assertEqual(self.sequence.get_id(sequence), '022')
-
- transaction.cursor.rollback()
-
- def test0020decimal_timestamp(self):
+ pool = Pool()
+ Sequence = pool.get('ir.sequence')
+
+ sequence, = Sequence.create([{
+ 'name': 'Test incremental',
+ 'code': 'test',
+ 'prefix': '',
+ 'suffix': '',
+ 'type': 'incremental',
+ }])
+ self.assertEqual(sequence.number_next, 1)
+ self.assertEqual(Sequence.get_id(sequence), '1')
+
+ Sequence.write([sequence], {
+ 'number_increment': 10,
+ })
+ self.assertEqual(sequence.number_next, 2)
+ self.assertEqual(Sequence.get_id(sequence), '2')
+ self.assertEqual(Sequence.get_id(sequence), '12')
+
+ Sequence.write([sequence], {
+ 'padding': 3,
+ })
+ self.assertEqual(sequence.number_next, 22)
+ self.assertEqual(Sequence.get_id(sequence), '022')
+
+ @with_transaction()
+ def test_decimal_timestamp(self):
'Test Decimal Timestamp'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- sequence, = self.sequence.create([{
- 'name': 'Test decimal timestamp',
- 'code': 'test',
- 'prefix': '',
- 'suffix': '',
- 'type': 'decimal timestamp',
- }])
- timestamp = self.sequence.get_id(sequence)
- self.assertEqual(timestamp, str(sequence.last_timestamp))
-
- self.assertNotEqual(self.sequence.get_id(sequence), timestamp)
-
- next_timestamp = self.sequence._timestamp(sequence)
- self.assertRaises(UserError, self.sequence.write, [sequence], {
- 'last_timestamp': next_timestamp + 100,
- })
-
- transaction.cursor.rollback()
-
- def test0030hexadecimal_timestamp(self):
+ pool = Pool()
+ Sequence = pool.get('ir.sequence')
+
+ sequence, = Sequence.create([{
+ 'name': 'Test decimal timestamp',
+ 'code': 'test',
+ 'prefix': '',
+ 'suffix': '',
+ 'type': 'decimal timestamp',
+ }])
+ timestamp = Sequence.get_id(sequence)
+ self.assertEqual(timestamp, str(sequence.last_timestamp))
+
+ self.assertEqual(sequence.number_next, None)
+
+ self.assertNotEqual(Sequence.get_id(sequence), timestamp)
+
+ next_timestamp = Sequence._timestamp(sequence)
+ self.assertRaises(UserError, Sequence.write, [sequence], {
+ 'last_timestamp': next_timestamp + 100,
+ })
+
+ @with_transaction()
+ def test_hexadecimal_timestamp(self):
'Test Hexadecimal Timestamp'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- sequence, = self.sequence.create([{
- 'name': 'Test hexadecimal timestamp',
- 'code': 'test',
- 'prefix': '',
- 'suffix': '',
- 'type': 'hexadecimal timestamp',
- }])
- timestamp = self.sequence.get_id(sequence)
- self.assertEqual(timestamp,
- hex(int(sequence.last_timestamp))[2:].upper())
-
- self.assertNotEqual(self.sequence.get_id(sequence), timestamp)
-
- next_timestamp = self.sequence._timestamp(sequence)
- self.assertRaises(UserError, self.sequence.write, [sequence], {
- 'last_timestamp': next_timestamp + 100,
- })
-
- transaction.cursor.rollback()
-
- def test0040prefix_suffix(self):
+ pool = Pool()
+ Sequence = pool.get('ir.sequence')
+
+ sequence, = Sequence.create([{
+ 'name': 'Test hexadecimal timestamp',
+ 'code': 'test',
+ 'prefix': '',
+ 'suffix': '',
+ 'type': 'hexadecimal timestamp',
+ }])
+ timestamp = Sequence.get_id(sequence)
+ self.assertEqual(timestamp,
+ hex(int(sequence.last_timestamp))[2:].upper())
+
+ self.assertEqual(sequence.number_next, None)
+
+ self.assertNotEqual(Sequence.get_id(sequence), timestamp)
+
+ next_timestamp = Sequence._timestamp(sequence)
+ self.assertRaises(UserError, Sequence.write, [sequence], {
+ 'last_timestamp': next_timestamp + 100,
+ })
+
+ @with_transaction()
+ def test_prefix_suffix(self):
'Test prefix/suffix'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- sequence, = self.sequence.create([{
- 'name': 'Test incremental',
- 'code': 'test',
- 'prefix': 'prefix/',
- 'suffix': '/suffix',
- 'type': 'incremental',
- }])
- self.assertEqual(self.sequence.get_id(sequence),
- 'prefix/1/suffix')
-
- self.sequence.write([sequence], {
- 'prefix': '${year}-${month}-${day}/',
- 'suffix': '/${day}.${month}.${year}',
- })
- with Transaction().set_context(date=datetime.date(2010, 8, 15)):
- self.assertEqual(self.sequence.get_id(sequence),
- '2010-08-15/2/15.08.2010')
+ pool = Pool()
+ Sequence = pool.get('ir.sequence')
+
+ sequence, = Sequence.create([{
+ 'name': 'Test incremental',
+ 'code': 'test',
+ 'prefix': 'prefix/',
+ 'suffix': '/suffix',
+ 'type': 'incremental',
+ }])
+ self.assertEqual(Sequence.get_id(sequence),
+ 'prefix/1/suffix')
+
+ Sequence.write([sequence], {
+ 'prefix': '${year}-${month}-${day}/',
+ 'suffix': '/${day}.${month}.${year}',
+ })
+ with Transaction().set_context(date=datetime.date(2010, 8, 15)):
+ self.assertEqual(Sequence.get_id(sequence),
+ '2010-08-15/2/15.08.2010')
def suite():
diff --git a/trytond/tests/test_tools.py b/trytond/tests/test_tools.py
index 28d77e8..39ea282 100644
--- a/trytond/tests/test_tools.py
+++ b/trytond/tests/test_tools.py
@@ -16,41 +16,41 @@ class ToolsTestCase(unittest.TestCase):
'Test tools'
table = sql.Table('test')
- def test0000reduce_ids_empty(self):
+ def test_reduce_ids_empty(self):
'Test reduce_ids empty list'
self.assertEqual(reduce_ids(self.table.id, []), sql.Literal(False))
- def test0010reduce_ids_continue(self):
+ def test_reduce_ids_continue(self):
'Test reduce_ids continue list'
self.assertEqual(reduce_ids(self.table.id, range(10)),
sql.operators.Or(((self.table.id >= 0) & (self.table.id <= 9),)))
- def test0020reduce_ids_one_hole(self):
+ def test_reduce_ids_one_hole(self):
'Test reduce_ids continue list with one hole'
self.assertEqual(reduce_ids(self.table.id, range(10) + range(20, 30)),
((self.table.id >= 0) & (self.table.id <= 9))
| ((self.table.id >= 20) & (self.table.id <= 29)))
- def test0030reduce_ids_short_continue(self):
+ def test_reduce_ids_short_continue(self):
'Test reduce_ids short continue list'
self.assertEqual(reduce_ids(self.table.id, range(4)),
sql.operators.Or((self.table.id.in_(range(4)),)))
- def test0040reduce_ids_complex(self):
+ def test_reduce_ids_complex(self):
'Test reduce_ids complex list'
self.assertEqual(reduce_ids(self.table.id,
range(10) + range(25, 30) + range(15, 20)),
(((self.table.id >= 0) & (self.table.id <= 14))
| (self.table.id.in_(range(25, 30)))))
- def test0050reduce_ids_complex_small_continue(self):
+ def test_reduce_ids_complex_small_continue(self):
'Test reduce_ids complex list with small continue'
self.assertEqual(reduce_ids(self.table.id,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 18, 19, 21]),
(((self.table.id >= 1) & (self.table.id <= 12))
| (self.table.id.in_([15, 18, 19, 21]))))
- def test0055reduce_ids_float(self):
+ def test_reduce_ids_float(self):
'Test reduce_ids with integer as float'
self.assertEqual(reduce_ids(self.table.id,
[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
@@ -59,7 +59,7 @@ class ToolsTestCase(unittest.TestCase):
| (self.table.id.in_([15.0, 18.0, 19.0, 21.0]))))
self.assertRaises(AssertionError, reduce_ids, self.table.id, [1.1])
- def test0070datetime_strftime(self):
+ def test_datetime_strftime(self):
'Test datetime_strftime'
self.assert_(datetime_strftime(datetime.date(2005, 3, 2),
'%Y-%m-%d'), '2005-03-02')
diff --git a/trytond/tests/test_transaction.py b/trytond/tests/test_transaction.py
index 10aef42..6360d47 100644
--- a/trytond/tests/test_transaction.py
+++ b/trytond/tests/test_transaction.py
@@ -1,6 +1,7 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import unittest
+from mock import Mock
from trytond.tests.test_tryton import DB_NAME, USER, CONTEXT, install_module
from trytond.transaction import Transaction
@@ -16,24 +17,14 @@ def empty_transaction(*args, **kwargs):
return True
-def manipulate_cursor(*args, **kwargs):
- '''
- Just start a transaction in the context manager and close the cursor
- during the transaction so that the cursor.close in the stop fails
- '''
- with Transaction().start(*args, **kwargs) as transaction:
- transaction.cursor.close()
- transaction.cursor = None
- return True
-
-
class TransactionTestCase(unittest.TestCase):
'Test the Transaction Context manager'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- def test0010nonexistdb(self):
+ def test_nonexistdb(self):
'''Attempt opening a transaction with a non existant DB
and ensure that it stops cleanly and allows starting of next
transaction'''
@@ -42,15 +33,7 @@ class TransactionTestCase(unittest.TestCase):
context=CONTEXT)
self.assertTrue(empty_transaction(DB_NAME, USER, context=CONTEXT))
- def test0020cursorclose(self):
- '''Manipulate the cursor during the transaction so that
- the close in transaction stop fails.
- Ensure that this does not affect opening of another transaction'''
- self.assertRaises(
- Exception, manipulate_cursor, DB_NAME, USER, context=CONTEXT)
- self.assertTrue(empty_transaction(DB_NAME, USER, context=CONTEXT))
-
- def test0030set_user(self):
+ def test_set_user(self):
'Test set_user'
with Transaction().start(DB_NAME, USER, context=CONTEXT) \
as transaction:
@@ -83,6 +66,61 @@ class TransactionTestCase(unittest.TestCase):
with Transaction().set_user(2):
self.assertEqual(transaction.user, 2)
+ def test_stacked_transactions(self):
+ 'Test that transactions are stacked / unstacked correctly'
+ with Transaction().start(DB_NAME, USER, context=CONTEXT) \
+ as transaction:
+ with transaction.new_transaction() as new_transaction:
+ self.assertIsNot(new_transaction, transaction)
+ self.assertIsNot(Transaction(), transaction)
+ self.assertIs(Transaction(), new_transaction)
+ self.assertIs(Transaction(), transaction)
+
+ def test_two_phase_commit(self):
+ # A successful transaction
+ dm = Mock()
+ with Transaction().start(DB_NAME, USER, context=CONTEXT) \
+ as transaction:
+ transaction.join(dm)
+
+ dm.tpc_begin.assert_called_once_with(transaction)
+ dm.commit.assert_called_once_with(transaction)
+ dm.tpc_vote.assert_called_once_with(transaction)
+ dm.tpc_abort.not_called()
+ dm.tpc_finish.assert_called_once_with(transaction)
+
+ # Failing in the datamanager
+ dm.reset_mock()
+ dm.tpc_vote.side_effect = ValueError('Failing the datamanager')
+ try:
+ with Transaction().start(DB_NAME, USER, context=CONTEXT) \
+ as transaction:
+ transaction.join(dm)
+ except ValueError:
+ pass
+
+ dm.tpc_begin.assert_called_once_with(transaction)
+ dm.commit.assert_called_once_with(transaction)
+ dm.tpc_vote.assert_called_once_with(transaction)
+ dm.tpc_abort.assert_called_once_with(transaction)
+ dm.tpc_finish.assert_not_called()
+
+ # Failing in tryton
+ dm.reset_mock()
+ try:
+ with Transaction().start(DB_NAME, USER, context=CONTEXT) \
+ as transaction:
+ transaction.join(dm)
+ raise ValueError('Failing in tryton')
+ except ValueError:
+ pass
+
+ dm.tpc_begin.assert_not_called()
+ dm.commit.assert_not_called()
+ dm.tpc_vote.assert_not_called()
+ dm.tpc_abort.assert_called_once_with(transaction)
+ dm.tpc_finish.assert_not_called()
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(TransactionTestCase)
diff --git a/trytond/tests/test_trigger.py b/trytond/tests/test_trigger.py
index 4f24741..1d89838 100644
--- a/trytond/tests/test_trigger.py
+++ b/trytond/tests/test_trigger.py
@@ -6,10 +6,10 @@ import time
import datetime
from itertools import combinations
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
+from trytond.tests.test_tryton import install_module, with_transaction
from trytond.tests.trigger import TRIGGER_LOGS
from trytond.transaction import Transaction
+from trytond.pool import Pool
from trytond.exceptions import UserError
from trytond.pyson import PYSONEncoder, Eval
@@ -17,429 +17,446 @@ from trytond.pyson import PYSONEncoder, Eval
class TriggerTestCase(unittest.TestCase):
'Test Trigger'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.triggered = POOL.get('test.triggered')
- self.trigger = POOL.get('ir.trigger')
- self.trigger_log = POOL.get('ir.trigger.log')
- self.model = POOL.get('ir.model')
- def test0010constraints(self):
+ @with_transaction()
+ def test_constraints(self):
'Test constraints'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- model, = self.model.search([
- ('model', '=', 'test.triggered'),
- ])
- action_model, = self.model.search([
- ('model', '=', 'test.trigger_action'),
- ])
-
- values = {
- 'name': 'Test',
- 'model': model.id,
- 'on_time': True,
- 'condition': 'true',
- 'action_model': action_model.id,
- 'action_function': 'test',
- }
- self.assert_(self.trigger.create([values]))
+ pool = Pool()
+ Model = pool.get('ir.model')
+ Trigger = pool.get('ir.trigger')
+ transaction = Transaction()
+
+ model, = Model.search([
+ ('model', '=', 'test.triggered'),
+ ])
+ action_model, = Model.search([
+ ('model', '=', 'test.trigger_action'),
+ ])
+
+ values = {
+ 'name': 'Test',
+ 'model': model.id,
+ 'on_time': True,
+ 'condition': 'true',
+ 'action_model': action_model.id,
+ 'action_function': 'test',
+ }
+ self.assert_(Trigger.create([values]))
+
+ transaction.rollback()
# 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(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(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()
-
- def test0020on_create(self):
+ combination_values = values.copy()
+ for mode in combination:
+ combination_values['on_%s' % mode] = True
+ self.assertRaises(UserError, Trigger.create,
+ [combination_values])
+ transaction.rollback()
+
+ # check_condition
+ condition_values = values.copy()
+ condition_values['condition'] = '='
+ self.assertRaises(UserError, Trigger.create,
+ [condition_values])
+ transaction.rollback()
+
+ # Restart the cache on the get_triggers method of ir.trigger
+ Trigger._get_triggers_cache.clear()
+
+ @with_transaction()
+ def test_on_create(self):
'Test on_create'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- model, = self.model.search([
- ('model', '=', 'test.triggered'),
- ])
- action_model, = self.model.search([
- ('model', '=', 'test.trigger_action'),
- ])
-
- trigger, = self.trigger.create([{
- 'name': 'Test',
- 'model': model.id,
- 'on_create': True,
- 'condition': 'true',
- 'action_model': action_model.id,
- 'action_function': 'trigger',
- }])
-
- triggered, = self.triggered.create([{
- 'name': 'Test',
- }])
-
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
-
- # Trigger with condition
- condition = PYSONEncoder().encode(
- Eval('self', {}).get('name') == 'Bar')
- self.trigger.write([trigger], {
- 'condition': condition,
- })
-
- # Matching condition
- triggered, = self.triggered.create([{
- 'name': 'Bar',
- }])
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
-
- # Non matching condition
- triggered, = self.triggered.create([{
- 'name': 'Foo',
- }])
- self.assertEqual(TRIGGER_LOGS, [])
-
- # With limit number
- self.trigger.write([trigger], {
+ pool = Pool()
+ Model = pool.get('ir.model')
+ Trigger = pool.get('ir.trigger')
+ Triggered = pool.get('test.triggered')
+
+ model, = Model.search([
+ ('model', '=', 'test.triggered'),
+ ])
+ action_model, = Model.search([
+ ('model', '=', 'test.trigger_action'),
+ ])
+
+ trigger, = Trigger.create([{
+ 'name': 'Test',
+ 'model': model.id,
+ 'on_create': True,
'condition': 'true',
- 'limit_number': 1,
- })
- triggered, = self.triggered.create([{
- 'name': 'Test',
- }])
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
-
- # With minimum delay
- self.trigger.write([trigger], {
- 'limit_number': 0,
- 'minimum_time_delay': datetime.timedelta(hours=1),
- })
- triggered, = self.triggered.create([{
- 'name': 'Test',
- }])
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
+ 'action_model': action_model.id,
+ 'action_function': 'trigger',
+ }])
+
+ triggered, = Triggered.create([{
+ 'name': 'Test',
+ }])
+
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+
+ # Trigger with condition
+ condition = PYSONEncoder().encode(
+ Eval('self', {}).get('name') == 'Bar')
+ Trigger.write([trigger], {
+ 'condition': condition,
+ })
+
+ # Matching condition
+ triggered, = Triggered.create([{
+ 'name': 'Bar',
+ }])
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
- # Restart the cache on the get_triggers method of ir.trigger
- self.trigger._get_triggers_cache.clear()
- transaction.cursor.rollback()
+ # Non matching condition
+ triggered, = Triggered.create([{
+ 'name': 'Foo',
+ }])
+ self.assertEqual(TRIGGER_LOGS, [])
+ # With limit number
+ Trigger.write([trigger], {
+ 'condition': 'true',
+ 'limit_number': 1,
+ })
+ triggered, = Triggered.create([{
+ 'name': 'Test',
+ }])
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+
+ # With minimum delay
+ Trigger.write([trigger], {
+ 'limit_number': 0,
+ 'minimum_time_delay': datetime.timedelta(hours=1),
+ })
+ triggered, = Triggered.create([{
+ 'name': 'Test',
+ }])
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+
+ # Restart the cache on the get_triggers method of ir.trigger
+ Trigger._get_triggers_cache.clear()
+
+ @with_transaction()
def test0030on_write(self):
'Test on_write'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- model, = self.model.search([
- ('model', '=', 'test.triggered'),
- ])
- action_model, = self.model.search([
- ('model', '=', 'test.trigger_action'),
- ])
-
- trigger, = self.trigger.create([{
- 'name': 'Test',
- 'model': model.id,
- 'on_write': True,
- 'condition': 'true',
- 'action_model': action_model.id,
- 'action_function': 'trigger',
- }])
-
- triggered, = self.triggered.create([{
- 'name': 'Test',
- }])
-
- self.triggered.write([triggered], {
- 'name': 'Foo',
- })
- self.assertEqual(TRIGGER_LOGS, [])
-
- # Trigger with condition
- condition = PYSONEncoder().encode(
- Eval('self', {}).get('name') == 'Bar')
- self.trigger.write([trigger], {
- 'condition': condition,
- })
-
- # Matching condition
- self.triggered.write([triggered], {
- 'name': 'Bar',
- })
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
-
- # No change in condition
- self.triggered.write([triggered], {
- 'name': 'Bar',
- })
- self.assertEqual(TRIGGER_LOGS, [])
-
- # Different change in condition
- self.triggered.write([triggered], {
+ pool = Pool()
+ Model = pool.get('ir.model')
+ Trigger = pool.get('ir.trigger')
+ Triggered = pool.get('test.triggered')
+
+ model, = Model.search([
+ ('model', '=', 'test.triggered'),
+ ])
+ action_model, = Model.search([
+ ('model', '=', 'test.trigger_action'),
+ ])
+
+ trigger, = Trigger.create([{
+ 'name': 'Test',
+ 'model': model.id,
+ 'on_write': True,
+ 'condition': 'true',
+ 'action_model': action_model.id,
+ 'action_function': 'trigger',
+ }])
+
+ triggered, = Triggered.create([{
+ 'name': 'Test',
+ }])
+
+ Triggered.write([triggered], {
+ 'name': 'Foo',
+ })
+ self.assertEqual(TRIGGER_LOGS, [])
+
+ # Trigger with condition
+ condition = PYSONEncoder().encode(
+ Eval('self', {}).get('name') == 'Bar')
+ Trigger.write([trigger], {
+ 'condition': condition,
+ })
+
+ # Matching condition
+ Triggered.write([triggered], {
+ 'name': 'Bar',
+ })
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+
+ # No change in condition
+ Triggered.write([triggered], {
+ 'name': 'Bar',
+ })
+ self.assertEqual(TRIGGER_LOGS, [])
+
+ # Different change in condition
+ Triggered.write([triggered], {
+ 'name': 'Foo',
+ })
+ self.assertEqual(TRIGGER_LOGS, [])
+
+ # With limit number
+ condition = PYSONEncoder().encode(
+ Eval('self', {}).get('name') == 'Bar')
+ Trigger.write([trigger], {
+ 'condition': condition,
+ 'limit_number': 1,
+ })
+ triggered, = Triggered.create([{
'name': 'Foo',
- })
- self.assertEqual(TRIGGER_LOGS, [])
-
- # With limit number
- condition = PYSONEncoder().encode(
- Eval('self', {}).get('name') == 'Bar')
- self.trigger.write([trigger], {
- 'condition': condition,
- 'limit_number': 1,
- })
- triggered, = self.triggered.create([{
- 'name': 'Foo',
- }])
- self.triggered.write([triggered], {
- 'name': 'Bar',
- })
- self.triggered.write([triggered], {
+ }])
+ Triggered.write([triggered], {
+ 'name': 'Bar',
+ })
+ Triggered.write([triggered], {
+ 'name': 'Foo',
+ })
+ Triggered.write([triggered], {
+ 'name': 'Bar',
+ })
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+
+ # With minimum delay
+ Trigger.write([trigger], {
+ 'limit_number': 0,
+ 'minimum_time_delay': datetime.timedelta.max,
+ })
+ triggered, = Triggered.create([{
'name': 'Foo',
+ }])
+ for name in ('Bar', 'Foo', 'Bar'):
+ Triggered.write([triggered], {
+ 'name': name,
})
- self.triggered.write([triggered], {
- 'name': 'Bar',
- })
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
- # With minimum delay
- self.trigger.write([trigger], {
- 'limit_number': 0,
- 'minimum_time_delay': datetime.timedelta.max,
- })
- triggered, = self.triggered.create([{
- 'name': 'Foo',
- }])
- for name in ('Bar', 'Foo', 'Bar'):
- self.triggered.write([triggered], {
- 'name': name,
- })
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
-
- self.trigger.write([trigger], {
- 'minimum_time_delay': datetime.timedelta(seconds=1),
- })
- triggered, = self.triggered.create([{
- 'name': 'Foo',
- }])
- for name in ('Bar', 'Foo'):
- self.triggered.write([triggered], {
- 'name': name,
- })
- time.sleep(1.2)
- self.triggered.write([triggered], {
- 'name': 'Bar',
+ Trigger.write([trigger], {
+ 'minimum_time_delay': datetime.timedelta(seconds=1),
+ })
+ triggered, = Triggered.create([{
+ 'name': 'Foo',
+ }])
+ for name in ('Bar', 'Foo'):
+ Triggered.write([triggered], {
+ 'name': name,
})
- self.assertEqual(TRIGGER_LOGS,
- [([triggered], trigger), ([triggered], trigger)])
- TRIGGER_LOGS.pop()
- TRIGGER_LOGS.pop()
-
- # Restart the cache on the get_triggers method of ir.trigger
- self.trigger._get_triggers_cache.clear()
- transaction.cursor.rollback()
-
+ time.sleep(1.2)
+ Triggered.write([triggered], {
+ 'name': 'Bar',
+ })
+ self.assertEqual(TRIGGER_LOGS,
+ [([triggered], trigger), ([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+ TRIGGER_LOGS.pop()
+
+ # Restart the cache on the get_triggers method of ir.trigger
+ Trigger._get_triggers_cache.clear()
+
+ @with_transaction()
def test0040on_delete(self):
'Test on_delete'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- model, = self.model.search([
- ('model', '=', 'test.triggered'),
- ])
- action_model, = self.model.search([
- ('model', '=', 'test.trigger_action'),
- ])
-
- triggered, = self.triggered.create([{
- 'name': 'Test',
- }])
-
- trigger, = self.trigger.create([{
- 'name': 'Test',
- 'model': model.id,
- 'on_delete': True,
- 'condition': 'true',
- 'action_model': action_model.id,
- 'action_function': 'trigger',
- }])
-
- self.triggered.delete([triggered])
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
- Transaction().delete = {}
-
- # Trigger with condition
- condition = PYSONEncoder().encode(
- Eval('self', {}).get('name') == 'Bar')
- self.trigger.write([trigger], {
- 'condition': condition,
- })
-
- triggered, = self.triggered.create([{
- 'name': 'Bar',
- }])
-
- # Matching condition
- self.triggered.delete([triggered])
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
- Transaction().delete = {}
-
- triggered, = self.triggered.create([{
- 'name': 'Foo',
- }])
-
- # Non matching condition
- self.triggered.delete([triggered])
- self.assertEqual(TRIGGER_LOGS, [])
- Transaction().delete = {}
-
- triggered, = self.triggered.create([{
- 'name': 'Test',
- }])
-
- # With limit number
- self.trigger.write([trigger], {
+ pool = Pool()
+ Model = pool.get('ir.model')
+ Trigger = pool.get('ir.trigger')
+ Triggered = pool.get('test.triggered')
+ TriggerLog = pool.get('ir.trigger.log')
+
+ model, = Model.search([
+ ('model', '=', 'test.triggered'),
+ ])
+ action_model, = Model.search([
+ ('model', '=', 'test.trigger_action'),
+ ])
+
+ triggered, = Triggered.create([{
+ 'name': 'Test',
+ }])
+
+ trigger, = Trigger.create([{
+ 'name': 'Test',
+ 'model': model.id,
+ 'on_delete': True,
'condition': 'true',
- 'limit_number': 1,
- })
- self.triggered.delete([triggered])
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
- Transaction().delete = {}
- # Delete trigger logs because SQLite reuse the same triggered_id
- self.trigger_log.delete(self.trigger_log.search([
- ('trigger', '=', trigger.id),
- ]))
-
- triggered, = self.triggered.create([{
- 'name': 'Test',
- }])
-
- # With minimum delay
- self.trigger.write([trigger], {
- 'limit_number': 0,
- 'minimum_time_delay': datetime.timedelta(hours=1),
- })
- self.triggered.delete([triggered])
- self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
- TRIGGER_LOGS.pop()
- Transaction().delete = {}
+ 'action_model': action_model.id,
+ 'action_function': 'trigger',
+ }])
+
+ Triggered.delete([triggered])
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+ Transaction().delete = {}
+
+ # Trigger with condition
+ condition = PYSONEncoder().encode(
+ Eval('self', {}).get('name') == 'Bar')
+ Trigger.write([trigger], {
+ 'condition': condition,
+ })
+
+ triggered, = Triggered.create([{
+ 'name': 'Bar',
+ }])
- # Restart the cache on the get_triggers method of ir.trigger
- self.trigger._get_triggers_cache.clear()
- transaction.cursor.rollback()
+ # Matching condition
+ Triggered.delete([triggered])
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+ Transaction().delete = {}
- def test0050on_time(self):
- 'Test on_time'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- model, = self.model.search([
- ('model', '=', 'test.triggered'),
- ])
- action_model, = self.model.search([
- ('model', '=', 'test.trigger_action'),
- ])
-
- trigger, = self.trigger.create([{
- 'name': 'Test',
- 'model': model.id,
- 'on_time': True,
- 'condition': 'true',
- 'action_model': action_model.id,
- 'action_function': 'trigger',
- }])
-
- triggered, = self.triggered.create([{
- 'name': 'Test',
- }])
- self.trigger.trigger_time()
- self.assert_(TRIGGER_LOGS == [([triggered], trigger)])
- TRIGGER_LOGS.pop()
-
- # Trigger with condition
- condition = PYSONEncoder().encode(
- Eval('self', {}).get('name') == 'Bar')
- self.trigger.write([trigger], {
- 'condition': condition,
- })
+ triggered, = Triggered.create([{
+ 'name': 'Foo',
+ }])
- # Matching condition
- self.triggered.write([triggered], {
- 'name': 'Bar',
- })
- self.trigger.trigger_time()
- self.assert_(TRIGGER_LOGS == [([triggered], trigger)])
- TRIGGER_LOGS.pop()
+ # Non matching condition
+ Triggered.delete([triggered])
+ self.assertEqual(TRIGGER_LOGS, [])
+ Transaction().delete = {}
- # Non matching condition
- self.triggered.write([triggered], {
- 'name': 'Foo',
- })
- self.trigger.trigger_time()
- self.assert_(TRIGGER_LOGS == [])
+ triggered, = Triggered.create([{
+ 'name': 'Test',
+ }])
- # With limit number
- self.trigger.write([trigger], {
+ # With limit number
+ Trigger.write([trigger], {
+ 'condition': 'true',
+ 'limit_number': 1,
+ })
+ Triggered.delete([triggered])
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+ Transaction().delete = {}
+ # Delete trigger logs because SQLite reuse the same triggered_id
+ TriggerLog.delete(TriggerLog.search([
+ ('trigger', '=', trigger.id),
+ ]))
+
+ triggered, = Triggered.create([{
+ 'name': 'Test',
+ }])
+
+ # With minimum delay
+ Trigger.write([trigger], {
+ 'limit_number': 0,
+ 'minimum_time_delay': datetime.timedelta(hours=1),
+ })
+ Triggered.delete([triggered])
+ self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+ Transaction().delete = {}
+
+ # Restart the cache on the get_triggers method of ir.trigger
+ Trigger._get_triggers_cache.clear()
+
+ @with_transaction()
+ def test_on_time(self):
+ 'Test on_time'
+ pool = Pool()
+ Model = pool.get('ir.model')
+ Trigger = pool.get('ir.trigger')
+ Triggered = pool.get('test.triggered')
+ TriggerLog = pool.get('ir.trigger.log')
+
+ model, = Model.search([
+ ('model', '=', 'test.triggered'),
+ ])
+ action_model, = Model.search([
+ ('model', '=', 'test.trigger_action'),
+ ])
+
+ trigger, = Trigger.create([{
+ 'name': 'Test',
+ 'model': model.id,
+ 'on_time': True,
'condition': 'true',
- 'limit_number': 1,
- })
- self.trigger.trigger_time()
- self.trigger.trigger_time()
- self.assert_(TRIGGER_LOGS == [([triggered], trigger)])
- TRIGGER_LOGS.pop()
-
- # Delete trigger logs of limit number test
- self.trigger_log.delete(self.trigger_log.search([
- ('trigger', '=', trigger.id),
- ]))
-
- # With minimum delay
- self.trigger.write([trigger], {
- 'limit_number': 0,
- 'minimum_time_delay': datetime.timedelta.max,
- })
- self.trigger.trigger_time()
- self.trigger.trigger_time()
- self.assert_(TRIGGER_LOGS == [([triggered], trigger)])
- TRIGGER_LOGS.pop()
- Transaction().delete = {}
-
- # Delete trigger logs of previous minimum delay test
- self.trigger_log.delete(self.trigger_log.search([
- ('trigger', '=', trigger.id),
- ]))
-
- self.trigger.write([trigger], {
- 'minimum_time_delay': datetime.timedelta(seconds=1),
- })
- self.trigger.trigger_time()
- time.sleep(1.2)
- self.trigger.trigger_time()
- self.assert_(TRIGGER_LOGS == [([triggered], trigger),
- ([triggered], trigger)])
- TRIGGER_LOGS.pop()
- TRIGGER_LOGS.pop()
- Transaction().delete = {}
-
- # Restart the cache on the get_triggers method of ir.trigger
- self.trigger._get_triggers_cache.clear()
- transaction.cursor.rollback()
+ 'action_model': action_model.id,
+ 'action_function': 'trigger',
+ }])
+
+ triggered, = Triggered.create([{
+ 'name': 'Test',
+ }])
+ Trigger.trigger_time()
+ self.assert_(TRIGGER_LOGS == [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+
+ # Trigger with condition
+ condition = PYSONEncoder().encode(
+ Eval('self', {}).get('name') == 'Bar')
+ Trigger.write([trigger], {
+ 'condition': condition,
+ })
+
+ # Matching condition
+ Triggered.write([triggered], {
+ 'name': 'Bar',
+ })
+ Trigger.trigger_time()
+ self.assert_(TRIGGER_LOGS == [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+
+ # Non matching condition
+ Triggered.write([triggered], {
+ 'name': 'Foo',
+ })
+ Trigger.trigger_time()
+ self.assert_(TRIGGER_LOGS == [])
+
+ # With limit number
+ Trigger.write([trigger], {
+ 'condition': 'true',
+ 'limit_number': 1,
+ })
+ Trigger.trigger_time()
+ Trigger.trigger_time()
+ self.assert_(TRIGGER_LOGS == [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+
+ # Delete trigger logs of limit number test
+ TriggerLog.delete(TriggerLog.search([
+ ('trigger', '=', trigger.id),
+ ]))
+
+ # With minimum delay
+ Trigger.write([trigger], {
+ 'limit_number': 0,
+ 'minimum_time_delay': datetime.timedelta.max,
+ })
+ Trigger.trigger_time()
+ Trigger.trigger_time()
+ self.assert_(TRIGGER_LOGS == [([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+ Transaction().delete = {}
+
+ # Delete trigger logs of previous minimum delay test
+ TriggerLog.delete(TriggerLog.search([
+ ('trigger', '=', trigger.id),
+ ]))
+
+ Trigger.write([trigger], {
+ 'minimum_time_delay': datetime.timedelta(seconds=1),
+ })
+ Trigger.trigger_time()
+ time.sleep(1.2)
+ Trigger.trigger_time()
+ self.assert_(TRIGGER_LOGS == [([triggered], trigger),
+ ([triggered], trigger)])
+ TRIGGER_LOGS.pop()
+ TRIGGER_LOGS.pop()
+ Transaction().delete = {}
+
+ # Restart the cache on the get_triggers method of ir.trigger
+ Trigger._get_triggers_cache.clear()
def suite():
diff --git a/trytond/tests/test_tryton.py b/trytond/tests/test_tryton.py
index b92a742..fad1a5b 100644
--- a/trytond/tests/test_tryton.py
+++ b/trytond/tests/test_tryton.py
@@ -5,8 +5,11 @@ import os
import sys
import unittest
import doctest
+import re
from itertools import chain
import operator
+from functools import wraps
+
from lxml import etree
from trytond.pool import Pool, isregisteredby
@@ -17,10 +20,11 @@ from trytond.protocols.dispatcher import create, drop
from trytond.tools import is_instance_method
from trytond.transaction import Transaction
from trytond import security
+from trytond.cache import Cache
__all__ = ['POOL', 'DB_NAME', 'USER', 'USER_PASSWORD', 'CONTEXT',
- 'install_module', 'ModuleTestCase',
- 'doctest_setup', 'doctest_teardown',
+ 'install_module', 'ModuleTestCase', 'with_transaction',
+ 'doctest_setup', 'doctest_teardown', 'doctest_checker',
'suite', 'all_suite', 'modules_suite']
Pool.start()
@@ -39,8 +43,7 @@ def install_module(name):
Install module for the tested database
'''
create_db()
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
+ with Transaction().start(DB_NAME, 1) as transaction:
Module = POOL.get('ir.module')
modules = Module.search([
@@ -57,212 +60,238 @@ def install_module(name):
return
Module.install(modules)
- transaction.cursor.commit()
+ transaction.commit()
InstallUpgrade = POOL.get('ir.module.install_upgrade',
type='wizard')
instance_id, _, _ = InstallUpgrade.create()
- transaction.cursor.commit()
+ transaction.commit()
InstallUpgrade(instance_id).transition_upgrade()
InstallUpgrade.delete(instance_id)
- transaction.cursor.commit()
+ transaction.commit()
+
+
+def with_transaction(user=1, context=None):
+ def decorator(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ transaction = Transaction()
+ with transaction.start(DB_NAME, user, context=context):
+ result = func(*args, **kwargs)
+ transaction.rollback()
+ # Drop the cache as the transaction is rollbacked
+ Cache.drop(DB_NAME)
+ return result
+ return wrapper
+ return decorator
class ModuleTestCase(unittest.TestCase):
'Trytond Test Case'
module = None
- def setUp(self):
- install_module(self.module)
+ @classmethod
+ def setUpClass(cls):
+ drop_create()
+ install_module(cls.module)
+ super(ModuleTestCase, cls).setUpClass()
+ @classmethod
+ def tearDownClass(cls):
+ super(ModuleTestCase, cls).tearDownClass()
+ drop_db()
+
+ @with_transaction()
def test_rec_name(self):
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- for mname, model in Pool().iterobject():
- if not isregisteredby(model, self.module):
- continue
- # Skip testing default value even if the field doesn't exist
- # as there is a fallback to id
- if model._rec_name == 'name':
- continue
- assert model._rec_name in model._fields, (
- 'Wrong _rec_name "%s" for %s'
- % (model._rec_name, mname))
+ for mname, model in Pool().iterobject():
+ if not isregisteredby(model, self.module):
+ continue
+ # Skip testing default value even if the field doesn't exist
+ # as there is a fallback to id
+ if model._rec_name == 'name':
+ continue
+ assert model._rec_name in model._fields, (
+ 'Wrong _rec_name "%s" for %s'
+ % (model._rec_name, mname))
+ @with_transaction()
def test_view(self):
'Test validity of all views of the module'
- with Transaction().start(DB_NAME, USER,
- context=CONTEXT) as transaction:
- View = POOL.get('ir.ui.view')
- views = View.search([
- ('module', '=', self.module),
- ('model', '!=', ''),
- ])
- for view in views:
- if view.inherit and view.inherit.model == view.model:
- view_id = view.inherit.id
- else:
- view_id = view.id
- model = view.model
- Model = POOL.get(model)
- res = Model.fields_view_get(view_id)
- assert res['model'] == model
- tree = etree.fromstring(res['arch'])
-
- validator = etree.RelaxNG(etree=View.get_rng(res['type']))
- validator.assert_(tree)
-
- tree_root = tree.getroottree().getroot()
-
- for element in tree_root.iter():
- if element.tag in ('field', 'label', 'separator', 'group'):
- for attr in ('name', 'icon'):
- field = element.get(attr)
- if field:
- assert field in res['fields'], (
- 'Missing field: %s' % field)
- transaction.cursor.rollback()
-
+ View = POOL.get('ir.ui.view')
+ views = View.search([
+ ('module', '=', self.module),
+ ('model', '!=', ''),
+ ])
+ for view in views:
+ if view.inherit and view.inherit.model == view.model:
+ view_id = view.inherit.id
+ else:
+ view_id = view.id
+ model = view.model
+ Model = POOL.get(model)
+ res = Model.fields_view_get(view_id)
+ assert res['model'] == model
+ tree = etree.fromstring(res['arch'])
+
+ validator = etree.RelaxNG(etree=View.get_rng(res['type']))
+ # Don't use assert_ because 2to3 convert to assertTrue
+ validator.assertValid(tree)
+
+ tree_root = tree.getroottree().getroot()
+
+ for element in tree_root.iter():
+ if element.tag in ('field', 'label', 'separator', 'group'):
+ for attr in ('name', 'icon'):
+ field = element.get(attr)
+ if field:
+ assert field in res['fields'], (
+ 'Missing field: %s' % field)
+
+ @with_transaction()
def test_depends(self):
'Test for missing depends'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- for mname, model in Pool().iterobject():
- if not isregisteredby(model, self.module):
- continue
- for fname, field in model._fields.iteritems():
- fields = set()
- fields |= get_eval_fields(field.domain)
- if hasattr(field, 'digits'):
- fields |= get_eval_fields(field.digits)
- if hasattr(field, 'add_remove'):
- fields |= get_eval_fields(field.add_remove)
- fields.discard(fname)
- fields.discard('context')
- fields.discard('_user')
- depends = set(field.depends)
- assert fields <= depends, (
- 'Missing depends %s in "%s"."%s"' % (
- list(fields - depends), mname, fname))
- assert depends <= set(model._fields), (
- 'Unknown depends %s in "%s"."%s"' % (
- list(depends - set(model._fields)), mname, fname))
-
+ for mname, model in Pool().iterobject():
+ if not isregisteredby(model, self.module):
+ continue
+ for fname, field in model._fields.iteritems():
+ fields = set()
+ fields |= get_eval_fields(field.domain)
+ if hasattr(field, 'digits'):
+ fields |= get_eval_fields(field.digits)
+ if hasattr(field, 'add_remove'):
+ fields |= get_eval_fields(field.add_remove)
+ fields.discard(fname)
+ fields.discard('context')
+ fields.discard('_user')
+ depends = set(field.depends)
+ assert fields <= depends, (
+ 'Missing depends %s in "%s"."%s"' % (
+ list(fields - depends), mname, fname))
+ assert depends <= set(model._fields), (
+ 'Unknown depends %s in "%s"."%s"' % (
+ list(depends - set(model._fields)), mname, fname))
+
+ @with_transaction()
def test_field_methods(self):
'Test field methods'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- for mname, model in Pool().iterobject():
- if not isregisteredby(model, self.module):
- continue
- for attr in dir(model):
- for prefixes in [['default_'],
- ['on_change_', 'on_change_with_'],
- ['order_'], ['domain_'], ['autocomplete_']]:
- if attr == 'on_change_with':
- continue
- # TODO those method should be renamed
- if attr == 'default_get':
- continue
- if mname == 'ir.rule' and attr == 'domain_get':
- continue
-
- # Skip if it is a field
- if attr in model._fields:
- continue
- fnames = [attr[len(prefix):] for prefix in prefixes
- if attr.startswith(prefix)]
- if not fnames:
- continue
- assert any(f in model._fields for f in fnames), (
- 'Field method "%s"."%s" for unknown field' % (
- mname, attr))
+ for mname, model in Pool().iterobject():
+ if not isregisteredby(model, self.module):
+ continue
+ for attr in dir(model):
+ for prefixes in [['default_'],
+ ['on_change_', 'on_change_with_'],
+ ['order_'], ['domain_'], ['autocomplete_']]:
+ if attr == 'on_change_with':
+ continue
+ # TODO those method should be renamed
+ if attr == 'default_get':
+ continue
+ if mname == 'ir.rule' and attr == 'domain_get':
+ continue
+
+ # Skip if it is a field
+ if attr in model._fields:
+ continue
+ fnames = [attr[len(prefix):] for prefix in prefixes
+ if attr.startswith(prefix)]
+ if not fnames:
+ continue
+ assert any(f in model._fields for f in fnames), (
+ 'Field method "%s"."%s" for unknown field' % (
+ mname, attr))
+
+ if attr.startswith('default_'):
+ getattr(model, attr)()
+ elif attr.startswith('order_'):
+ tables = {None: (model.__table__(), None)}
+ getattr(model, attr)(tables)
+ @with_transaction()
def test_menu_action(self):
'Test that menu actions are accessible to menu\'s group'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- pool = Pool()
- Menu = pool.get('ir.ui.menu')
- ModelData = pool.get('ir.model.data')
-
- module_menus = ModelData.search([
- ('model', '=', 'ir.ui.menu'),
- ('module', '=', self.module),
- ])
- menus = Menu.browse([mm.db_id for mm in module_menus])
- for menu, module_menu in zip(menus, module_menus):
- if not menu.action_keywords:
- continue
- menu_groups = set(menu.groups)
- actions_groups = reduce(operator.or_,
- (set(k.action.groups) for k in menu.action_keywords
- if k.keyword == 'tree_open'))
- if not actions_groups:
- continue
- assert menu_groups <= actions_groups, (
- 'Menu "%(menu_xml_id)s" actions are not accessible to '
- '%(groups)s' % {
- 'menu_xml_id': module_menu.fs_id,
- 'groups': ','.join(g.name
- for g in menu_groups - actions_groups),
- })
+ pool = Pool()
+ Menu = pool.get('ir.ui.menu')
+ ModelData = pool.get('ir.model.data')
- def test_model_access(self):
- 'Test missing default model access'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- Access = POOL.get('ir.model.access')
- no_groups = {a.model.name for a in Access.search([
- ('group', '=', None),
- ])}
- with_groups = {a.model.name for a in Access.search([
- ('group', '!=', None),
- ])}
-
- assert no_groups >= with_groups, (
- 'Model "%(models)s" are missing a default access' % {
- 'models': list(with_groups - no_groups),
+ module_menus = ModelData.search([
+ ('model', '=', 'ir.ui.menu'),
+ ('module', '=', self.module),
+ ])
+ menus = Menu.browse([mm.db_id for mm in module_menus])
+ for menu, module_menu in zip(menus, module_menus):
+ if not menu.action_keywords:
+ continue
+ menu_groups = set(menu.groups)
+ actions_groups = reduce(operator.or_,
+ (set(k.action.groups) for k in menu.action_keywords
+ if k.keyword == 'tree_open'))
+ if not actions_groups:
+ continue
+ assert menu_groups <= actions_groups, (
+ 'Menu "%(menu_xml_id)s" actions are not accessible to '
+ '%(groups)s' % {
+ 'menu_xml_id': module_menu.fs_id,
+ 'groups': ','.join(g.name
+ for g in menu_groups - actions_groups),
})
+ @with_transaction()
+ def test_model_access(self):
+ 'Test missing default model access'
+ pool = Pool()
+ Access = pool.get('ir.model.access')
+ no_groups = {a.model.name for a in Access.search([
+ ('group', '=', None),
+ ])}
+ with_groups = {a.model.name for a in Access.search([
+ ('group', '!=', None),
+ ])}
+
+ assert no_groups >= with_groups, (
+ 'Model "%(models)s" are missing a default access' % {
+ 'models': list(with_groups - no_groups),
+ })
+
+ @with_transaction()
def test_workflow_transitions(self):
'Test all workflow transitions exist'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- for mname, model in Pool().iterobject():
- if not isregisteredby(model, self.module):
- continue
- if not issubclass(model, Workflow):
+ for mname, model in Pool().iterobject():
+ if not isregisteredby(model, self.module):
+ continue
+ if not issubclass(model, Workflow):
+ continue
+ field = getattr(model, model._transition_state)
+ if isinstance(field.selection, (tuple, list)):
+ values = field.selection
+ else:
+ # instance method may not return all the possible values
+ if is_instance_method(model, field.selection):
continue
- field = getattr(model, model._transition_state)
- if isinstance(field.selection, (tuple, list)):
- values = field.selection
- else:
- # instance method may not return all the possible values
- if is_instance_method(model, field.selection):
- continue
- values = getattr(model, field.selection)()
- states = set(dict(values))
- transition_states = set(chain(*model._transitions))
- assert transition_states <= states, (
- ('Unknown transition states "%(states)s" '
- 'in model "%(model)s". ') % {
- 'states': list(transition_states - states),
- 'model': model.__name__,
- })
+ values = getattr(model, field.selection)()
+ states = set(dict(values))
+ transition_states = set(chain(*model._transitions))
+ assert transition_states <= states, (
+ ('Unknown transition states "%(states)s" '
+ 'in model "%(model)s". ') % {
+ 'states': list(transition_states - states),
+ 'model': model.__name__,
+ })
def db_exist():
Database = backend.get('Database')
database = Database().connect()
- cursor = database.cursor()
- databases = database.list(cursor)
- cursor.close()
- return DB_NAME in databases
+ return DB_NAME in database.list()
def create_db():
if not db_exist():
- create(DB_NAME, None, 'en_US', USER_PASSWORD)
+ create(None, DB_NAME, None, 'en_US', USER_PASSWORD)
def drop_db():
if db_exist():
- drop(DB_NAME, None)
+ drop(None, DB_NAME, None)
def drop_create():
@@ -274,11 +303,30 @@ doctest_setup = lambda test: drop_create()
doctest_teardown = lambda test: drop_db()
+class Py23DocChecker(doctest.OutputChecker):
+ def check_output(self, want, got, optionflags):
+ if sys.version_info[0] > 2:
+ want = re.sub("u'(.*?)'", "'\\1'", want)
+ want = re.sub('u"(.*?)"', '"\\1"', want)
+ return doctest.OutputChecker.check_output(self, want, got, optionflags)
+
+doctest_checker = Py23DocChecker()
+
+
+class TestSuite(unittest.TestSuite):
+ def run(self, *args, **kwargs):
+ exist = db_exist()
+ result = super(TestSuite, self).run(*args, **kwargs)
+ if not exist:
+ drop_db()
+ return result
+
+
def suite():
'''
Return test suite for other modules
'''
- return unittest.TestSuite()
+ return TestSuite()
def all_suite(modules=None):
@@ -322,25 +370,7 @@ def modules_suite(modules=None, doc=True):
else:
continue
for test in test_mod.suite():
- found = False
- for other in suite_:
- if type(test) == type(other):
- if isinstance(test, doctest.DocTestCase):
- if str(test) == str(other):
- found = True
- break
- elif test._testMethodName == other._testMethodName:
- found = True
- break
- if not found:
- suite_.addTest(test)
- tests = []
- doc_tests = []
- for test in suite_:
- if isinstance(test, doctest.DocTestCase):
- if doc:
- doc_tests.append(test)
- else:
- tests.append(test)
- tests.extend(doc_tests)
- return unittest.TestSuite(tests)
+ if isinstance(test, doctest.DocTestCase) and not doc:
+ continue
+ suite_.addTest(test)
+ return suite_
diff --git a/trytond/tests/test_union.py b/trytond/tests/test_union.py
index fd05707..dd38a4f 100644
--- a/trytond/tests/test_union.py
+++ b/trytond/tests/test_union.py
@@ -2,100 +2,105 @@
# 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
+from trytond.tests.test_tryton import install_module, with_transaction
+from trytond.pool import Pool
class UnionMixinTestCase(unittest.TestCase):
'Test UnionMixin'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
+ @with_transaction()
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')
-
+ pool = Pool()
+ 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')
+
+ @with_transaction()
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')
-
+ pool = Pool()
+ 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')
+
+ @with_transaction()
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')
+ pool = Pool()
+ 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():
diff --git a/trytond/tests/test_user.py b/trytond/tests/test_user.py
index 8014845..a6a75e9 100644
--- a/trytond/tests/test_user.py
+++ b/trytond/tests/test_user.py
@@ -2,60 +2,65 @@
# repository contains the full copyright notices and license terms.
import unittest
-from trytond.transaction import Transaction
-from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
- install_module
+from trytond.tests.test_tryton import install_module, with_transaction
+from trytond.pool import Pool
from trytond.res.user import bcrypt
class UserTestCase(unittest.TestCase):
'Test User'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('res')
- self.user = POOL.get('res.user')
def create_user(self, login, password, hash_method=None):
- user, = self.user.create([{
+ pool = Pool()
+ User = pool.get('res.user')
+
+ user, = User.create([{
'name': login,
'login': login,
}])
if hash_method:
- hash = getattr(self.user, 'hash_' + hash_method)
- self.user.write([user], {
+ hash = getattr(User, 'hash_' + hash_method)
+ User.write([user], {
'password_hash': hash(password),
})
else:
- self.user.write([user], {
+ User.write([user], {
'password': password,
})
def check_user(self, login, password):
- user, = self.user.search([('login', '=', login)])
- user_id = self.user.get_login(login, password)
+ pool = Pool()
+ User = pool.get('res.user')
+
+ user, = User.search([('login', '=', login)])
+ user_id = User.get_login(login, password)
self.assertEqual(user_id, user.id)
- bad_user_id = self.user.get_login(login, password + 'wrong')
+ bad_user_id = User.get_login(login, password + 'wrong')
self.assertEqual(bad_user_id, 0)
- def test0010test_hash(self):
+ @with_transaction()
+ def test_test_hash(self):
'Test default hash password'
- with Transaction().start(DB_NAME, USER, CONTEXT):
- self.create_user('user', '12345')
- self.check_user('user', '12345')
+ self.create_user('user', '12345')
+ self.check_user('user', '12345')
- def test0011test_sha1(self):
+ @with_transaction()
+ def test_test_sha1(self):
'Test sha1 password'
- with Transaction().start(DB_NAME, USER, CONTEXT):
- self.create_user('user', '12345', 'sha1')
- self.check_user('user', '12345')
+ self.create_user('user', '12345', 'sha1')
+ self.check_user('user', '12345')
@unittest.skipIf(bcrypt is None, 'requires bcrypt')
- def test0012test_bcrypt(self):
+ @with_transaction()
+ def test_test_bcrypt(self):
'Test bcrypt password'
- with Transaction().start(DB_NAME, USER, CONTEXT):
- self.create_user('user', '12345', 'bcrypt')
- self.check_user('user', '12345')
+ self.create_user('user', '12345', 'bcrypt')
+ self.check_user('user', '12345')
def suite():
diff --git a/trytond/tests/test_webdav.py b/trytond/tests/test_webdav.py
deleted file mode 100644
index 4957b36..0000000
--- a/trytond/tests/test_webdav.py
+++ /dev/null
@@ -1,14 +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.
-import unittest
-
-from .test_tryton import ModuleTestCase
-
-
-class WebDAVTestCase(ModuleTestCase):
- 'Test ir module'
- module = 'webdav'
-
-
-def suite():
- return unittest.TestLoader().loadTestsFromTestCase(WebDAVTestCase)
diff --git a/trytond/tests/test_wizard.py b/trytond/tests/test_wizard.py
index 4e99bed..aba0bac 100644
--- a/trytond/tests/test_wizard.py
+++ b/trytond/tests/test_wizard.py
@@ -1,95 +1,108 @@
# 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.tests.test_tryton import install_module, with_transaction
from trytond.transaction import Transaction
+from trytond.pool import Pool
class WizardTestCase(unittest.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.wizard = POOL.get('test.test_wizard', type='wizard')
- self.session = POOL.get('ir.session.wizard')
- self.group = POOL.get('res.group')
- def test0010create(self):
+ @with_transaction()
+ def test_create(self):
'Create Session Wizard'
- with Transaction().start(DB_NAME, USER, CONTEXT):
- session_id, start_state, end_state = self.wizard.create()
- self.assertEqual(start_state, 'start')
- self.assertEqual(end_state, 'end')
- self.assert_(session_id)
+ pool = Pool()
+ Wizard = pool.get('test.test_wizard', type='wizard')
- def test0020delete(self):
+ session_id, start_state, end_state = Wizard.create()
+ self.assertEqual(start_state, 'start')
+ self.assertEqual(end_state, 'end')
+ self.assert_(session_id)
+
+ @with_transaction()
+ def test_delete(self):
'Delete Session Wizard'
- with Transaction().start(DB_NAME, USER, CONTEXT):
- session_id, _, _ = self.wizard.create()
- self.wizard.delete(session_id)
+ pool = Pool()
+ Wizard = pool.get('test.test_wizard', type='wizard')
+
+ session_id, _, _ = Wizard.create()
+ Wizard.delete(session_id)
- def test0030session(self):
+ @with_transaction()
+ def test_session(self):
'Session Wizard'
- with Transaction().start(DB_NAME, USER, CONTEXT):
- session_id, = self.session.create([{}])
- session = self.wizard(session_id)
- self.assertEqual(session.start.id, None)
- self.assertRaises(AttributeError, getattr, session.start, 'name')
- self.assertEqual(hasattr(session.start, 'name'), False)
- session.start.name = 'Test'
- self.assertRaises(AttributeError, getattr, session.start, 'user')
- self.assertEqual(hasattr(session.start, 'user'), False)
- session.start.user = USER
- group_a, = self.group.create([{
- 'name': 'Group A',
- }])
- group_b, = self.group.create([{
- 'name': 'Group B',
- }])
- session.start.groups = [
- group_a,
- group_b,
- ]
- session._save()
- session = self.wizard(session_id)
- self.assertEqual(session.start.id, None)
- self.assertEqual(session.start.name, 'Test')
- self.assertEqual(session.start.user.id, USER)
- self.assertEqual(session.start.user.login, 'admin')
- group_a, group_b = session.start.groups
- self.assertEqual(group_a.name, 'Group A')
- self.assertEqual(group_b.name, 'Group B')
+ pool = Pool()
+ Wizard = pool.get('test.test_wizard', type='wizard')
+ Session = pool.get('ir.session.wizard')
+ Group = pool.get('res.group')
+ transaction = Transaction()
- def test0040execute(self):
+ session_id, = Session.create([{}])
+ session = Wizard(session_id)
+ self.assertEqual(session.start.id, None)
+ self.assertRaises(AttributeError, getattr, session.start, 'name')
+ self.assertEqual(hasattr(session.start, 'name'), False)
+ session.start.name = 'Test'
+ self.assertRaises(AttributeError, getattr, session.start, 'user')
+ self.assertEqual(hasattr(session.start, 'user'), False)
+ session.start.user = transaction.user
+ group_a, = Group.create([{
+ 'name': 'Group A',
+ }])
+ group_b, = Group.create([{
+ 'name': 'Group B',
+ }])
+ session.start.groups = [
+ group_a,
+ group_b,
+ ]
+ session._save()
+ session = Wizard(session_id)
+ self.assertEqual(session.start.id, None)
+ self.assertEqual(session.start.name, 'Test')
+ self.assertEqual(session.start.user.id, transaction.user)
+ self.assertEqual(session.start.user.login, 'admin')
+ group_a, group_b = session.start.groups
+ self.assertEqual(group_a.name, 'Group A')
+ self.assertEqual(group_b.name, 'Group B')
+
+ @with_transaction()
+ def test_execute(self):
'Execute Wizard'
- with Transaction().start(DB_NAME, USER, CONTEXT):
- session_id, start_state, end_state = self.wizard.create()
- result = self.wizard.execute(session_id, {}, start_state)
- self.assertEqual(result.keys(), ['view'])
- self.assertEqual(result['view']['defaults'], {
- 'name': 'Test wizard',
- })
- self.assertEqual(result['view']['buttons'], [
- {
- 'state': 'end',
- 'states': '{}',
- 'icon': 'tryton-cancel',
- 'default': False,
- 'string': 'Cancel',
- },
- {
- 'state': 'next_',
- 'states': '{}',
- 'icon': 'tryton-next',
- 'default': True,
- 'string': 'Next',
- },
- ])
- result = self.wizard.execute(session_id, {
- start_state: {
- 'name': 'Test Update',
- }}, 'next_')
- self.assertEqual(len(result['actions']), 1)
+ pool = Pool()
+ Wizard = pool.get('test.test_wizard', type='wizard')
+
+ session_id, start_state, end_state = Wizard.create()
+ result = Wizard.execute(session_id, {}, start_state)
+ self.assertEqual(result.keys(), ['view'])
+ self.assertEqual(result['view']['defaults'], {
+ 'name': 'Test wizard',
+ })
+ self.assertEqual(result['view']['buttons'], [
+ {
+ 'state': 'end',
+ 'states': '{}',
+ 'icon': 'tryton-cancel',
+ 'default': False,
+ 'string': 'Cancel',
+ },
+ {
+ 'state': 'next_',
+ 'states': '{}',
+ 'icon': 'tryton-next',
+ 'default': True,
+ 'string': 'Next',
+ },
+ ])
+ result = Wizard.execute(session_id, {
+ start_state: {
+ 'name': 'Test Update',
+ }}, 'next_')
+ self.assertEqual(len(result['actions']), 1)
def suite():
diff --git a/trytond/tests/test_workflow.py b/trytond/tests/test_workflow.py
index 74adbdb..b18d9a7 100644
--- a/trytond/tests/test_workflow.py
+++ b/trytond/tests/test_workflow.py
@@ -3,30 +3,32 @@
import unittest
-from trytond.transaction import Transaction
-from trytond.tests.test_tryton import (POOL, DB_NAME, USER, CONTEXT,
- install_module)
+from trytond.tests.test_tryton import install_module, with_transaction
+from trytond.pool import Pool
class WorkflowTestCase(unittest.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
install_module('tests')
- self.workflow = POOL.get('test.workflowed')
# TODO add test for Workflow.transition
- def test0010transition(self):
+ @with_transaction()
+ def test_transition(self):
'Test transition'
- with Transaction().start(DB_NAME, USER, context=CONTEXT):
- wkf, = self.workflow.create([{}])
+ pool = Pool()
+ Workflowed = pool.get('test.workflowed')
- self.workflow.run([wkf])
- self.assertEqual(wkf.state, 'running')
+ wkf, = Workflowed.create([{}])
- wkf.state = 'end'
- wkf.save()
- self.workflow.run([wkf])
- self.assertEqual(wkf.state, 'end')
+ Workflowed.run([wkf])
+ self.assertEqual(wkf.state, 'running')
+
+ wkf.state = 'end'
+ wkf.save()
+ Workflowed.run([wkf])
+ self.assertEqual(wkf.state, 'end')
def suite():
diff --git a/trytond/tools/__init__.py b/trytond/tools/__init__.py
index c5c9170..5854f45 100644
--- a/trytond/tools/__init__.py
+++ b/trytond/tools/__init__.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 izip
+
from .misc import *
from .datetime_strftime import *
from .decimal_ import *
@@ -8,3 +10,13 @@ from .decimal_ import *
class ClassProperty(property):
def __get__(self, cls, owner):
return self.fget.__get__(None, owner)()
+
+
+def cursor_dict(cursor, size=None):
+ size = cursor.arraysize if size is None else size
+ while True:
+ rows = cursor.fetchmany(size)
+ if not rows:
+ break
+ for row in rows:
+ yield {d[0]: v for d, v in izip(cursor.description, row)}
diff --git a/trytond/tools/decimal_.py b/trytond/tools/decimal_.py
index ee156c0..08d36dd 100644
--- a/trytond/tools/decimal_.py
+++ b/trytond/tools/decimal_.py
@@ -1,36 +1,73 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-import tokenize
-from io import StringIO
+import sys
+from tokenize import *
+from io import BytesIO, StringIO
# code snippet taken from http://docs.python.org/library/tokenize.html
-def decistmt(s):
- """Substitute Decimals for floats in a string of statements.
-
- >>> from decimal import Decimal
- >>> s = 'print +21.3e-5*-.1234/81.7'
- >>> decistmt(s)
- u"print +Decimal (u'21.3e-5')*-Decimal (u'.1234')/Decimal (u'81.7')"
-
- >>> exec(s)
- -3.21716034272e-07
- >>> exec(decistmt(s))
- -3.217160342717258261933904529E-7
- """
- result = []
- # tokenize the string
- g = tokenize.generate_tokens(StringIO(s.decode('utf-8')).readline)
- for toknum, tokval, _, _, _ in g:
- # replace NUMBER tokens
- if toknum == tokenize.NUMBER and '.' in tokval:
- result.extend([
- (tokenize.NAME, 'Decimal'),
- (tokenize.OP, '('),
- (tokenize.STRING, repr(tokval)),
- (tokenize.OP, ')')
- ])
- else:
- result.append((toknum, tokval))
- return tokenize.untokenize(result)
+if sys.version_info < (3,):
+ def decistmt(s):
+ """Substitute Decimals for floats in a string of statements.
+
+ >>> from decimal import Decimal
+ >>> s = 'print +21.3e-5*-.1234/81.7'
+ >>> decistmt(s)
+ u"print +Decimal (u'21.3e-5')*-Decimal (u'.1234')/Decimal (u'81.7')"
+
+ >>> exec(s)
+ -3.21716034272e-07
+ >>> exec(decistmt(s))
+ -3.217160342717258261933904529E-7
+
+ """
+ result = []
+ g = generate_tokens(StringIO(s.decode('utf-8')).readline) # tokenize the string
+ for toknum, tokval, _, _, _ in g:
+ if toknum == NUMBER and '.' in tokval: # replace NUMBER tokens
+ result.extend([
+ (NAME, 'Decimal'),
+ (OP, '('),
+ (STRING, repr(tokval)),
+ (OP, ')')
+ ])
+ else:
+ result.append((toknum, tokval))
+ return untokenize(result)
+else:
+ def decistmt(s):
+ """Substitute Decimals for floats in a string of statements.
+
+ >>> from decimal import Decimal
+ >>> s = 'print(+21.3e-5*-.1234/81.7)'
+ >>> decistmt(s)
+ "print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))"
+
+ The format of the exponent is inherited from the platform C library.
+ Known cases are "e-007" (Windows) and "e-07" (not Windows). Since
+ we're only showing 12 digits, and the 13th isn't close to 5, the
+ rest of the output should be platform-independent.
+
+ >>> exec(s) #doctest: +ELLIPSIS
+ -3.217160342717258e-0...7
+
+ Output from calculations with Decimal should be identical across all
+ platforms.
+
+ >>> exec(decistmt(s))
+ -3.217160342717258261933904529E-7
+ """
+ result = []
+ g = tokenize(BytesIO(s.encode('utf-8')).readline) # tokenize the string
+ for toknum, tokval, _, _, _ in g:
+ if toknum == NUMBER and '.' in tokval: # replace NUMBER tokens
+ result.extend([
+ (NAME, 'Decimal'),
+ (OP, '('),
+ (STRING, repr(tokval)),
+ (OP, ')')
+ ])
+ else:
+ result.append((toknum, tokval))
+ return untokenize(result).decode('utf-8')
diff --git a/trytond/tools/misc.py b/trytond/tools/misc.py
index 16f65fa..b1ada39 100644
--- a/trytond/tools/misc.py
+++ b/trytond/tools/misc.py
@@ -7,18 +7,16 @@ Miscelleanous tools used by tryton
import os
import sys
import subprocess
-from threading import local
-import smtplib
from array import array
from itertools import islice
import types
-import urllib
+import io
+import warnings
from sql import Literal
from sql.operators import Or
from trytond.const import OPERATORS
-from trytond.config import config, parse_uri
def find_in_path(name):
@@ -50,11 +48,14 @@ def exec_command_pipe(name, *args, **kwargs):
stdout=subprocess.PIPE, env=child_env)
-def file_open(name, mode="r", subdir='modules'):
+def file_open(name, mode="r", subdir='modules', encoding=None):
"""Open a file from the root dir, using a subdir folder."""
from trytond.modules import EGG_MODULES
- root_path = os.path.dirname(os.path.dirname(os.path.abspath(
- unicode(__file__, sys.getfilesystemencoding()))))
+ if sys.version_info < (3,):
+ filename = __file__.decode(sys.getfilesystemencoding())
+ else:
+ filename = __file__
+ root_path = os.path.dirname(os.path.dirname(os.path.abspath(filename)))
egg_name = False
if subdir == 'modules':
@@ -82,7 +83,6 @@ def file_open(name, mode="r", subdir='modules'):
if (subdir == 'modules'
and (name.startswith('ir' + os.sep)
or name.startswith('res' + os.sep)
- or name.startswith('webdav' + os.sep)
or name.startswith('tests' + os.sep))):
name = os.path.join(root_path, name)
else:
@@ -92,7 +92,7 @@ def file_open(name, mode="r", subdir='modules'):
for i in (name, egg_name):
if i and os.path.isfile(i):
- return open(i, mode)
+ return io.open(i, mode, encoding=encoding)
raise IOError('File not found : %s ' % name)
@@ -104,21 +104,11 @@ def get_smtp_server():
:return: A SMTP instance. The quit() method must be call when all
the calls to sendmail() have been made.
"""
- 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(uri.hostname, uri.port)
-
- if 'tls' in uri.scheme:
- smtp_server.starttls()
-
- if uri.username and uri.password:
- smtp_server.login(
- urllib.unquote_plus(uri.username),
- urllib.unquote_plus(uri.password))
-
- return smtp_server
+ from ..sendmail import _get_smtp_server
+ warnings.warn(
+ 'get_smtp_server is deprecated use trytond.sendmail',
+ DeprecationWarning)
+ return _get_smtp_server()
def memoize(maxsize):
@@ -201,103 +191,6 @@ def mod10r(number):
return result + str((10 - report) % 10)
-class LocalDict(local):
-
- def __init__(self):
- super(LocalDict, self).__init__()
- self._dict = {}
-
- def __str__(self):
- return str(self._dict)
-
- def __repr__(self):
- return str(self._dict)
-
- def clear(self):
- return self._dict.clear()
-
- def keys(self):
- return self._dict.keys()
-
- def __setitem__(self, i, y):
- self._dict.__setitem__(i, y)
-
- def __getitem__(self, i):
- return self._dict.__getitem__(i)
-
- def copy(self):
- return self._dict.copy()
-
- def iteritems(self):
- return self._dict.iteritems()
-
- def iterkeys(self):
- return self._dict.iterkeys()
-
- def itervalues(self):
- return self._dict.itervalues()
-
- def pop(self, k, d=None):
- return self._dict.pop(k, d)
-
- def popitem(self):
- return self._dict.popitem()
-
- def setdefault(self, k, d=None):
- return self._dict.setdefault(k, d)
-
- def update(self, E, **F):
- return self._dict.update(E, F)
-
- def values(self):
- return self._dict.values()
-
- def get(self, k, d=None):
- return self._dict.get(k, d)
-
- def has_key(self, k):
- return k in self._dict
-
- def items(self):
- return self._dict.items()
-
- def __cmp__(self, y):
- return self._dict.__cmp__(y)
-
- def __contains__(self, k):
- return self._dict.__contains__(k)
-
- def __delitem__(self, y):
- return self._dict.__delitem__(y)
-
- def __eq__(self, y):
- return self._dict.__eq__(y)
-
- def __ge__(self, y):
- return self._dict.__ge__(y)
-
- def __gt__(self, y):
- return self._dict.__gt__(y)
-
- def __hash__(self):
- return self._dict.__hash__()
-
- def __iter__(self):
- return self._dict.__iter__()
-
- def __le__(self, y):
- return self._dict.__le__(y)
-
- def __len__(self):
- return self._dict.__len__()
-
- def __lt__(self, y):
- return self._dict.__lt__(y)
-
- def __ne__(self, y):
- return self._dict.__ne__(y)
-
-
def reduce_ids(field, ids):
'''
Return a small SQL expression for the list of ids and the sql column
@@ -371,7 +264,7 @@ def grouped_slice(records, count=None):
'Grouped slice'
from trytond.transaction import Transaction
if count is None:
- count = Transaction().cursor.IN_MAX
+ count = Transaction().database.IN_MAX
for i in xrange(0, len(records), count):
yield islice(records, i, i + count)
diff --git a/trytond/transaction.py b/trytond/transaction.py
index ef4ca63..754b90f 100644
--- a/trytond/transaction.py
+++ b/trytond/transaction.py
@@ -1,22 +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 logging
from threading import local
from sql import Flavor
-from trytond.tools.singleton import Singleton
from trytond import backend
+from trytond.config import config
-
-class _TransactionManager(object):
- '''
- Manage transaction start/stop
- '''
-
- def __enter__(self):
- return Transaction()
-
- def __exit__(self, type, value, traceback):
- Transaction().stop()
+logger = logging.getLogger(__name__)
class _AttributeManager(object):
@@ -35,30 +26,25 @@ class _AttributeManager(object):
setattr(Transaction(), name, value)
-class _CursorManager(object):
- '''
- Manage cursor of transaction
- '''
-
- def __init__(self, cursor):
- self.cursor = cursor
+class _Local(local):
- def __enter__(self):
- return Transaction()
+ def __init__(self):
+ # Transaction stack control
+ self.transactions = []
- def __exit__(self, type, value, traceback):
- Transaction().cursor.close()
- Transaction().cursor = self.cursor
-
-class Transaction(local):
+class Transaction(object):
'''
Control the transaction
'''
- __metaclass__ = Singleton
- cursor = None
+ _local = _Local()
+
+ cache_keys = {'language', 'fuzzy_translation', '_datetime'}
+
database = None
+ readonly = False
+ connection = None
close = None
user = None
context = None
@@ -67,6 +53,24 @@ class Transaction(local):
delete = None # TODO check to merge with delete_records
timestamp = None
+ def __new__(cls, new=False):
+ transactions = cls._local.transactions
+ if new or not transactions:
+ instance = super(Transaction, cls).__new__(cls)
+ instance.cache = {}
+ transactions.append(instance)
+ else:
+ instance = transactions[-1]
+ return instance
+
+ def get_cache(self):
+ from trytond.cache import LRUDict
+ keys = tuple(((key, self.context[key])
+ for key in sorted(self.cache_keys)
+ if key in self.context))
+ return self.cache.setdefault((self.user, keys),
+ LRUDict(config.getint('cache', 'model')))
+
def start(self, database_name, user, readonly=False, context=None,
close=False, autocommit=False):
'''
@@ -75,7 +79,6 @@ class Transaction(local):
Database = backend.get('Database')
assert self.user is None
assert self.database is None
- assert self.cursor is None
assert self.close is None
assert self.context is None
if not database_name:
@@ -83,11 +86,11 @@ class Transaction(local):
else:
database = Database(database_name).connect()
Flavor.set(Database.flavor)
- cursor = database.cursor(readonly=readonly,
- autocommit=autocommit)
self.user = user
self.database = database
- self.cursor = cursor
+ self.readonly = readonly
+ self.connection = database.get_connection(readonly=readonly,
+ autocommit=autocommit)
self.close = close
self.context = context or {}
self.create_records = {}
@@ -95,24 +98,40 @@ class Transaction(local):
self.delete = {}
self.timestamp = {}
self.counter = 0
- return _TransactionManager()
+ self._datamanagers = []
+ return self
- def stop(self):
- '''
- Stop transaction
- '''
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ transactions = self._local.transactions
try:
- self.cursor.close(close=self.close)
+ if transactions.count(self) == 1:
+ try:
+ try:
+ if type is None and not self.readonly:
+ self.commit()
+ else:
+ self.rollback()
+ finally:
+ self.database.put_connection(
+ self.connection, self.close)
+ finally:
+ self.database = None
+ self.readonly = False
+ self.connection = None
+ self.close = None
+ self.user = None
+ self.context = None
+ self.create_records = None
+ self.delete_records = None
+ self.delete = None
+ self.timestamp = None
+ self._datamanagers = []
finally:
- self.cursor = None
- self.database = None
- self.close = None
- self.user = None
- self.context = None
- self.create_records = None
- self.delete_records = None
- self.delete = None
- self.timestamp = None
+ current_instance = transactions.pop()
+ assert current_instance is self, transactions
def set_context(self, context=None, **kwargs):
if context is None:
@@ -143,17 +162,52 @@ class Transaction(local):
self.user = user
return manager
- def set_cursor(self, cursor):
- manager = _AttributeManager(cursor=self.cursor)
- self.cursor = cursor
- return manager
+ def set_current_transaction(self, transaction):
+ self._local.transactions.append(transaction)
+ return transaction
- def new_cursor(self, autocommit=False, readonly=False):
- Database = backend.get('Database')
- manager = _CursorManager(self.cursor)
- database = Database(self.cursor.database_name).connect()
- self.cursor = database.cursor(autocommit=autocommit, readonly=readonly)
- return manager
+ def new_transaction(self, autocommit=False, readonly=False):
+ transaction = Transaction(new=True)
+ return transaction.start(self.database.name, self.user,
+ context=self.context, close=self.close, readonly=readonly,
+ autocommit=autocommit)
+
+ def commit(self):
+ try:
+ if self._datamanagers:
+ for datamanager in self._datamanagers:
+ datamanager.tpc_begin(self)
+ for datamanager in self._datamanagers:
+ datamanager.commit(self)
+ for datamanager in self._datamanagers:
+ datamanager.tpc_vote(self)
+ self.connection.commit()
+ except:
+ self.rollback()
+ raise
+ else:
+ try:
+ for datamanager in self._datamanagers:
+ datamanager.tpc_finish(self)
+ except:
+ logger.critical('A datamanager raised an exception in'
+ ' tpc_finish, the data might be inconsistant',
+ exc_info=True)
+
+ def rollback(self):
+ for cache in self.cache.itervalues():
+ cache.clear()
+ for datamanager in self._datamanagers:
+ datamanager.tpc_abort(self)
+ self.connection.rollback()
+
+ def join(self, datamanager):
+ try:
+ idx = self._datamanagers.index(datamanager)
+ return self._datamanagers[idx]
+ except ValueError:
+ self._datamanagers.append(datamanager)
+ return datamanager
@property
def language(self):
diff --git a/trytond/url.py b/trytond/url.py
index 2e40fad..80f445e 100644
--- a/trytond/url.py
+++ b/trytond/url.py
@@ -10,10 +10,10 @@ from trytond.transaction import Transaction
__all__ = ['URLMixin']
-HOSTNAME = (config.get('jsonrpc', 'hostname')
- or unicode(socket.getfqdn(), 'utf8'))
-HOSTNAME = '.'.join(encodings.idna.ToASCII(part) if part else ''
- for part in HOSTNAME.split('.'))
+HOSTNAME = (config.get('web', 'hostname')
+ or socket.getfqdn())
+HOSTNAME = '.'.join(encodings.idna.ToASCII(part).decode('ascii')
+ if part else '' for part in HOSTNAME.split('.'))
class URLAccessor(object):
@@ -34,7 +34,7 @@ class URLAccessor(object):
raise NotImplementedError
url_part['name'] = cls.__name__
- url_part['database'] = Transaction().cursor.database_name
+ url_part['database'] = Transaction().database.name
local_part = urllib.quote('%(database)s/%(type)s/%(name)s' % url_part)
if isinstance(inst, Model) and inst.id:
diff --git a/trytond/webdav/__init__.py b/trytond/webdav/__init__.py
deleted file mode 100644
index c1fe716..0000000
--- a/trytond/webdav/__init__.py
+++ /dev/null
@@ -1,12 +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.
-from ..pool import Pool
-from .webdav import *
-
-
-def register():
- Pool.register(
- Collection,
- Share,
- Attachment,
- module='webdav', type_='model')
diff --git a/trytond/webdav/locale/bg_BG.po b/trytond/webdav/locale/bg_BG.po
deleted file mode 100644
index 3c7cb47..0000000
--- a/trytond/webdav/locale/bg_BG.po
+++ /dev/null
@@ -1,183 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "Името на колекцията трябва да уникално вътре в колекцията!"
-
-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 ""
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Път"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Споделени папки"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Наследници"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Пълно име"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Създадено на"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Създадено от"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Домейн"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Модел"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Име"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Родител"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Име"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Променено на"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Променено от"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Създадено на"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Създадено от"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Крайна дата"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Ключ"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Бележка"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Път"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Име"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Потребител"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Променено на"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Променено от"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Колекции"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Колекции"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Споделени папки"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Колекции"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Колекции"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Споделени папки"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Колекция"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Споделена папка"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Колекция"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Колекции"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Споделена папка"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Споделени папки"
diff --git a/trytond/webdav/locale/ca_ES.po b/trytond/webdav/locale/ca_ES.po
deleted file mode 100644
index 673dcfd..0000000
--- a/trytond/webdav/locale/ca_ES.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"No podeu crear un adjunt anomenat \"%(attachment)s\" al directori "
-"\"%(collection)s\" perquè ja existeix un directori amb aquest nom."
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "El nom del directori ha de ser únic dins del directori."
-
-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 ""
-"No podeu crear un directori amb el nom \"%(parent)s\" dins del directori "
-"\"%(child)s\" perquè ja existeix un altre amb aquest nom."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Directoris compartits"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Fills"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Nom complet"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Data creació"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Usuari creació"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Domini"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Model"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Nom"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Pare"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Nom"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Data modificació"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Usuari modificació"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Data creació"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Usuari creació"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Data expiració"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Clau"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Nota"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Nom"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Usuari"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Data modificació"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Usuari modificació"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Directoris"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Directoris"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Directoris compartits"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Directoris"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Directoris"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Directoris compartits"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Directori"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Directori compartit"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Directori"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Directoris"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Directori compartit"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Directoris compartits"
diff --git a/trytond/webdav/locale/cs_CZ.po b/trytond/webdav/locale/cs_CZ.po
deleted file mode 100644
index d437783..0000000
--- a/trytond/webdav/locale/cs_CZ.po
+++ /dev/null
@@ -1,183 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr ""
-
-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 ""
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr ""
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr ""
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr ""
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr ""
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr ""
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr ""
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr ""
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr ""
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr ""
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr ""
diff --git a/trytond/webdav/locale/de_DE.po b/trytond/webdav/locale/de_DE.po
deleted file mode 100644
index 5bc1e2b..0000000
--- a/trytond/webdav/locale/de_DE.po
+++ /dev/null
@@ -1,189 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"Erstellung von Anhang \"%(attachment)s\" in Sammlung \"%(collection)s\" "
-"nicht möglich, weil es bereits eine Sammlung mit diesem Namen gibt"
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr ""
-"Der Name einer untergeordneten Sammlung kann nur einmal in einer Sammlung "
-"vergeben werden!"
-
-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 ""
-"Sammlung \"%(parent)s\" in Sammlung \"%(child)s\" kann nicht angelegt "
-"werden, weil es bereits eine Datei mit diesem Namen gibt."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Pfad"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Freigaben"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Untergeordnet (Sammlungen)"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Vollständiger Name"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Erstellungsdatum"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Erstellt durch"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Wertebereich (Domain)"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Modell"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Name"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Übergeordnet (Sammlung)"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Name"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Zuletzt geändert"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Letzte Änderung durch"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Erstellungsdatum"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Erstellt durch"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Ablaufdatum"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Schlüssel"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Notiz"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Pfad"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Name"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Benutzer"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Zuletzt geändert"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Letzte Änderung durch"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Sammlungen"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Sammlungen"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Freigaben"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Sammlungen"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Sammlungen"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Freigaben"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Sammlung"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Freigabe"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Sammlung"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Sammlungen"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Freigabe"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Freigaben"
diff --git a/trytond/webdav/locale/es_AR.po b/trytond/webdav/locale/es_AR.po
deleted file mode 100644
index 3174fa4..0000000
--- a/trytond/webdav/locale/es_AR.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"No puede crear un archivo adjunto llamado «%(attachment)s» en la colección "
-"«%(collection)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 "¡El nombre de la colección debe ser único dentro de dicha 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 ""
-"No puede crear una colección denominada «%(parent)s» en la colección "
-"«%(child)s» porque ya existe un archivo con ese nombre."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Recursos compartidos"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Hijos"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Nombre completo"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Fecha creación"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Usuario creación"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Dominio"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Modelo"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Padre"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Fecha modificación"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Usuario modificación"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Fecha creación"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Usuario creación"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Fecha vencimiento"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Clave"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Nota"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Usuario"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Fecha modificación"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Usuario modificación"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Recurso compartido"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Recursos compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Colección"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Recurso compartido"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Colección"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Recurso compartido"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Recursos compartidos"
diff --git a/trytond/webdav/locale/es_CO.po b/trytond/webdav/locale/es_CO.po
deleted file mode 100644
index 3e97429..0000000
--- a/trytond/webdav/locale/es_CO.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"No puede crear un adjunto nombrado \"%(attachments)s\" en el repositorio "
-"\"%(collections)s\" porque hay actualmente un repositorio con ese nombre."
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "El nombre de la colección debe ser único dentro de dicha 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 ""
-"No puede crear una colección llamada \"%(parent)s\" en la colección "
-"\"%(child)s\" porque existe un archivo con el mismo nombre."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Recuros Compartidos"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Hijos"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Nombre Completo"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Fecha de Creación"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Creado por Usuario"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Dominio"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Modelo"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Padre"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Fecha de Modificación"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Modificado por Usuario"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Fecha de Creación"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Creado por Usuario"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Fecha de Expiración"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Clave"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Nota"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Usuario"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Fecha de Modificación"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Modificado por Usuario"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Recuros Compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Recuros Compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Colección"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Recurso Compartido"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Colección"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Recurso Compartido"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Recuros Compartidos"
diff --git a/trytond/webdav/locale/es_EC.po b/trytond/webdav/locale/es_EC.po
deleted file mode 100644
index eeadcc9..0000000
--- a/trytond/webdav/locale/es_EC.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"No puede crear un adjunto llamado \"%(attachment)s\" en la colección "
-"\"%(collection)s\" porque ya existe una colección con ese nombre."
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-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 ""
-"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 "Ruta"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Recursos compartidos"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Hijos"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Nombre completo"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Fecha de creación"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Creado por usuario"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Dominio"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Modelo"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Padre"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Fecha de modificación"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Modificado por usuario"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Fecha de creación"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Creado por usuario"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Fecha de expiración"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Clave"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Nota"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Usuario"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Fecha de modificación"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Modificado por usuario"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Recursos compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Recursos compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Colección"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Recurso compartido"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Colección"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Colecciones"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Recurso compartido"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Recursos compartidos"
diff --git a/trytond/webdav/locale/es_ES.po b/trytond/webdav/locale/es_ES.po
deleted file mode 100644
index 2dbda01..0000000
--- a/trytond/webdav/locale/es_ES.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"No puede crear un adjunto llamado \"%(attachment)s\" en el directorio "
-"\"%(collection)s\" porque ya existe un directorio con este nombre."
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "El nombre del directorio debe ser único dentro del directorio."
-
-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 ""
-"No puede crear un directorio con el nombre \"%(parent)s\" en el directorio "
-"\"%(child)s\" porque ya existe otro con ese nombre."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Directorios compartidos"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Hijos"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Nombre completo"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Fecha creación"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Usuario creación"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Dominio"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Modelo"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Padre"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Fecha modificación"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Usuario modificación"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Fecha creación"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Usuario creación"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Fecha expiración"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Clave"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Nota"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Usuario"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Fecha modificación"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Usuario modificación"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Directorios compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Directorios compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Directorio"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Directorio compartido"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Directorio"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Directorio compartido"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Directorios compartidos"
diff --git a/trytond/webdav/locale/es_MX.po b/trytond/webdav/locale/es_MX.po
deleted file mode 100644
index 2eaf8ef..0000000
--- a/trytond/webdav/locale/es_MX.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"No puede crear un adjunto llamado \"%(attachment)s\" en el directorio "
-"\"%(collection)s\" porque ya existe un directorio con este nombre."
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "El nombre del directorio debe ser único dentro del directorio."
-
-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 ""
-"No puede crear un directorio con el nombre \"%(parent)s\" en el directorio "
-"\"%(child)s\" porque ya existe otro con ese nombre."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Directorios compartidos"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Hijos"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Nombre completo"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Fecha creación"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Usuario creación"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Dominio"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Modelo"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Padre"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Fecha modificación"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Usuario modificación"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Fecha creación"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Usuario creación"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Fecha expiración"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Clave"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Nota"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Ruta"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Nombre"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Usuario"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Fecha modificación"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Usuario modificación"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Directorios compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Directorios"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Directorios compartidos"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Directorio"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Directorio compartido"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr ""
diff --git a/trytond/webdav/locale/fr_FR.po b/trytond/webdav/locale/fr_FR.po
deleted file mode 100644
index 512036e..0000000
--- a/trytond/webdav/locale/fr_FR.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"Vous ne pouvez pas créer un attachement nommé « %(attachment)s » dans la "
-"collection « %(collection)s » car il y a déjà une collection avec ce nom."
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a 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."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Chemin"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Partages"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Enfants"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Nom complet"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Date de création"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Créé par"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Domaine"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Modèle"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Nom"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Parent"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Nom"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Date de mise à jour"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Mis à jour par"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Date de création"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Créé par"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Date d'expiration"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Clé"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Note"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Chemin"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Nom"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Utilisateur"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Date de mise à jour"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Mis à jour par"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Collections"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Collections"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Partages"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Collections"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Collections"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Partages"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Collection"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Partage"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Collection"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Collections"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Partage"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Partages"
diff --git a/trytond/webdav/locale/hu_HU.po b/trytond/webdav/locale/hu_HU.po
deleted file mode 100644
index d437783..0000000
--- a/trytond/webdav/locale/hu_HU.po
+++ /dev/null
@@ -1,183 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr ""
-
-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 ""
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr ""
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr ""
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr ""
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr ""
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr ""
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr ""
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr ""
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr ""
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr ""
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr ""
diff --git a/trytond/webdav/locale/it_IT.po b/trytond/webdav/locale/it_IT.po
deleted file mode 100644
index d437783..0000000
--- a/trytond/webdav/locale/it_IT.po
+++ /dev/null
@@ -1,183 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr ""
-
-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 ""
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr ""
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr ""
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr ""
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr ""
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr ""
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr ""
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr ""
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr ""
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr ""
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr ""
diff --git a/trytond/webdav/locale/ja_JP.po b/trytond/webdav/locale/ja_JP.po
deleted file mode 100644
index d437783..0000000
--- a/trytond/webdav/locale/ja_JP.po
+++ /dev/null
@@ -1,183 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr ""
-
-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 ""
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr ""
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr ""
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr ""
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr ""
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr ""
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr ""
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr ""
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr ""
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr ""
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr ""
diff --git a/trytond/webdav/locale/lt_LT.po b/trytond/webdav/locale/lt_LT.po
deleted file mode 100644
index d437783..0000000
--- a/trytond/webdav/locale/lt_LT.po
+++ /dev/null
@@ -1,183 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr ""
-
-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 ""
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr ""
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr ""
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr ""
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr ""
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr ""
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr ""
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr ""
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr ""
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr ""
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr ""
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr ""
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr ""
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr ""
diff --git a/trytond/webdav/locale/nl_NL.po b/trytond/webdav/locale/nl_NL.po
deleted file mode 100644
index 139c028..0000000
--- a/trytond/webdav/locale/nl_NL.po
+++ /dev/null
@@ -1,194 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "De naam van de verzameling moet uniek zijn binnen de verzameling!"
-
-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 ""
-
-#, fuzzy
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Pad"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr ""
-
-#, fuzzy
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Onderliggende niveaus"
-
-#, fuzzy
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Volledige naam"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Domein"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Model"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Naam"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Bovenliggend niveau"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Naam"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr ""
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr ""
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr ""
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr ""
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr ""
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr ""
-
-#, fuzzy
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Aantekening"
-
-#, fuzzy
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Pad"
-
-#, fuzzy
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Naam bijlage"
-
-#, fuzzy
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-#, fuzzy
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Gebruiker"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr ""
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr ""
-
-#, fuzzy
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Verzamelingen"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Verzamelingen"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr ""
-
-#, fuzzy
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Verzamelingen"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Verzamelingen"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr ""
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Verzameling"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr ""
-
-#, fuzzy
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Verzameling"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Verzamelingen"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr ""
diff --git a/trytond/webdav/locale/pt_BR.po b/trytond/webdav/locale/pt_BR.po
deleted file mode 100644
index 0160957..0000000
--- a/trytond/webdav/locale/pt_BR.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"Você não pode criar um anexo chamado \"%(attachment)s\" na coleção "
-"\"%(collection)s\" porque já existe uma coleção com este nome."
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "O nome da coleção deve ser único dentro da coleção!"
-
-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 ""
-"Você não pode criar uma coleção chamada \"%(parent)s\" na coleção "
-"\"%(child)s\" porque já existe um arquivo com este nome."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Caminho"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Compartilhamentos"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Filhos"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Nome completo"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Data de criação"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Criado por"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Domínio"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Modelo"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Nome"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Pai"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Nome"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Data de edição"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Editado por"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Data de criação"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Criado por"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Data de expiração"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Chave"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Nota"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Caminho"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Nome"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Usuário"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Data de edição"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Editado por"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Coleções"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Coleções"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Compartilhamentos"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Coleções"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Coleções"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Compartilhamentos"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Coleção"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Compartilhado"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr ""
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr ""
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr ""
diff --git a/trytond/webdav/locale/ru_RU.po b/trytond/webdav/locale/ru_RU.po
deleted file mode 100644
index 8cda1ed..0000000
--- a/trytond/webdav/locale/ru_RU.po
+++ /dev/null
@@ -1,185 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "Имя каталога должно быть уникальным внутри каталога!"
-
-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 ""
-"Вы не можете создать коллекцию \"%(parent)s\" в коллекции \"%(child)s\" так "
-"как существует файл с таким именем."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Путь"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Общие папки"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "Ссылка"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Подчиненый"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Полное название"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Дата создания"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Создано пользователем"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Область"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Модель"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Наименование"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Предок"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Наименование"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Дата изменения"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Изменено пользователем"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Дата создания"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Создано пользователем"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Дата окончания"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Ключ"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Комментарий"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Путь"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Наименование"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "Ссылка"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Пользователь"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Дата изменения"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Изменено пользователем"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Коллекции"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Коллекции"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Общие папки"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Коллекции"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Коллекции в виде дерева"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Общие папки"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Каталог"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Общая папка"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Каталог"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Коллекции"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Общая папка"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Общие папки"
diff --git a/trytond/webdav/locale/sl_SI.po b/trytond/webdav/locale/sl_SI.po
deleted file mode 100644
index ffa39d4..0000000
--- a/trytond/webdav/locale/sl_SI.po
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-msgid ""
-msgstr "Content-Type: text/plain; charset=utf-8\n"
-
-msgctxt "error:ir.attachment:"
-msgid ""
-"You can not create an attachment named \"%(attachment)s\" in collection "
-"\"%(collection)s\" because there is already a collection with that name."
-msgstr ""
-"Priloge \"%(attachment)s\" ni možno pripeti v zbirko \"%(collection)s\", ker"
-" že obstaja zbirka s tem imenom."
-
-msgctxt "error:webdav.collection:"
-msgid "The collection name must be unique inside a collection!"
-msgstr "Ime zbirke mora biti edinstveno znotraj zbirke."
-
-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 izdelati, ker"
-" že obstaja datoteka z istim imenom."
-
-msgctxt "field:ir.attachment,path:"
-msgid "Path"
-msgstr "Pot"
-
-msgctxt "field:ir.attachment,shares:"
-msgid "Shares"
-msgstr "Skupne rabe"
-
-msgctxt "field:ir.attachment,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.collection,childs:"
-msgid "Children"
-msgstr "Podzbirke"
-
-msgctxt "field:webdav.collection,complete_name:"
-msgid "Complete Name"
-msgstr "Polno ime"
-
-msgctxt "field:webdav.collection,create_date:"
-msgid "Create Date"
-msgstr "Izdelano"
-
-msgctxt "field:webdav.collection,create_uid:"
-msgid "Create User"
-msgstr "Izdelal"
-
-msgctxt "field:webdav.collection,domain:"
-msgid "Domain"
-msgstr "Domena"
-
-msgctxt "field:webdav.collection,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.collection,model:"
-msgid "Model"
-msgstr "Model"
-
-msgctxt "field:webdav.collection,name:"
-msgid "Name"
-msgstr "Naziv"
-
-msgctxt "field:webdav.collection,parent:"
-msgid "Parent"
-msgstr "Prednik"
-
-msgctxt "field:webdav.collection,rec_name:"
-msgid "Name"
-msgstr "Ime"
-
-msgctxt "field:webdav.collection,write_date:"
-msgid "Write Date"
-msgstr "Zapisano"
-
-msgctxt "field:webdav.collection,write_uid:"
-msgid "Write User"
-msgstr "Zapisal"
-
-msgctxt "field:webdav.share,create_date:"
-msgid "Create Date"
-msgstr "Izdelano"
-
-msgctxt "field:webdav.share,create_uid:"
-msgid "Create User"
-msgstr "Izdelal"
-
-msgctxt "field:webdav.share,expiration_date:"
-msgid "Expiration Date"
-msgstr "Datum veljavnosti"
-
-msgctxt "field:webdav.share,id:"
-msgid "ID"
-msgstr "ID"
-
-msgctxt "field:webdav.share,key:"
-msgid "Key"
-msgstr "Ključ"
-
-msgctxt "field:webdav.share,note:"
-msgid "Note"
-msgstr "Opomba"
-
-msgctxt "field:webdav.share,path:"
-msgid "Path"
-msgstr "Pot"
-
-msgctxt "field:webdav.share,rec_name:"
-msgid "Name"
-msgstr "Ime"
-
-msgctxt "field:webdav.share,url:"
-msgid "URL"
-msgstr "URL"
-
-msgctxt "field:webdav.share,user:"
-msgid "User"
-msgstr "Uporabnik"
-
-msgctxt "field:webdav.share,write_date:"
-msgid "Write Date"
-msgstr "Zapisano"
-
-msgctxt "field:webdav.share,write_uid:"
-msgid "Write User"
-msgstr "Zapisal"
-
-msgctxt "model:ir.action,name:act_collection_list"
-msgid "Collections"
-msgstr "Zbirke"
-
-msgctxt "model:ir.action,name:act_collection_tree"
-msgid "Collections"
-msgstr "Zbirke"
-
-msgctxt "model:ir.action,name:act_share_list"
-msgid "Shares"
-msgstr "Skupne rabe"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_list"
-msgid "Collections"
-msgstr "Zbirke"
-
-msgctxt "model:ir.ui.menu,name:menu_collection_tree"
-msgid "Collections"
-msgstr "Zbirke"
-
-msgctxt "model:ir.ui.menu,name:menu_share_list"
-msgid "Shares"
-msgstr "Skupne rabe"
-
-msgctxt "model:ir.ui.menu,name:menu_webdav"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "model:webdav.collection,name:"
-msgid "Collection"
-msgstr "Zbirka"
-
-msgctxt "model:webdav.share,name:"
-msgid "Share"
-msgstr "Skupna raba"
-
-msgctxt "view:ir.attachment:"
-msgid "WebDAV"
-msgstr "WebDAV"
-
-msgctxt "view:webdav.collection:"
-msgid "Collection"
-msgstr "Zbirka"
-
-msgctxt "view:webdav.collection:"
-msgid "Collections"
-msgstr "Zbirke"
-
-msgctxt "view:webdav.share:"
-msgid "Share"
-msgstr "Skupna raba"
-
-msgctxt "view:webdav.share:"
-msgid "Shares"
-msgstr "Skupne rabe"
diff --git a/trytond/webdav/tryton.cfg b/trytond/webdav/tryton.cfg
deleted file mode 100644
index 6912072..0000000
--- a/trytond/webdav/tryton.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[tryton]
-depends:
- ir
- res
-xml:
- webdav.xml
diff --git a/trytond/webdav/view/attachment_form.xml b/trytond/webdav/view/attachment_form.xml
deleted file mode 100644
index 73ee3ae..0000000
--- a/trytond/webdav/view/attachment_form.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?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. -->
-<data>
- <xpath expr="/form/notebook/page[@name='description']"
- position="after">
- <page string="WebDAV" id="webdav">
- <label name="url"/>
- <group id="url">
- <field name="url"/>
- <field name="url" widget="url"/>
- </group>
- <field name="shares" colspan="4"/>
- </page>
- </xpath>
-</data>
diff --git a/trytond/webdav/view/collection_form.xml b/trytond/webdav/view/collection_form.xml
deleted file mode 100644
index f0c12d8..0000000
--- a/trytond/webdav/view/collection_form.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?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="Collection">
- <label name="name"/>
- <field name="name"/>
- <label name="parent"/>
- <field name="parent"/>
- <label name="model"/>
- <field name="model"/>
- <label name="domain"/>
- <field name="domain"/>
- <field name="childs" colspan="4"/>
-</form>
diff --git a/trytond/webdav/view/collection_list.xml b/trytond/webdav/view/collection_list.xml
deleted file mode 100644
index d6d170f..0000000
--- a/trytond/webdav/view/collection_list.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?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="Collections">
- <field name="name"/>
- <field name="model"/>
-</tree>
diff --git a/trytond/webdav/view/collection_tree.xml b/trytond/webdav/view/collection_tree.xml
deleted file mode 100644
index 5cc3702..0000000
--- a/trytond/webdav/view/collection_tree.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?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="Collections">
- <field name="name"/>
- <field name="model"/>
- <field name="parent" tree_invisible="1"/>
- <field name="childs" tree_invisible="1"/>
-</tree>
diff --git a/trytond/webdav/view/share_form.xml b/trytond/webdav/view/share_form.xml
deleted file mode 100644
index a1a19de..0000000
--- a/trytond/webdav/view/share_form.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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="Share">
- <label name="path"/>
- <field name="path"/>
- <label name="key"/>
- <field name="key"/>
- <label name="user"/>
- <field name="user"/>
- <label name="expiration_date"/>
- <field name="expiration_date"/>
- <label name="url"/>
- <group id="url" colspan="2">
- <field name="url"/>
- <field name="url" widget="url"/>
- </group>
- <notebook>
- <page name="note">
- <field name="note" colspan="4"/>
- </page>
- </notebook>
-</form>
diff --git a/trytond/webdav/view/share_list.xml b/trytond/webdav/view/share_list.xml
deleted file mode 100644
index 5502df7..0000000
--- a/trytond/webdav/view/share_list.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?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="Shares">
- <field name="path"/>
- <field name="user"/>
- <field name="expiration_date"/>
-</tree>
diff --git a/trytond/webdav/webdav.py b/trytond/webdav/webdav.py
deleted file mode 100644
index d79d6df..0000000
--- a/trytond/webdav/webdav.py
+++ /dev/null
@@ -1,832 +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.
-import os
-import time
-import urllib
-import urlparse
-import socket
-import encodings
-import uuid
-import datetime
-from ast import literal_eval
-
-from dateutil.relativedelta import relativedelta
-from sql.functions import Extract
-from sql.conditionals import Coalesce
-
-from trytond.model import ModelView, ModelSQL, fields, Unique
-from trytond.tools import reduce_ids
-from trytond.transaction import Transaction
-from trytond.pool import Pool
-from trytond.config import config
-from trytond.pyson import Eval
-from trytond.tools import grouped_slice
-
-__all__ = [
- 'Collection', 'Share', 'Attachment',
- ]
-
-
-def get_webdav_url():
- if config.get('ssl', 'privatekey'):
- protocol = 'https'
- else:
- protocol = 'http'
- hostname = (config.get('webdav', 'hostname')
- or unicode(socket.getfqdn(), 'utf8'))
- hostname = '.'.join(encodings.idna.ToASCII(part) for part in
- hostname.split('.'))
- return urlparse.urlunsplit((protocol, hostname,
- urllib.quote(
- Transaction().cursor.database_name.encode('utf-8') + '/'),
- None, None))
-
-
-class Collection(ModelSQL, ModelView):
- "Collection"
- __name__ = "webdav.collection"
- name = fields.Char('Name', required=True, select=True)
- parent = fields.Many2One('webdav.collection', 'Parent',
- ondelete='RESTRICT', domain=[('model', '=', None)])
- childs = fields.One2Many('webdav.collection', 'parent', 'Children')
- model = fields.Many2One('ir.model', 'Model')
- domain = fields.Char('Domain')
- complete_name = fields.Function(fields.Char('Complete Name'),
- 'get_rec_name')
-
- @classmethod
- def __setup__(cls):
- super(Collection, cls).__setup__()
- table = cls.__table__()
- cls._sql_constraints += [
- ('name_parent_uniq', Unique(table, table.name, table.parent),
- 'The collection name must be unique inside a collection!'),
- ]
- cls._error_messages.update({
- 'collection_file_name': ('You can not create a collection '
- 'named "%(parent)s" in collection "%(child)s" because '
- 'there is already a file with that name.'),
- })
- cls.ext2mime = {
- '.png': 'image/png',
- '.odt': 'application/vnd.oasis.opendocument.text',
- '.pdf': 'application/pdf',
- }
-
- @classmethod
- def order_complete_name(cls, tables):
- return cls.name.convert_order('name', tables, cls)
-
- @staticmethod
- def default_domain():
- return '[]'
-
- def get_rec_name(self, name):
- if self.parent:
- return self.parent.rec_name + '/' + self.name
- else:
- return self.name
-
- @classmethod
- def validate(cls, collections):
- super(Collection, cls).validate(collections)
- cls.check_recursion(collections, rec_name='name')
- cls.check_attachment(collections)
-
- @classmethod
- def check_attachment(cls, collections):
- pool = Pool()
- Attachment = pool.get('ir.attachment')
- for collection in collections:
- if collection.parent:
- attachments = Attachment.search([
- ('resource', '=', '%s,%s' %
- (cls.__name__, collection.parent.id)),
- ])
- for attachment in attachments:
- if attachment.name == collection.name:
- cls.raise_user_error('collection_file_name', {
- 'parent': collection.parent.rec_name,
- 'child': collection.rec_name,
- })
-
- @classmethod
- def _uri2object(cls, uri, object_name=__name__, object_id=None,
- cache=None):
- pool = Pool()
- Attachment = pool.get('ir.attachment')
- Report = pool.get('ir.action.report')
- cache_uri = uri
-
- if cache is not None:
- cache.setdefault('_uri2object', {})
- if cache_uri in cache['_uri2object']:
- return cache['_uri2object'][cache_uri]
-
- if not uri:
- if cache is not None:
- cache['_uri2object'][cache_uri] = (cls.__name__, None)
- return cls.__name__, None
- name, uri = (uri.split('/', 1) + [None])[0:2]
- if object_name == cls.__name__:
- collection_ids = None
- if cache is not None:
- cache.setdefault('_parent2collection_ids', {})
- if object_id in cache['_parent2collection_ids']:
- collection_ids = cache['_parent2collection_ids'][
- object_id].get(name, [])
- if collection_ids is None:
- collections = cls.search([
- ('parent', '=', object_id),
- ])
- collection_ids = []
- if cache is not None:
- cache['_parent2collection_ids'].setdefault(object_id, {})
- for collection in collections:
- if cache is not None:
- cache['_parent2collection_ids'][object_id]\
- .setdefault(collection.name, [])
- cache['_parent2collection_ids'][object_id][
- collection.name].append(collection.id)
- cache.setdefault('_collection_name', {})
- if collection.model and uri:
- cache['_collection_name'][collection.id] = \
- collection.model.model
- else:
- cache['_collection_name'][collection.id] = \
- cls.__name__
- if collection.name == name:
- collection_ids.append(collection.id)
- if collection_ids:
- object_id = collection_ids[0]
- object_name2 = None
- if cache is not None:
- cache.setdefault('_collection_name', {})
- if object_id in cache['_collection_name']:
- object_name2 = cache['_collection_name'][object_id]
- if object_name2 is None:
- collection = cls(object_id)
- if collection.model and uri:
- object_name = collection.model.model
- if cache is not None:
- cache['_collection_name'][object_id] = object_name
- else:
- object_name = object_name2
- else:
- if uri:
- if cache is not None:
- cache['_uri2object'][cache_uri] = (None, 0)
- return None, 0
-
- attachment_ids = None
- if cache is not None:
- cache.setdefault('_model&id2attachment_ids', {})
- if (object_name, object_id) in \
- cache['_model&id2attachment_ids']:
- attachment_ids = cache['_model&id2attachment_ids'][
- (object_name, object_id)].get(name, [])
- attachment_id = None
- if attachment_ids is None:
- attachments = Attachment.search([
- ('resource', '=', '%s,%s' % (object_name, object_id)),
- ])
- key = (object_name, object_id)
- attachment_ids = []
- if cache is not None:
- cache['_model&id2attachment_ids'].setdefault(key, {})
- for attachment in attachments:
- if cache is not None:
- cache.setdefault('_model&id&name2attachment_ids',
- {})
- cache['_model&id&name2attachment_ids'].setdefault(
- key, {})
- cache['_model&id&name2attachment_ids'][key]\
- .setdefault(attachment.name, [])
- cache['_model&id&name2attachment_ids'][key][
- attachment.name].append(attachment.id)
- if attachment.name == name:
- attachment_id = attachment.id
- else:
- key = (object_name, object_id)
- cache.setdefault('_model&id&name2attachment_ids', {})
- cache['_model&id&name2attachment_ids'].setdefault(key, {})
- attachment_id = cache['_model&id&name2attachment_ids'][
- key].get(name, [None])[0]
- if attachment_id:
- object_name = 'ir.attachment'
- object_id = attachment_id
- else:
- object_name = None
- object_id = None
- else:
- splitted_name = name.rsplit('-', 1)
- if len(splitted_name) != 2:
- if cache is not None:
- cache['_uri2object'][cache_uri] = (object_name, 0)
- return object_name, 0
- object_id = int(splitted_name[1].strip())
- if uri:
- if '/' in uri:
- if cache is not None:
- cache['_uri2object'][cache_uri] = (None, 0)
- return None, 0
- reports = Report.search([
- ('model', '=', object_name),
- ])
- for report in reports:
- report_name = (report.name + '-' + str(report.id)
- + '.' + report.extension)
- if uri == report_name:
- if cache is not None:
- cache['_uri2object'][cache_uri] = \
- ('ir.action.report', object_id)
- return 'ir.action.report', object_id
- name = uri
- attachment_ids = None
- if cache is not None:
- cache.setdefault('_model&id2attachment_ids', {})
- if (object_name, object_id) in \
- cache['_model&id2attachment_ids']:
- attachment_ids = cache['_model&id2attachment_ids'][
- (object_name, object_id)].get(name, [])
- if attachment_ids is None:
- attachments = Attachment.search([
- ('resource', '=', '%s,%s' % (object_name, object_id)),
- ])
- key = (object_name, object_id)
- attachment_ids = []
- if cache is not None:
- cache['_model&id2attachment_ids'].setdefault(key, {})
- for attachment in attachments:
- if cache is not None:
- cache['_model&id2attachment_ids'][key]\
- .setdefault(attachment.name, [])
- cache['_model&id2attachment_ids'][key][
- attachment.name].append(attachment.id)
- if attachment.name == name:
- attachment_ids.append(attachment.id)
- if attachment_ids:
- object_name = 'ir.attachment'
- object_id = attachment_ids[0]
- else:
- object_name = None
- object_id = None
- if cache is not None:
- cache['_uri2object'][cache_uri] = (object_name, object_id)
- return object_name, object_id
- if uri:
- res = cls._uri2object(uri, object_name, object_id, cache=cache)
- if cache is not None:
- cache['_uri2object'][cache_uri] = res
- return res
- if cache is not None:
- cache['_uri2object'][cache_uri] = (object_name, object_id)
- return object_name, object_id
-
- @classmethod
- def get_childs(cls, uri, filter=None, cache=None):
- pool = Pool()
- Report = pool.get('ir.action.report')
- res = []
- if filter:
- return []
- if not uri:
- collections = cls.search([
- ('parent', '=', None),
- ])
- for collection in collections:
- if '/' in collection.name:
- continue
- res.append(collection.name)
- if cache is not None:
- cache.setdefault(cls.__name__, {})
- cache[cls.__name__][collection.id] = {}
- return res
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if object_name == cls.__name__ and object_id:
- collection = cls(object_id)
- if collection.model:
- Model = pool.get(collection.model.model)
- if not Model:
- return res
- models = Model.search(
- literal_eval(collection.domain))
- for child in models:
- if '/' in child.rec_name:
- continue
- res.append(child.rec_name + '-' + str(child.id))
- if cache is not None:
- cache.setdefault(Model.__name__, {})
- cache[Model.__name__][child.id] = {}
- return res
- else:
- for child in collection.childs:
- if '/' in child.name:
- continue
- res.append(child.name)
- if cache is not None:
- cache.setdefault(cls.__name__, {})
- cache[cls.__name__][child.id] = {}
- if object_name not in ('ir.attachment', 'ir.action.report'):
- reports = Report.search([
- ('model', '=', object_name),
- ])
- for report in reports:
- report_name = (report.name + '-' + str(report.id)
- + '.' + report.extension)
- if '/' in report_name:
- continue
- res.append(report_name)
- if cache is not None:
- cache.setdefault(Report.__name__, {})
- cache[Report.__name__][report.id] = {}
-
- Attachment = pool.get('ir.attachment')
- attachments = Attachment.search([
- ('resource', '=', '%s,%s' % (object_name, object_id)),
- ])
- for attachment in attachments:
- if attachment.name and not attachment.link:
- if '/' in attachment.name:
- continue
- res.append(attachment.name)
- if cache is not None:
- cache.setdefault(Attachment.__name__, {})
- cache[Attachment.__name__][attachment.id] = {}
- return res
-
- @classmethod
- def get_resourcetype(cls, uri, cache=None):
- from pywebdav.lib.constants import COLLECTION, OBJECT
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if object_name in ('ir.attachment', 'ir.action.report'):
- return OBJECT
- return COLLECTION
-
- @classmethod
- def get_displayname(cls, uri, cache=None):
- object_name, object_id = cls._uri2object(uri, cache=cache)
- Model = Pool().get(object_name)
- return Model(object_id).rec_name
-
- @classmethod
- def get_contentlength(cls, uri, cache=None):
- pool = Pool()
- Attachment = pool.get('ir.attachment')
-
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if object_name == 'ir.attachment':
-
- if cache is not None:
- cache.setdefault('ir.attachment', {})
- ids = cache['ir.attachment'].keys()
- if object_id not in ids:
- ids.append(object_id)
- elif 'contentlength' in cache['ir.attachment'][object_id]:
- return cache['ir.attachment'][object_id]['contentlength']
- else:
- ids = [object_id]
-
- attachments = Attachment.browse(ids)
-
- res = '0'
- for attachment in attachments:
- size = '0'
- try:
- if attachment.data_size:
- size = str(attachment.data_size)
- except Exception:
- pass
- if attachment.id == object_id:
- res = size
- if cache is not None:
- cache['ir.attachment'].setdefault(attachment.id, {})
- cache['ir.attachment'][attachment.id]['contentlength'] = \
- size
- return res
- return '0'
-
- @classmethod
- def get_contenttype(cls, uri, cache=None):
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if object_name in ('ir.attachment', 'ir.action.report'):
- ext = os.path.splitext(uri)[1]
- if not ext:
- return "application/octet-stream"
- return cls.ext2mime.get(ext, 'application/octet-stream')
- return "application/octet-stream"
-
- @classmethod
- def get_creationdate(cls, uri, cache=None):
- pool = Pool()
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if object_name == 'ir.attachment':
- Model = pool.get(object_name)
- if object_id:
- if cache is not None:
- cache.setdefault(Model.__name__, {})
- ids = cache[Model.__name__].keys()
- if object_id not in ids:
- ids.append(object_id)
- elif 'creationdate' in cache[Model.__name__][object_id]:
- return cache[Model.__name__][object_id][
- 'creationdate']
- else:
- ids = [object_id]
- res = None
- cursor = Transaction().cursor
- table = Model.__table__()
- 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),
- where=red_sql))
- for object_id2, date in cursor.fetchall():
- if object_id2 == object_id:
- res = date
- if cache is not None:
- cache[Model.__name__].setdefault(object_id2, {})
- cache[Model.__name__][object_id2][
- 'creationdate'] = date
- if res is not None:
- return res
- return time.time()
-
- @classmethod
- def get_lastmodified(cls, uri, cache=None):
- pool = Pool()
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if object_name == 'ir.attachment':
- Model = pool.get(object_name)
- if object_id:
- if cache is not None:
- cache.setdefault(Model.__name__, {})
- ids = cache[Model.__name__].keys()
- if object_id not in ids:
- ids.append(object_id)
- elif 'lastmodified' in cache[Model.__name__][object_id]:
- return cache[Model.__name__][object_id][
- 'lastmodified']
- else:
- ids = [object_id]
- res = None
- cursor = Transaction().cursor
- table = Model.__table__()
- for sub_ids in grouped_slice(ids):
- red_sql = reduce_ids(table.id, sub_ids)
- cursor.execute(*table.select(table.id,
- Extract('EPOCH',
- Coalesce(table.write_date, table.create_date)),
- where=red_sql))
- for object_id2, date in cursor.fetchall():
- if object_id2 == object_id:
- res = date
- if cache is not None:
- cache[Model.__name__].setdefault(object_id2, {})
- cache[Model.__name__][object_id2][
- 'lastmodified'] = date
- if res is not None:
- return res
- return time.time()
-
- @classmethod
- def get_data(cls, uri, cache=None):
- from pywebdav.lib.errors import DAV_NotFound
- pool = Pool()
- Attachment = pool.get('ir.attachment')
- Report = pool.get('ir.action.report')
-
- if uri:
- object_name, object_id = cls._uri2object(uri, cache=cache)
-
- if object_name == 'ir.attachment' and object_id:
- if cache is not None:
- cache.setdefault('ir.attachment', {})
- ids = cache['ir.attachment'].keys()
- if object_id not in ids:
- ids.append(object_id)
- elif 'data' in cache['ir.attachment'][object_id]:
- res = cache['ir.attachment'][object_id]['data']
- if res == DAV_NotFound:
- raise DAV_NotFound
- return res
- else:
- ids = [object_id]
- attachments = Attachment.browse(ids)
-
- res = DAV_NotFound
- for attachment in attachments:
- data = DAV_NotFound
- try:
- if attachment.data is not None:
- data = str(attachment.data)
- except Exception:
- pass
- if attachment.id == object_id:
- res = data
- if cache is not None:
- cache['ir.attachment'].setdefault(attachment.id, {})
- cache['ir.attachment'][attachment.id]['data'] = data
- if res == DAV_NotFound:
- raise DAV_NotFound
- return res
-
- if object_name == 'ir.action.report' and object_id:
- report_id = int(uri.rsplit('/', 1)[-1].rsplit('-',
- 1)[-1].rsplit('.', 1)[0])
- report = Report(report_id)
- if report.report_name:
- Report = pool.get(report.report_name,
- type='report')
- val = Report.execute([object_id],
- {'id': object_id, 'ids': [object_id]})
- return val[1]
- raise DAV_NotFound
-
- @classmethod
- def put(cls, uri, data, content_type, cache=None):
- from pywebdav.lib.errors import DAV_Forbidden
- from pywebdav.lib.utils import get_uriparentpath, get_urifilename
- object_name, object_id = cls._uri2object(get_uriparentpath(uri),
- cache=cache)
- if not object_name \
- or object_name == 'ir.attachment' \
- or not object_id:
- raise DAV_Forbidden
- pool = Pool()
- Attachment = pool.get('ir.attachment')
- object_name2, object_id2 = cls._uri2object(uri, cache=cache)
- if not object_id2:
- name = get_urifilename(uri)
- try:
- Attachment.create([{
- 'name': name,
- 'data': data,
- 'resource': '%s,%s' % (object_name, object_id),
- }])
- except Exception:
- raise DAV_Forbidden
- else:
- try:
- Attachment.write(object_id2, {
- 'data': data,
- })
- except Exception:
- raise DAV_Forbidden
- return
-
- @classmethod
- def mkcol(cls, uri, cache=None):
- from pywebdav.lib.errors import DAV_Forbidden
- from pywebdav.lib.utils import get_uriparentpath, get_urifilename
- if uri[-1:] == '/':
- uri = uri[:-1]
- object_name, object_id = cls._uri2object(get_uriparentpath(uri),
- cache=cache)
- if object_name != 'webdav.collection':
- raise DAV_Forbidden
- name = get_urifilename(uri)
- try:
- cls.create([{
- 'name': name,
- 'parent': object_id,
- }])
- except Exception:
- raise DAV_Forbidden
- return 201
-
- @classmethod
- def rmcol(cls, uri, cache=None):
- from pywebdav.lib.errors import DAV_Forbidden
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if object_name != 'webdav.collection' \
- or not object_id:
- raise DAV_Forbidden
- try:
- cls.delete([cls(object_id)])
- except Exception:
- raise DAV_Forbidden
- return 200
-
- @classmethod
- def rm(cls, uri, cache=None):
- from pywebdav.lib.errors import DAV_Forbidden
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if not object_name:
- raise DAV_Forbidden
- if object_name != 'ir.attachment' \
- or not object_id:
- raise DAV_Forbidden
- pool = Pool()
- Model = pool.get(object_name)
- try:
- Model.delete([Model(object_id)])
- except Exception:
- raise DAV_Forbidden
- return 200
-
- @classmethod
- def exists(cls, uri, cache=None):
- object_name, object_id = cls._uri2object(uri, cache=cache)
- if object_name and object_id:
- return 1
- return None
-
- @staticmethod
- def current_user_privilege_set(uri, cache=None):
- return ['create', 'read', 'write', 'delete']
-
-
-class Share(ModelSQL, ModelView):
- "Share"
- __name__ = 'webdav.share'
- _rec_name = 'key'
-
- path = fields.Char('Path', required=True, select=True)
- key = fields.Char('Key', required=True, select=True,
- states={
- 'readonly': True,
- })
- user = fields.Many2One('res.user', 'User', required=True)
- expiration_date = fields.Date('Expiration Date', required=True)
- note = fields.Text('Note')
- url = fields.Function(fields.Char('URL'), 'get_url')
-
- @staticmethod
- def default_key():
- return uuid.uuid4().hex
-
- @staticmethod
- def default_user():
- return Transaction().user
-
- @staticmethod
- def default_expiration_date():
- return datetime.date.today() + relativedelta(months=1)
-
- def get_url(self, name):
- return urlparse.urljoin(get_webdav_url(),
- urlparse.urlunsplit((None, None,
- urllib.quote(self.path.encode('utf-8')),
- urllib.urlencode([('key', self.key)]), None)))
-
- @staticmethod
- def match(share, command, path):
- "Test if share match with command and path"
- today = datetime.date.today()
- return (path.startswith(share.path)
- and share.expiration_date > today
- and command == 'GET')
-
- @classmethod
- def get_login(cls, key, command, path):
- """Validate the key for the command and path
- Return the user id if succeed or None
- """
- shares = cls.search([
- ('key', '=', key),
- ])
- if not shares:
- return None
- for share in shares:
- if cls.match(share, command, path):
- return share.user.id
- return None
-
-
-class Attachment(ModelSQL, ModelView):
- __name__ = 'ir.attachment'
-
- path = fields.Function(fields.Char('Path'), 'get_path')
- url = fields.Function(fields.Char('URL'), 'get_url')
- shares = fields.Function(fields.One2Many('webdav.share', None, 'Shares',
- domain=[
- ('path', '=', Eval('path')),
- ],
- depends=['path']), 'get_shares', 'set_shares')
-
- @classmethod
- def __setup__(cls):
- super(Attachment, cls).__setup__()
- cls._error_messages.update({
- 'collection_attachment_name': ('You can not create an '
- 'attachment named "%(attachment)s" in collection '
- '"%(collection)s" because there is already a collection '
- 'with that name.')
- })
-
- @classmethod
- def validate(cls, attachments):
- super(Attachment, cls).validate(attachments)
- cls.check_collection(attachments)
-
- @classmethod
- def check_collection(cls, attachments):
- pool = Pool()
- Collection = pool.get('webdav.collection')
- for attachment in attachments:
- if attachment.resource:
- model_name = attachment.resource.__name__
- record_id = attachment.resource.id
- if model_name == 'webdav.collection':
- collection = Collection(int(record_id))
- for child in collection.childs:
- if child.name == attachment.name:
- cls.raise_user_error(
- 'collection_attachment_name', {
- 'attachment': attachment.rec_name,
- 'collection': collection.rec_name,
- })
-
- @classmethod
- def get_path(cls, attachments, name):
- pool = Pool()
- Collection = pool.get('webdav.collection')
- paths = dict((a.id, None) for a in attachments)
-
- resources = {}
- resource2attachments = {}
- for attachment in attachments:
- if not attachment.resource:
- paths[attachment.id] = None
- continue
- model_name = attachment.resource.__name__
- record_id = attachment.resource.id
- resources.setdefault(model_name, set()).add(record_id)
- resource2attachments.setdefault((model_name, record_id),
- []).append(attachment)
- collections = Collection.search([
- ('model.model', 'in', resources.keys()),
- ])
- for collection in collections:
- model_name = collection.model.model
- Model = pool.get(model_name)
- ids = list(resources[model_name])
- domain = literal_eval(collection.domain)
- domain = [domain, ('id', 'in', ids)]
- records = Model.search(domain)
- for record in records:
- for attachment in resource2attachments[
- (model_name, record.id)]:
- paths[attachment.id] = '/'.join((collection.rec_name,
- record.rec_name + '-' + str(record.id),
- attachment.name))
- if 'webdav.collection' in resources:
- collection_ids = list(resources['webdav.collection'])
- for collection in Collection.browse(collection_ids):
- for attachment in resource2attachments[
- ('webdav.collection', collection.id)]:
- paths[attachment.id] = '/'.join((collection.rec_name,
- attachment.name))
- return paths
-
- def get_url(self, name):
- if self.path:
- return urlparse.urljoin(get_webdav_url(),
- urllib.quote(self.path.encode('utf-8')))
-
- @classmethod
- def get_shares(cls, attachments, name):
- Share = Pool().get('webdav.share')
- result = dict((a.id, []) for a in attachments)
- path2attachement = dict((a.path, a) for a in attachments)
- shares = Share.search([
- ('path', 'in', path2attachement.keys()),
- ])
- for share in shares:
- attachment = path2attachement[share.path]
- result[attachment.id].append(share.id)
- return result
-
- @classmethod
- def set_shares(cls, attachments, name, values):
- Share = Pool().get('webdav.share')
-
- if not values:
- return
-
- def create(vlist):
- to_create = []
- for attachment in attachments:
- for values in vlist:
- values = values.copy()
- values['path'] = attachment.path
- to_create.append(values)
- if to_create:
- Share.create(to_create)
-
- def write(ids, values):
- Share.write(Share.browse(ids), values)
-
- def delete(share_ids):
- Share.delete(Share.browse(share_ids))
-
- actions = {
- 'create': create,
- 'write': write,
- 'delete': delete,
- }
- for value in values:
- action = value[0]
- args = value[1:]
- actions[action](*args)
diff --git a/trytond/webdav/webdav.xml b/trytond/webdav/webdav.xml
deleted file mode 100644
index 5345da9..0000000
--- a/trytond/webdav/webdav.xml
+++ /dev/null
@@ -1,147 +0,0 @@
-<?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. -->
-<tryton>
- <data>
- <menuitem name="WebDAV" id="menu_webdav"
- parent="ir.menu_administration"/>
- <record model="ir.ui.menu-res.group" id="menu_webdav_group_admin">
- <field name="menu" ref="menu_webdav"/>
- <field name="group" ref="res.group_admin"/>
- </record>
-
- <record model="ir.ui.view" id="collection_view_tree">
- <field name="model">webdav.collection</field>
- <field name="type">tree</field>
- <field name="field_childs">childs</field>
- <field name="name">collection_tree</field>
- </record>
-
- <record model="ir.ui.view" id="collection_view_list">
- <field name="model">webdav.collection</field>
- <field name="type">tree</field>
- <field name="name">collection_list</field>
- </record>
-
- <record model="ir.ui.view" id="collection_view_form">
- <field name="model">webdav.collection</field>
- <field name="type">form</field>
- <field name="name">collection_form</field>
- </record>
- <record model="ir.action.act_window" id="act_collection_tree">
- <field name="name">Collections</field>
- <field name="type">ir.action.act_window</field>
- <field name="res_model">webdav.collection</field>
- <field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
- </record>
- <record model="ir.action.act_window.view"
- id="act_collection_tree_view1">
- <field name="sequence" eval="10"/>
- <field name="view" ref="collection_view_tree"/>
- <field name="act_window" ref="act_collection_tree"/>
- </record>
- <record model="ir.action.act_window.view"
- id="act_collection_tree_view2">
- <field name="sequence" eval="20"/>
- <field name="view" ref="collection_view_form"/>
- <field name="act_window" ref="act_collection_tree"/>
- </record>
- <menuitem parent="menu_webdav"
- action="act_collection_tree" id="menu_collection_tree"/>
- <record model="ir.ui.menu-res.group" id="menu_collection_tree_group_admin">
- <field name="menu" ref="menu_collection_tree"/>
- <field name="group" ref="res.group_admin"/>
- </record>
-
- <record model="ir.action.act_window" id="act_collection_list">
- <field name="name">Collections</field>
- <field name="res_model">webdav.collection</field>
- </record>
- <record model="ir.action.act_window.view"
- id="act_collection_list_view1">
- <field name="sequence" eval="10"/>
- <field name="view" ref="collection_view_list"/>
- <field name="act_window" ref="act_collection_list"/>
- </record>
- <record model="ir.action.act_window.view"
- id="act_collection_lis_view2">
- <field name="sequence" eval="20"/>
- <field name="view" ref="collection_view_form"/>
- <field name="act_window" ref="act_collection_list"/>
- </record>
- <menuitem name="Collections" parent="menu_collection_tree"
- action="act_collection_list" id="menu_collection_list"/>
- <record model="ir.ui.menu-res.group" id="menu_collection_list_group_admin">
- <field name="menu" ref="menu_collection_list"/>
- <field name="group" ref="res.group_admin"/>
- </record>
-
- <record model="ir.ui.view" id="share_view_list">
- <field name="model">webdav.share</field>
- <field name="type">tree</field>
- <field name="name">share_list</field>
- </record>
-
- <record model="ir.ui.view" id="share_view_form">
- <field name="model">webdav.share</field>
- <field name="type">form</field>
- <field name="name">share_form</field>
- </record>
-
- <record model="ir.action.act_window" id="act_share_list">
- <field name="name">Shares</field>
- <field name="type">ir.action.act_window</field>
- <field name="res_model">webdav.share</field>
- </record>
- <record model="ir.action.act_window.view" id="act_share_list_view1">
- <field name="sequence" eval="10"/>
- <field name="view" ref="share_view_list"/>
- <field name="act_window" ref="act_share_list"/>
- </record>
- <record model="ir.action.act_window.view" id="act_share_list_view2">
- <field name="sequence" eval="20"/>
- <field name="view" ref="share_view_form"/>
- <field name="act_window" ref="act_share_list"/>
- </record>
- <menuitem parent="menu_webdav" action="act_share_list"
- id="menu_share_list"/>
-
- <record model="ir.rule.group" id="rule_group_share">
- <field name="model" search="[('model', '=', 'webdav.share')]"/>
- <field name="global_p" eval="False"/>
- <field name="default_p" eval="True"/>
- <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.rule" id="rule_share">
- <field name="domain"
- eval="[('user', '=', Eval('user', {}).get('id', -1))]"
- pyson="1"/>
- <field name="rule_group" ref="rule_group_share"/>
- </record>
-
- <record model="ir.rule.group" id="rule_group_share_admin">
- <field name="model" search="[('model', '=', 'webdav.share')]"/>
- <field name="global_p" eval="False"/>
- <field name="default_p" eval="False"/>
- <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.rule.group-res.group"
- id="rule_group_share_admin_group_admin">
- <field name="rule_group" ref="rule_group_share_admin"/>
- <field name="group" ref="res.group_admin"/>
- </record>
-
- <record model="ir.ui.view" id="attachment_view_form">
- <field name="model">ir.attachment</field>
- <field name="type">form</field>
- <field name="inherit" ref="ir.attachment_view_form"/>
- <field name="name">attachment_form</field>
- </record>
- </data>
-</tryton>
diff --git a/trytond/wizard/wizard.py b/trytond/wizard/wizard.py
index 4aa0df3..fda671a 100644
--- a/trytond/wizard/wizard.py
+++ b/trytond/wizard/wizard.py
@@ -314,8 +314,7 @@ class Wizard(WarningErrorMixin, URLMixin, PoolBase):
Session = pool.get('ir.session.wizard')
self._session_id = session_id
session = Session(session_id)
- data = json.loads(session.data.encode('utf-8'),
- object_hook=JSONDecoder())
+ data = json.loads(session.data, object_hook=JSONDecoder())
for state_name, state in self.states.iteritems():
if isinstance(state, StateView):
Target = pool.get(state.model_name)
diff --git a/trytond/wsgi.py b/trytond/wsgi.py
new file mode 100644
index 0000000..30deb51
--- /dev/null
+++ b/trytond/wsgi.py
@@ -0,0 +1,86 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import logging
+
+from werkzeug.wrappers import Response
+from werkzeug.routing import Map, Rule
+from werkzeug.exceptions import abort
+
+import wrapt
+
+from trytond.protocols.wrappers import Request
+from trytond.protocols.jsonrpc import JSONProtocol
+from trytond.protocols.xmlrpc import XMLProtocol
+
+__all__ = ['TrytondWSGI', 'app']
+
+logger = logging.getLogger(__name__)
+
+
+class TrytondWSGI(object):
+
+ def __init__(self):
+ self.url_map = Map([])
+ self.protocols = [JSONProtocol, XMLProtocol]
+ self.error_handlers = []
+
+ def route(self, string, methods=None):
+ def decorator(func):
+ self.url_map.add(Rule(string, endpoint=func, methods=methods))
+ return func
+ return decorator
+
+ @wrapt.decorator
+ def auth_required(self, wrapped, instance, args, kwargs):
+ request = args[0]
+ if request.user_id:
+ return wrapped(*args, **kwargs)
+ else:
+ abort(303)
+
+ def dispatch_request(self, request):
+ adapter = self.url_map.bind_to_environ(request.environ)
+ try:
+ endpoint, request.view_args = adapter.match()
+ return endpoint(request, **request.view_args)
+ except Exception, e:
+ response = e
+ for error_handler in self.error_handlers:
+ rv = error_handler(e)
+ if isinstance(rv, Response):
+ response = rv
+ return response
+
+ def wsgi_app(self, environ, start_response):
+ for cls in self.protocols:
+ if cls.content_type in environ.get('CONTENT_TYPE', ''):
+ request = cls.request(environ)
+ break
+ else:
+ request = Request(environ)
+ data = self.dispatch_request(request)
+ if not isinstance(data, Response):
+ for cls in self.protocols:
+ for mimetype in request.accept_mimetypes:
+ if cls.content_type in mimetype:
+ response = cls.response(data, request)
+ break
+ else:
+ continue
+ break
+ else:
+ for cls in self.protocols:
+ if cls.content_type in environ.get('CONTENT_TYPE', ''):
+ response = cls.response(data, request)
+ break
+ else:
+ response = Response(data)
+ else:
+ response = data
+ # TODO custom process response
+ return response(environ, start_response)
+
+ def __call__(self, environ, start_response):
+ return self.wsgi_app(environ, start_response)
+
+app = TrytondWSGI()
--
tryton-server
More information about the tryton-debian-vcs
mailing list