merge upstream

bzr revid: chs@openerp.com-20121210132406-9ro3amw6s6pzeez4
This commit is contained in:
Christophe Simonis 2012-12-10 14:24:06 +01:00
commit 236df95b22
36 changed files with 942 additions and 1412 deletions

View File

@ -1,19 +0,0 @@
#! /usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import sys
if __name__ == "__main__":
print '-' * 70
print "DEPRECATED: you are starting the OpenERP server with its old path,"
print "please use the new executable (available in the parent directory)."
print '-' * 70
# Change to the parent directory ...
os.chdir(os.path.normpath(os.path.dirname(__file__)))
os.chdir('..')
# ... and execute the new executable.
os.execv('openerp-server', sys.argv)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

2
debian/control vendored
View File

@ -23,6 +23,7 @@ Depends:
python-libxslt1,
python-lxml,
python-mako,
python-mock,
python-openid,
python-psutil,
python-psycopg2,
@ -33,6 +34,7 @@ Depends:
python-reportlab,
python-simplejson,
python-tz,
python-unittest2,
python-vatnumber,
python-vobject,
python-webdav,

View File

@ -40,7 +40,6 @@ import service
import sql_db
import test
import tools
import wizard
import workflow
# backward compatilbility
# TODO: This is for the web addons, can be removed later.

View File

@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-03 16:01+0000\n"
"PO-Revision-Date: 2012-12-05 20:42+0000\n"
"Last-Translator: Santi (Pexego) <santiago@pexego.es>\n"
"PO-Revision-Date: 2012-12-09 23:26+0000\n"
"Last-Translator: lambdasoftware <development@lambdasoftware.net>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-12-06 04:39+0000\n"
"X-Launchpad-Export-Date: 2012-12-10 04:36+0000\n"
"X-Generator: Launchpad (build 16341)\n"
#. module: base
@ -541,7 +541,7 @@ msgstr "Nombre de relación"
#. module: base
#: view:ir.rule:0
msgid "Create Access Right"
msgstr ""
msgstr "Otorge derecho de acceso"
#. module: base
#: model:res.country,name:base.tv
@ -581,7 +581,7 @@ msgstr ""
#. module: base
#: view:workflow.transition:0
msgid "Workflow Transition"
msgstr ""
msgstr "Transición del flujo de trabajo"
#. module: base
#: model:res.country,name:base.gf
@ -704,7 +704,7 @@ msgstr "Colombia"
#. module: base
#: model:res.partner.title,name:base.res_partner_title_mister
msgid "Mister"
msgstr ""
msgstr "Señor"
#. module: base
#: help:res.country,code:0
@ -733,7 +733,7 @@ msgstr "Sin traducir"
#. module: base
#: view:ir.mail_server:0
msgid "Outgoing Mail Server"
msgstr ""
msgstr "Servidor de correo saliente"
#. module: base
#: help:ir.actions.act_window,context:0
@ -927,7 +927,7 @@ msgstr "Sueco / svenska"
#: field:base.language.export,name:0
#: field:ir.attachment,datas_fname:0
msgid "File Name"
msgstr ""
msgstr "Nombre del archivo"
#. module: base
#: model:res.country,name:base.rs
@ -1024,7 +1024,7 @@ msgstr "Preferencias de email"
#: code:addons/base/ir/ir_fields.py:196
#, python-format
msgid "'%s' does not seem to be a valid date for field '%%(field)s'"
msgstr ""
msgstr "'%s' no parece ser una fecha valida para el campo '%%(field)s'"
#. module: base
#: view:res.partner:0
@ -1156,7 +1156,7 @@ msgstr "Usuarios google"
#. module: base
#: model:ir.module.module,shortdesc:base.module_fleet
msgid "Fleet Management"
msgstr ""
msgstr "Gestión de flotas"
#. module: base
#: help:ir.server.object.lines,value:0
@ -2721,7 +2721,7 @@ msgstr "Nombre de acceso rápido"
#. module: base
#: field:res.partner,contact_address:0
msgid "Complete Address"
msgstr ""
msgstr "Dirección completa"
#. module: base
#: help:ir.actions.act_window,limit:0
@ -2793,7 +2793,7 @@ msgstr "ID de registro"
#. module: base
#: view:ir.filters:0
msgid "My Filters"
msgstr ""
msgstr "Mis filtros"
#. module: base
#: field:ir.actions.server,email:0
@ -2879,7 +2879,7 @@ msgstr "Responsable"
#: code:addons/base/ir/ir_model.py:718
#, python-format
msgid "Sorry, you are not allowed to access this document."
msgstr ""
msgstr "Lo siento, no está autorizado para acceder a este documento."
#. module: base
#: model:res.country,name:base.py
@ -2894,7 +2894,7 @@ msgstr "Fiji"
#. module: base
#: view:ir.actions.report.xml:0
msgid "Report Xml"
msgstr ""
msgstr "Informe Xml"
#. module: base
#: model:ir.module.module,description:base.module_purchase
@ -2965,7 +2965,7 @@ msgstr "Hededado"
#: code:addons/base/ir/ir_fields.py:147
#, python-format
msgid "yes"
msgstr ""
msgstr ""
#. module: base
#: field:ir.model.fields,serialization_field_id:0
@ -3056,7 +3056,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_linkedin
msgid "LinkedIn Integration"
msgstr ""
msgstr "Integración con LinkedIn"
#. module: base
#: code:addons/orm.py:2021
@ -3370,7 +3370,7 @@ msgstr "Calendario"
#. module: base
#: model:ir.module.category,name:base.module_category_knowledge_management
msgid "Knowledge"
msgstr ""
msgstr "Conocimiento"
#. module: base
#: field:workflow.activity,signal_send:0
@ -3535,7 +3535,7 @@ msgstr "workflow.actividad"
#. module: base
#: view:base.language.export:0
msgid "Export Complete"
msgstr ""
msgstr "Exportación completada"
#. module: base
#: help:ir.ui.view_sc,res_id:0
@ -3564,7 +3564,7 @@ msgstr "Finlandés / Suomi"
#. module: base
#: view:ir.config_parameter:0
msgid "System Properties"
msgstr ""
msgstr "Propiedades de sistema"
#. module: base
#: field:ir.sequence,prefix:0
@ -3621,7 +3621,7 @@ msgstr ""
#. module: base
#: field:base.language.export,modules:0
msgid "Modules To Export"
msgstr ""
msgstr "Módulos a exportar"
#. module: base
#: model:res.country,name:base.mt
@ -3724,7 +3724,7 @@ msgstr "Antártida"
#. module: base
#: view:res.partner:0
msgid "Persons"
msgstr ""
msgstr "Personas"
#. module: base
#: view:base.language.import:0
@ -3903,7 +3903,7 @@ msgstr "Tayiko / اردو"
#: code:addons/orm.py:3870
#, python-format
msgid "Access Denied"
msgstr ""
msgstr "Acceso denegado"
#. module: base
#: field:res.company,name:0
@ -3996,7 +3996,7 @@ msgstr "%x - Representación apropiada de fecha."
#. module: base
#: view:res.partner:0
msgid "Tag"
msgstr ""
msgstr "Etiqueta"
#. module: base
#: view:res.lang:0
@ -4048,7 +4048,7 @@ msgstr ""
#. module: base
#: model:res.country,name:base.sk
msgid "Slovakia"
msgstr ""
msgstr "Eslovaquia"
#. module: base
#: model:res.country,name:base.nr
@ -4599,7 +4599,7 @@ msgstr ""
#. module: base
#: field:ir.module.module,latest_version:0
msgid "Installed Version"
msgstr ""
msgstr "Versión instalada"
#. module: base
#: field:ir.module.module,license:0
@ -4895,7 +4895,7 @@ msgstr "Flujos"
#. module: base
#: model:ir.ui.menu,name:base.next_id_73
msgid "Purchase"
msgstr ""
msgstr "Compra"
#. module: base
#: selection:base.language.install,lang:0
@ -5054,7 +5054,7 @@ msgstr "Establecer a NULL"
#. module: base
#: view:res.users:0
msgid "Save"
msgstr ""
msgstr "Guardar"
#. module: base
#: field:ir.actions.report.xml,report_xml:0
@ -5248,7 +5248,7 @@ msgstr "Tasas"
#. module: base
#: model:ir.module.module,shortdesc:base.module_email_template
msgid "Email Templates"
msgstr ""
msgstr "Plantillas de correo electrónico"
#. module: base
#: model:res.country,name:base.sy
@ -5397,7 +5397,7 @@ msgstr ""
#. module: base
#: view:ir.rule:0
msgid "Write Access Right"
msgstr ""
msgstr "Permiso de escritura"
#. module: base
#: model:ir.actions.act_window,help:base.action_res_groups
@ -5432,7 +5432,7 @@ msgstr "Historial"
#. module: base
#: model:res.country,name:base.im
msgid "Isle of Man"
msgstr ""
msgstr "Isla de Man"
#. module: base
#: help:ir.actions.client,res_model:0
@ -6109,7 +6109,7 @@ msgstr "Nombre del recurso"
#. module: base
#: field:res.partner,is_company:0
msgid "Is a Company"
msgstr ""
msgstr "¿Es una empresa?"
#. module: base
#: selection:ir.cron,interval_type:0
@ -6189,7 +6189,7 @@ msgstr ""
#. module: base
#: sql_constraint:ir.filters:0
msgid "Filter names must be unique"
msgstr ""
msgstr "Los nombres de filtro deben ser únicos"
#. module: base
#: help:multi_company.default,object_id:0
@ -6204,7 +6204,7 @@ msgstr ""
#. module: base
#: field:ir.filters,is_default:0
msgid "Default filter"
msgstr ""
msgstr "Filtro por defecto"
#. module: base
#: report:ir.module.reference:0
@ -6932,7 +6932,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_auth_anonymous
msgid "Anonymous"
msgstr ""
msgstr "Anónimo"
#. module: base
#: model:ir.module.module,description:base.module_base_import
@ -7439,7 +7439,7 @@ msgstr ""
#. module: base
#: model:res.partner.category,name:base.res_partner_category_14
msgid "Manufacturer"
msgstr ""
msgstr "Fabricante"
#. module: base
#: help:res.users,company_id:0
@ -8055,7 +8055,7 @@ msgstr "Francés (CH) / Français (CH)"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_13
msgid "Distributor"
msgstr ""
msgstr "Distribuidor"
#. module: base
#: help:ir.actions.server,subject:0
@ -15511,7 +15511,7 @@ msgstr ""
#. module: base
#: view:res.partner:0
msgid "Internal Notes"
msgstr ""
msgstr "Notas internas"
#. module: base
#: selection:res.partner.address,type:0
@ -15553,7 +15553,7 @@ msgstr "Empresas: "
#. module: base
#: view:res.partner:0
msgid "Is a Company?"
msgstr ""
msgstr "¿Es una empresa?"
#. module: base
#: code:addons/base/res/res_company.py:159
@ -15575,7 +15575,7 @@ msgstr "Crear objeto"
#. module: base
#: model:res.country,name:base.ss
msgid "South Sudan"
msgstr ""
msgstr "Sudán del Sur"
#. module: base
#: field:ir.filters,context:0

View File

@ -8,13 +8,13 @@ msgstr ""
"Project-Id-Version: openobject-server\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-03 16:01+0000\n"
"PO-Revision-Date: 2012-12-06 20:42+0000\n"
"PO-Revision-Date: 2012-12-07 15:49+0000\n"
"Last-Translator: Carlos Matos Villar <carlosmvillar19@hotmail.com>\n"
"Language-Team: Spanish (Dominican Republic) <es_DO@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-12-07 04:34+0000\n"
"X-Launchpad-Export-Date: 2012-12-08 04:58+0000\n"
"X-Generator: Launchpad (build 16341)\n"
#. module: base
@ -9141,7 +9141,7 @@ msgstr ""
#. module: base
#: view:res.lang:0
msgid "2. %a ,%A ==> Fri, Friday"
msgstr ""
msgstr "2. %a ,%A ==> Vie, Viernes"
#. module: base
#: code:addons/base/ir/ir_translation.py:341
@ -9150,6 +9150,8 @@ msgid ""
"Translation features are unavailable until you install an extra OpenERP "
"translation."
msgstr ""
"Las Características de Traducción no están disponibles hasta que usted "
"instala una traducción extra en OpenERP."
#. module: base
#: model:ir.module.module,description:base.module_l10n_nl
@ -9193,16 +9195,56 @@ msgid ""
"\n"
" "
msgstr ""
"\n"
"Este es el modulo para gestionar el plan de cuenta para Neerlandés en "
"OpenERP.\n"
"============================================================================="
"=======\n"
"\n"
"Lea los cambios en el archivo __openerp__.py para la versión de "
"información.\n"
"Read changelog in file __openerp__.py for version information.\n"
"Dit is een basismodule om een uitgebreid grootboek- en BTW schema voor\n"
"Nederlandse bedrijven te installeren in OpenERP versie 7.0.\n"
"\n"
"De BTW rekeningen zijn waar nodig gekoppeld om de juiste rapportage te "
"genereren,\n"
"denk b.v. aan intracommunautaire verwervingen waarbij u 21% BTW moet "
"opvoeren,\n"
"maar tegelijkertijd ook 21% als voorheffing weer mag aftrekken.\n"
"\n"
"Na installatie van deze module word de configuratie wizard voor 'Accounting' "
"aangeroepen.\n"
"* U krijgt een lijst met grootboektemplates aangeboden waarin zich ook het\n"
"Nederlandse grootboekschema bevind.\n"
"\n"
"* Als de configuratie wizard start, wordt u gevraagd om de naam van uw "
"bedrijf\n"
"in te voeren, welke grootboekschema te installeren, uit hoeveel cijfers een\n"
"grootboekrekening mag bestaan, het rekeningnummer van uw bank en de "
"currency\n"
"om Journalen te creeren.\n"
"\n"
"Let op!! -> De template van het Nederlandse rekeningschema is opgebouwd uit "
"4\n"
"cijfers. Dit is het minimale aantal welk u moet invullen, u mag het aantal "
"verhogen.\n"
"De extra cijfers worden dan achter het rekeningnummer aangevult met "
"'nullen'.\n"
"\n"
" "
#. module: base
#: help:ir.rule,global:0
msgid "If no group is specified the rule is global and applied to everyone"
msgstr ""
"Si no se especifica ningún grupo, la regla es global y se aplica a todo el "
"mundo."
#. module: base
#: model:res.country,name:base.td
msgid "Chad"
msgstr ""
msgstr "Chad"
#. module: base
#: help:ir.cron,priority:0
@ -9210,31 +9252,33 @@ msgid ""
"The priority of the job, as an integer: 0 means higher priority, 10 means "
"lower priority."
msgstr ""
"Prioridad del trabajo, expresada con un entero: 0 significa la máxima "
"prioridad, 10 significa la prioridad más baja."
#. module: base
#: model:ir.model,name:base.model_workflow_transition
msgid "workflow.transition"
msgstr ""
msgstr "workflow.transition"
#. module: base
#: view:res.lang:0
msgid "%a - Abbreviated weekday name."
msgstr ""
msgstr "%a - Nombre abreviado del día de la semana."
#. module: base
#: view:ir.ui.menu:0
msgid "Submenus"
msgstr ""
msgstr "Submenús"
#. module: base
#: report:ir.module.reference:0
msgid "Introspection report on objects"
msgstr ""
msgstr "Introspección informe sobre los objetos"
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_analytics
msgid "Google Analytics"
msgstr ""
msgstr "Google Analytics"
#. module: base
#: model:ir.module.module,description:base.module_note
@ -9257,83 +9301,84 @@ msgstr ""
#. module: base
#: model:res.country,name:base.dm
msgid "Dominica"
msgstr ""
msgstr "Dominica"
#. module: base
#: field:ir.translation,name:0
msgid "Translated field"
msgstr ""
msgstr "Campos Traducidos"
#. module: base
#: model:ir.module.module,shortdesc:base.module_stock_location
msgid "Advanced Routes"
msgstr ""
msgstr "Rutas avanzadas"
#. module: base
#: model:ir.module.module,shortdesc:base.module_pad
msgid "Collaborative Pads"
msgstr ""
msgstr "Pads colaborativos"
#. module: base
#: model:res.country,name:base.np
msgid "Nepal"
msgstr ""
msgstr "Nepal"
#. module: base
#: model:ir.module.module,shortdesc:base.module_document_page
msgid "Document Page"
msgstr ""
msgstr "Página de Documentos"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_ar
msgid "Argentina Localization Chart Account"
msgstr ""
msgstr "Plan de Cuenta Localización Argentina"
#. module: base
#: field:ir.module.module,description_html:0
msgid "Description HTML"
msgstr ""
msgstr "HTML de Descripción"
#. module: base
#: help:res.groups,implied_ids:0
msgid "Users of this group automatically inherit those groups"
msgstr ""
"Los usuarios de este grupo automaticamente heredan de aquellos grupos"
#. module: base
#: model:ir.module.module,summary:base.module_note
msgid "Sticky notes, Collaborative, Memos"
msgstr ""
msgstr "Notas adesivas, Colaborativas, Memos"
#. module: base
#: model:ir.module.module,shortdesc:base.module_hr_attendance
#: model:res.groups,name:base.group_hr_attendance
msgid "Attendances"
msgstr ""
msgstr "Asistencias"
#. module: base
#: model:ir.module.module,shortdesc:base.module_warning
msgid "Warning Messages and Alerts"
msgstr ""
msgstr "Mensajes de aviso y alertas"
#. module: base
#: model:ir.actions.act_window,name:base.action_ui_view_custom
#: model:ir.ui.menu,name:base.menu_action_ui_view_custom
#: view:ir.ui.view.custom:0
msgid "Customized Views"
msgstr ""
msgstr "Vistas personalizadas"
#. module: base
#: view:base.module.import:0
#: model:ir.actions.act_window,name:base.action_view_base_module_import
msgid "Module Import"
msgstr ""
msgstr "Importación de módulo"
#. module: base
#: model:ir.actions.act_window,name:base.act_values_form_action
#: model:ir.ui.menu,name:base.menu_values_form_action
#: view:ir.values:0
msgid "Action Bindings"
msgstr ""
msgstr "Enlaces de acciones"
#. module: base
#: help:res.partner,lang:0
@ -9341,6 +9386,9 @@ msgid ""
"If the selected language is loaded in the system, all documents related to "
"this contact will be printed in this language. If not, it will be English."
msgstr ""
"Si el lenguaje seleccionado esta cargado en el sistema, todos los documentos "
"relacionados a este contacto serán impresos en este lenguaje. Si no, este "
"será Ingles."
#. module: base
#: model:ir.module.module,description:base.module_hr_evaluation
@ -9378,7 +9426,7 @@ msgstr ""
#. module: base
#: model:ir.ui.menu,name:base.menu_view_base_module_update
msgid "Update Modules List"
msgstr ""
msgstr "Actualizar lista de módulos"
#. module: base
#: code:addons/base/module/module.py:338
@ -9386,11 +9434,13 @@ msgstr ""
msgid ""
"Unable to upgrade module \"%s\" because an external dependency is not met: %s"
msgstr ""
"Imposible actualizar el módulo \"%s\" porqué hay una dependencia externa no "
"resuelta: %s"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account
msgid "eInvoicing"
msgstr ""
msgstr "Facturación electrónica"
#. module: base
#: code:addons/base/res/res_users.py:175
@ -9401,37 +9451,41 @@ msgid ""
"sure to save and close all forms before switching to a different company. "
"(You can click on Cancel in the User Preferences now)"
msgstr ""
"Tenga en cuenta que los documentos que se muestran actualmente pueden no ser "
"relevantes después de cambiar a otra compañía. Asegúrese de guardar y cerrar "
"todas los formularios modificados antes de cambiar a una compañía diferente "
"(ahora puede hacer clic en Cancelar en las preferencias del usuario)"
#. module: base
#: code:addons/orm.py:2821
#, python-format
msgid "The value \"%s\" for the field \"%s.%s\" is not in the selection"
msgstr ""
msgstr "El valor \"%s\" para el campo \"%s.%s\" no está en la selección"
#. module: base
#: view:ir.actions.configuration.wizard:0
msgid "Continue"
msgstr ""
msgstr "Siguiente"
#. module: base
#: selection:base.language.install,lang:0
msgid "Thai / ภาษาไทย"
msgstr ""
msgstr "Tailandés / ภาษาไทย"
#. module: base
#: view:res.lang:0
msgid "%j - Day of the year [001,366]."
msgstr ""
msgstr "%j - Día del año [001,366]."
#. module: base
#: selection:base.language.install,lang:0
msgid "Slovenian / slovenščina"
msgstr ""
msgstr "Esloveno / slovenščina"
#. module: base
#: field:res.currency,position:0
msgid "Symbol Position"
msgstr ""
msgstr "Posición de Simbolo"
#. module: base
#: model:ir.module.module,description:base.module_l10n_de
@ -9445,16 +9499,20 @@ msgid ""
"German accounting chart and localization.\n"
" "
msgstr ""
"\n"
"Plan de cuentas y localización alemana.\n"
"===============================\n"
" "
#. module: base
#: field:ir.actions.report.xml,attachment_use:0
msgid "Reload from Attachment"
msgstr ""
msgstr "Recargar desde adjunto"
#. module: base
#: model:res.country,name:base.mx
msgid "Mexico"
msgstr ""
msgstr "México"
#. module: base
#: code:addons/orm.py:3871
@ -9465,11 +9523,15 @@ msgid ""
"\n"
"(Document type: %s)"
msgstr ""
"Para este tipo de documentos, usted solo puede acceder a archivos que usted "
"mismo creó.\n"
"\n"
"(Tipo de Documento: %s)"
#. module: base
#: view:base.language.export:0
msgid "documentation"
msgstr ""
msgstr "documentación"
#. module: base
#: help:ir.model,osv_memory:0
@ -9477,62 +9539,64 @@ msgid ""
"This field specifies whether the model is transient or not (i.e. if records "
"are automatically deleted from the database or not)"
msgstr ""
"Este campo especifica si el modelo es trascendente o no (Ejemplo: Si los "
"archivos son automáticamente eliminados desde la base de datos o no)"
#. module: base
#: code:addons/base/ir/ir_mail_server.py:441
#, python-format
msgid "Missing SMTP Server"
msgstr ""
msgstr "Servidor SMTP ausente"
#. module: base
#: field:ir.attachment,name:0
msgid "Attachment Name"
msgstr ""
msgstr "Nombre del adjunto"
#. module: base
#: field:base.language.export,data:0
#: field:base.language.import,data:0
msgid "File"
msgstr ""
msgstr "Archivo"
#. module: base
#: model:ir.actions.act_window,name:base.action_view_base_module_upgrade_install
msgid "Module Upgrade Install"
msgstr ""
msgstr "Instalar actualizar módulo"
#. module: base
#: model:ir.model,name:base.model_ir_actions_configuration_wizard
msgid "ir.actions.configuration.wizard"
msgstr ""
msgstr "ir.actions.configuration.wizard"
#. module: base
#: view:res.lang:0
msgid "%b - Abbreviated month name."
msgstr ""
msgstr "%b - Nombre abreviado del mes."
#. module: base
#: code:addons/base/ir/ir_model.py:721
#, python-format
msgid "Sorry, you are not allowed to delete this document."
msgstr ""
msgstr "Lo sentimos, usted no tiene permitod eliminar este documento."
#. module: base
#: constraint:ir.rule:0
msgid "Rules can not be applied on the Record Rules model."
msgstr ""
msgstr "Las reglas no pueden ser aplicadas en el archivo modelo de reglas."
#. module: base
#: field:res.partner,supplier:0
#: field:res.partner.address,is_supplier_add:0
#: model:res.partner.category,name:base.res_partner_category_1
msgid "Supplier"
msgstr ""
msgstr "Proveedor"
#. module: base
#: view:ir.actions.server:0
#: selection:ir.actions.server,state:0
msgid "Multi Actions"
msgstr ""
msgstr "Multi acciones"
#. module: base
#: model:ir.module.module,summary:base.module_mail
@ -9559,91 +9623,109 @@ msgid ""
"* Show all costs associated to a vehicle or to a type of service\n"
"* Analysis graph for costs\n"
msgstr ""
"\n"
"Vehículo, Alquiler, seguros, costos\n"
"================================\n"
"Con este modulo, OpenERP les ayuda a administrar todos sus vehículos, \n"
"los contratos asociados a esos vehículos así como el servicio, log de\n"
"entradas de combustible, costos y algunas otras características necesarias "
"para la gestión \n"
"de sus fletes de vehículo(s)\n"
"\n"
"Características Principales\n"
"------------------\n"
"*Agrega vehículos a sus fletes\n"
"*Gestiona contratos para vehículos\n"
"*Recordatorios cuando un contrato alcanza su fecha de expiración\n"
"*Agregar servicios, entradas de log de combustible, valores de cuenta "
"kilómetros para todos los vehículos\n"
"*Muestra todo el costo asociado a un vehículo o a un tipo de servicio\n"
"*Análisis gráficos para costos\n"
#. module: base
#: field:multi_company.default,company_dest_id:0
msgid "Default Company"
msgstr ""
msgstr "Compañía por defecto"
#. module: base
#: selection:base.language.install,lang:0
msgid "Spanish (EC) / Español (EC)"
msgstr ""
msgstr "Español (EC) / Español (EC)"
#. module: base
#: help:ir.ui.view,xml_id:0
msgid "ID of the view defined in xml file"
msgstr ""
msgstr "El ID de la vista definido en el archivo xml."
#. module: base
#: model:ir.model,name:base.model_base_module_import
msgid "Import Module"
msgstr ""
msgstr "Importar módulo"
#. module: base
#: model:res.country,name:base.as
msgid "American Samoa"
msgstr ""
msgstr "Samoa Americana"
#. module: base
#: view:ir.attachment:0
msgid "My Document(s)"
msgstr ""
msgstr "Mis Documentos"
#. module: base
#: help:ir.actions.act_window,res_model:0
msgid "Model name of the object to open in the view window"
msgstr ""
msgstr "Nombre del modelo del objeto a abrir en la ventana de la vista."
#. module: base
#: field:ir.model.fields,selectable:0
msgid "Selectable"
msgstr ""
msgstr "Seleccionable"
#. module: base
#: code:addons/base/ir/ir_mail_server.py:220
#, python-format
msgid "Everything seems properly set up!"
msgstr ""
msgstr "¡Todo parece correctamente configurado!"
#. module: base
#: view:res.request.link:0
msgid "Request Link"
msgstr ""
msgstr "Solicitar enlace"
#. module: base
#: view:ir.attachment:0
#: selection:ir.attachment,type:0
#: field:ir.module.module,url:0
msgid "URL"
msgstr ""
msgstr "URL"
#. module: base
#: help:res.country,name:0
msgid "The full name of the country."
msgstr ""
msgstr "El nombre completo del país."
#. module: base
#: selection:ir.actions.server,state:0
msgid "Iteration"
msgstr ""
msgstr "Iteración"
#. module: base
#: code:addons/orm.py:4213
#: code:addons/orm.py:4314
#, python-format
msgid "UserError"
msgstr ""
msgstr "Error de usuario"
#. module: base
#: model:ir.module.module,summary:base.module_project_issue
msgid "Support, Bug Tracker, Helpdesk"
msgstr ""
msgstr "Soporte, Rastreador de Bug, Ayudante de Escritorio"
#. module: base
#: model:res.country,name:base.ae
msgid "United Arab Emirates"
msgstr ""
msgstr "Emiratos Árabes Unidos"
#. module: base
#: help:ir.ui.menu,needaction_enabled:0
@ -9659,21 +9741,23 @@ msgstr ""
msgid ""
"Unable to delete this document because it is used as a default property"
msgstr ""
"No se ha podido eliminar este documento ya que se utiliza como una propiedad "
"por defecto"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_5
msgid "Silver"
msgstr ""
msgstr "Plateado"
#. module: base
#: field:res.partner.title,shortcut:0
msgid "Abbreviation"
msgstr ""
msgstr "Abreviación"
#. module: base
#: model:ir.ui.menu,name:base.menu_crm_case_job_req_main
msgid "Recruitment"
msgstr ""
msgstr "Proceso de selección"
#. module: base
#: model:ir.module.module,description:base.module_l10n_gr
@ -9685,11 +9769,17 @@ msgid ""
"Greek accounting chart and localization.\n"
" "
msgstr ""
"\n"
"Módulo para administrar el plan de cuentas para Grecia.\n"
"============================================\n"
"\n"
"Plan de cuentas y localización griego.\n"
" "
#. module: base
#: view:ir.values:0
msgid "Action Reference"
msgstr ""
msgstr "Referencia de la acción"
#. module: base
#: model:ir.module.module,description:base.module_auth_ldap

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-03 16:01+0000\n"
"PO-Revision-Date: 2012-11-24 21:10+0000\n"
"PO-Revision-Date: 2012-12-09 19:36+0000\n"
"Last-Translator: Goran Kliska <gkliska@gmail.com>\n"
"Language-Team: openerp-translators\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-12-04 04:59+0000\n"
"X-Generator: Launchpad (build 16335)\n"
"X-Launchpad-Export-Date: 2012-12-10 04:36+0000\n"
"X-Generator: Launchpad (build 16341)\n"
"Language: hr\n"
#. module: base
@ -532,7 +532,7 @@ msgstr "Naziv relacije"
#. module: base
#: view:ir.rule:0
msgid "Create Access Right"
msgstr ""
msgstr "Kreiraj pravo pristupa"
#. module: base
#: model:res.country,name:base.tv
@ -582,7 +582,7 @@ msgstr "Francuska Guyana"
#. module: base
#: model:ir.module.module,summary:base.module_hr
msgid "Jobs, Departments, Employees Details"
msgstr ""
msgstr "Radna mjesta, odjeli, podaci o radnicima"
#. module: base
#: model:ir.module.module,description:base.module_analytic
@ -695,7 +695,7 @@ msgstr "Kolumbija"
#. module: base
#: model:res.partner.title,name:base.res_partner_title_mister
msgid "Mister"
msgstr ""
msgstr "Gospodin"
#. module: base
#: help:res.country,code:0
@ -828,6 +828,11 @@ msgid ""
"This module provides the Integration of the LinkedIn with OpenERP.\n"
" "
msgstr ""
"\n"
"OpenERP Web LinkedIn modul.\n"
"============================\n"
"Integracija LinkedIn-a sa OpenERP-om.\n"
" "
#. module: base
#: help:ir.actions.act_window,src_model:0
@ -1132,7 +1137,7 @@ msgstr "Google korisnici"
#. module: base
#: model:ir.module.module,shortdesc:base.module_fleet
msgid "Fleet Management"
msgstr ""
msgstr "Upravljanje flotom (automobili)"
#. module: base
#: help:ir.server.object.lines,value:0
@ -1234,7 +1239,7 @@ msgstr "Zadnja verzija"
#. module: base
#: view:ir.rule:0
msgid "Delete Access Right"
msgstr ""
msgstr "Prava brisanja"
#. module: base
#: code:addons/base/ir/ir_mail_server.py:213
@ -1304,7 +1309,7 @@ msgstr "Vidljivo"
#. module: base
#: model:ir.actions.client,name:base.action_client_base_menu
msgid "Open Settings Menu"
msgstr ""
msgstr "Otvori izbornik postavki"
#. module: base
#: selection:base.language.install,lang:0
@ -1427,7 +1432,7 @@ msgstr "Haiti"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_fr_hr_payroll
msgid "French Payroll"
msgstr ""
msgstr "Francuska plaća"
#. module: base
#: view:ir.ui.view:0
@ -2571,7 +2576,7 @@ msgstr "Grčka / Ελληνικά"
#. module: base
#: field:res.company,custom_footer:0
msgid "Custom Footer"
msgstr ""
msgstr "Prilagođeno podnožje"
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale_crm
@ -2879,7 +2884,7 @@ msgstr "Naslijeđeno"
#: code:addons/base/ir/ir_fields.py:147
#, python-format
msgid "yes"
msgstr ""
msgstr "da"
#. module: base
#: field:ir.model.fields,serialization_field_id:0
@ -14826,7 +14831,7 @@ msgstr ""
#. module: base
#: field:ir.sequence,implementation:0
msgid "Implementation"
msgstr ""
msgstr "Implementacija"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_ve
@ -14931,7 +14936,7 @@ msgstr "Ažuriranje sustava"
#: field:ir.actions.report.xml,report_sxw_content:0
#: field:ir.actions.report.xml,report_sxw_content_data:0
msgid "SXW Content"
msgstr ""
msgstr "SXW Sadržaj"
#. module: base
#: help:ir.sequence,prefix:0
@ -14946,7 +14951,7 @@ msgstr "Sejšeli"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_4
msgid "Gold"
msgstr ""
msgstr "Zlatni"
#. module: base
#: code:addons/base/res/res_company.py:159
@ -14973,7 +14978,7 @@ msgstr "Opći podaci"
#. module: base
#: field:ir.model.data,complete_name:0
msgid "Complete ID"
msgstr ""
msgstr "Puni ID"
#. module: base
#: model:res.country,name:base.tc
@ -14985,7 +14990,7 @@ msgstr "Turks and Caicos Islands"
msgid ""
"Tax Identification Number. Check the box if this contact is subjected to "
"taxes. Used by the some of the legal statements."
msgstr ""
msgstr "Porezni broj (OIB) ."
#. module: base
#: field:res.partner.bank,partner_id:0
@ -15070,7 +15075,7 @@ msgstr ""
#. module: base
#: view:res.partner:0
msgid "Internal Notes"
msgstr ""
msgstr "Interne bilješke"
#. module: base
#: selection:res.partner.address,type:0
@ -15086,7 +15091,7 @@ msgstr "Corp."
#. module: base
#: model:ir.module.module,shortdesc:base.module_purchase_requisition
msgid "Purchase Requisitions"
msgstr ""
msgstr "Natječaji u nabavi"
#. module: base
#: selection:ir.actions.act_window,target:0
@ -15112,7 +15117,7 @@ msgstr "Partneri: "
#. module: base
#: view:res.partner:0
msgid "Is a Company?"
msgstr ""
msgstr "Je tvrtka="
#. module: base
#: code:addons/base/res/res_company.py:159
@ -15134,7 +15139,7 @@ msgstr "Kreiraj objekt"
#. module: base
#: model:res.country,name:base.ss
msgid "South Sudan"
msgstr ""
msgstr "Južni Sudan"
#. module: base
#: field:ir.filters,context:0

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-03 16:01+0000\n"
"PO-Revision-Date: 2012-12-01 13:29+0000\n"
"Last-Translator: Sergio Corato <Unknown>\n"
"PO-Revision-Date: 2012-12-09 12:05+0000\n"
"Last-Translator: Davide Corio - agilebg.com <davide.corio@agilebg.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-12-04 04:57+0000\n"
"X-Generator: Launchpad (build 16335)\n"
"X-Launchpad-Export-Date: 2012-12-10 04:36+0000\n"
"X-Generator: Launchpad (build 16341)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -92,7 +92,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,summary:base.module_point_of_sale
msgid "Touchscreen Interface for Shops"
msgstr "Interfaccia Touchscreen per Negozzi"
msgstr "Interfaccia Touchscreen per Negozi"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_in_hr_payroll
@ -140,6 +140,9 @@ msgid ""
"The module adds google user in res user.\n"
"========================================\n"
msgstr ""
"\n"
"Aggiunge lo username Google a res.user.\n"
"========================================\n"
#. module: base
#: help:res.partner,employee:0
@ -322,7 +325,7 @@ msgstr "Gestione Vendite"
msgid ""
"The internal user that is in charge of communicating with this contact if "
"any."
msgstr ""
msgstr "L'utente responsabile delle comunicazioni con questo contatto."
#. module: base
#: view:res.partner:0

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-03 16:01+0000\n"
"PO-Revision-Date: 2012-12-04 13:10+0000\n"
"PO-Revision-Date: 2012-12-07 10:59+0000\n"
"Last-Translator: Rui Franco (multibase.pt) <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-12-05 05:18+0000\n"
"X-Generator: Launchpad (build 16335)\n"
"X-Launchpad-Export-Date: 2012-12-08 04:58+0000\n"
"X-Generator: Launchpad (build 16341)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -268,7 +268,7 @@ msgstr "Criado"
#. module: base
#: field:ir.actions.report.xml,report_xsl:0
msgid "XSL Path"
msgstr ""
msgstr "Caminho do XSL"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_tr
@ -570,7 +570,7 @@ msgstr ""
#. module: base
#: view:workflow.transition:0
msgid "Workflow Transition"
msgstr ""
msgstr "Transição do fluxo de trabalho"
#. module: base
#: model:res.country,name:base.gf
@ -580,7 +580,7 @@ msgstr "Guiana Francesa"
#. module: base
#: model:ir.module.module,summary:base.module_hr
msgid "Jobs, Departments, Employees Details"
msgstr ""
msgstr "Funções, departamentos, informações sobre os empregados"
#. module: base
#: model:ir.module.module,description:base.module_analytic
@ -872,7 +872,7 @@ msgstr "Roménia - Contabilidade"
#. module: base
#: model:ir.model,name:base.model_res_config_settings
msgid "res.config.settings"
msgstr ""
msgstr "res.config.settings"
#. module: base
#: help:res.partner,image_small:0
@ -1271,7 +1271,7 @@ msgstr "Ilhas Caimão"
#. module: base
#: view:ir.rule:0
msgid "Record Rule"
msgstr ""
msgstr "Regra de gravação"
#. module: base
#: model:res.country,name:base.kr
@ -1314,7 +1314,7 @@ msgstr "Visível"
#. module: base
#: model:ir.actions.client,name:base.action_client_base_menu
msgid "Open Settings Menu"
msgstr ""
msgstr "Abrir o menu de configuração"
#. module: base
#: selection:base.language.install,lang:0
@ -1616,7 +1616,7 @@ msgstr "%Y - ."
#. module: base
#: view:res.company:0
msgid "Report Footer Configuration"
msgstr ""
msgstr "Configuração do rodapé de relatório"
#. module: base
#: field:ir.translation,comments:0
@ -1775,7 +1775,7 @@ msgstr ""
#. module: base
#: field:res.partner,image_small:0
msgid "Small-sized image"
msgstr ""
msgstr "Imagem pequena"
#. module: base
#: model:ir.module.module,shortdesc:base.module_stock
@ -1897,7 +1897,7 @@ msgstr ""
#. module: base
#: selection:ir.translation,state:0
msgid "Translation in Progress"
msgstr ""
msgstr "Tradução em progresso"
#. module: base
#: model:ir.model,name:base.model_ir_rule
@ -1912,7 +1912,7 @@ msgstr "Dias"
#. module: base
#: model:ir.module.module,summary:base.module_fleet
msgid "Vehicle, leasing, insurances, costs"
msgstr ""
msgstr "Veículo, 'leasing', seguros, custos"
#. module: base
#: view:ir.model.access:0
@ -1999,7 +1999,7 @@ msgstr "Criar tarefas no SO"
#: code:addons/base/ir/ir_model.py:316
#, python-format
msgid "This column contains module data and cannot be removed!"
msgstr ""
msgstr "Esta coluna contém dados do módulo e não pode ser removida!"
#. module: base
#: field:ir.attachment,res_model:0
@ -2744,7 +2744,7 @@ msgstr "ID do Registo"
#. module: base
#: view:ir.filters:0
msgid "My Filters"
msgstr ""
msgstr "Os meus filtros"
#. module: base
#: field:ir.actions.server,email:0
@ -2797,7 +2797,7 @@ msgstr "Argumentos enviados ao cliente junto com a tag vista"
#. module: base
#: model:ir.module.module,summary:base.module_contacts
msgid "Contacts, People and Companies"
msgstr ""
msgstr "Contactos, pessoas e empresas"
#. module: base
#: model:res.country,name:base.tt
@ -2830,7 +2830,7 @@ msgstr "Gestor"
#: code:addons/base/ir/ir_model.py:718
#, python-format
msgid "Sorry, you are not allowed to access this document."
msgstr ""
msgstr "Infelizmente, não está autorizado a aceder a este documento."
#. module: base
#: model:res.country,name:base.py
@ -2845,7 +2845,7 @@ msgstr "Fiji"
#. module: base
#: view:ir.actions.report.xml:0
msgid "Report Xml"
msgstr ""
msgstr "XML de Relatório"
#. module: base
#: model:ir.module.module,description:base.module_purchase
@ -2916,7 +2916,7 @@ msgstr "Herdado"
#: code:addons/base/ir/ir_fields.py:147
#, python-format
msgid "yes"
msgstr ""
msgstr "sim"
#. module: base
#: field:ir.model.fields,serialization_field_id:0
@ -3007,7 +3007,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_linkedin
msgid "LinkedIn Integration"
msgstr ""
msgstr "Integração com o LinkedIn"
#. module: base
#: code:addons/orm.py:2021
@ -3252,7 +3252,7 @@ msgstr "Suécia"
#. module: base
#: field:ir.actions.report.xml,report_file:0
msgid "Report File"
msgstr ""
msgstr "Ficheiro de relatório"
#. module: base
#: selection:ir.actions.act_window.view,view_mode:0
@ -3285,7 +3285,7 @@ msgstr ""
#: code:addons/orm.py:3839
#, python-format
msgid "Missing document(s)"
msgstr ""
msgstr "Documento(s) em falta"
#. module: base
#: model:ir.model,name:base.model_res_partner_bank_type
@ -3300,6 +3300,8 @@ msgid ""
"For more details about translating OpenERP in your language, please refer to "
"the"
msgstr ""
"Para mais informações acerca da tradução do OpenERP para o seu idioma, por "
"favor, veja em"
#. module: base
#: field:res.partner,image:0
@ -3486,7 +3488,7 @@ msgstr "workflow.activity"
#. module: base
#: view:base.language.export:0
msgid "Export Complete"
msgstr ""
msgstr "Exportação concluída"
#. module: base
#: help:ir.ui.view_sc,res_id:0
@ -3577,6 +3579,8 @@ msgstr "Malta"
msgid ""
"Only users with the following access level are currently allowed to do that"
msgstr ""
"Apenas utilizadores com o nível de acesso indicado de seguida estão "
"autorizados a fazer isso"
#. module: base
#: field:ir.actions.server,fields_lines:0
@ -3667,7 +3671,7 @@ msgstr "Antártida"
#. module: base
#: view:res.partner:0
msgid "Persons"
msgstr ""
msgstr "Pessoas"
#. module: base
#: view:base.language.import:0
@ -4144,7 +4148,7 @@ msgstr "Partilhar qualquer documento"
#. module: base
#: model:ir.module.module,summary:base.module_crm
msgid "Leads, Opportunities, Phone Calls"
msgstr ""
msgstr "Dicas, oportunidades, telefonemas"
#. module: base
#: view:res.lang:0
@ -4269,7 +4273,7 @@ msgstr "Campo base"
#. module: base
#: model:ir.module.category,name:base.module_category_managing_vehicles_and_contracts
msgid "Managing vehicles and contracts"
msgstr ""
msgstr "Gestão de veículos e contratos"
#. module: base
#: model:ir.module.module,description:base.module_base_setup
@ -4519,7 +4523,7 @@ msgstr "Tipo de Sequência"
#. module: base
#: view:base.language.export:0
msgid "Unicode/UTF-8"
msgstr ""
msgstr "Unicode/UTF-8"
#. module: base
#: selection:base.language.install,lang:0
@ -4531,12 +4535,12 @@ msgstr "Hindi / हिंदी"
#: model:ir.actions.act_window,name:base.action_view_base_language_install
#: model:ir.ui.menu,name:base.menu_view_base_language_install
msgid "Load a Translation"
msgstr ""
msgstr "Carregar uma tradução"
#. module: base
#: field:ir.module.module,latest_version:0
msgid "Installed Version"
msgstr ""
msgstr "Versão instalada"
#. module: base
#: field:ir.module.module,license:0
@ -4679,7 +4683,7 @@ msgstr "ir.actions.report.xml"
#. module: base
#: model:res.country,name:base.ps
msgid "Palestinian Territory, Occupied"
msgstr ""
msgstr "Palestina"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_ch
@ -4836,12 +4840,12 @@ msgstr ""
#. module: base
#: selection:base.language.install,lang:0
msgid "Portuguese (BR) / Português (BR)"
msgstr ""
msgstr "Português (BR)"
#. module: base
#: model:ir.model,name:base.model_ir_needaction_mixin
msgid "ir.needaction_mixin"
msgstr ""
msgstr "ir.needaction_mixin"
#. module: base
#: view:base.language.export:0
@ -4861,13 +4865,13 @@ msgstr "Aplicações para uma Indústria Específica"
#. module: base
#: model:ir.module.module,shortdesc:base.module_google_docs
msgid "Google Docs integration"
msgstr ""
msgstr "Integração com o Google Docs"
#. module: base
#: code:addons/base/ir/ir_fields.py:328
#, python-format
msgid "name"
msgstr ""
msgstr "nome"
#. module: base
#: model:ir.module.module,description:base.module_mrp_operations
@ -4923,7 +4927,7 @@ msgstr "Lesoto"
#. module: base
#: view:base.language.export:0
msgid ", or your preferred text editor"
msgstr ""
msgstr ", ou o seu editor de texto preferido"
#. module: base
#: model:ir.module.module,shortdesc:base.module_crm_partner_assign
@ -4990,12 +4994,12 @@ msgstr "Definir como Nulo"
#. module: base
#: view:res.users:0
msgid "Save"
msgstr ""
msgstr "Gravar"
#. module: base
#: field:ir.actions.report.xml,report_xml:0
msgid "XML Path"
msgstr ""
msgstr "Caminho do XML"
#. module: base
#: model:res.country,name:base.bj
@ -5077,7 +5081,7 @@ msgstr "Segurança"
#. module: base
#: selection:base.language.install,lang:0
msgid "Portuguese / Português"
msgstr ""
msgstr "Português"
#. module: base
#: code:addons/base/ir/ir_model.py:364
@ -5168,7 +5172,7 @@ msgstr "Taxas"
#. module: base
#: model:ir.module.module,shortdesc:base.module_email_template
msgid "Email Templates"
msgstr ""
msgstr "Modelos de mensagens"
#. module: base
#: model:res.country,name:base.sy
@ -5273,7 +5277,7 @@ msgstr ""
#. module: base
#: view:res.company:0
msgid "Preview Header/Footer"
msgstr ""
msgstr "Antevisão do Cabeçalho/Rodapé"
#. module: base
#: field:ir.ui.menu,parent_id:0
@ -5311,7 +5315,7 @@ msgstr ""
#. module: base
#: model:ir.model,name:base.model_res_partner_address
msgid "res.partner.address"
msgstr ""
msgstr "res.partner.address"
#. module: base
#: view:ir.rule:0
@ -5350,7 +5354,7 @@ msgstr "Histórico"
#. module: base
#: model:res.country,name:base.im
msgid "Isle of Man"
msgstr ""
msgstr "Ilha de Man"
#. module: base
#: help:ir.actions.client,res_model:0
@ -5513,7 +5517,7 @@ msgstr ""
#. module: base
#: model:res.groups,name:base.group_sale_salesman_all_leads
msgid "See all Leads"
msgstr ""
msgstr "Ver todas as dicas"
#. module: base
#: model:res.country,name:base.ci
@ -5590,7 +5594,7 @@ msgstr "Configuração de precisão decimal"
#: model:ir.model,name:base.model_ir_actions_act_url
#: selection:ir.ui.menu,action:0
msgid "ir.actions.act_url"
msgstr ""
msgstr "ir.actions.act_ur"
#. module: base
#: model:ir.ui.menu,name:base.menu_translation_app
@ -5743,7 +5747,7 @@ msgstr ""
#. module: base
#: view:ir.translation:0
msgid "Comments"
msgstr ""
msgstr "Comentários"
#. module: base
#: model:res.country,name:base.et
@ -5753,7 +5757,7 @@ msgstr "Etiópia"
#. module: base
#: model:ir.module.category,name:base.module_category_authentication
msgid "Authentication"
msgstr ""
msgstr "Autenticação"
#. module: base
#: model:res.country,name:base.sj
@ -5787,7 +5791,7 @@ msgstr "Título"
#: code:addons/base/ir/ir_fields.py:147
#, python-format
msgid "true"
msgstr ""
msgstr "verdadeiro"
#. module: base
#: model:ir.model,name:base.model_base_language_install
@ -5797,7 +5801,7 @@ msgstr "Instalar Idioma"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_11
msgid "Services"
msgstr ""
msgstr "Serviços"
#. module: base
#: view:ir.translation:0
@ -5901,7 +5905,7 @@ msgstr ""
#: code:addons/base/ir/workflow/workflow.py:99
#, python-format
msgid "Operation forbidden"
msgstr ""
msgstr "Operação proibida"
#. module: base
#: view:ir.actions.server:0
@ -5993,7 +5997,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_be_hr_payroll
msgid "Belgium - Payroll"
msgstr ""
msgstr "Bélgica - folha de pagamentos"
#. module: base
#: view:workflow.activity:0
@ -6025,7 +6029,7 @@ msgstr "Nome do Recurso"
#. module: base
#: field:res.partner,is_company:0
msgid "Is a Company"
msgstr ""
msgstr "É uma empresa"
#. module: base
#: selection:ir.cron,interval_type:0
@ -6076,6 +6080,7 @@ msgstr ""
msgid ""
"Administrative divisions of a country. E.g. Fed. State, Departement, Canton"
msgstr ""
"Divisões administrativas de um país. Exemplos: Distrito, Concelho, Freguesia"
#. module: base
#: view:res.partner.bank:0
@ -6105,7 +6110,7 @@ msgstr ""
#. module: base
#: sql_constraint:ir.filters:0
msgid "Filter names must be unique"
msgstr ""
msgstr "Os nomes dos filtros devem ser únicos"
#. module: base
#: help:multi_company.default,object_id:0
@ -6120,7 +6125,7 @@ msgstr ""
#. module: base
#: field:ir.filters,is_default:0
msgid "Default filter"
msgstr ""
msgstr "Filtro padrão"
#. module: base
#: report:ir.module.reference:0
@ -6237,7 +6242,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_auth_reset_password
msgid "Reset Password"
msgstr ""
msgstr "Repor a senha"
#. module: base
#: view:ir.attachment:0
@ -6254,7 +6259,7 @@ msgstr "Malásia"
#: code:addons/base/ir/ir_sequence.py:131
#, python-format
msgid "Increment number must not be zero."
msgstr ""
msgstr "O valor de incremento não pode ser zero."
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_cancel
@ -6454,7 +6459,7 @@ msgstr "ir.cron"
#. module: base
#: model:res.country,name:base.cw
msgid "Curaçao"
msgstr ""
msgstr "Curaçao"
#. module: base
#: view:ir.sequence:0
@ -6601,7 +6606,7 @@ msgstr "Israel"
#: code:addons/base/res/res_config.py:444
#, python-format
msgid "Cannot duplicate configuration!"
msgstr ""
msgstr "Não se pode duplicar a configuração!"
#. module: base
#: model:ir.module.module,shortdesc:base.module_l10n_syscohada
@ -6655,7 +6660,7 @@ msgstr ""
#. module: base
#: model:res.groups,name:base.group_partner_manager
msgid "Contact Creation"
msgstr ""
msgstr "Criação de contacto"
#. module: base
#: view:ir.module.module:0
@ -6711,7 +6716,7 @@ msgstr ""
#. module: base
#: view:ir.sequence:0
msgid "Week of the Year: %(woy)s"
msgstr ""
msgstr "Semana (do ano): %(woy)s"
#. module: base
#: field:res.users,id:0
@ -6780,7 +6785,7 @@ msgstr "Títulos do Parceiro"
#: code:addons/base/ir/ir_fields.py:228
#, python-format
msgid "Use the format '%s'"
msgstr ""
msgstr "Use o formato '%s'"
#. module: base
#: help:ir.actions.act_window,auto_refresh:0
@ -6812,12 +6817,12 @@ msgstr "WorkItems"
#: code:addons/base/res/res_bank.py:195
#, python-format
msgid "Invalid Bank Account Type Name format."
msgstr ""
msgstr "Formato inválido para o nome do tipo de conta bancária."
#. module: base
#: view:ir.filters:0
msgid "Filters visible only for one user"
msgstr ""
msgstr "Filtros visíveis só para o utilizador"
#. module: base
#: model:ir.model,name:base.model_ir_attachment
@ -6837,7 +6842,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_auth_anonymous
msgid "Anonymous"
msgstr ""
msgstr "Anónimo"
#. module: base
#: model:ir.module.module,description:base.module_base_import
@ -6993,7 +6998,7 @@ msgstr "Somália"
#. module: base
#: model:res.partner.title,shortcut:base.res_partner_title_doctor
msgid "Dr."
msgstr ""
msgstr "Dr."
#. module: base
#: model:res.groups,name:base.group_user
@ -7058,7 +7063,7 @@ msgstr "Copiar de"
#. module: base
#: field:ir.model.data,display_name:0
msgid "Record Name"
msgstr ""
msgstr "Nome do registo"
#. module: base
#: model:ir.model,name:base.model_ir_actions_client
@ -7074,7 +7079,7 @@ msgstr "Território Britânico do Oceano Índico"
#. module: base
#: model:ir.actions.server,name:base.action_server_module_immediate_install
msgid "Module Immediate Install"
msgstr ""
msgstr "Instalação imediata do módulo"
#. module: base
#: view:ir.actions.server:0
@ -7201,7 +7206,7 @@ msgstr "Em múltiplos docs"
#: view:res.users:0
#: view:wizard.ir.model.menu.create:0
msgid "or"
msgstr ""
msgstr "ou"
#. module: base
#: model:ir.module.module,shortdesc:base.module_account_accountant
@ -7344,7 +7349,7 @@ msgstr ""
#. module: base
#: model:res.partner.category,name:base.res_partner_category_14
msgid "Manufacturer"
msgstr ""
msgstr "Fabricante"
#. module: base
#: help:res.users,company_id:0
@ -7794,7 +7799,7 @@ msgstr "init"
#: view:res.partner:0
#: field:res.partner,user_id:0
msgid "Salesperson"
msgstr ""
msgstr "Vendedor"
#. module: base
#: view:res.lang:0
@ -7852,7 +7857,7 @@ msgstr "Password opcional para autenticação SMTP"
#: code:addons/base/ir/ir_model.py:719
#, python-format
msgid "Sorry, you are not allowed to modify this document."
msgstr ""
msgstr "Infelizmente, não pode alterar este documento."
#. module: base
#: code:addons/base/res/res_config.py:350
@ -7942,7 +7947,7 @@ msgstr "Francês (CH) / Français (CH)"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_13
msgid "Distributor"
msgstr ""
msgstr "Distribuidor"
#. module: base
#: help:ir.actions.server,subject:0
@ -8157,7 +8162,7 @@ msgstr "Contabilidade Analítica"
#. module: base
#: model:ir.model,name:base.model_ir_model_constraint
msgid "ir.model.constraint"
msgstr ""
msgstr "ir.model.constraint"
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_graph
@ -8190,7 +8195,7 @@ msgstr "Bélgica - Contabilidade"
#. module: base
#: view:ir.model.access:0
msgid "Access Control"
msgstr ""
msgstr "Controlo de acesso"
#. module: base
#: model:res.country,name:base.kw
@ -8294,7 +8299,7 @@ msgstr "Filipinas"
#. module: base
#: model:ir.module.module,summary:base.module_hr_timesheet_sheet
msgid "Timesheets, Attendances, Activities"
msgstr ""
msgstr "Folhas de horas, assiduidade, atividades"
#. module: base
#: model:res.country,name:base.ma
@ -8407,7 +8412,7 @@ msgstr "Relatório de introspeção em objetos"
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_analytics
msgid "Google Analytics"
msgstr ""
msgstr "Google Analytics"
#. module: base
#: model:ir.module.module,description:base.module_note
@ -8435,7 +8440,7 @@ msgstr "Dominica"
#. module: base
#: field:ir.translation,name:0
msgid "Translated field"
msgstr ""
msgstr "Traduzir campo"
#. module: base
#: model:ir.module.module,shortdesc:base.module_stock_location
@ -8649,7 +8654,7 @@ msgstr ""
#. module: base
#: view:base.language.export:0
msgid "documentation"
msgstr ""
msgstr "documentação"
#. module: base
#: help:ir.model,osv_memory:0
@ -8694,7 +8699,7 @@ msgstr "%b - Nome do mês abreviado."
#: code:addons/base/ir/ir_model.py:721
#, python-format
msgid "Sorry, you are not allowed to delete this document."
msgstr ""
msgstr "Infelizmente, não pode apagar este documento."
#. module: base
#: constraint:ir.rule:0
@ -8768,7 +8773,7 @@ msgstr "Samoa Americana"
#. module: base
#: view:ir.attachment:0
msgid "My Document(s)"
msgstr ""
msgstr "Os meus documentos"
#. module: base
#: help:ir.actions.act_window,res_model:0
@ -8850,7 +8855,7 @@ msgstr ""
#. module: base
#: field:res.partner.title,shortcut:0
msgid "Abbreviation"
msgstr ""
msgstr "Abreviatura"
#. module: base
#: model:ir.ui.menu,name:base.menu_crm_case_job_req_main
@ -9047,7 +9052,7 @@ msgstr ""
#. module: base
#: model:ir.module.category,name:base.module_category_warehouse_management
msgid "Warehouse"
msgstr ""
msgstr "Armazém"
#. module: base
#: field:ir.exports,resource:0
@ -9096,7 +9101,7 @@ msgstr "Relatório"
#. module: base
#: model:res.partner.title,shortcut:base.res_partner_title_prof
msgid "Prof."
msgstr ""
msgstr "Prof."
#. module: base
#: code:addons/base/ir/ir_mail_server.py:239
@ -9228,7 +9233,7 @@ msgstr "Ref. do Parceiro"
#. module: base
#: model:ir.module.module,shortdesc:base.module_hr_expense
msgid "Expense Management"
msgstr ""
msgstr "Gestão de despesas"
#. module: base
#: field:ir.attachment,create_date:0
@ -9307,7 +9312,7 @@ msgstr "Modelos"
#: code:addons/base/module/module.py:472
#, python-format
msgid "The `base` module cannot be uninstalled"
msgstr ""
msgstr "O módulo \"base\" não pode ser desinstalado"
#. module: base
#: code:addons/base/ir/ir_cron.py:390
@ -9334,7 +9339,7 @@ msgstr "osv_memory.autovacuum"
#: code:addons/base/ir/ir_model.py:720
#, python-format
msgid "Sorry, you are not allowed to create this kind of document."
msgstr ""
msgstr "Infelizmente, não pode criar este tipo de documento."
#. module: base
#: field:base.language.export,lang:0
@ -9376,7 +9381,7 @@ msgstr "%H - Hora (24-horas) [00,23]."
#. module: base
#: field:ir.model.fields,on_delete:0
msgid "On Delete"
msgstr ""
msgstr "Ao apagar"
#. module: base
#: code:addons/base/ir/ir_model.py:340
@ -9584,7 +9589,7 @@ msgstr "O código da moeda deve ser único por empresa!"
#: code:addons/base/module/wizard/base_export_language.py:38
#, python-format
msgid "New Language (Empty translation template)"
msgstr ""
msgstr "Novo idioma (modelo de tradução limpo)"
#. module: base
#: model:ir.module.module,description:base.module_auth_reset_password
@ -9654,7 +9659,7 @@ msgstr "Egipto"
#. module: base
#: view:ir.attachment:0
msgid "Creation"
msgstr ""
msgstr "Criação"
#. module: base
#: help:ir.actions.server,model_id:0
@ -9726,7 +9731,7 @@ msgstr ""
#. module: base
#: field:res.partner,use_parent_address:0
msgid "Use Company Address"
msgstr ""
msgstr "Usar o endereço da empresa"
#. module: base
#: model:ir.module.module,summary:base.module_hr_holidays
@ -10107,7 +10112,7 @@ msgstr "Gravar Regras"
#. module: base
#: view:multi_company.default:0
msgid "Multi Company"
msgstr ""
msgstr "Multi-empresas"
#. module: base
#: model:ir.module.category,name:base.module_category_portal
@ -10118,13 +10123,13 @@ msgstr "Portal"
#. module: base
#: selection:ir.translation,state:0
msgid "To Translate"
msgstr ""
msgstr "Para traduzir"
#. module: base
#: code:addons/base/ir/ir_fields.py:295
#, python-format
msgid "See all possible values"
msgstr ""
msgstr "Ver todos os valores possíveis"
#. module: base
#: model:ir.module.module,description:base.module_claim_from_delivery
@ -10291,7 +10296,7 @@ msgstr "Itália"
#. module: base
#: model:res.groups,name:base.group_sale_salesman
msgid "See Own Leads"
msgstr ""
msgstr "Ver as próprias dicas"
#. module: base
#: view:ir.actions.todo:0
@ -10523,7 +10528,7 @@ msgstr ""
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale_stock
msgid "Sales and Warehouse Management"
msgstr ""
msgstr "Gestão de vendas e de armazém"
#. module: base
#: field:ir.model.fields,model:0
@ -10664,7 +10669,7 @@ msgstr ""
#: code:addons/base/ir/ir_model.py:1023
#, python-format
msgid "Permission Denied"
msgstr ""
msgstr "Permissão negada"
#. module: base
#: field:ir.ui.menu,child_id:0
@ -10809,7 +10814,7 @@ msgstr ""
#. module: base
#: model:res.country,name:base.bl
msgid "Saint Barthélémy"
msgstr ""
msgstr "Saint Barthélémy"
#. module: base
#: selection:ir.module.module,license:0
@ -10890,12 +10895,12 @@ msgstr ""
#: model:ir.actions.act_window,name:base.action_inventory_form
#: model:ir.ui.menu,name:base.menu_action_inventory_form
msgid "Default Company per Object"
msgstr ""
msgstr "Empresa padrão por objeto"
#. module: base
#: field:ir.ui.menu,needaction_counter:0
msgid "Number of actions the user has to perform"
msgstr ""
msgstr "Número de ações que o utilizador tem de realizar"
#. module: base
#: model:ir.module.module,shortdesc:base.module_web_hello
@ -11195,7 +11200,7 @@ msgstr "Operações de Produção"
#. module: base
#: view:base.language.export:0
msgid "Here is the exported translation file:"
msgstr ""
msgstr "Aqui está o ficheiro de tradução:"
#. module: base
#: field:ir.actions.report.xml,report_rml_content:0
@ -11465,7 +11470,7 @@ msgstr "ir.fields.converter"
#: code:addons/base/res/res_partner.py:436
#, python-format
msgid "Couldn't create contact without email address !"
msgstr ""
msgstr "Não foi possível criar o contacto sem o endereço eletrónico!"
#. module: base
#: model:ir.module.category,name:base.module_category_manufacturing
@ -11717,7 +11722,7 @@ msgstr "UK - Contabilidade"
#. module: base
#: model:res.partner.title,shortcut:base.res_partner_title_madam
msgid "Mrs."
msgstr ""
msgstr "Srª."
#. module: base
#: code:addons/base/ir/ir_model.py:424
@ -11753,7 +11758,7 @@ msgstr "Repetição da Expressão"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_16
msgid "Retailer"
msgstr ""
msgstr "Revendedor"
#. module: base
#: view:ir.model.fields:0

File diff suppressed because it is too large Load Diff

View File

@ -42,7 +42,7 @@ class actions(osv.osv):
_order = 'name'
_columns = {
'name': fields.char('Name', size=64, required=True),
'type': fields.char('Action Type', required=True, size=32,readonly=True),
'type': fields.char('Action Type', required=True, size=32),
'usage': fields.char('Action Usage', size=32),
'help': fields.text('Action description',
help='Optional help text for the users with a description of the target view, such as its usage and purpose.',

View File

@ -31,7 +31,6 @@ import netsvc
import openerp
import pooler
import tools
from openerp.cron import WAKE_UP_NOW
from osv import fields, osv
from tools import DEFAULT_SERVER_DATETIME_FORMAT
from tools.safe_eval import safe_eval as eval
@ -142,130 +141,6 @@ class ir_cron(osv.osv):
except Exception, e:
self._handle_callback_exception(cr, uid, model_name, method_name, args, job_id, e)
def _run_job(self, cr, job, now):
""" Run a given job taking care of the repetition.
The cursor has a lock on the job (aquired by _run_jobs_multithread()) and this
method is run in a worker thread (spawned by _run_jobs_multithread())).
:param job: job to be run (as a dictionary).
:param now: timestamp (result of datetime.now(), no need to call it multiple time).
"""
try:
nextcall = datetime.strptime(job['nextcall'], DEFAULT_SERVER_DATETIME_FORMAT)
numbercall = job['numbercall']
ok = False
while nextcall < now and numbercall:
if numbercall > 0:
numbercall -= 1
if not ok or job['doall']:
self._callback(cr, job['user_id'], job['model'], job['function'], job['args'], job['id'])
if numbercall:
nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
ok = True
addsql = ''
if not numbercall:
addsql = ', active=False'
cr.execute("UPDATE ir_cron SET nextcall=%s, numbercall=%s"+addsql+" WHERE id=%s",
(nextcall.strftime(DEFAULT_SERVER_DATETIME_FORMAT), numbercall, job['id']))
if numbercall:
# Reschedule our own main cron thread if necessary.
# This is really needed if this job runs longer than its rescheduling period.
nextcall = calendar.timegm(nextcall.timetuple())
openerp.cron.schedule_wakeup(nextcall, cr.dbname)
finally:
cr.commit()
cr.close()
openerp.cron.release_thread_slot()
def _run_jobs_multithread(self):
# TODO remove 'check' argument from addons/base_action_rule/base_action_rule.py
""" Process the cron jobs by spawning worker threads.
This selects in database all the jobs that should be processed. It then
tries to lock each of them and, if it succeeds, spawns a thread to run
the cron job (if it doesn't succeed, it means the job was already
locked to be taken care of by another thread).
The cursor used to lock the job in database is given to the worker
thread (which has to close it itself).
"""
db = self.pool.db
cr = db.cursor()
db_name = db.dbname
try:
jobs = {} # mapping job ids to jobs for all jobs being processed.
now = datetime.now()
# Careful to compare timestamps with 'UTC' - everything is UTC as of v6.1.
cr.execute("""SELECT * FROM ir_cron
WHERE numbercall != 0
AND active AND nextcall <= (now() at time zone 'UTC')
ORDER BY priority""")
for job in cr.dictfetchall():
if not openerp.cron.get_thread_slots():
break
jobs[job['id']] = job
task_cr = db.cursor()
try:
# Try to grab an exclusive lock on the job row from within the task transaction
acquired_lock = False
task_cr.execute("""SELECT *
FROM ir_cron
WHERE id=%s
FOR UPDATE NOWAIT""",
(job['id'],), log_exceptions=False)
acquired_lock = True
except psycopg2.OperationalError, e:
if e.pgcode == '55P03':
# Class 55: Object not in prerequisite state; 55P03: lock_not_available
_logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['name'])
continue
else:
# Unexpected OperationalError
raise
finally:
if not acquired_lock:
# we're exiting due to an exception while acquiring the lot
task_cr.close()
# Got the lock on the job row, now spawn a thread to execute it in the transaction with the lock
task_thread = threading.Thread(target=self._run_job, name=job['name'], args=(task_cr, job, now))
# force non-daemon task threads (the runner thread must be daemon, and this property is inherited by default)
task_thread.setDaemon(False)
openerp.cron.take_thread_slot()
task_thread.start()
_logger.debug('Cron execution thread for job `%s` spawned', job['name'])
# Find next earliest job ignoring currently processed jobs (by this and other cron threads)
find_next_time_query = """SELECT min(nextcall) AS min_next_call
FROM ir_cron WHERE numbercall != 0 AND active"""
if jobs:
cr.execute(find_next_time_query + " AND id NOT IN %s", (tuple(jobs.keys()),))
else:
cr.execute(find_next_time_query)
next_call = cr.dictfetchone()['min_next_call']
if next_call:
next_call = calendar.timegm(time.strptime(next_call, DEFAULT_SERVER_DATETIME_FORMAT))
else:
# no matching cron job found in database, re-schedule arbitrarily in 1 day,
# this delay will likely be modified when running jobs complete their tasks
next_call = time.time() + (24*3600)
openerp.cron.schedule_wakeup(next_call, db_name)
except Exception, ex:
_logger.warning('Exception in cron:', exc_info=True)
finally:
cr.commit()
cr.close()
def _process_job(self, cr, job):
""" Run a given job taking care of the repetition.
@ -365,19 +240,6 @@ class ir_cron(osv.osv):
return False
def update_running_cron(self, cr):
""" Schedule as soon as possible a wake-up for this database. """
# Verify whether the server is already started and thus whether we need to commit
# immediately our changes and restart the cron agent in order to apply the change
# immediately. The commit() is needed because as soon as the cron is (re)started it
# will query the database with its own cursor, possibly before the end of the
# current transaction.
# This commit() is not an issue in most cases, but we must absolutely avoid it
# when the server is only starting or loading modules (hence the test on pool._init).
if not self.pool._init:
cr.commit()
openerp.cron.schedule_wakeup(WAKE_UP_NOW, self.pool.db.dbname)
def _try_lock(self, cr, uid, ids, context=None):
"""Try to grab a dummy exclusive write-lock to the rows with the given ids,
to make sure a following write() or unlink() will not block due
@ -393,20 +255,16 @@ class ir_cron(osv.osv):
def create(self, cr, uid, vals, context=None):
res = super(ir_cron, self).create(cr, uid, vals, context=context)
self.update_running_cron(cr)
return res
def write(self, cr, uid, ids, vals, context=None):
self._try_lock(cr, uid, ids, context)
res = super(ir_cron, self).write(cr, uid, ids, vals, context=context)
self.update_running_cron(cr)
return res
def unlink(self, cr, uid, ids, context=None):
self._try_lock(cr, uid, ids, context)
res = super(ir_cron, self).unlink(cr, uid, ids, context=context)
self.update_running_cron(cr)
return res
ir_cron()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -43,7 +43,7 @@ class ir_ui_menu(osv.osv):
def __init__(self, *args, **kwargs):
self.cache_lock = threading.RLock()
self.clear_cache()
self._cache = {}
r = super(ir_ui_menu, self).__init__(*args, **kwargs)
self.pool.get('ir.model.access').register_cache_clearing_method(self._name, 'clear_cache')
return r
@ -51,6 +51,10 @@ class ir_ui_menu(osv.osv):
def clear_cache(self):
with self.cache_lock:
# radical but this doesn't frequently happen
if self._cache:
# Normally this is done by openerp.tools.ormcache
# but since we do not use it, set it by ourself.
self.pool._any_cache_cleared = True
self._cache = {}
def _filter_visible_menus(self, cr, uid, ids, context=None):
@ -265,17 +269,33 @@ class ir_ui_menu(osv.osv):
return res
def _get_needaction(self, cr, uid, ids, field_names, args, context=None):
def _get_needaction_enabled(self, cr, uid, ids, field_names, args, context=None):
""" needaction_enabled: tell whether the menu has a related action
that uses the needaction mechanism. """
res = dict.fromkeys(ids, False)
for menu in self.browse(cr, uid, ids, context=context):
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.res_model:
obj = self.pool.get(menu.action.res_model)
if obj and obj._needaction:
res[menu.id] = True
return res
def get_needaction_data(self, cr, uid, ids, context=None):
""" Return for each menu entry of ids :
- if it uses the needaction mechanism (needaction_enabled)
- the needaction counter of the related action, taking into account
the action domain
"""
res = {}
for menu in self.browse(cr, uid, ids, context=context):
res[menu.id] = {
'needaction_enabled': False,
'needaction_counter': False,
}
if menu.action and menu.action.type in ('ir.actions.act_window','ir.actions.client') and menu.action.res_model:
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.res_model:
obj = self.pool.get(menu.action.res_model)
if obj and obj._needaction:
if menu.action.type=='ir.actions.act_window':
if menu.action.type == 'ir.actions.act_window':
dom = menu.action.domain and eval(menu.action.domain, {'uid': uid}) or []
else:
dom = eval(menu.action.params_store or '{}', {'uid': uid}).get('domain')
@ -286,7 +306,7 @@ class ir_ui_menu(osv.osv):
_columns = {
'name': fields.char('Menu', size=64, required=True, translate=True),
'sequence': fields.integer('Sequence'),
'child_id' : fields.one2many('ir.ui.menu', 'parent_id','Child IDs'),
'child_id': fields.one2many('ir.ui.menu', 'parent_id', 'Child IDs'),
'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True),
'groups_id': fields.many2many('res.groups', 'ir_ui_menu_group_rel',
'menu_id', 'gid', 'Groups', help="If you have groups, the visibility of this menu will be based on these groups. "\
@ -296,11 +316,14 @@ class ir_ui_menu(osv.osv):
'icon': fields.selection(tools.icons, 'Icon', size=64),
'icon_pict': fields.function(_get_icon_pict, type='char', size=32),
'web_icon': fields.char('Web Icon File', size=128),
'web_icon_hover':fields.char('Web Icon File (hover)', size=128),
'web_icon_hover': fields.char('Web Icon File (hover)', size=128),
'web_icon_data': fields.function(_get_image_icon, string='Web Icon Image', type='binary', readonly=True, store=True, multi='icon'),
'web_icon_hover_data':fields.function(_get_image_icon, string='Web Icon Image (hover)', type='binary', readonly=True, store=True, multi='icon'),
'needaction_enabled': fields.function(_get_needaction, string='Target model uses the need action mechanism', type='boolean', help='If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false.', multi='_get_needaction'),
'needaction_counter': fields.function(_get_needaction, string='Number of actions the user has to perform', type='integer', help='If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform.', multi='_get_needaction'),
'web_icon_hover_data': fields.function(_get_image_icon, string='Web Icon Image (hover)', type='binary', readonly=True, store=True, multi='icon'),
'needaction_enabled': fields.function(_get_needaction_enabled,
type='boolean',
store=True,
string='Target model uses the need action mechanism',
help='If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false.'),
'action': fields.function(_action, fnct_inv=_action_inv,
type='reference', string='Action',
selection=[
@ -317,12 +340,12 @@ class ir_ui_menu(osv.osv):
return _('Error ! You can not create recursive Menu.')
_constraints = [
(osv.osv._check_recursion, _rec_message , ['parent_id'])
(osv.osv._check_recursion, _rec_message, ['parent_id'])
]
_defaults = {
'icon' : 'STOCK_OPEN',
'icon_pict': ('stock', ('STOCK_OPEN','ICON_SIZE_MENU')),
'sequence' : 10,
'icon': 'STOCK_OPEN',
'icon_pict': ('stock', ('STOCK_OPEN', 'ICON_SIZE_MENU')),
'sequence': 10,
}
_order = "sequence,id"

View File

@ -406,6 +406,8 @@ class module(osv.osv):
# Mark them to be installed.
if to_install_ids:
self.button_install(cr, uid, to_install_ids, context=context)
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return dict(ACTION_DICT, name=_('Install'))
def button_immediate_install(self, cr, uid, ids, context=None):

View File

@ -258,7 +258,7 @@
</group>
<group>
<field name="customer"/>
<field name="supplier" invisible="not context.get('default_supplier')"/>
<field name="supplier"/>
</group>
<group>
<field name="ref"/>

View File

@ -2,6 +2,8 @@ import logging
import sys
import openerp
from openerp import tools
from openerp.modules import module
_logger = logging.getLogger(__name__)
@ -32,8 +34,25 @@ import server
def main():
args = sys.argv[1:]
# The only shared option is '--addons-path=' needed to discover additional
# commands from modules
if len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith("-"):
tools.config.parse_config([args[0]])
args = args[1:]
# Default legacy command
command = "server"
# Subcommand discovery
if len(args) and not args[0].startswith("-"):
for m in module.get_modules():
m = 'openerp.addons.' + m
__import__(m)
#try:
#except Exception, e:
# raise
# print e
command = args[0]
args = args[1:]

View File

@ -35,10 +35,6 @@ must be used.
import deprecation
# Maximum number of threads processing concurrently cron jobs.
max_cron_threads = 4 # Actually the default value here is meaningless,
# look at tools.config for the default value.
# Paths to search for OpenERP addons.
addons_paths = []

View File

@ -1,212 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2011 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
""" Cron jobs scheduling
Cron jobs are defined in the ir_cron table/model. This module deals with all
cron jobs, for all databases of a single OpenERP server instance.
It defines a single master thread that will spawn (a bounded number of)
threads to process individual cron jobs.
The thread runs forever, checking every 60 seconds for new
'database wake-ups'. It maintains a heapq of database wake-ups. At each
wake-up, it will call ir_cron._run_jobs_multithread() for the given database. _run_jobs_multithread
will check the jobs defined in the ir_cron table and spawn accordingly threads
to process them.
This module's behavior depends on the following configuration variable:
openerp.conf.max_cron_threads.
"""
import heapq
import logging
import threading
import time
import openerp
import tools
_logger = logging.getLogger(__name__)
# Heapq of database wake-ups. Note that 'database wake-up' meaning is in
# the context of the cron management. This is not originally about loading
# a database, although having the database name in the queue will
# cause it to be loaded when the schedule time is reached, even if it was
# unloaded in the mean time. Normally a database's wake-up is cancelled by
# the RegistryManager when the database is unloaded - so this should not
# cause it to be reloaded.
#
# TODO: perhaps in the future we could consider a flag on ir.cron jobs
# that would cause database wake-up even if the database has not been
# loaded yet or was already unloaded (e.g. 'force_db_wakeup' or something)
#
# Each element is a triple (timestamp, database-name, boolean). The boolean
# specifies if the wake-up is canceled (so a wake-up can be canceled without
# relying on the heapq implementation detail; no need to remove the job from
# the heapq).
_wakeups = []
# Mapping of database names to the wake-up defined in the heapq,
# so that we can cancel the wake-up without messing with the heapq
# invariant: lookup the wake-up by database-name, then set
# its third element to True.
_wakeup_by_db = {}
# Re-entrant lock to protect the above _wakeups and _wakeup_by_db variables.
# We could use a simple (non-reentrant) lock if the runner function below
# was more fine-grained, but we are fine with the loop owning the lock
# while spawning a few threads.
_wakeups_lock = threading.RLock()
# Maximum number of threads allowed to process cron jobs concurrently. This
# variable is set by start_master_thread using openerp.conf.max_cron_threads.
_thread_slots = None
# A (non re-entrant) lock to protect the above _thread_slots variable.
_thread_slots_lock = threading.Lock()
# Sleep duration limits - must not loop too quickly, but can't sleep too long
# either, because a new job might be inserted in ir_cron with a much sooner
# execution date than current known ones. We won't see it until we wake!
MAX_SLEEP = 60 # 1 min
MIN_SLEEP = 1 # 1 sec
# Dummy wake-up timestamp that can be used to force a database wake-up asap
WAKE_UP_NOW = 1
def get_thread_slots():
""" Return the number of available thread slots """
return _thread_slots
def release_thread_slot():
""" Increment the number of available thread slots """
global _thread_slots
with _thread_slots_lock:
_thread_slots += 1
def take_thread_slot():
""" Decrement the number of available thread slots """
global _thread_slots
with _thread_slots_lock:
_thread_slots -= 1
def cancel(db_name):
""" Cancel the next wake-up of a given database, if any.
:param db_name: database name for which the wake-up is canceled.
"""
_logger.debug("Cancel next wake-up for database '%s'.", db_name)
with _wakeups_lock:
if db_name in _wakeup_by_db:
_wakeup_by_db[db_name][2] = True
def cancel_all():
""" Cancel all database wake-ups. """
_logger.debug("Cancel all database wake-ups")
global _wakeups
global _wakeup_by_db
with _wakeups_lock:
_wakeups = []
_wakeup_by_db = {}
def schedule_wakeup(timestamp, db_name):
""" Schedule a new wake-up for a database.
If an earlier wake-up is already defined, the new wake-up is discarded.
If another wake-up is defined, that wake-up is discarded and the new one
is scheduled.
:param db_name: database name for which a new wake-up is scheduled.
:param timestamp: when the wake-up is scheduled.
"""
if not timestamp:
return
with _wakeups_lock:
if db_name in _wakeup_by_db:
task = _wakeup_by_db[db_name]
if not task[2] and timestamp > task[0]:
# existing wakeup is valid and occurs earlier than new one
return
task[2] = True # cancel existing task
task = [timestamp, db_name, False]
heapq.heappush(_wakeups, task)
_wakeup_by_db[db_name] = task
_logger.debug("Wake-up scheduled for database '%s' @ %s", db_name,
'NOW' if timestamp == WAKE_UP_NOW else timestamp)
def runner():
"""Neverending function (intended to be run in a dedicated thread) that
checks every 60 seconds the next database wake-up. TODO: make configurable
"""
while True:
runner_body()
def runner_body():
with _wakeups_lock:
while _wakeups and _wakeups[0][0] < time.time() and get_thread_slots():
task = heapq.heappop(_wakeups)
timestamp, db_name, canceled = task
if canceled:
continue
del _wakeup_by_db[db_name]
registry = openerp.pooler.get_pool(db_name)
if not registry._init:
_logger.debug("Database '%s' wake-up! Firing multi-threaded cron job processing", db_name)
registry['ir.cron']._run_jobs_multithread()
amount = MAX_SLEEP
with _wakeups_lock:
# Sleep less than MAX_SLEEP if the next known wake-up will happen before that.
if _wakeups and get_thread_slots():
amount = min(MAX_SLEEP, max(MIN_SLEEP, _wakeups[0][0] - time.time()))
_logger.debug("Going to sleep for %ss", amount)
time.sleep(amount)
def start_master_thread():
""" Start the above runner function in a daemon thread.
The thread is a typical daemon thread: it will never quit and must be
terminated when the main process exits - with no consequence (the processing
threads it spawns are not marked daemon).
"""
global _thread_slots
_thread_slots = openerp.conf.max_cron_threads
db_maxconn = tools.config['db_maxconn']
if _thread_slots >= tools.config.get('db_maxconn', 64):
_logger.warning("Connection pool size (%s) is set lower than max number of cron threads (%s), "
"this may cause trouble if you reach that number of parallel cron tasks.",
db_maxconn, _thread_slots)
t = threading.Thread(target=runner, name="openerp.cron.master_thread")
t.setDaemon(True)
t.start()
_logger.debug("Master cron daemon started!")
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -434,8 +434,9 @@ def get_modules():
return name
def is_really_module(name):
name = opj(dir, name)
return os.path.isdir(name) or zipfile.is_zipfile(name)
manifest_name = opj(dir, name, '__openerp__.py')
zipfile_name = opj(dir, name)
return os.path.isfile(manifest_name) or zipfile.is_zipfile(zipfile_name)
return map(clean, filter(is_really_module, os.listdir(dir)))
plist = []

View File

@ -28,7 +28,6 @@ import threading
import openerp.sql_db
import openerp.osv.orm
import openerp.cron
import openerp.tools
import openerp.modules.db
import openerp.tools.config
@ -58,6 +57,21 @@ class Registry(object):
self.db_name = db_name
self.db = openerp.sql_db.db_connect(db_name)
# In monoprocess cron jobs flag (pooljobs)
self.cron = False
# Inter-process signaling (used only when openerp.multi_process is True):
# The `base_registry_signaling` sequence indicates the whole registry
# must be reloaded.
# The `base_cache_signaling sequence` indicates all caches must be
# invalidated (i.e. cleared).
self.base_registry_signaling_sequence = 1
self.base_cache_signaling_sequence = 1
# Flag indicating if at least one model cache has been cleared.
# Useful only in a multi-process context.
self._any_cache_cleared = False
cr = self.db.cursor()
has_unaccent = openerp.modules.db.has_unaccent(cr)
if openerp.tools.config['unaccent'] and not has_unaccent:
@ -112,7 +126,7 @@ class Registry(object):
monitor the ir.cron model for future jobs. See openerp.cron for
details.
"""
openerp.cron.schedule_wakeup(openerp.cron.WAKE_UP_NOW, self.db.dbname)
self.cron = True
def clear_caches(self):
""" Clear the caches
@ -121,6 +135,36 @@ class Registry(object):
"""
for model in self.models.itervalues():
model.clear_caches()
# Special case for ir_ui_menu which does not use openerp.tools.ormcache.
ir_ui_menu = self.models.get('ir.ui.menu')
if ir_ui_menu:
ir_ui_menu.clear_cache()
# Useful only in a multi-process context.
def reset_any_cache_cleared(self):
self._any_cache_cleared = False
# Useful only in a multi-process context.
def any_cache_cleared(self):
return self._any_cache_cleared
@classmethod
def setup_multi_process_signaling(cls, cr):
if not openerp.multi_process:
return
# Inter-process signaling:
# The `base_registry_signaling` sequence indicates the whole registry
# must be reloaded.
# The `base_cache_signaling sequence` indicates all caches must be
# invalidated (i.e. cleared).
cr.execute("""SELECT sequence_name FROM information_schema.sequences WHERE sequence_name='base_registry_signaling'""")
if not cr.fetchall():
cr.execute("""CREATE SEQUENCE base_registry_signaling INCREMENT BY 1 START WITH 1""")
cr.execute("""SELECT nextval('base_registry_signaling')""")
cr.execute("""CREATE SEQUENCE base_cache_signaling INCREMENT BY 1 START WITH 1""")
cr.execute("""SELECT nextval('base_cache_signaling')""")
@contextmanager
def cursor(self, auto_commit=True):
@ -182,6 +226,7 @@ class RegistryManager(object):
cr = registry.db.cursor()
try:
Registry.setup_multi_process_signaling(cr)
registry.do_parent_store(cr)
registry.get('ir.actions.report.xml').register_all(cr)
cr.commit()
@ -195,20 +240,11 @@ class RegistryManager(object):
@classmethod
def delete(cls, db_name):
"""Delete the registry linked to a given database.
This also cleans the associated caches. For good measure this also
cancels the associated cron job. But please note that the cron job can
be running and take some time before ending, and that you should not
remove a registry if it can still be used by some thread. So it might
be necessary to call yourself openerp.cron.Agent.cancel(db_name) and
and join (i.e. wait for) the thread.
"""
"""Delete the registry linked to a given database. """
with cls.registries_lock:
if db_name in cls.registries:
cls.registries[db_name].clear_caches()
del cls.registries[db_name]
openerp.cron.cancel(db_name)
@classmethod
def delete_all(cls):
@ -232,5 +268,71 @@ class RegistryManager(object):
if db_name in cls.registries:
cls.registries[db_name].clear_caches()
@classmethod
def check_registry_signaling(cls, db_name):
if openerp.multi_process and db_name in cls.registries:
registry = cls.get(db_name, pooljobs=False)
cr = registry.db.cursor()
try:
cr.execute("""
SELECT base_registry_signaling.last_value,
base_cache_signaling.last_value
FROM base_registry_signaling, base_cache_signaling""")
r, c = cr.fetchone()
# Check if the model registry must be reloaded (e.g. after the
# database has been updated by another process).
if registry.base_registry_signaling_sequence != r:
_logger.info("Reloading the model registry after database signaling.")
# Don't run the cron in the Gunicorn worker.
registry = cls.new(db_name, pooljobs=False)
registry.base_registry_signaling_sequence = r
# Check if the model caches must be invalidated (e.g. after a write
# occured on another process). Don't clear right after a registry
# has been reload.
elif registry.base_cache_signaling_sequence != c:
_logger.info("Invalidating all model caches after database signaling.")
registry.base_cache_signaling_sequence = c
registry.clear_caches()
registry.reset_any_cache_cleared()
# One possible reason caches have been invalidated is the
# use of decimal_precision.write(), in which case we need
# to refresh fields.float columns.
for model in registry.models.values():
for column in model._columns.values():
if hasattr(column, 'digits_change'):
column.digits_change(cr)
finally:
cr.close()
@classmethod
def signal_caches_change(cls, db_name):
if openerp.multi_process and db_name in cls.registries:
# Check the registries if any cache has been cleared and signal it
# through the database to other processes.
registry = cls.get(db_name, pooljobs=False)
if registry.any_cache_cleared():
_logger.info("At least one model cache has been cleared, signaling through the database.")
cr = registry.db.cursor()
r = 1
try:
cr.execute("select nextval('base_cache_signaling')")
r = cr.fetchone()[0]
finally:
cr.close()
registry.base_cache_signaling_sequence = r
registry.reset_any_cache_cleared()
@classmethod
def signal_registry_change(cls, db_name):
if openerp.multi_process and db_name in cls.registries:
registry = cls.get(db_name, pooljobs=False)
cr = registry.db.cursor()
r = 1
try:
cr.execute("select nextval('base_registry_signaling')")
r = cr.fetchone()[0]
finally:
cr.close()
registry.base_registry_signaling_sequence = r
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -20,6 +20,9 @@
#
##############################################################################
#.apidoc title: Common Services: netsvc
#.apidoc module-mods: member-order: bysource
import errno
import logging
import logging.handlers
@ -40,6 +43,7 @@ import openerp
_logger = logging.getLogger(__name__)
def close_socket(sock):
""" Closes a socket instance cleanly
@ -58,18 +62,14 @@ def close_socket(sock):
raise
sock.close()
#.apidoc title: Common Services: netsvc
#.apidoc module-mods: member-order: bysource
def abort_response(dummy_1, description, dummy_2, details):
# TODO Replace except_{osv,orm} with these directly.
raise openerp.osv.osv.except_osv(description, details)
class Service(object):
""" Base class for *Local* services
Functionality here is trusted, no authentication.
""" Base class for Local services
Functionality here is trusted, no authentication.
Workflow engine and reports subclass this.
"""
_services = {}
def __init__(self, name):
@ -145,7 +145,6 @@ class ColoredFormatter(DBFormatter):
record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
return DBFormatter.format(self, record)
def init_logger():
from tools.translate import resetlocale
resetlocale()
@ -246,85 +245,6 @@ def init_alternative_logger():
logger.addHandler(handler)
logger.setLevel(logging.ERROR)
class Server:
""" Generic interface for all servers with an event loop etc.
Override this to impement http, net-rpc etc. servers.
Servers here must have threaded behaviour. start() must not block,
there is no run().
"""
__is_started = False
__servers = []
__starter_threads = []
# we don't want blocking server calls (think select()) to
# wait forever and possibly prevent exiting the process,
# but instead we want a form of polling/busy_wait pattern, where
# _server_timeout should be used as the default timeout for
# all I/O blocking operations
_busywait_timeout = 0.5
def __init__(self):
Server.__servers.append(self)
if Server.__is_started:
# raise Exception('All instances of servers must be inited before the startAll()')
# Since the startAll() won't be called again, allow this server to
# init and then start it after 1sec (hopefully). Register that
# timer thread in a list, so that we can abort the start if quitAll
# is called in the meantime
t = threading.Timer(1.0, self._late_start)
t.name = 'Late start timer for %s' % str(self.__class__)
Server.__starter_threads.append(t)
t.start()
def start(self):
_logger.debug("called stub Server.start")
def _late_start(self):
self.start()
for thr in Server.__starter_threads:
if thr.finished.is_set():
Server.__starter_threads.remove(thr)
def stop(self):
_logger.debug("called stub Server.stop")
def stats(self):
""" This function should return statistics about the server """
return "%s: No statistics" % str(self.__class__)
@classmethod
def startAll(cls):
if cls.__is_started:
return
_logger.info("Starting %d services" % len(cls.__servers))
for srv in cls.__servers:
srv.start()
cls.__is_started = True
@classmethod
def quitAll(cls):
if not cls.__is_started:
return
_logger.info("Stopping %d services" % len(cls.__servers))
for thr in cls.__starter_threads:
if not thr.finished.is_set():
thr.cancel()
cls.__starter_threads.remove(thr)
for srv in cls.__servers:
srv.stop()
cls.__is_started = False
@classmethod
def allStats(cls):
res = ["Servers %s" % ('stopped', 'started')[cls.__is_started]]
res.extend(srv.stats() for srv in cls.__servers)
return '\n'.join(res)
def _close_socket(self):
close_socket(self.socket)
def replace_request_password(args):
# password is always 3rd argument in a request, we replace it in RPC logs
# so it's easier to forward logs for diagnostics/debugging purposes...

View File

@ -163,7 +163,7 @@ class boolean(_column):
_logger.debug(
"required=True is deprecated: making a boolean field"
" `required` has no effect, as NULL values are "
"automatically turned into False.")
"automatically turned into False. args: %r",args)
class integer(_column):
_type = 'integer'

View File

@ -2509,6 +2509,7 @@ class BaseModel(object):
try:
getattr(self, '_ormcache')
self._ormcache = {}
self.pool._any_cache_cleared = True
except AttributeError:
pass
@ -3234,7 +3235,7 @@ class BaseModel(object):
def _create_table(self, cr):
cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id))' % (self._table,))
cr.execute(("COMMENT ON TABLE \"%s\" IS %%s" % self._table), (self._description,))
_schema.debug("Table '%s': created", self._table)
@ -3318,7 +3319,7 @@ class BaseModel(object):
raise except_orm('Programming Error', ('Many2Many destination model does not exist: `%s`') % (f._obj,))
dest_model = self.pool.get(f._obj)
ref = dest_model._table
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (m2m_tbl, col1, col2, col1, col2))
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s"))' % (m2m_tbl, col1, col2, col1, col2))
# create foreign key references with ondelete=cascade, unless the targets are SQL views
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (ref,))
if not cr.fetchall():

View File

@ -23,6 +23,8 @@
from functools import wraps
import logging
import threading
from psycopg2 import IntegrityError, errorcodes
import orm
@ -168,6 +170,7 @@ class object_proxy(object):
@check
def execute(self, db, uid, obj, method, *args, **kw):
threading.currentThread().dbname = db
cr = pooler.get_db(db).cursor()
try:
try:

View File

@ -28,16 +28,17 @@ import sys
import threading
import time
import cron
import netrpc_server
import web_services
import web_services
import wsgi_server
import openerp.cron
import openerp.modules
import openerp.netsvc
import openerp.osv
from openerp.release import nt_service_name
import openerp.tools
import openerp.service.wsgi_server
#.apidoc title: RPC Services
@ -73,37 +74,32 @@ def start_internal():
return
openerp.netsvc.init_logger()
openerp.modules.loading.open_openerp_namespace()
# Instantiate local services (this is a legacy design).
openerp.osv.osv.start_object_proxy()
# Export (for RPC) services.
web_services.start_web_services()
web_services.start_service()
load_server_wide_modules()
start_internal_done = True
def start_services():
""" Start all services including http, netrpc and cron """
start_internal()
# Initialize the HTTP stack.
netrpc_server.init_servers()
# Start the main cron thread.
if openerp.conf.max_cron_threads:
openerp.cron.start_master_thread()
# Start the top-level servers threads (normally HTTP, HTTPS, and NETRPC).
openerp.netsvc.Server.startAll()
# Initialize the NETRPC server.
netrpc_server.start_service()
# Start the WSGI server.
openerp.service.wsgi_server.start_server()
wsgi_server.start_service()
# Start the main cron thread.
cron.start_service()
def stop_services():
""" Stop all services. """
# stop scheduling new jobs; we will have to wait for the jobs to complete below
openerp.cron.cancel_all()
# stop services
cron.stop_service()
netrpc_server.stop_service()
wsgi_server.stop_service()
openerp.netsvc.Server.quitAll()
openerp.service.wsgi_server.stop_server()
_logger.info("Initiating shutdown")
_logger.info("Hit CTRL-C again or send a second signal to force the shutdown.")

74
openerp/service/cron.py Normal file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2011 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
""" Cron jobs scheduling
Cron jobs are defined in the ir_cron table/model. This module deals with all
cron jobs, for all databases of a single OpenERP server instance.
"""
import logging
import threading
import time
import openerp
_logger = logging.getLogger(__name__)
SLEEP_INTERVAL = 60 # 1 min
def cron_runner(number):
while True:
time.sleep(SLEEP_INTERVAL + number) # Steve Reich timing style
registries = openerp.modules.registry.RegistryManager.registries
_logger.debug('cron%d polling for jobs', number)
for db_name, registry in registries.items():
while True and registry.cron:
# acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
# TODO why isnt openerp.addons.base defined ?
import sys
base = sys.modules['addons.base']
acquired = base.ir.ir_cron.ir_cron._acquire_job(db_name)
if not acquired:
break
def start_service():
""" Start the above runner function in a daemon thread.
The thread is a typical daemon thread: it will never quit and must be
terminated when the main process exits - with no consequence (the processing
threads it spawns are not marked daemon).
"""
for i in range(openerp.tools.config['max_cron_threads']):
def target():
cron_runner(i)
t = threading.Thread(target=target, name="openerp.service.cron.cron%d" % i)
t.setDaemon(True)
t.start()
_logger.debug("cron%d started!" % i)
def stop_service():
pass
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -176,6 +176,4 @@ class OpenERPAuthProvider(AuthProvider):
self.auth_tries += 1
raise AuthRequiredExc(atype='Basic', realm=self.realm)
#eof
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -22,8 +22,6 @@
#.apidoc title: NET-RPC Server
""" This file contains instance of the net-rpc server
"""
import logging
import select
@ -38,6 +36,85 @@ import openerp.tools as tools
_logger = logging.getLogger(__name__)
class Server:
""" Generic interface for all servers with an event loop etc.
Override this to impement http, net-rpc etc. servers.
Servers here must have threaded behaviour. start() must not block,
there is no run().
"""
__is_started = False
__servers = []
__starter_threads = []
# we don't want blocking server calls (think select()) to
# wait forever and possibly prevent exiting the process,
# but instead we want a form of polling/busy_wait pattern, where
# _server_timeout should be used as the default timeout for
# all I/O blocking operations
_busywait_timeout = 0.5
def __init__(self):
Server.__servers.append(self)
if Server.__is_started:
# raise Exception('All instances of servers must be inited before the startAll()')
# Since the startAll() won't be called again, allow this server to
# init and then start it after 1sec (hopefully). Register that
# timer thread in a list, so that we can abort the start if quitAll
# is called in the meantime
t = threading.Timer(1.0, self._late_start)
t.name = 'Late start timer for %s' % str(self.__class__)
Server.__starter_threads.append(t)
t.start()
def start(self):
_logger.debug("called stub Server.start")
def _late_start(self):
self.start()
for thr in Server.__starter_threads:
if thr.finished.is_set():
Server.__starter_threads.remove(thr)
def stop(self):
_logger.debug("called stub Server.stop")
def stats(self):
""" This function should return statistics about the server """
return "%s: No statistics" % str(self.__class__)
@classmethod
def startAll(cls):
if cls.__is_started:
return
_logger.info("Starting %d services" % len(cls.__servers))
for srv in cls.__servers:
srv.start()
cls.__is_started = True
@classmethod
def quitAll(cls):
if not cls.__is_started:
return
_logger.info("Stopping %d services" % len(cls.__servers))
for thr in cls.__starter_threads:
if not thr.finished.is_set():
thr.cancel()
cls.__starter_threads.remove(thr)
for srv in cls.__servers:
srv.stop()
cls.__is_started = False
@classmethod
def allStats(cls):
res = ["Servers %s" % ('stopped', 'started')[cls.__is_started]]
res.extend(srv.stats() for srv in cls.__servers)
return '\n'.join(res)
def _close_socket(self):
netsvc.close_socket(self.socket)
class TinySocketClientThread(threading.Thread):
def __init__(self, sock, threads):
spn = sock and sock.getpeername()
@ -99,10 +176,10 @@ def netrpc_handle_exception_legacy(e):
return 'AccessDenied ' + str(e)
return openerp.tools.exception_to_unicode(e)
class TinySocketServerThread(threading.Thread,netsvc.Server):
class TinySocketServerThread(threading.Thread,Server):
def __init__(self, interface, port, secure=False):
threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port)
netsvc.Server.__init__(self)
Server.__init__(self)
self.__port = port
self.__interface = interface
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -157,11 +234,12 @@ class TinySocketServerThread(threading.Thread,netsvc.Server):
netrpcd = None
def init_servers():
def start_service():
global netrpcd
if tools.config.get('netrpc', False):
netrpcd = TinySocketServerThread(
tools.config.get('netrpc_interface', ''),
int(tools.config.get('netrpc_port', 8070)))
netrpcd = TinySocketServerThread(tools.config.get('netrpc_interface', ''), int(tools.config.get('netrpc_port', 8070)))
def stop_service():
Server.quitAll()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -168,6 +168,7 @@ class db(netsvc.ExportService):
def exp_duplicate_database(self, db_original_name, db_name):
_logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name)
sql_db.close_db(db_original_name)
db = sql_db.db_connect('postgres')
cr = db.cursor()
try:
@ -606,68 +607,13 @@ class objects_proxy(netsvc.ExportService):
raise NameError("Method not available %s" % method)
security.check(db,uid,passwd)
assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy."
openerp.modules.registry.RegistryManager.check_registry_signaling(db)
fn = getattr(openerp.osv.osv.service, method)
res = fn(db, uid, *params)
openerp.modules.registry.RegistryManager.signal_caches_change(db)
return res
#
# Wizard ID: 1
# - None = end of wizard
#
# Wizard Type: 'form'
# - form
# - print
#
# Wizard datas: {}
# TODO: change local request to OSE request/reply pattern
#
class wizard(netsvc.ExportService):
def __init__(self, name='wizard'):
netsvc.ExportService.__init__(self,name)
self.id = 0
self.wiz_datas = {}
self.wiz_name = {}
self.wiz_uid = {}
def dispatch(self, method, params):
(db, uid, passwd ) = params[0:3]
threading.current_thread().uid = uid
params = params[3:]
if method not in ['execute','create']:
raise KeyError("Method not supported %s" % method)
security.check(db,uid,passwd)
fn = getattr(self, 'exp_'+method)
res = fn(db, uid, *params)
return res
def _execute(self, db, uid, wiz_id, datas, action, context):
self.wiz_datas[wiz_id].update(datas)
wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id])
return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context)
def exp_create(self, db, uid, wiz_name, datas=None):
if not datas:
datas={}
#FIXME: this is not thread-safe
self.id += 1
self.wiz_datas[self.id] = {}
self.wiz_name[self.id] = wiz_name
self.wiz_uid[self.id] = uid
return self.id
def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None):
if not context:
context={}
if wiz_id in self.wiz_uid:
if self.wiz_uid[wiz_id] == uid:
return self._execute(db, uid, wiz_id, datas, action, context)
else:
raise openerp.exceptions.AccessDenied()
else:
raise openerp.exceptions.Warning('Wizard not found.')
#
# TODO: set a maximum report number per user to avoid DOS attacks
#
@ -689,8 +635,10 @@ class report_spool(netsvc.ExportService):
if method not in ['report', 'report_get', 'render_report']:
raise KeyError("Method not supported %s" % method)
security.check(db,uid,passwd)
openerp.modules.registry.RegistryManager.check_registry_signaling(db)
fn = getattr(self, 'exp_' + method)
res = fn(db, uid, *params)
openerp.modules.registry.RegistryManager.signal_caches_change(db)
return res
def exp_render_report(self, db, uid, object, ids, datas=None, context=None):
@ -802,13 +750,11 @@ class report_spool(netsvc.ExportService):
raise Exception, 'ReportNotFound'
def start_web_services():
def start_service():
db()
common()
objects_proxy()
wizard()
report_spool()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -16,6 +16,10 @@ import sys
import time
import werkzeug.serving
try:
from setproctitle import setproctitle
except ImportError:
setproctitle = lambda x: None
import openerp
import openerp.tools.config as config
@ -134,7 +138,7 @@ class Multicorn(object):
def process_spawn(self):
while len(self.workers_http) < self.population:
self.worker_spawn(WorkerHTTP, self.workers_http)
while len(self.workers_cron) < 1: # config option ?
while len(self.workers_cron) < config['max_cron_threads']:
self.worker_spawn(WorkerCron, self.workers_cron)
def sleep(self):
@ -189,8 +193,7 @@ class Multicorn(object):
for pid in self.workers.keys():
self.worker_kill(pid, signal.SIGTERM)
self.socket.close()
import __main__
__main__.quit_signals_received = 1
openerp.cli.server.quit_signals_received = 1
def run(self):
self.start()
@ -252,7 +255,7 @@ class Worker(object):
# Reset the worker if it consumes too much memory (e.g. caused by a memory leak).
rss, vms = psutil.Process(os.getpid()).get_memory_info()
if vms > config['limit_memory_soft']:
_logger.info('Virtual memory consumption too high, rebooting the worker.')
_logger.info('Worker (%d) virtual memory limit (%s) reached.', self.pid, vms)
self.alive = False # Commit suicide after the request.
# VMS and RLIMIT_AS are the same thing: virtual memory, a.k.a. address space
@ -263,7 +266,8 @@ class Worker(object):
r = resource.getrusage(resource.RUSAGE_SELF)
cpu_time = r.ru_utime + r.ru_stime
def time_expired(n, stack):
_logger.info('CPU time limit exceeded.')
_logger.info('Worker (%d) CPU time limit (%s) reached.', config['limit_time_cpu'])
# We dont suicide in such case
raise Exception('CPU time limit exceeded.')
signal.signal(signal.SIGXCPU, time_expired)
soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
@ -274,6 +278,7 @@ class Worker(object):
def start(self):
self.pid = os.getpid()
setproctitle('openerp: %s %s' % (self.__class__.__name__, self.pid))
_logger.info("Worker %s (%s) alive", self.__class__.__name__, self.pid)
# Reseed the random number generator
random.seed()
@ -346,19 +351,27 @@ class WorkerBaseWSGIServer(werkzeug.serving.BaseWSGIServer):
class WorkerCron(Worker):
""" Cron workers """
def sleep(self):
time.sleep(60)
interval = 60 + self.pid % 10 # chorus effect
time.sleep(interval)
def process_work(self):
_logger.debug("WorkerCron (%s) polling for jobs", self.pid)
if config['db_name']:
db_names = config['db_name'].split(',')
else:
db_names = openerp.netsvc.ExportService._services['db'].exp_list(True)
for db_name in db_names:
while True:
# TODO Each job should be considered as one request in multiprocessing
acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
# acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
# TODO why isnt openerp.addons.base defined ?
import base
acquired = base.ir.ir_cron.ir_cron._acquire_job(db_name)
if not acquired:
break
# dont keep cursors in multi database mode
if len(db_names) > 1:
openerp.sql_db.close_db(db_name)
# TODO Each job should be considered as one request instead of each db
self.request_count += 1
def start(self):

View File

@ -428,14 +428,14 @@ def serve():
_logger.info('HTTP service (werkzeug) running on %s:%s', interface, port)
httpd.serve_forever()
def start_server():
def start_service():
""" Call serve() in its own thread.
The WSGI server can be shutdown with stop_server() below.
"""
threading.Thread(target=serve).start()
def stop_server():
def stop_service():
""" Initiate the shutdown of the WSGI server.
The server is supposed to have been started by start_server() above.

View File

@ -57,10 +57,12 @@ class ormcache(object):
try:
key = args[self.skiparg-2:]
del d[key]
self2.pool._any_cache_cleared = True
except KeyError:
pass
else:
d.clear()
self2.pool._any_cache_cleared = True
class ormcache_multi(ormcache):
def __init__(self, skiparg=2, size=8192, multi=3):

View File

@ -146,8 +146,9 @@ class configmanager(object):
help="specify the TCP IP address for the NETRPC protocol")
group.add_option("--netrpc-port", dest="netrpc_port", my_default=8070,
help="specify the TCP port for the NETRPC protocol", type="int")
group.add_option("--no-netrpc", dest="netrpc", action="store_false", my_default=True,
help="disable the NETRPC protocol")
# Needed a few day for runbot and saas
group.add_option("--no-netrpc", dest="netrpc", action="store_false", my_default=False, help="disable the NETRPC protocol")
group.add_option("--netrpc", dest="netrpc", action="store_true", my_default=False, help="enable the NETRPC protocol")
parser.add_option_group(group)
# WEB
@ -269,8 +270,8 @@ class configmanager(object):
"osv_memory tables. This is a decimal value expressed in hours, "
"and the default is 1 hour.",
type="float")
group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=4,
help="Maximum number of threads processing concurrently cron jobs.",
group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=2,
help="Maximum number of threads processing concurrently cron jobs (default 2).",
type="int")
group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true",
help="Use the unaccent function provided by the database when available.")
@ -282,19 +283,19 @@ class configmanager(object):
help="Specify the number of workers, 0 disable prefork mode.",
type="int")
group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=640 * 1024 * 1024,
help="Maximum allowed virtual memory per worker, when reached the worker be reset after the current request.",
help="Maximum allowed virtual memory per worker, when reached the worker be reset after the current request (default 640M).",
type="int")
group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=768 * 1024 * 1024,
help="Maximum allowed virtual memory per worker, when reached, any memory allocation will fail.",
help="Maximum allowed virtual memory per worker, when reached, any memory allocation will fail (default 768M).",
type="int")
group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60,
help="Maximum allowed CPU time per request.",
help="Maximum allowed CPU time per request (default 60).",
type="int")
group.add_option("--limit-time-real", dest="limit_time_real", my_default=60,
help="Maximum allowed Real time per request. ",
group.add_option("--limit-time-real", dest="limit_time_real", my_default=120,
help="Maximum allowed Real time per request (default 120).",
type="int")
group.add_option("--limit-request", dest="limit_request", my_default=8192,
help="Maximum number of request to be processed per worker.",
help="Maximum number of request to be processed per worker (default 8192).",
type="int")
parser.add_option_group(group)
@ -479,8 +480,6 @@ class configmanager(object):
if opt.save:
self.save()
openerp.conf.max_cron_threads = self.options['max_cron_threads']
openerp.conf.addons_paths = self.options['addons_path'].split(',')
if opt.server_wide_modules:
openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))

View File

@ -68,7 +68,7 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
return base64_source
# create a thumbnail: will resize and keep ratios, then sharpen for better looking result
image.thumbnail(size, Image.ANTIALIAS)
sharpener = ImageEnhance.Sharpness(image.convert('RGB'))
sharpener = ImageEnhance.Sharpness(image.convert('RGBA'))
image = sharpener.enhance(2.0)
# create a transparent image for background
background = Image.new('RGBA', size, (255, 255, 255, 0))

View File

@ -1,269 +0,0 @@
# -*- coding: utf-8 -*-
#Copyright (c) 2004-2005, CherryPy Team (team@cherrypy.org)
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the CherryPy Team nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
#FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# This is a backport of Python-2.4's threading.local() implementation
"""Thread-local objects
(Note that this module provides a Python version of thread
threading.local class. Depending on the version of Python you're
using, there may be a faster one available. You should always import
the local class from threading.)
Thread-local objects support the management of thread-local data.
If you have data that you want to be local to a thread, simply create
a thread-local object and use its attributes:
>>> mydata = local()
>>> mydata.number = 42
>>> mydata.number
42
You can also access the local-object's dictionary:
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]
What's important about thread-local objects is that their data are
local to a thread. If we access the data in a different thread:
>>> log = []
>>> def f():
... items = mydata.__dict__.items()
... items.sort()
... log.append(items)
... mydata.number = 11
... log.append(mydata.number)
>>> import threading
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
>>> log
[[], 11]
we get different data. Furthermore, changes made in the other thread
don't affect data seen in this thread:
>>> mydata.number
42
Of course, values you get from a local object, including a __dict__
attribute, are for whatever thread was current at the time the
attribute was read. For that reason, you generally don't want to save
these values across threads, as they apply only to the thread they
came from.
You can create custom local objects by subclassing the local class:
>>> class MyLocal(local):
... number = 2
... initialized = False
... def __init__(self, **kw):
... if self.initialized:
... raise SystemError('__init__ called too many times')
... self.initialized = True
... self.__dict__.update(kw)
... def squared(self):
... return self.number ** 2
This can be useful to support default values, methods and
initialization. Note that if you define an __init__ method, it will be
called each time the local object is used in a separate thread. This
is necessary to initialize each thread's dictionary.
Now if we create a local object:
>>> mydata = MyLocal(color='red')
Now we have a default number:
>>> mydata.number
2
an initial color:
>>> mydata.color
'red'
>>> del mydata.color
And a method that operates on the data:
>>> mydata.squared()
4
As before, we can access the data in a separate thread:
>>> log = []
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
>>> log
[[('color', 'red'), ('initialized', True)], 11]
without affecting this thread's data:
>>> mydata.number
2
>>> mydata.color
Traceback (most recent call last):
...
AttributeError: 'MyLocal' object has no attribute 'color'
Note that subclasses can define slots, but they are not thread
local. They are shared across threads:
>>> class MyLocal(local):
... __slots__ = 'number'
>>> mydata = MyLocal()
>>> mydata.number = 42
>>> mydata.color = 'red'
So, the separate thread:
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
affects what we see:
>>> mydata.number
11
>>> del mydata
"""
# Threading import is at end
class _localbase(object):
__slots__ = '_local__key', '_local__args', '_local__lock'
def __new__(cls, *args, **kw):
self = object.__new__(cls)
key = '_local__key', 'thread.local.' + str(id(self))
object.__setattr__(self, '_local__key', key)
object.__setattr__(self, '_local__args', (args, kw))
object.__setattr__(self, '_local__lock', RLock())
if args or kw and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported")
# We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it
# again ourselves.
dict = object.__getattribute__(self, '__dict__')
currentThread().__dict__[key] = dict
return self
def _patch(self):
key = object.__getattribute__(self, '_local__key')
d = currentThread().__dict__.get(key)
if d is None:
d = {}
currentThread().__dict__[key] = d
object.__setattr__(self, '__dict__', d)
# we have a new instance dict, so call out __init__ if we have
# one
cls = type(self)
if cls.__init__ is not object.__init__:
args, kw = object.__getattribute__(self, '_local__args')
cls.__init__(self, *args, **kw)
else:
object.__setattr__(self, '__dict__', d)
class local(_localbase):
def __getattribute__(self, name):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__getattribute__(self, name)
finally:
lock.release()
def __setattr__(self, name, value):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__setattr__(self, name, value)
finally:
lock.release()
def __delattr__(self, name):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__delattr__(self, name)
finally:
lock.release()
def __del__():
threading_enumerate = enumerate
__getattribute__ = object.__getattribute__
def __del__(self):
key = __getattribute__(self, '_local__key')
try:
threads = list(threading_enumerate())
except:
# if enumerate fails, as it seems to do during
# shutdown, we'll skip cleanup under the assumption
# that there is nothing to clean up
return
for thread in threads:
try:
__dict__ = thread.__dict__
except AttributeError:
# Thread is dying, rest in peace
continue
if key in __dict__:
try:
del __dict__[key]
except KeyError:
pass # didn't have anything in this thread
return __del__
__del__ = __del__()
from threading import currentThread, enumerate, RLock
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,191 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import copy
import logging
import openerp.netsvc as netsvc
from openerp.tools.misc import UpdateableStr, UpdateableDict
from openerp.tools.translate import translate
from lxml import etree
import openerp.pooler as pooler
from openerp.osv.osv import except_osv
from openerp.osv.orm import except_orm
_logger = logging.getLogger(__name__)
class except_wizard(Exception):
def __init__(self, name, value):
self.name = name
self.value = value
self.args = (name, value)
class interface(netsvc.Service):
"""
This is the base class used to implement Wizards. This class is deprecated
and `openerp.osv.TransientModel` must be used instead.
"""
states = {}
def __init__(self, name):
assert not self.exists('wizard.'+name), 'The wizard "%s" already exists!' % (name,)
_logger.warning(
"The wizard %s uses the deprecated openerp.wizard.interface class.\n"
"It must use the openerp.osv.TransientModel class instead." % \
name)
super(interface, self).__init__('wizard.'+name)
self.wiz_name = name
def translate_view(self, cr, node, state, lang):
if node.get('string'):
trans = translate(cr, self.wiz_name+','+state, 'wizard_view', lang, node.get('string').encode('utf8'))
if trans:
node.set('string', trans)
for n in node:
self.translate_view(cr, n, state, lang)
def execute_cr(self, cr, uid, data, state='init', context=None):
if not context:
context={}
res = {}
try:
state_def = self.states[state]
result_def = state_def.get('result', {})
actions_res = {}
# iterate through the list of actions defined for this state
for action in state_def.get('actions', []):
# execute them
action_res = action(self, cr, uid, data, context)
assert isinstance(action_res, dict), 'The return value of wizard actions should be a dictionary'
actions_res.update(action_res)
res = copy.copy(result_def)
res['datas'] = actions_res
lang = context.get('lang', False)
if result_def['type'] == 'action':
res['action'] = result_def['action'](self, cr, uid, data, context)
elif result_def['type'] == 'form':
fields = copy.deepcopy(result_def['fields'])
arch = copy.copy(result_def['arch'])
button_list = copy.copy(result_def['state'])
if isinstance(fields, UpdateableDict):
fields = fields.dict
if isinstance(arch, UpdateableStr):
arch = arch.string
# fetch user-set defaut values for the field... shouldn't we pass it the uid?
ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
defaults = ir_values_obj.get(cr, uid, 'default', False, [('wizard.'+self.wiz_name, False)])
default_values = dict([(x[1], x[2]) for x in defaults])
for val in fields.keys():
if 'default' in fields[val]:
# execute default method for this field
if callable(fields[val]['default']):
fields[val]['value'] = fields[val]['default'](uid, data, state)
else:
fields[val]['value'] = fields[val]['default']
del fields[val]['default']
else:
# if user has set a default value for the field, use it
if val in default_values:
fields[val]['value'] = default_values[val]
if 'selection' in fields[val]:
if not isinstance(fields[val]['selection'], (tuple, list)):
fields[val] = copy.copy(fields[val])
fields[val]['selection'] = fields[val]['selection'](self, cr, uid, context)
elif lang:
res_name = "%s,%s,%s" % (self.wiz_name, state, val)
trans = lambda x: translate(cr, res_name, 'selection', lang, x) or x
for idx, (key, val2) in enumerate(fields[val]['selection']):
fields[val]['selection'][idx] = (key, trans(val2))
if lang:
# translate fields
for field in fields:
res_name = "%s,%s,%s" % (self.wiz_name, state, field)
trans = translate(cr, res_name, 'wizard_field', lang)
if trans:
fields[field]['string'] = trans
if 'help' in fields[field]:
t = translate(cr, res_name, 'help', lang, fields[field]['help'])
if t:
fields[field]['help'] = t
# translate arch
if not isinstance(arch, UpdateableStr):
doc = etree.XML(arch)
self.translate_view(cr, doc, state, lang)
arch = etree.tostring(doc)
# translate buttons
button_list = list(button_list)
for i, aa in enumerate(button_list):
button_name = aa[0]
trans = translate(cr, self.wiz_name+','+state+','+button_name, 'wizard_button', lang)
if trans:
aa = list(aa)
aa[1] = trans
button_list[i] = aa
res['fields'] = fields
res['arch'] = arch
res['state'] = button_list
elif result_def['type'] == 'choice':
next_state = result_def['next_state'](self, cr, uid, data, context)
return self.execute_cr(cr, uid, data, next_state, context)
except Exception, e:
if isinstance(e, except_wizard) \
or isinstance(e, except_osv) \
or isinstance(e, except_orm):
netsvc.abort_response(2, e.name, 'warning', e.value)
else:
_logger.exception('Exception in call:')
raise
return res
def execute(self, db, uid, data, state='init', context=None):
if not context:
context={}
cr = pooler.get_db(db).cursor()
try:
try:
res = self.execute_cr(cr, uid, data, state, context)
cr.commit()
except Exception:
cr.rollback()
raise
finally:
cr.close()
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -108,6 +108,7 @@ setuptools.setup(
'gdata',
'lxml < 3', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
'mako',
'mock',
'PIL', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
'psutil', # windows binary code.google.com/p/psutil/downloads/list
'psycopg2',
@ -120,6 +121,7 @@ setuptools.setup(
'pyyaml',
'reportlab', # windows binary pypi.python.org/pypi/reportlab
'simplejson',
'unittest2',
'vatnumber',
'vobject',
'werkzeug',