[MERGE] from trunk
bzr revid: rco@openerp.com-20121221134259-scc92nby1dn91pe8
This commit is contained in:
+20
-13
@@ -1,14 +1,21 @@
|
||||
.*
|
||||
*.egg-info
|
||||
*.orig
|
||||
*.vim
|
||||
.*.swp
|
||||
.bzrignore
|
||||
.idea
|
||||
.project
|
||||
.pydevproject
|
||||
.ropeproject
|
||||
.settings
|
||||
.DS_Store
|
||||
openerp/addons/*
|
||||
openerp/filestore*
|
||||
.Python
|
||||
*.pyc
|
||||
*.pyo
|
||||
bin/*
|
||||
build/
|
||||
RE:^bin/
|
||||
RE:^dist/
|
||||
RE:^include/
|
||||
|
||||
RE:^share/
|
||||
RE:^man/
|
||||
RE:^lib/
|
||||
|
||||
RE:^addons/\w+/doc/_build/
|
||||
include/
|
||||
lib/
|
||||
share/
|
||||
doc/_build/*
|
||||
win32/*.bat
|
||||
win32/meta.py
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010, 2012 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2012 OpenERP s.a. (<http://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
|
||||
@@ -80,6 +80,7 @@ The kernel of OpenERP, needed for all installation.
|
||||
'res/res_bank_view.xml',
|
||||
'res/res_country_view.xml',
|
||||
'res/res_currency_view.xml',
|
||||
'res/wizard/change_password_wizard_view.xml',
|
||||
'res/res_users_view.xml',
|
||||
'res/res_partner_data.xml',
|
||||
'res/ir_property_view.xml',
|
||||
@@ -93,19 +94,17 @@ The kernel of OpenERP, needed for all installation.
|
||||
'res/res_partner_image_demo.xml',
|
||||
],
|
||||
'test': [
|
||||
'test/base_test.xml',
|
||||
'test/base_test.yml',
|
||||
'test/test_context.xml',
|
||||
'test/bug_lp541545.xml',
|
||||
'test/test_osv_expression.yml',
|
||||
'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules.
|
||||
# Commented because this takes some time.
|
||||
# This must be (un)commented with the corresponding import statement
|
||||
# in test/__init__.py.
|
||||
# 'test/test_ir_cron.yml', # <-- These tests perform a roolback.
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'css': ['static/src/css/modules.css'],
|
||||
'js': [
|
||||
'static/src/js/apps.js',
|
||||
],
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<record id="main_company" model="res.company">
|
||||
<field name="name">Your Company</field>
|
||||
<field name="partner_id" ref="main_partner"/>
|
||||
<field name="rml_header1">Your Company Slogan</field>
|
||||
<field name="rml_header1">Your Company Tagline</field>
|
||||
<field name="currency_id" ref="base.EUR"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ 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-17 20:41+0000\n"
|
||||
"PO-Revision-Date: 2012-12-19 19:54+0000\n"
|
||||
"Last-Translator: David Bowers <sales@skitzotek.com>\n"
|
||||
"Language-Team: English (United Kingdom) <en_GB@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-18 04:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16372)\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-20 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
@@ -4199,6 +4199,13 @@ msgid ""
|
||||
"invite.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"This module updates memos inside OpenERP when using an external pad\n"
|
||||
"=====================================================================\n"
|
||||
"\n"
|
||||
"Use to update your text memo in real time with a user following that you "
|
||||
"invite.\n"
|
||||
"\n"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/base/module/module.py:609
|
||||
|
||||
+959
-196
File diff suppressed because it is too large
Load Diff
@@ -7,14 +7,14 @@ msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 6.0.0-rc1\n"
|
||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
||||
"POT-Creation-Date: 2012-12-03 16:01+0000\n"
|
||||
"PO-Revision-Date: 2012-11-05 00:42+0000\n"
|
||||
"PO-Revision-Date: 2012-12-20 19:00+0000\n"
|
||||
"Last-Translator: Cristian Salamea (Gnuthink) <ovnicraft@gmail.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 05:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16335)\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-21 04:39+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
@@ -24,6 +24,10 @@ msgid ""
|
||||
"================================================\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Módulo para la emisión e impresión de cheques\n"
|
||||
"================================================\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.sh
|
||||
@@ -59,7 +63,7 @@ msgstr "Código vista"
|
||||
#. module: base
|
||||
#: model:ir.module.module,summary:base.module_sale_stock
|
||||
msgid "Quotation, Sale Orders, Delivery & Invoicing Control"
|
||||
msgstr ""
|
||||
msgstr "Presupuesto, órdenes de venta, entrega y control de facturación"
|
||||
|
||||
#. module: base
|
||||
#: selection:ir.sequence,implementation:0
|
||||
@@ -88,12 +92,12 @@ msgstr ""
|
||||
#. module: base
|
||||
#: model:ir.module.module,summary:base.module_point_of_sale
|
||||
msgid "Touchscreen Interface for Shops"
|
||||
msgstr ""
|
||||
msgstr "Interfaz de pantalla táctil para tiendas"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_l10n_in_hr_payroll
|
||||
msgid "Indian Payroll"
|
||||
msgstr ""
|
||||
msgstr "Nomina de la India"
|
||||
|
||||
#. module: base
|
||||
#: help:ir.cron,model:0
|
||||
@@ -123,6 +127,17 @@ msgid ""
|
||||
" * Product Attributes\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Módulo que añade fabricantes y atributos en el formulario de producto.\n"
|
||||
"===========================================================\n"
|
||||
"\n"
|
||||
"Puede definir los siguientes campos para un producto:\n"
|
||||
"-------------------------------------------------\n"
|
||||
"* Fabricante\n"
|
||||
"* Nombre del producto del fabricante\n"
|
||||
"* Código del producto del fabricante\n"
|
||||
"* Atributos del producto\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: field:ir.actions.client,params:0
|
||||
@@ -136,11 +151,14 @@ msgid ""
|
||||
"The module adds google user in res user.\n"
|
||||
"========================================\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"El módulo añade usuarios Google al registro de usuarios\n"
|
||||
"============================================\n"
|
||||
|
||||
#. module: base
|
||||
#: help:res.partner,employee:0
|
||||
msgid "Check this box if this contact is an Employee."
|
||||
msgstr ""
|
||||
msgstr "Marque si el contacto es un empleado"
|
||||
|
||||
#. module: base
|
||||
#: help:ir.model.fields,domain:0
|
||||
@@ -171,7 +189,7 @@ msgstr "Ventana destino"
|
||||
#. module: base
|
||||
#: field:ir.actions.report.xml,report_rml:0
|
||||
msgid "Main Report File Path"
|
||||
msgstr ""
|
||||
msgstr "Ruta de archivo del informe principal"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_sale_analytic_plans
|
||||
@@ -192,6 +210,15 @@ msgid ""
|
||||
"revenue\n"
|
||||
"reports."
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Genere facturas desde los gastos y partes de horas.\n"
|
||||
"========================================================\n"
|
||||
"\n"
|
||||
"Módulo para generar facturas basadas en los costes (recursos humanos, "
|
||||
"gastos, ...).\n"
|
||||
"\n"
|
||||
"Puede definir tarifas en la contabilidad analítica, realizando algún informe "
|
||||
"teórico de beneficios."
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_crm
|
||||
@@ -269,7 +296,7 @@ msgstr "creado."
|
||||
#. module: base
|
||||
#: field:ir.actions.report.xml,report_xsl:0
|
||||
msgid "XSL Path"
|
||||
msgstr ""
|
||||
msgstr "Ruta XSL"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_l10n_tr
|
||||
@@ -295,7 +322,7 @@ msgstr "Inuktitut / ᐃᓄᒃᑎᑐᑦ"
|
||||
#. module: base
|
||||
#: model:res.groups,name:base.group_multi_currency
|
||||
msgid "Multi Currencies"
|
||||
msgstr ""
|
||||
msgstr "Multimoneda"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_l10n_cl
|
||||
@@ -307,6 +334,12 @@ msgid ""
|
||||
"\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Plan de cuentas y localización de impuestos chilenos.\n"
|
||||
"==============================================\n"
|
||||
"Plan contable chileno e impuestos de acuerdo a disposiciones vigentes\n"
|
||||
"\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_sale
|
||||
@@ -319,6 +352,7 @@ msgid ""
|
||||
"The internal user that is in charge of communicating with this contact if "
|
||||
"any."
|
||||
msgstr ""
|
||||
"El usuario interno encargado de comunicarse con este contacto si lo hubiese."
|
||||
|
||||
#. module: base
|
||||
#: view:res.partner:0
|
||||
@@ -353,6 +387,15 @@ msgid ""
|
||||
" * Commitment Date\n"
|
||||
" * Effective Date\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Añade información adicional al pedido de venta.\n"
|
||||
"===================================================\n"
|
||||
"\n"
|
||||
"Puede añadir las siguientes fechas adicionales a un pedido de venta:\n"
|
||||
"-----------------------------------------------------------\n"
|
||||
" * Fecha solicitada\n"
|
||||
" * Fecha de confirmación\n"
|
||||
" * Fecha efectiva\n"
|
||||
|
||||
#. module: base
|
||||
#: help:ir.actions.act_window,res_id:0
|
||||
@@ -360,6 +403,8 @@ msgid ""
|
||||
"Database ID of record to open in form view, when ``view_mode`` is set to "
|
||||
"'form' only"
|
||||
msgstr ""
|
||||
"Id. de la base datos del registro para abrir en el formulario de vista, "
|
||||
"cuando se establece el modo de visto únicamente a 'formulario'"
|
||||
|
||||
#. module: base
|
||||
#: field:res.partner.address,name:0
|
||||
@@ -435,6 +480,8 @@ msgid ""
|
||||
"There is already a shared filter set as default for %(model)s, delete or "
|
||||
"change it before setting a new default"
|
||||
msgstr ""
|
||||
"Ya existe un filtro compartido por defecto para %(model)s, elimínelo o "
|
||||
"cámbielo antes de configurar uno nuevo"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/orm.py:2648
|
||||
@@ -529,12 +576,12 @@ msgstr ""
|
||||
#. module: base
|
||||
#: field:ir.model.relation,name:0
|
||||
msgid "Relation Name"
|
||||
msgstr ""
|
||||
msgstr "Nombre de relación"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.rule:0
|
||||
msgid "Create Access Right"
|
||||
msgstr ""
|
||||
msgstr "Crear permisos de acceso"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.tv
|
||||
@@ -574,7 +621,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
|
||||
@@ -697,7 +744,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
|
||||
@@ -726,7 +773,7 @@ msgstr "Untranslated"
|
||||
#. 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
|
||||
@@ -820,6 +867,14 @@ msgid ""
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
"Pulse para añadir un contacto en la libreta de direcciones.\n"
|
||||
"</p><p>\n"
|
||||
"OpenERP le ayuda a gestionar todas las actividades relacionadas con los "
|
||||
"clientes: discusiones, historial de oportunidades de negocio, documentos, "
|
||||
"etc.\n"
|
||||
"</p>\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_web_linkedin
|
||||
@@ -876,7 +931,7 @@ msgstr "Rumanía - Contabilidad"
|
||||
#. module: base
|
||||
#: model:ir.model,name:base.model_res_config_settings
|
||||
msgid "res.config.settings"
|
||||
msgstr ""
|
||||
msgstr "Parámetros de configuración"
|
||||
|
||||
#. module: base
|
||||
#: help:res.partner,image_small:0
|
||||
@@ -885,6 +940,9 @@ msgid ""
|
||||
"image, with aspect ratio preserved. Use this field anywhere a small image is "
|
||||
"required."
|
||||
msgstr ""
|
||||
"Imagen de tamaño pequeño de este contacto. Se redimensiona automáticamente a "
|
||||
"64x64 px, con el ratio de aspecto preservado. Use este campo donde se "
|
||||
"requiera una imagen pequeña."
|
||||
|
||||
#. module: base
|
||||
#: help:ir.actions.server,mobile:0
|
||||
@@ -906,7 +964,7 @@ msgstr "Seguridad y autenticación"
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_web_calendar
|
||||
msgid "Web Calendar"
|
||||
msgstr ""
|
||||
msgstr "Calendario web"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
@@ -917,7 +975,7 @@ msgstr "Swedish / svenska"
|
||||
#: field:base.language.export,name:0
|
||||
#: field:ir.attachment,datas_fname:0
|
||||
msgid "File Name"
|
||||
msgstr ""
|
||||
msgstr "Nombre de Archivo"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.rs
|
||||
@@ -1013,7 +1071,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
|
||||
@@ -1030,6 +1088,7 @@ msgstr "Zimbabwe"
|
||||
msgid ""
|
||||
"Type of the constraint: `f` for a foreign key, `u` for other constraints."
|
||||
msgstr ""
|
||||
"Tipo de restricción: 'f' para un clave foránea, 'u' para otras restricciones."
|
||||
|
||||
#. module: base
|
||||
#: view:ir.actions.report.xml:0
|
||||
@@ -1111,7 +1170,7 @@ msgstr "Otra licencia OSI Aprobada"
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_web_gantt
|
||||
msgid "Web Gantt"
|
||||
msgstr ""
|
||||
msgstr "Diagrama Gantt web"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.actions.act_window,name:base.act_menu_create
|
||||
@@ -1138,7 +1197,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
|
||||
@@ -1164,7 +1223,7 @@ msgstr "Principado de Andorra"
|
||||
#. module: base
|
||||
#: field:ir.rule,perm_read:0
|
||||
msgid "Apply for Read"
|
||||
msgstr ""
|
||||
msgstr "Aplicar para lectura"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.mn
|
||||
@@ -1194,7 +1253,7 @@ msgstr ""
|
||||
#: code:addons/base/ir/ir_model.py:727
|
||||
#, python-format
|
||||
msgid "Document model"
|
||||
msgstr ""
|
||||
msgstr "Modelo de documento"
|
||||
|
||||
#. module: base
|
||||
#: view:res.lang:0
|
||||
@@ -1244,12 +1303,12 @@ msgstr "El nombre del país debe ser único"
|
||||
#. module: base
|
||||
#: field:ir.module.module,installed_version:0
|
||||
msgid "Latest Version"
|
||||
msgstr ""
|
||||
msgstr "Última versión"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.rule:0
|
||||
msgid "Delete Access Right"
|
||||
msgstr ""
|
||||
msgstr "Eliminar permiso de acceso"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/base/ir/ir_mail_server.py:213
|
||||
@@ -1276,7 +1335,7 @@ msgstr "Islas Caimán"
|
||||
#. module: base
|
||||
#: view:ir.rule:0
|
||||
msgid "Record Rule"
|
||||
msgstr ""
|
||||
msgstr "Regla de registro"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.kr
|
||||
@@ -1304,7 +1363,7 @@ msgstr "Contribuyentes"
|
||||
#. module: base
|
||||
#: field:ir.rule,perm_unlink:0
|
||||
msgid "Apply for Delete"
|
||||
msgstr ""
|
||||
msgstr "Aplicar para eliminación"
|
||||
|
||||
#. module: base
|
||||
#: selection:ir.property,type:0
|
||||
@@ -1319,7 +1378,7 @@ msgstr "Visible"
|
||||
#. module: base
|
||||
#: model:ir.actions.client,name:base.action_client_base_menu
|
||||
msgid "Open Settings Menu"
|
||||
msgstr ""
|
||||
msgstr "Abrir menú 'Configuración'"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
@@ -1405,7 +1464,7 @@ msgstr "Tests"
|
||||
#. module: base
|
||||
#: field:ir.actions.report.xml,attachment:0
|
||||
msgid "Save as Attachment Prefix"
|
||||
msgstr ""
|
||||
msgstr "Prefijo del adjunto al guardar como"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.ui.view_sc,res_id:0
|
||||
@@ -1442,7 +1501,7 @@ msgstr "Haití"
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_l10n_fr_hr_payroll
|
||||
msgid "French Payroll"
|
||||
msgstr ""
|
||||
msgstr "Nomina de Francia"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.ui.view:0
|
||||
@@ -1562,6 +1621,12 @@ msgid ""
|
||||
"=============\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Este módulo añade el menú y características de reclamaciones al portal si "
|
||||
"los módulos 'claim' y 'portal' están instalados.\n"
|
||||
"============================================================================="
|
||||
"====================\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:ir.actions.act_window,help:base.action_res_partner_bank_account_form
|
||||
@@ -1593,7 +1658,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: model:res.country,name:base.mf
|
||||
msgid "Saint Martin (French part)"
|
||||
msgstr ""
|
||||
msgstr "San Martín (zona francesa)"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.model,name:base.model_ir_exports
|
||||
@@ -1610,7 +1675,7 @@ msgstr "Ninguna lengua con el código \"%s\" existe"
|
||||
#: model:ir.module.category,name:base.module_category_social_network
|
||||
#: model:ir.module.module,shortdesc:base.module_mail
|
||||
msgid "Social Network"
|
||||
msgstr ""
|
||||
msgstr "Red Social"
|
||||
|
||||
#. module: base
|
||||
#: view:res.lang:0
|
||||
@@ -1620,12 +1685,12 @@ msgstr "%Y - Año con siglo."
|
||||
#. module: base
|
||||
#: view:res.company:0
|
||||
msgid "Report Footer Configuration"
|
||||
msgstr ""
|
||||
msgstr "Configuración de pie de informe"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.translation,comments:0
|
||||
msgid "Translation comments"
|
||||
msgstr ""
|
||||
msgstr "Comentarios de traducción"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_lunch
|
||||
@@ -1690,7 +1755,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: help:res.partner,website:0
|
||||
msgid "Website of Partner or Company"
|
||||
msgstr ""
|
||||
msgstr "Sitio web de la empresa o compañía"
|
||||
|
||||
#. module: base
|
||||
#: help:base.language.install,overwrite:0
|
||||
@@ -1737,7 +1802,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: model:ir.module.module,summary:base.module_sale
|
||||
msgid "Quotations, Sale Orders, Invoicing"
|
||||
msgstr ""
|
||||
msgstr "Presupuestos, órdenes de venta, facturación"
|
||||
|
||||
#. module: base
|
||||
#: field:res.users,login:0
|
||||
|
||||
@@ -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-18 22:00+0000\n"
|
||||
"PO-Revision-Date: 2012-12-20 18:03+0000\n"
|
||||
"Last-Translator: Ahti Hinnov <sipelgas@gmail.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-19 05:13+0000\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-21 04:38+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
|
||||
#. module: base
|
||||
@@ -531,7 +531,7 @@ msgstr "Kuupäeva vorming"
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_base_report_designer
|
||||
msgid "OpenOffice Report Designer"
|
||||
msgstr ""
|
||||
msgstr "OpenOffice aruande kujundaja"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.an
|
||||
@@ -980,7 +980,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: view:res.users:0
|
||||
msgid "Email Preferences"
|
||||
msgstr ""
|
||||
msgstr "E-posti seadistused"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/base/ir/ir_fields.py:196
|
||||
@@ -1355,7 +1355,7 @@ msgstr ""
|
||||
#: code:addons/base/module/wizard/base_language_install.py:53
|
||||
#, python-format
|
||||
msgid "Language Pack"
|
||||
msgstr ""
|
||||
msgstr "Keelepakett"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_web_tests
|
||||
@@ -1571,7 +1571,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: view:res.company:0
|
||||
msgid "Report Footer Configuration"
|
||||
msgstr ""
|
||||
msgstr "Aruande jaluse seadistamine"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.translation,comments:0
|
||||
@@ -1951,7 +1951,7 @@ msgstr "Lisatud mudel"
|
||||
#. module: base
|
||||
#: field:res.partner.bank,footer:0
|
||||
msgid "Display on Reports"
|
||||
msgstr ""
|
||||
msgstr "Kuva aruannetes"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_project_timesheet
|
||||
@@ -2005,7 +2005,7 @@ msgstr "Valem"
|
||||
#: code:addons/base/res/res_users.py:311
|
||||
#, python-format
|
||||
msgid "Can not remove root user!"
|
||||
msgstr "Ei saa eemaldada root kasutajat!"
|
||||
msgstr "Ei saa eemaldada juurkasutajat!"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.mw
|
||||
@@ -2144,7 +2144,7 @@ msgstr "Hispaania / Español"
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
msgid "Korean (KP) / 한국어 (KP)"
|
||||
msgstr ""
|
||||
msgstr "Korea (KP) / 한국어 (KP)"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.ax
|
||||
@@ -2487,7 +2487,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: field:res.company,rml_header1:0
|
||||
msgid "Company Slogan"
|
||||
msgstr ""
|
||||
msgstr "Ettevõtte loosung"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.bb
|
||||
@@ -2537,7 +2537,7 @@ msgstr "Kreeka / Ελληνικά"
|
||||
#. module: base
|
||||
#: field:res.company,custom_footer:0
|
||||
msgid "Custom Footer"
|
||||
msgstr ""
|
||||
msgstr "Kohandatud jalus"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_sale_crm
|
||||
@@ -2593,7 +2593,7 @@ msgstr "Arveldus"
|
||||
#. module: base
|
||||
#: field:ir.ui.view_sc,name:0
|
||||
msgid "Shortcut Name"
|
||||
msgstr "Kiirkorralduse nimi"
|
||||
msgstr "Otsetee nimi"
|
||||
|
||||
#. module: base
|
||||
#: field:res.partner,contact_address:0
|
||||
@@ -2750,7 +2750,7 @@ msgstr "Ekspordi tõlked"
|
||||
#: model:res.groups,name:base.group_sale_manager
|
||||
#: model:res.groups,name:base.group_tool_manager
|
||||
msgid "Manager"
|
||||
msgstr ""
|
||||
msgstr "Juht"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/base/ir/ir_model.py:718
|
||||
@@ -3009,7 +3009,7 @@ msgstr "Norfolki saar"
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
msgid "Korean (KR) / 한국어 (KR)"
|
||||
msgstr ""
|
||||
msgstr "Korea (KR) / 한국어 (KR)"
|
||||
|
||||
#. module: base
|
||||
#: help:ir.model.fields,model:0
|
||||
@@ -3055,7 +3055,7 @@ msgstr "Viga! Sa ei saa luua rekursiivseid ettevõtteid."
|
||||
#: view:res.users:0
|
||||
#, python-format
|
||||
msgid "Application"
|
||||
msgstr ""
|
||||
msgstr "Rakendamine"
|
||||
|
||||
#. module: base
|
||||
#: model:res.groups,comment:base.group_hr_manager
|
||||
@@ -3636,7 +3636,7 @@ msgstr ""
|
||||
#: field:res.company,rml_footer:0
|
||||
#: field:res.company,rml_footer_readonly:0
|
||||
msgid "Report Footer"
|
||||
msgstr ""
|
||||
msgstr "Aruande jalus"
|
||||
|
||||
#. module: base
|
||||
#: selection:res.lang,direction:0
|
||||
@@ -3868,7 +3868,7 @@ msgstr "Peata kõik"
|
||||
#. module: base
|
||||
#: field:res.company,paper_format:0
|
||||
msgid "Paper Format"
|
||||
msgstr ""
|
||||
msgstr "Paberi formaat"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_note_pad
|
||||
@@ -4467,6 +4467,8 @@ msgid ""
|
||||
"The selected language has been successfully installed. You must change the "
|
||||
"preferences of the user and open a new menu to view the changes."
|
||||
msgstr ""
|
||||
"Valitud keel on edukalt paigaldatud. Muudatuste nägemiseks pead sa muutma "
|
||||
"kasutaja eelistusi ja avama uue menüü."
|
||||
|
||||
#. module: base
|
||||
#: field:ir.actions.act_window.view,view_id:0
|
||||
@@ -4714,7 +4716,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
msgid "Portuguese (BR) / Português (BR)"
|
||||
msgstr ""
|
||||
msgstr "Portugali (BR) / Português (BR)"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.model,name:base.model_ir_needaction_mixin
|
||||
@@ -5154,7 +5156,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: view:res.company:0
|
||||
msgid "Preview Header/Footer"
|
||||
msgstr ""
|
||||
msgstr "Päise/jaluse eelvaade"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.ui.menu,parent_id:0
|
||||
@@ -5694,7 +5696,7 @@ msgstr "Omadus kustutamisel many2one väljadel"
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_accounting_and_finance
|
||||
msgid "Accounting & Finance"
|
||||
msgstr ""
|
||||
msgstr "Raamatupidamine ja rahandus"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.actions.server,write_id:0
|
||||
@@ -5717,7 +5719,7 @@ msgstr ""
|
||||
#: model:ir.module.category,name:base.module_category_usability
|
||||
#: view:res.users:0
|
||||
msgid "Usability"
|
||||
msgstr ""
|
||||
msgstr "Kasutatavus"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.actions.act_window,domain:0
|
||||
@@ -5836,12 +5838,12 @@ msgstr "Grupi nimi ei tohi alata \"-\" märgiga"
|
||||
#. module: base
|
||||
#: view:ir.module.module:0
|
||||
msgid "Apps"
|
||||
msgstr ""
|
||||
msgstr "Rakendused"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.ui.view_sc:0
|
||||
msgid "Shortcut"
|
||||
msgstr "Lühend"
|
||||
msgstr "Otsetee"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.model.data,date_init:0
|
||||
@@ -6508,7 +6510,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: model:res.groups,name:base.group_partner_manager
|
||||
msgid "Contact Creation"
|
||||
msgstr ""
|
||||
msgstr "Kontaktide loomine"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.module.module:0
|
||||
@@ -6718,7 +6720,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: selection:res.currency,position:0
|
||||
msgid "After Amount"
|
||||
msgstr ""
|
||||
msgstr "Peale summat"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
@@ -7044,7 +7046,7 @@ msgstr "Mitmel dokumendil"
|
||||
#: view:res.users:0
|
||||
#: view:wizard.ir.model.menu.create:0
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
msgstr "või"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_account_accountant
|
||||
@@ -7608,7 +7610,7 @@ msgstr "Kohandatud väli"
|
||||
#. module: base
|
||||
#: model:ir.module.module,summary:base.module_account_accountant
|
||||
msgid "Financial and Analytic Accounting"
|
||||
msgstr ""
|
||||
msgstr "Finants- ja analüütiline raamatupidamine"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_portal_project
|
||||
@@ -7708,7 +7710,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: model:res.partner.bank.type,name:base.bank_normal
|
||||
msgid "Normal Bank Account"
|
||||
msgstr ""
|
||||
msgstr "Tavaline pangakonto"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.actions.wizard:0
|
||||
@@ -7980,7 +7982,7 @@ msgstr "Tühista eemaldus"
|
||||
#. module: base
|
||||
#: view:res.bank:0
|
||||
msgid "Communication"
|
||||
msgstr "Suhtlemine"
|
||||
msgstr "Kontakt"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_analytic
|
||||
@@ -9180,7 +9182,7 @@ msgstr "Gambia"
|
||||
#: view:res.partner:0
|
||||
#: field:res.users,company_ids:0
|
||||
msgid "Companies"
|
||||
msgstr "Firmad"
|
||||
msgstr "Ettevõtted"
|
||||
|
||||
#. module: base
|
||||
#: help:res.currency,symbol:0
|
||||
@@ -9249,7 +9251,7 @@ msgstr ""
|
||||
#: view:res.users:0
|
||||
#: view:wizard.ir.model.menu.create:0
|
||||
msgid "Cancel"
|
||||
msgstr "Tühista"
|
||||
msgstr "Loobu"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/orm.py:1509
|
||||
@@ -9571,7 +9573,7 @@ msgstr "Paigaldamise ootel"
|
||||
#: model:ir.module.module,shortdesc:base.module_base
|
||||
#: field:res.currency,base:0
|
||||
msgid "Base"
|
||||
msgstr "Baas"
|
||||
msgstr "Alus"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.model.data,model:0
|
||||
@@ -9660,12 +9662,12 @@ msgstr "Minutid"
|
||||
#. module: base
|
||||
#: view:res.currency:0
|
||||
msgid "Display"
|
||||
msgstr ""
|
||||
msgstr "Kuvamine"
|
||||
|
||||
#. module: base
|
||||
#: model:res.groups,name:base.group_multi_company
|
||||
msgid "Multi Companies"
|
||||
msgstr ""
|
||||
msgstr "Mitu ettevõtet"
|
||||
|
||||
#. module: base
|
||||
#: help:res.users,menu_id:0
|
||||
@@ -10034,7 +10036,7 @@ msgstr "Ettevõttega seotud pangakontod"
|
||||
#: model:ir.ui.menu,name:base.next_id_64
|
||||
#: view:res.users:0
|
||||
msgid "Sales"
|
||||
msgstr "Müügid"
|
||||
msgstr "Müük"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.actions.server,child_ids:0
|
||||
@@ -10321,7 +10323,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_sale_stock
|
||||
msgid "Sales and Warehouse Management"
|
||||
msgstr ""
|
||||
msgstr "Müük ja laohaldus"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.model.fields,model:0
|
||||
@@ -12095,7 +12097,7 @@ msgstr "ir.values"
|
||||
#. module: base
|
||||
#: model:res.groups,name:base.group_no_one
|
||||
msgid "Technical Features"
|
||||
msgstr ""
|
||||
msgstr "Tehnilised funktsioonid"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
@@ -12471,7 +12473,7 @@ msgstr "Küpros"
|
||||
#. module: base
|
||||
#: field:res.users,new_password:0
|
||||
msgid "Set Password"
|
||||
msgstr ""
|
||||
msgstr "Määra salasõna"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.actions.server,subject:0
|
||||
@@ -12503,7 +12505,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: selection:res.currency,position:0
|
||||
msgid "Before Amount"
|
||||
msgstr ""
|
||||
msgstr "Enne summat"
|
||||
|
||||
#. module: base
|
||||
#: field:res.request,act_from:0
|
||||
@@ -12572,7 +12574,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: field:res.company,company_registry:0
|
||||
msgid "Company Registry"
|
||||
msgstr "Ettevõtte register"
|
||||
msgstr "Ettevõtte registrikood"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.actions.report.xml:0
|
||||
@@ -12917,7 +12919,7 @@ msgstr "Zaire"
|
||||
#. module: base
|
||||
#: model:ir.module.module,summary:base.module_project
|
||||
msgid "Projects, Tasks"
|
||||
msgstr ""
|
||||
msgstr "Projektid, ülesanded"
|
||||
|
||||
#. module: base
|
||||
#: field:workflow.instance,res_id:0
|
||||
@@ -14247,7 +14249,7 @@ msgstr "Faks"
|
||||
#: view:res.users:0
|
||||
#: field:res.users,company_id:0
|
||||
msgid "Company"
|
||||
msgstr "Firma"
|
||||
msgstr "Ettevõte"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_report_designer
|
||||
|
||||
@@ -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-17 20:19+0000\n"
|
||||
"PO-Revision-Date: 2012-12-19 20:48+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-18 04:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16372)\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-20 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
"Language: hr\n"
|
||||
|
||||
#. module: base
|
||||
@@ -714,7 +714,7 @@ msgstr "Palau"
|
||||
#. module: base
|
||||
#: view:res.partner:0
|
||||
msgid "Sales & Purchases"
|
||||
msgstr "Prodaja i naručivanje"
|
||||
msgstr "Prodaja i nabava"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.translation:0
|
||||
@@ -1511,7 +1511,7 @@ msgstr ""
|
||||
#: model:ir.module.category,name:base.module_category_purchase_management
|
||||
#: model:ir.ui.menu,name:base.menu_purchase_root
|
||||
msgid "Purchases"
|
||||
msgstr "Nabave"
|
||||
msgstr "Nabava"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.md
|
||||
|
||||
+640
-64
File diff suppressed because it is too large
Load Diff
@@ -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-18 23:39+0000\n"
|
||||
"PO-Revision-Date: 2012-12-20 23:39+0000\n"
|
||||
"Last-Translator: Sergio Corato <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-19 05:14+0000\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-21 04:38+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
|
||||
#. module: base
|
||||
@@ -158,7 +158,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: help:res.partner,employee:0
|
||||
msgid "Check this box if this contact is an Employee."
|
||||
msgstr "Seleziona questo box se il contatto è un Impiegato."
|
||||
msgstr "Seleziona questo box se il contatto è un Dipendente."
|
||||
|
||||
#. module: base
|
||||
#: help:ir.model.fields,domain:0
|
||||
|
||||
+168
-22
@@ -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-18 18:59+0000\n"
|
||||
"PO-Revision-Date: 2012-12-20 20:38+0000\n"
|
||||
"Last-Translator: Fekete Mihai <mihai@erpsystems.ro>\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-19 05:14+0000\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-21 04:38+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
|
||||
#. module: base
|
||||
@@ -327,7 +327,7 @@ msgstr "creat(a)."
|
||||
#. module: base
|
||||
#: field:ir.actions.report.xml,report_xsl:0
|
||||
msgid "XSL Path"
|
||||
msgstr ""
|
||||
msgstr "Ruta XSL"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_l10n_tr
|
||||
@@ -353,7 +353,7 @@ msgstr "Inuktitut / ᐃᓄᒃᑎᑐᑦ"
|
||||
#. module: base
|
||||
#: model:res.groups,name:base.group_multi_currency
|
||||
msgid "Multi Currencies"
|
||||
msgstr ""
|
||||
msgstr "Mzi multe valute"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_l10n_cl
|
||||
@@ -365,11 +365,17 @@ msgid ""
|
||||
"\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Localizarea planului contabil si fiscal chilian\n"
|
||||
"==============================================\n"
|
||||
"Plan contabil si fiscal chilian in conformitate cu dispozitiile in vigoare\n"
|
||||
"\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_sale
|
||||
msgid "Sales Management"
|
||||
msgstr "Gestiune vanzari"
|
||||
msgstr "Gestionarea vanzarilor"
|
||||
|
||||
#. module: base
|
||||
#: help:res.partner,user_id:0
|
||||
@@ -377,6 +383,8 @@ msgid ""
|
||||
"The internal user that is in charge of communicating with this contact if "
|
||||
"any."
|
||||
msgstr ""
|
||||
"Utilizatorul intern care este responsabil cu comunicarea cu acest contact, "
|
||||
"daca exista."
|
||||
|
||||
#. module: base
|
||||
#: view:res.partner:0
|
||||
@@ -386,7 +394,7 @@ msgstr "Cauta Partener"
|
||||
#. module: base
|
||||
#: field:ir.module.category,module_nr:0
|
||||
msgid "Number of Modules"
|
||||
msgstr "Numar de module"
|
||||
msgstr "Numar de Module"
|
||||
|
||||
#. module: base
|
||||
#: help:multi_company.default,company_dest_id:0
|
||||
@@ -411,6 +419,15 @@ msgid ""
|
||||
" * Commitment Date\n"
|
||||
" * Effective Date\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Adauga informatii suplimentare despre date la comanda de vanzare.\n"
|
||||
"===================================================\n"
|
||||
"\n"
|
||||
"Puteti adauga urmatoarele date suplimentare la o comanda de vanzare:\n"
|
||||
"-----------------------------------------------------------\n"
|
||||
" * Data Solicitata\n"
|
||||
" * Data Angajament\n"
|
||||
" * Data Efectiva\n"
|
||||
|
||||
#. module: base
|
||||
#: help:ir.actions.act_window,res_id:0
|
||||
@@ -418,6 +435,8 @@ msgid ""
|
||||
"Database ID of record to open in form view, when ``view_mode`` is set to "
|
||||
"'form' only"
|
||||
msgstr ""
|
||||
"ID baza de date a inregistrarii de deschis in vizualizare formular, atunci "
|
||||
"cand ``modul_vizualizare`` este setat doar pe 'formular'"
|
||||
|
||||
#. module: base
|
||||
#: field:res.partner.address,name:0
|
||||
@@ -468,11 +487,18 @@ msgid ""
|
||||
"document and Wiki based Hidden.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Programul de instalare Ascuns bazat pe cunostinte.\n"
|
||||
"=====================================\n"
|
||||
"\n"
|
||||
"Face Configuratia Aplicatiei Cunostinte disponibila, de unde puteti instala\n"
|
||||
"documente si Ascuns bazat pe Wiki.\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_customer_relationship_management
|
||||
msgid "Customer Relationship Management"
|
||||
msgstr "Gestionarea relatiilor cu clientii"
|
||||
msgstr "Gestionarea Relatiilor cu Clientii"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_delivery
|
||||
@@ -486,6 +512,14 @@ msgid ""
|
||||
"invoices from picking, OpenERP is able to add and compute the shipping "
|
||||
"line.\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Va permite sa adaugati metode de livrare la comenzile de vanzare si "
|
||||
"ridicare.\n"
|
||||
"==============================================================\n"
|
||||
"\n"
|
||||
"Va puteti defini propriile grile de preturi pentru transport si livrare. "
|
||||
"Atunci cand creati \n"
|
||||
"facturi la ridicare, OpenERP poate adauga si calcula linia de transport.\n"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/base/ir/ir_filters.py:83
|
||||
@@ -494,6 +528,8 @@ msgid ""
|
||||
"There is already a shared filter set as default for %(model)s, delete or "
|
||||
"change it before setting a new default"
|
||||
msgstr ""
|
||||
"Exista deja un filtru comun setat ca default pentru %(model)s, stergeti-l "
|
||||
"sau schimbati-l inainte de a seta unul nou."
|
||||
|
||||
#. module: base
|
||||
#: code:addons/orm.py:2648
|
||||
@@ -536,7 +572,7 @@ msgstr "Obiect Sursa"
|
||||
#. module: base
|
||||
#: model:res.partner.bank.type,format_layout:base.bank_normal
|
||||
msgid "%(bank_name)s: %(acc_number)s"
|
||||
msgstr "%(bank_name)s: %(acc_number)s"
|
||||
msgstr "%(nume_banca)s: %(numar_cont)s"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.actions.todo:0
|
||||
@@ -591,12 +627,12 @@ msgstr ""
|
||||
#. module: base
|
||||
#: field:ir.model.relation,name:0
|
||||
msgid "Relation Name"
|
||||
msgstr ""
|
||||
msgstr "Nume Relatie"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.rule:0
|
||||
msgid "Create Access Right"
|
||||
msgstr ""
|
||||
msgstr "Creeaza Drept de acces"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.tv
|
||||
@@ -611,7 +647,7 @@ msgstr "Wizardul urmator"
|
||||
#. module: base
|
||||
#: field:res.lang,date_format:0
|
||||
msgid "Date Format"
|
||||
msgstr "Format data"
|
||||
msgstr "Formatul Datei"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_base_report_designer
|
||||
@@ -636,7 +672,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: view:workflow.transition:0
|
||||
msgid "Workflow Transition"
|
||||
msgstr ""
|
||||
msgstr "Tranzitie Flux de lucru"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.gf
|
||||
@@ -646,7 +682,7 @@ msgstr "Guyana Franceza"
|
||||
#. module: base
|
||||
#: model:ir.module.module,summary:base.module_hr
|
||||
msgid "Jobs, Departments, Employees Details"
|
||||
msgstr ""
|
||||
msgstr "Posturi, Departamente, Detalii angajati"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_analytic
|
||||
@@ -662,6 +698,16 @@ msgid ""
|
||||
"that have no counterpart in the general financial accounts.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Modul pentru definirea obiectului contabilitatii analitice.\n"
|
||||
"===============================================\n"
|
||||
"\n"
|
||||
"In OpenERP, conturile analitice sunt legate de conturile generale, dar sunt "
|
||||
"tratate\n"
|
||||
"in mod total independent. Astfel, puteti introduce diverse operatiuni "
|
||||
"analitice\n"
|
||||
"care nu au niciun corespondent in conturile financiare generale.\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: field:ir.ui.view.custom,ref_id:0
|
||||
@@ -685,6 +731,19 @@ msgid ""
|
||||
"* Use emails to automatically confirm and send acknowledgements for any "
|
||||
"event registration\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Organizarea si gestionarea Evenimentelor.\n"
|
||||
"======================================\n"
|
||||
"\n"
|
||||
"Modulul eveniment va permite sa organizati eficient evenimente si toate "
|
||||
"sarcinile asociate lor: planificare, urmarirea inregistrarii,\n"
|
||||
"prezente, etc.\n"
|
||||
"\n"
|
||||
"Caracteristici cheie:\n"
|
||||
"------------\n"
|
||||
"* Gestioneaza Evenimentele si Inregistrarile dumneavoastra\n"
|
||||
"* Foloseste email-urile pentru a confirma si trimite automat confirmarile "
|
||||
"pentru inregistrarea oricarui eveniment\n"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
@@ -710,6 +769,21 @@ msgid ""
|
||||
"requested it.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Traduceri Automate prin Gengo API\n"
|
||||
"----------------------------------------\n"
|
||||
"\n"
|
||||
"Acest modul va instala programatorul pasiv pentru traduceri automate \n"
|
||||
"folosind Gengo API. Pentru a-l activa, trebuie sa\n"
|
||||
"1) Va configurati parametrii de autentificare Gengo din `Setari > Companii > "
|
||||
"Parametrii Gengo`\n"
|
||||
"2) Lansati wizard-ul din `Setari > Termenii Aplicatiei > Gengo: Solicitare "
|
||||
"Manuala a Traducerii` si sa urmeze wizard-ul.\n"
|
||||
"\n"
|
||||
"Acest wizard va activa CRON si Programatorul si va incepe traducerea "
|
||||
"automata prin intermediul Serviciilor Gengo pentru toti termenii solicitati "
|
||||
"de dumneavoastra.\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: help:ir.actions.report.xml,attachment_use:0
|
||||
@@ -759,7 +833,7 @@ msgstr "Columbia"
|
||||
#. module: base
|
||||
#: model:res.partner.title,name:base.res_partner_title_mister
|
||||
msgid "Mister"
|
||||
msgstr ""
|
||||
msgstr "Domnul"
|
||||
|
||||
#. module: base
|
||||
#: help:res.country,code:0
|
||||
@@ -788,7 +862,7 @@ msgstr "Netradus(e)"
|
||||
#. module: base
|
||||
#: view:ir.mail_server:0
|
||||
msgid "Outgoing Mail Server"
|
||||
msgstr ""
|
||||
msgstr "Server Iesire E-mail-uri"
|
||||
|
||||
#. module: base
|
||||
#: help:ir.actions.act_window,context:0
|
||||
@@ -822,7 +896,7 @@ msgstr "Mexic - Contabilitate"
|
||||
#: help:ir.actions.server,action_id:0
|
||||
msgid "Select the Action Window, Report, Wizard to be executed."
|
||||
msgstr ""
|
||||
"Selectati Fereastra de Actiune, Raport sau Asistent care urmeaza a fi "
|
||||
"Selectati Fereastra de Actiune, Raportul sau Asistentul care urmeaza a fi "
|
||||
"executat(a)."
|
||||
|
||||
#. module: base
|
||||
@@ -866,6 +940,33 @@ msgid ""
|
||||
"module named account_voucher.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Contabilitate si Management Financiar.\n"
|
||||
"====================================\n"
|
||||
"\n"
|
||||
"Modulul Finante si contabilitate acopera:\n"
|
||||
"--------------------------------------------\n"
|
||||
" * Contabilitatea Generala\n"
|
||||
" * Costuri/Contabilitatea analitica\n"
|
||||
" * Contabilitatea partii terte\n"
|
||||
" * Gestonarea taxelor si impozitelor\n"
|
||||
" * Bugete\n"
|
||||
" * Facturile Clientului si ale Furnizorului\n"
|
||||
" * Extrase bancare\n"
|
||||
" * Procesul de reconciliere dupa partener\n"
|
||||
"\n"
|
||||
"Creeaza un panou pentru contabili care include:\n"
|
||||
"--------------------------------------------------\n"
|
||||
" Lista cu Facturile Clientilor care trebuie aprobate\n"
|
||||
" Analiza Companiei\n"
|
||||
" Grafic Trezorerie\n"
|
||||
"\n"
|
||||
"Procesele precum pastrarea unui registru contabil general sunt efectuate "
|
||||
"prin Jurnalele financiare definite (linia inregistrarii sau gruparea se face "
|
||||
"prin jurnal) \n"
|
||||
"pentru un anumit an financiar, iar pentru pregatirea voucher-elor exista un "
|
||||
"modul numit account_voucher (cont_voucher).\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: view:ir.model:0
|
||||
@@ -885,6 +986,16 @@ msgid ""
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<p clasa=\"oe_vizualizare_niciuncontinut_creeaza\">\n"
|
||||
" Faceti click pentru a adauga un contact in agenda "
|
||||
"dumneavoastra.\n"
|
||||
" </p><p>\n"
|
||||
" OpenERP va ajuta sa tineti evidenta cu usurinta a tuturor "
|
||||
"activitatilor legate de\n"
|
||||
" un client: discutii, istoricul oportunitatilor de afaceri,\n"
|
||||
" documente, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_web_linkedin
|
||||
@@ -895,6 +1006,11 @@ msgid ""
|
||||
"This module provides the Integration of the LinkedIn with OpenERP.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Modulul OpenERP Web LinkedIn.\n"
|
||||
"============================\n"
|
||||
"Acest modul asigura Integrarea lui LinkedIn in OpenERP.\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: help:ir.actions.act_window,src_model:0
|
||||
@@ -917,7 +1033,7 @@ msgstr "Iordania"
|
||||
#. module: base
|
||||
#: help:ir.cron,nextcall:0
|
||||
msgid "Next planned execution date for this job."
|
||||
msgstr "Data urmatoarei executii planificate pentru acest proiect"
|
||||
msgstr "Data urmatoarei executii planificate pentru aceasta sarcina."
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.er
|
||||
@@ -932,7 +1048,7 @@ msgstr "Numele companiei trebuie sa fie unic !"
|
||||
#. module: base
|
||||
#: model:ir.ui.menu,name:base.menu_base_action_rule_admin
|
||||
msgid "Automated Actions"
|
||||
msgstr "Actiuni automate"
|
||||
msgstr "Actiuni Automate"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_l10n_ro
|
||||
@@ -942,7 +1058,7 @@ msgstr "Romania - Contabilitate"
|
||||
#. module: base
|
||||
#: model:ir.model,name:base.model_res_config_settings
|
||||
msgid "res.config.settings"
|
||||
msgstr ""
|
||||
msgstr "res.configurare.setari"
|
||||
|
||||
#. module: base
|
||||
#: help:res.partner,image_small:0
|
||||
@@ -951,6 +1067,9 @@ msgid ""
|
||||
"image, with aspect ratio preserved. Use this field anywhere a small image is "
|
||||
"required."
|
||||
msgstr ""
|
||||
"Imagine de dimensiune mica a acestui contact. Este redimensionata automat ca "
|
||||
"o imagine de 64x64px, cu raportul aspect pastrat. Folositi acest camp "
|
||||
"oriunde este necesara o imagine mica."
|
||||
|
||||
#. module: base
|
||||
#: help:ir.actions.server,mobile:0
|
||||
@@ -972,7 +1091,7 @@ msgstr "Securitate si Autentificare"
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_web_calendar
|
||||
msgid "Web Calendar"
|
||||
msgstr ""
|
||||
msgstr "Calendar Web"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
@@ -983,7 +1102,7 @@ msgstr "Suedeza / svenska"
|
||||
#: field:base.language.export,name:0
|
||||
#: field:ir.attachment,datas_fname:0
|
||||
msgid "File Name"
|
||||
msgstr ""
|
||||
msgstr "Nume Fisier"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.rs
|
||||
@@ -1034,6 +1153,33 @@ msgid ""
|
||||
"also possible in order to automatically create a meeting when a holiday "
|
||||
"request is accepted by setting up a type of meeting in Leave Type.\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Gestioneaza concediile si cererile de alocare\n"
|
||||
"=====================================\n"
|
||||
"\n"
|
||||
"Aceasta aplicatie controleaza programul de concedii al companiei "
|
||||
"dumneavoastra. Le permite angajatilor sa solicite concedii. Astfel, "
|
||||
"managerii pot verifica cererile pentru concedii si le pot aproba sau "
|
||||
"respinge. In acest mod puteti controla planificarea in ansamblu a "
|
||||
"concediilor pentru companie sau departament.\n"
|
||||
"\n"
|
||||
"Puteti configura diverse tipuri de concedii (de boala, de odihna, zile "
|
||||
"platite, ...) si aloca repede concedii unui angajat sau departament folosind "
|
||||
"cererile de alocare. Un angajat poate de asemenea sa faca o cerere pentru "
|
||||
"mai multe zile libere facand o noua ALocare. Acest lucru va mari totalul "
|
||||
"zilelor disponibile pentru acel tip de concediu (daca cererea este "
|
||||
"acceptata).\n"
|
||||
"\n"
|
||||
"Puteti tine evidenta concediilor in diverse moduri urmarind rapoartele: \n"
|
||||
"\n"
|
||||
"* Rezumat Concedii \n"
|
||||
"* Concedii pe Departamente\n"
|
||||
"* Analiza Concediilor\n"
|
||||
"\n"
|
||||
"O sincronizare cu o agenda interna (Intalnirile modulului MRC) este de "
|
||||
"asemenea posibila pentru a crea in mod automat o intalnire atunci cand o "
|
||||
"cerere de concediu este acceptata prin setarea unui tip de intalnire in "
|
||||
"Tipul Concediului.\n"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
@@ -1081,7 +1227,7 @@ msgstr "Preferinte 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' nu pare a fi o data valabila pentru campul '%%(field)s'"
|
||||
|
||||
#. module: base
|
||||
#: view:res.partner:0
|
||||
|
||||
@@ -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-06 18:17+0000\n"
|
||||
"Last-Translator: ccdos <ccdos@163.com>\n"
|
||||
"PO-Revision-Date: 2012-12-19 07:05+0000\n"
|
||||
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.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-07 04:34+0000\n"
|
||||
"X-Generator: Launchpad (build 16341)\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-20 04:43+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
@@ -1517,7 +1517,7 @@ msgstr "海地"
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_l10n_fr_hr_payroll
|
||||
msgid "French Payroll"
|
||||
msgstr ""
|
||||
msgstr "法国薪酬管理"
|
||||
|
||||
#. module: base
|
||||
#: view:ir.ui.view:0
|
||||
@@ -2871,6 +2871,9 @@ msgid ""
|
||||
"Module to attach a google document to any model.\n"
|
||||
"================================================\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"用于附加 Google 文档到任意模型的模块。\n"
|
||||
"================================================\n"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/base/ir/ir_fields.py:334
|
||||
@@ -3118,7 +3121,7 @@ msgstr ""
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_web_linkedin
|
||||
msgid "LinkedIn Integration"
|
||||
msgstr ""
|
||||
msgstr "LinkedIn 集成"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/orm.py:2021
|
||||
@@ -3301,6 +3304,10 @@ msgid ""
|
||||
"==========================================\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"比利时薪酬规则的会计数据。\n"
|
||||
"==========================================\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.am
|
||||
@@ -4002,6 +4009,17 @@ msgid ""
|
||||
"If you need to manage your meetings, you should install the CRM module.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"这是功能完整的日程表系统\n"
|
||||
"========================================\n"
|
||||
"\n"
|
||||
"支持:\n"
|
||||
"------------\n"
|
||||
" - 约会排程\n"
|
||||
" - 重复性事件\n"
|
||||
"\n"
|
||||
"如果您需要管理您的会议,您应该安装 CRM 模块。\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.je
|
||||
@@ -4039,7 +4057,7 @@ msgstr "%x - 使用日期表示"
|
||||
#. module: base
|
||||
#: view:res.partner:0
|
||||
msgid "Tag"
|
||||
msgstr ""
|
||||
msgstr "标签"
|
||||
|
||||
#. module: base
|
||||
#: view:res.lang:0
|
||||
|
||||
@@ -18,11 +18,164 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import itertools
|
||||
|
||||
import hashlib
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import fields,osv
|
||||
|
||||
class ir_attachment(osv.osv):
|
||||
"""Attachments are used to link binary files or url to any openerp document.
|
||||
|
||||
External attachment storage
|
||||
---------------------------
|
||||
|
||||
The 'data' function field (_data_get,data_set) is implemented using
|
||||
_file_read, _file_write and _file_delete which can be overridden to
|
||||
implement other storage engines, shuch methods should check for other
|
||||
location pseudo uri (example: hdfs://hadoppserver)
|
||||
|
||||
The default implementation is the file:dirname location that stores files
|
||||
on the local filesystem using name based on their sha1 hash
|
||||
"""
|
||||
def _name_get_resname(self, cr, uid, ids, object, method, context):
|
||||
data = {}
|
||||
for attachment in self.browse(cr, uid, ids, context=context):
|
||||
model_object = attachment.res_model
|
||||
res_id = attachment.res_id
|
||||
if model_object and res_id:
|
||||
model_pool = self.pool.get(model_object)
|
||||
res = model_pool.name_get(cr,uid,[res_id],context)
|
||||
res_name = res and res[0][1] or False
|
||||
if res_name:
|
||||
field = self._columns.get('res_name',False)
|
||||
if field and len(res_name) > field.size:
|
||||
res_name = res_name[:field.size-3] + '...'
|
||||
data[attachment.id] = res_name
|
||||
else:
|
||||
data[attachment.id] = False
|
||||
return data
|
||||
|
||||
# 'data' field implementation
|
||||
def _full_path(self, cr, uid, location, path):
|
||||
# location = 'file:filestore'
|
||||
assert location.startswith('file:'), "Unhandled filestore location %s" % location
|
||||
location = location[5:]
|
||||
|
||||
# sanitize location name and path
|
||||
location = re.sub('[.]','',location)
|
||||
location = location.strip('/\\')
|
||||
|
||||
path = re.sub('[.]','',path)
|
||||
path = path.strip('/\\')
|
||||
return os.path.join(tools.config['root_path'], location, cr.dbname, path)
|
||||
|
||||
def _file_read(self, cr, uid, location, fname, bin_size=False):
|
||||
full_path = self._full_path(cr, uid, location, fname)
|
||||
r = ''
|
||||
try:
|
||||
if bin_size:
|
||||
r = os.path.getsize(full_path)
|
||||
else:
|
||||
r = open(full_path).read().encode('base64')
|
||||
except IOError:
|
||||
_logger.error("_read_file reading %s",full_path)
|
||||
return r
|
||||
|
||||
def _file_write(self, cr, uid, location, value):
|
||||
bin_value = value.decode('base64')
|
||||
fname = hashlib.sha1(bin_value).hexdigest()
|
||||
# scatter files across 1024 dirs
|
||||
# we use '/' in the db (even on windows)
|
||||
fname = fname[:3] + '/' + fname
|
||||
full_path = self._full_path(cr, uid, location, fname)
|
||||
try:
|
||||
dirname = os.path.dirname(full_path)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
open(full_path,'wb').write(bin_value)
|
||||
except IOError:
|
||||
_logger.error("_file_write writing %s",full_path)
|
||||
return fname
|
||||
|
||||
def _file_delete(self, cr, uid, location, fname):
|
||||
count = self.search(cr, 1, [('store_fname','=',fname)], count=True)
|
||||
if count <= 1:
|
||||
full_path = self._full_path(cr, uid, location, fname)
|
||||
try:
|
||||
os.unlink(full_path)
|
||||
except IOError:
|
||||
# Harmless and needed for race conditions
|
||||
_logger.error("_file_delete could not unlink %s",full_path)
|
||||
|
||||
def _data_get(self, cr, uid, ids, name, arg, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
result = {}
|
||||
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
|
||||
bin_size = context.get('bin_size')
|
||||
for attach in self.browse(cr, uid, ids, context=context):
|
||||
if location and attach.store_fname:
|
||||
result[attach.id] = self._file_read(cr, uid, location, attach.store_fname, bin_size)
|
||||
else:
|
||||
result[attach.id] = attach.db_datas
|
||||
return result
|
||||
|
||||
def _data_set(self, cr, uid, id, name, value, arg, context=None):
|
||||
# We dont handle setting data to null
|
||||
if not value:
|
||||
return True
|
||||
if context is None:
|
||||
context = {}
|
||||
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
|
||||
file_size = len(value.decode('base64'))
|
||||
if location:
|
||||
attach = self.browse(cr, uid, id, context=context)
|
||||
if attach.store_fname:
|
||||
self._file_delete(cr, uid, location, attach.store_fname)
|
||||
fname = self._file_write(cr, uid, location, value)
|
||||
super(ir_attachment, self).write(cr, uid, [id], {'store_fname': fname, 'file_size': file_size}, context=context)
|
||||
else:
|
||||
super(ir_attachment, self).write(cr, uid, [id], {'db_datas': value, 'file_size': file_size}, context=context)
|
||||
return True
|
||||
|
||||
_name = 'ir.attachment'
|
||||
_columns = {
|
||||
'name': fields.char('Attachment Name',size=256, required=True),
|
||||
'datas_fname': fields.char('File Name',size=256),
|
||||
'description': fields.text('Description'),
|
||||
'res_name': fields.function(_name_get_resname, type='char', size=128, string='Resource Name', store=True),
|
||||
'res_model': fields.char('Resource Model',size=64, readonly=True, help="The database object this attachment will be attached to"),
|
||||
'res_id': fields.integer('Resource ID', readonly=True, help="The record id this is attached to"),
|
||||
'create_date': fields.datetime('Date Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', 'Owner', readonly=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', change_default=True),
|
||||
'type': fields.selection( [ ('url','URL'), ('binary','Binary'), ],
|
||||
'Type', help="Binary File or URL", required=True, change_default=True),
|
||||
'url': fields.char('Url', size=1024),
|
||||
# al: We keep shitty field names for backward compatibility with document
|
||||
'datas': fields.function(_data_get, fnct_inv=_data_set, string='File Content', type="binary", nodrop=True),
|
||||
'store_fname': fields.char('Stored Filename', size=256),
|
||||
'db_datas': fields.binary('Database Data'),
|
||||
'file_size': fields.integer('File Size'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'type': 'binary',
|
||||
'file_size': 0,
|
||||
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c),
|
||||
}
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
super(ir_attachment, self)._auto_init(cr, context)
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
|
||||
cr.commit()
|
||||
|
||||
def check(self, cr, uid, ids, mode, context=None, values=None):
|
||||
"""Restricts the access to an ir.attachment, according to referred model
|
||||
In the 'document' module, it is overriden to relax this hard rule, since
|
||||
@@ -108,6 +261,8 @@ class ir_attachment(osv.osv):
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
self.check(cr, uid, ids, 'write', context=context, values=vals)
|
||||
if 'file_size' in vals:
|
||||
del vals['file_size']
|
||||
return super(ir_attachment, self).write(cr, uid, ids, vals, context)
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
@@ -116,70 +271,22 @@ class ir_attachment(osv.osv):
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
self.check(cr, uid, ids, 'unlink', context=context)
|
||||
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
|
||||
if location:
|
||||
for attach in self.browse(cr, uid, ids, context=context):
|
||||
if attach.store_fname:
|
||||
self._file_delete(cr, uid, location, attach.store_fname)
|
||||
return super(ir_attachment, self).unlink(cr, uid, ids, context)
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
self.check(cr, uid, [], mode='create', context=context, values=values)
|
||||
if 'file_size' in values:
|
||||
del values['file_size']
|
||||
return super(ir_attachment, self).create(cr, uid, values, context)
|
||||
|
||||
def action_get(self, cr, uid, context=None):
|
||||
return self.pool.get('ir.actions.act_window').for_xml_id(
|
||||
cr, uid, 'base', 'action_attachment', context=context)
|
||||
|
||||
def _name_get_resname(self, cr, uid, ids, object, method, context):
|
||||
data = {}
|
||||
for attachment in self.browse(cr, uid, ids, context=context):
|
||||
model_object = attachment.res_model
|
||||
res_id = attachment.res_id
|
||||
if model_object and res_id:
|
||||
model_pool = self.pool.get(model_object)
|
||||
res = model_pool.name_get(cr,uid,[res_id],context)
|
||||
res_name = res and res[0][1] or False
|
||||
if res_name:
|
||||
field = self._columns.get('res_name',False)
|
||||
if field and len(res_name) > field.size:
|
||||
res_name = res_name[:field.size-3] + '...'
|
||||
data[attachment.id] = res_name
|
||||
else:
|
||||
data[attachment.id] = False
|
||||
return data
|
||||
|
||||
_name = 'ir.attachment'
|
||||
_columns = {
|
||||
'name': fields.char('Attachment Name',size=256, required=True),
|
||||
'datas': fields.binary('Data'),
|
||||
'datas_fname': fields.char('File Name',size=256),
|
||||
'description': fields.text('Description'),
|
||||
'res_name': fields.function(_name_get_resname, type='char', size=128,
|
||||
string='Resource Name', store=True),
|
||||
'res_model': fields.char('Resource Object',size=64, readonly=True,
|
||||
help="The database object this attachment will be attached to"),
|
||||
'res_id': fields.integer('Resource ID', readonly=True,
|
||||
help="The record id this is attached to"),
|
||||
'url': fields.char('Url', size=512, oldname="link"),
|
||||
'type': fields.selection(
|
||||
[ ('url','URL'), ('binary','Binary'), ],
|
||||
'Type', help="Binary File or external URL", required=True, change_default=True),
|
||||
|
||||
'create_date': fields.datetime('Date Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', 'Owner', readonly=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', change_default=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'type': 'binary',
|
||||
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c),
|
||||
}
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
super(ir_attachment, self)._auto_init(cr, context)
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
|
||||
cr.commit()
|
||||
|
||||
ir_attachment()
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
||||
@@ -24,12 +24,22 @@ from docutils.transforms import Transform, writer_aux
|
||||
from docutils.writers.html4css1 import Writer
|
||||
import imp
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
import urllib
|
||||
import urllib2
|
||||
import zipfile
|
||||
import zipimport
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO # NOQA
|
||||
|
||||
import openerp
|
||||
from openerp import modules, pooler, release, tools, addons
|
||||
from openerp import modules, pooler, tools, addons
|
||||
from openerp.modules.db import create_categories
|
||||
from openerp.tools.parse_version import parse_version
|
||||
from openerp.tools.translate import _
|
||||
@@ -43,14 +53,29 @@ ACTION_DICT = {
|
||||
'res_model': 'base.module.upgrade',
|
||||
'target': 'new',
|
||||
'type': 'ir.actions.act_window',
|
||||
'nodestroy':True,
|
||||
'nodestroy': True,
|
||||
}
|
||||
|
||||
def backup(path, raise_exception=True):
|
||||
path = os.path.normpath(path)
|
||||
if not os.path.exists(path):
|
||||
if not raise_exception:
|
||||
return None
|
||||
raise OSError('path does not exists')
|
||||
cnt = 1
|
||||
while True:
|
||||
bck = '%s~%d' % (path, cnt)
|
||||
if not os.path.exists(bck):
|
||||
shutil.move(path, bck)
|
||||
return bck
|
||||
cnt += 1
|
||||
|
||||
|
||||
class module_category(osv.osv):
|
||||
_name = "ir.module.category"
|
||||
_description = "Application"
|
||||
|
||||
def _module_nbr(self,cr,uid, ids, prop, unknow_none, context):
|
||||
def _module_nbr(self, cr, uid, ids, prop, unknow_none, context):
|
||||
cr.execute('SELECT category_id, COUNT(*) \
|
||||
FROM ir_module_module \
|
||||
WHERE category_id IN %(ids)s \
|
||||
@@ -58,7 +83,7 @@ class module_category(osv.osv):
|
||||
FROM ir_module_category \
|
||||
WHERE parent_id IN %(ids)s) \
|
||||
GROUP BY category_id', {'ids': tuple(ids)}
|
||||
)
|
||||
)
|
||||
result = dict(cr.fetchall())
|
||||
for id in ids:
|
||||
cr.execute('select id from ir_module_category where parent_id=%s', (id,))
|
||||
@@ -71,16 +96,16 @@ class module_category(osv.osv):
|
||||
'parent_id': fields.many2one('ir.module.category', 'Parent Application', select=True),
|
||||
'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Applications'),
|
||||
'module_nr': fields.function(_module_nbr, string='Number of Modules', type='integer'),
|
||||
'module_ids' : fields.one2many('ir.module.module', 'category_id', 'Modules'),
|
||||
'description' : fields.text("Description", translate=True),
|
||||
'sequence' : fields.integer('Sequence'),
|
||||
'visible' : fields.boolean('Visible'),
|
||||
'module_ids': fields.one2many('ir.module.module', 'category_id', 'Modules'),
|
||||
'description': fields.text("Description", translate=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'visible': fields.boolean('Visible'),
|
||||
'xml_id': fields.function(osv.osv.get_external_id, type='char', size=128, string="External ID"),
|
||||
}
|
||||
_order = 'name'
|
||||
|
||||
_defaults = {
|
||||
'visible' : 1,
|
||||
'visible': 1,
|
||||
}
|
||||
|
||||
class MyFilterMessages(Transform):
|
||||
@@ -119,7 +144,6 @@ class module(osv.osv):
|
||||
info = {}
|
||||
try:
|
||||
info = modules.load_information_from_description_file(name)
|
||||
info['version'] = release.major_version + '.' + info['version']
|
||||
except Exception:
|
||||
_logger.debug('Error when trying to fetch informations for '
|
||||
'module %s', name, exc_info=True)
|
||||
@@ -134,9 +158,10 @@ class module(osv.osv):
|
||||
return res
|
||||
|
||||
def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None):
|
||||
res = dict.fromkeys(ids, '')
|
||||
default_version = modules.adapt_version('1.0')
|
||||
res = dict.fromkeys(ids, default_version)
|
||||
for m in self.browse(cr, uid, ids):
|
||||
res[m.id] = self.get_module_info(m.name).get('version', '')
|
||||
res[m.id] = self.get_module_info(m.name).get('version', default_version)
|
||||
return res
|
||||
|
||||
def _get_views(self, cr, uid, ids, field_name=None, arg=None, context=None):
|
||||
@@ -167,9 +192,11 @@ class module(osv.osv):
|
||||
continue
|
||||
|
||||
# then, search and group ir.model.data records
|
||||
imd_models = dict( [(m,[]) for m in dmodels])
|
||||
imd_ids = model_data_obj.search(cr,uid,[('module','=', module_rec.name),
|
||||
('model','in',tuple(dmodels))])
|
||||
imd_models = dict([(m, []) for m in dmodels])
|
||||
imd_ids = model_data_obj.search(cr, uid, [
|
||||
('module', '=', module_rec.name),
|
||||
('model', 'in', tuple(dmodels))
|
||||
])
|
||||
|
||||
for imd_res in model_data_obj.read(cr, uid, imd_ids, ['model', 'res_id'], context=context):
|
||||
imd_models[imd_res['model']].append(imd_res['res_id'])
|
||||
@@ -181,7 +208,7 @@ class module(osv.osv):
|
||||
view_ids = imd_models.get('ir.ui.view', [])
|
||||
for v in view_obj.browse(cr, uid, view_ids, context=context):
|
||||
aa = v.inherit_id and '* INHERIT ' or ''
|
||||
res_mod_dic['views_by_module'].append(aa + v.name + '('+v.type+')')
|
||||
res_mod_dic['views_by_module'].append('%s%s (%s)' % (aa, v.name, v.type))
|
||||
|
||||
report_ids = imd_models.get('ir.actions.report.xml', [])
|
||||
for rx in report_obj.browse(cr, uid, report_ids, context=context):
|
||||
@@ -191,15 +218,13 @@ class module(osv.osv):
|
||||
for um in menu_obj.browse(cr, uid, menu_ids, context=context):
|
||||
res_mod_dic['menus_by_module'].append(um.complete_name)
|
||||
except KeyError, e:
|
||||
_logger.warning(
|
||||
'Data not found for items of %s', module_rec.name)
|
||||
_logger.warning('Data not found for items of %s', module_rec.name)
|
||||
except AttributeError, e:
|
||||
_logger.warning(
|
||||
'Data not found for items of %s %s', module_rec.name, str(e))
|
||||
_logger.warning('Data not found for items of %s %s', module_rec.name, str(e))
|
||||
except Exception, e:
|
||||
_logger.warning('Unknown error while fetching data of %s',
|
||||
module_rec.name, exc_info=True)
|
||||
for key, _ in res.iteritems():
|
||||
_logger.warning('Unknown error while fetching data of %s', module_rec.name, exc_info=True)
|
||||
|
||||
for key in res.iterkeys():
|
||||
for k, v in res[key].iteritems():
|
||||
res[key][k] = "\n".join(sorted(v))
|
||||
return res
|
||||
@@ -232,36 +257,34 @@ class module(osv.osv):
|
||||
# installed_version refer the latest version (the one on disk)
|
||||
# latest_version refer the installed version (the one in database)
|
||||
# published_version refer the version available on the repository
|
||||
'installed_version': fields.function(_get_latest_version,
|
||||
string='Latest Version', type='char'),
|
||||
'installed_version': fields.function(_get_latest_version, string='Latest Version', type='char'),
|
||||
'latest_version': fields.char('Installed Version', size=64, readonly=True),
|
||||
'published_version': fields.char('Published Version', size=64, readonly=True),
|
||||
|
||||
'url': fields.char('URL', size=128, readonly=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'dependencies_id': fields.one2many('ir.module.module.dependency',
|
||||
'module_id', 'Dependencies', readonly=True),
|
||||
'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies', readonly=True),
|
||||
'auto_install': fields.boolean('Automatic Installation',
|
||||
help='An auto-installable module is automatically installed by the '
|
||||
'system when all its dependencies are satisfied. '
|
||||
'If the module has no dependency, it is always installed.'),
|
||||
help='An auto-installable module is automatically installed by the '
|
||||
'system when all its dependencies are satisfied. '
|
||||
'If the module has no dependency, it is always installed.'),
|
||||
'state': fields.selection([
|
||||
('uninstallable','Not Installable'),
|
||||
('uninstalled','Not Installed'),
|
||||
('installed','Installed'),
|
||||
('to upgrade','To be upgraded'),
|
||||
('to remove','To be removed'),
|
||||
('to install','To be installed')
|
||||
('uninstallable', 'Not Installable'),
|
||||
('uninstalled', 'Not Installed'),
|
||||
('installed', 'Installed'),
|
||||
('to upgrade', 'To be upgraded'),
|
||||
('to remove', 'To be removed'),
|
||||
('to install', 'To be installed')
|
||||
], string='Status', readonly=True, select=True),
|
||||
'demo': fields.boolean('Demo Data', readonly=True),
|
||||
'license': fields.selection([
|
||||
('GPL-2', 'GPL Version 2'),
|
||||
('GPL-2 or any later version', 'GPL-2 or later version'),
|
||||
('GPL-3', 'GPL Version 3'),
|
||||
('GPL-3 or any later version', 'GPL-3 or later version'),
|
||||
('AGPL-3', 'Affero GPL-3'),
|
||||
('Other OSI approved licence', 'Other OSI Approved Licence'),
|
||||
('Other proprietary', 'Other Proprietary')
|
||||
('GPL-2', 'GPL Version 2'),
|
||||
('GPL-2 or any later version', 'GPL-2 or later version'),
|
||||
('GPL-3', 'GPL Version 3'),
|
||||
('GPL-3 or any later version', 'GPL-3 or later version'),
|
||||
('AGPL-3', 'Affero GPL-3'),
|
||||
('Other OSI approved licence', 'Other OSI Approved Licence'),
|
||||
('Other proprietary', 'Other Proprietary')
|
||||
], string='License', readonly=True),
|
||||
'menus_by_module': fields.function(_get_views, string='Menus', type='text', multi="meta", store=True),
|
||||
'reports_by_module': fields.function(_get_views, string='Reports', type='text', multi="meta", store=True),
|
||||
@@ -283,7 +306,7 @@ class module(osv.osv):
|
||||
return _('The name of the module must be unique !')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'UNIQUE (name)',_name_uniq_msg ),
|
||||
('name_uniq', 'UNIQUE (name)', _name_uniq_msg),
|
||||
]
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
@@ -292,10 +315,9 @@ class module(osv.osv):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
mod_names = []
|
||||
for mod in self.read(cr, uid, ids, ['state','name'], context):
|
||||
for mod in self.read(cr, uid, ids, ['state', 'name'], context):
|
||||
if mod['state'] in ('installed', 'to upgrade', 'to remove', 'to install'):
|
||||
raise orm.except_orm(_('Error'),
|
||||
_('You try to remove a module that is installed or will be installed'))
|
||||
raise orm.except_orm(_('Error'), _('You try to remove a module that is installed or will be installed'))
|
||||
mod_names.append(mod['name'])
|
||||
#Removing the entry from ir_model_data
|
||||
#ids_meta = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', 'module_meta_information'), ('module', 'in', mod_names)])
|
||||
@@ -340,7 +362,7 @@ class module(osv.osv):
|
||||
raise orm.except_orm(_('Error'), msg % (module_name, e.args[0]))
|
||||
|
||||
def state_update(self, cr, uid, ids, newstate, states_to_update, context=None, level=100):
|
||||
if level<1:
|
||||
if level < 1:
|
||||
raise orm.except_orm(_('Error'), _('Recursion error in modules dependencies !'))
|
||||
demo = False
|
||||
for module in self.browse(cr, uid, ids, context=context):
|
||||
@@ -348,9 +370,9 @@ class module(osv.osv):
|
||||
for dep in module.dependencies_id:
|
||||
if dep.state == 'unknown':
|
||||
raise orm.except_orm(_('Error'), _("You try to install module '%s' that depends on module '%s'.\nBut the latter module is not available in your system.") % (module.name, dep.name,))
|
||||
ids2 = self.search(cr, uid, [('name','=',dep.name)])
|
||||
ids2 = self.search(cr, uid, [('name', '=', dep.name)])
|
||||
if dep.state != newstate:
|
||||
mdemo = self.state_update(cr, uid, ids2, newstate, states_to_update, context, level-1,) or mdemo
|
||||
mdemo = self.state_update(cr, uid, ids2, newstate, states_to_update, context, level - 1) or mdemo
|
||||
else:
|
||||
od = self.browse(cr, uid, ids2)[0]
|
||||
mdemo = od.demo or mdemo
|
||||
@@ -359,7 +381,7 @@ class module(osv.osv):
|
||||
if not module.dependencies_id:
|
||||
mdemo = module.demo
|
||||
if module.state in states_to_update:
|
||||
self.write(cr, uid, [module.id], {'state': newstate, 'demo':mdemo})
|
||||
self.write(cr, uid, [module.id], {'state': newstate, 'demo': mdemo})
|
||||
demo = demo or mdemo
|
||||
return demo
|
||||
|
||||
@@ -403,7 +425,7 @@ class module(osv.osv):
|
||||
return self._button_immediate_function(cr, uid, ids, self.button_install, context=context)
|
||||
|
||||
def button_install_cancel(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
|
||||
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo': False})
|
||||
return True
|
||||
|
||||
def module_uninstall(self, cr, uid, ids, context=None):
|
||||
@@ -421,12 +443,13 @@ class module(osv.osv):
|
||||
return True
|
||||
|
||||
def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None,
|
||||
exclude_states=['uninstalled','uninstallable','to remove'],
|
||||
exclude_states=['uninstalled', 'uninstallable', 'to remove'],
|
||||
context=None):
|
||||
"""Return the ids of all modules that directly or indirectly depend
|
||||
on the given module `ids`, and that satisfy the `exclude_states`
|
||||
filter"""
|
||||
if not ids: return []
|
||||
if not ids:
|
||||
return []
|
||||
known_dep_ids = set(known_dep_ids or [])
|
||||
cr.execute('''SELECT DISTINCT m.id
|
||||
FROM
|
||||
@@ -437,13 +460,13 @@ class module(osv.osv):
|
||||
d.name IN (SELECT name from ir_module_module where id in %s) AND
|
||||
m.state NOT IN %s AND
|
||||
m.id NOT IN %s ''',
|
||||
(tuple(ids),tuple(exclude_states), tuple(known_dep_ids or ids)))
|
||||
(tuple(ids), tuple(exclude_states), tuple(known_dep_ids or ids)))
|
||||
new_dep_ids = set([m[0] for m in cr.fetchall()])
|
||||
missing_mod_ids = new_dep_ids - known_dep_ids
|
||||
known_dep_ids |= new_dep_ids
|
||||
if missing_mod_ids:
|
||||
known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids),
|
||||
known_dep_ids, exclude_states,context))
|
||||
known_dep_ids, exclude_states, context))
|
||||
return list(known_dep_ids)
|
||||
|
||||
def _button_immediate_function(self, cr, uid, ids, function, context=None):
|
||||
@@ -460,9 +483,9 @@ class module(osv.osv):
|
||||
menu_obj = self.pool.get('ir.ui.menu')
|
||||
menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context)
|
||||
return {
|
||||
'type' : 'ir.actions.client',
|
||||
'tag' : 'reload',
|
||||
'params' : {'menu_id' : menu_ids and menu_ids[0] or False}
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload',
|
||||
'params': {'menu_id': menu_ids and menu_ids[0] or False}
|
||||
}
|
||||
|
||||
def button_immediate_uninstall(self, cr, uid, ids, context=None):
|
||||
@@ -496,20 +519,19 @@ class module(osv.osv):
|
||||
self.update_list(cr, uid)
|
||||
|
||||
i = 0
|
||||
while i<len(todo):
|
||||
while i < len(todo):
|
||||
mod = todo[i]
|
||||
i += 1
|
||||
if mod.state not in ('installed','to upgrade'):
|
||||
raise orm.except_orm(_('Error'),
|
||||
_("Can not upgrade module '%s'. It is not installed.") % (mod.name,))
|
||||
if mod.state not in ('installed', 'to upgrade'):
|
||||
raise orm.except_orm(_('Error'), _("Can not upgrade module '%s'. It is not installed.") % (mod.name,))
|
||||
self.check_external_dependencies(mod.name, 'to upgrade')
|
||||
iids = depobj.search(cr, uid, [('name', '=', mod.name)], context=context)
|
||||
for dep in depobj.browse(cr, uid, iids, context=context):
|
||||
if dep.module_id.state=='installed' and dep.module_id not in todo:
|
||||
if dep.module_id.state == 'installed' and dep.module_id not in todo:
|
||||
todo.append(dep.module_id)
|
||||
|
||||
ids = map(lambda x: x.id, todo)
|
||||
self.write(cr, uid, ids, {'state':'to upgrade'}, context=context)
|
||||
self.write(cr, uid, ids, {'state': 'to upgrade'}, context=context)
|
||||
|
||||
to_install = []
|
||||
for mod in todo:
|
||||
@@ -517,7 +539,7 @@ class module(osv.osv):
|
||||
if dep.state == 'unknown':
|
||||
raise orm.except_orm(_('Error'), _('You try to upgrade a module that depends on the module: %s.\nBut this module is not available in your system.') % (dep.name,))
|
||||
if dep.state == 'uninstalled':
|
||||
ids2 = self.search(cr, uid, [('name','=',dep.name)])
|
||||
ids2 = self.search(cr, uid, [('name', '=', dep.name)])
|
||||
to_install.extend(ids2)
|
||||
|
||||
self.button_install(cr, uid, to_install, context=context)
|
||||
@@ -550,8 +572,9 @@ class module(osv.osv):
|
||||
|
||||
# update the list of available packages
|
||||
def update_list(self, cr, uid, context=None):
|
||||
res = [0, 0] # [update, add]
|
||||
res = [0, 0] # [update, add]
|
||||
|
||||
default_version = modules.adapt_version('1.0')
|
||||
known_mods = self.browse(cr, uid, self.search(cr, uid, []))
|
||||
known_mods_names = dict([(m.name, m) for m in known_mods])
|
||||
|
||||
@@ -570,7 +593,7 @@ class module(osv.osv):
|
||||
updated_values[key] = values[key]
|
||||
if terp.get('installable', True) and mod.state == 'uninstallable':
|
||||
updated_values['state'] = 'uninstalled'
|
||||
if parse_version(terp.get('version', '')) > parse_version(mod.latest_version or ''):
|
||||
if parse_version(terp.get('version', default_version)) > parse_version(mod.latest_version or default_version):
|
||||
res[0] += 1
|
||||
if updated_values:
|
||||
self.write(cr, uid, mod.id, updated_values)
|
||||
@@ -587,24 +610,32 @@ class module(osv.osv):
|
||||
self._update_dependencies(cr, uid, mod, terp.get('depends', []))
|
||||
self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized'))
|
||||
|
||||
# Trigger load_addons if new module have been discovered it exists on
|
||||
# wsgi handlers, so they can react accordingly
|
||||
if tuple(res) != (0, 0):
|
||||
for handler in openerp.service.wsgi_server.module_handlers:
|
||||
if hasattr(handler,'load_addons'):
|
||||
handler.load_addons()
|
||||
|
||||
return res
|
||||
|
||||
def download(self, cr, uid, ids, download=True, context=None):
|
||||
res = []
|
||||
default_version = modules.adapt_version('1.0')
|
||||
for mod in self.browse(cr, uid, ids, context=context):
|
||||
if not mod.url:
|
||||
continue
|
||||
match = re.search('-([a-zA-Z0-9\._-]+)(\.zip)', mod.url, re.I)
|
||||
version = '0'
|
||||
version = default_version
|
||||
if match:
|
||||
version = match.group(1)
|
||||
if parse_version(mod.installed_version or '0') >= parse_version(version):
|
||||
if parse_version(mod.installed_version) >= parse_version(version):
|
||||
continue
|
||||
res.append(mod.url)
|
||||
if not download:
|
||||
continue
|
||||
zip_content = urllib.urlopen(mod.url).read()
|
||||
fname = modules.get_module_path(str(mod.name)+'.zip', downloaded=True)
|
||||
fname = modules.get_module_path(str(mod.name) + '.zip', downloaded=True)
|
||||
try:
|
||||
with open(fname, 'wb') as fp:
|
||||
fp.write(zip_content)
|
||||
@@ -614,17 +645,77 @@ class module(osv.osv):
|
||||
raise orm.except_orm(_('Error'), _('Can not create the module file:\n %s') % (fname,))
|
||||
terp = self.get_module_info(mod.name)
|
||||
self.write(cr, uid, mod.id, self.get_values_from_terp(terp))
|
||||
cr.execute('DELETE FROM ir_module_module_dependency ' \
|
||||
'WHERE module_id = %s', (mod.id,))
|
||||
self._update_dependencies(cr, uid, mod, terp.get('depends',
|
||||
[]))
|
||||
self._update_category(cr, uid, mod, terp.get('category',
|
||||
'Uncategorized'))
|
||||
cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s', (mod.id,))
|
||||
self._update_dependencies(cr, uid, mod, terp.get('depends', []))
|
||||
self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized'))
|
||||
# Import module
|
||||
zimp = zipimport.zipimporter(fname)
|
||||
zimp.load_module(mod.name)
|
||||
return res
|
||||
|
||||
def install_from_urls(self, cr, uid, urls, context=None):
|
||||
OPENERP = 'openerp'
|
||||
tmp = tempfile.mkdtemp()
|
||||
try:
|
||||
for module_name, url in urls.items():
|
||||
if not url:
|
||||
continue # nothing to download, local version is already the last one
|
||||
try:
|
||||
content = urllib2.urlopen(url).read()
|
||||
except Exception, e:
|
||||
_logger.exception('ggr')
|
||||
raise osv.except_osv('grrr', e)
|
||||
else:
|
||||
zipfile.ZipFile(StringIO(content)).extractall(tmp)
|
||||
assert os.path.isdir(os.path.join(tmp, module_name))
|
||||
|
||||
for module_name in urls:
|
||||
if module_name == OPENERP:
|
||||
continue # special case. handled below
|
||||
module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False)
|
||||
bck = backup(module_path, False)
|
||||
shutil.move(os.path.join(tmp, module_name), module_path)
|
||||
if bck:
|
||||
shutil.rmtree(bck)
|
||||
|
||||
if urls.get(OPENERP, None):
|
||||
# special case. it containt the server and the base module.
|
||||
# extract path is not the same
|
||||
|
||||
base_path = os.path.dirname(modules.get_module_path('base'))
|
||||
|
||||
# copy all modules in the SERVER/openerp/addons directory to the new "openerp" module (except base itself)
|
||||
for d in os.listdir(base_path):
|
||||
if d != 'base' and os.path.isdir(os.path.join(base_path, d)):
|
||||
destdir = os.path.join(tmp, OPENERP, 'addons', d) # XXX 'openerp' subdirectory ?
|
||||
shutil.copytree(os.path.join(base_path, d), destdir)
|
||||
|
||||
# then replace the server by the new "base" module
|
||||
server_dir = openerp.tools.config['root_path'] # XXX or dirname()
|
||||
bck = backup(server_dir)
|
||||
shutil.move(os.path.join(tmp, OPENERP), server_dir)
|
||||
#if bck:
|
||||
# shutil.rmtree(bck)
|
||||
|
||||
self.update_list(cr, uid, context=context)
|
||||
|
||||
ids = self.search(cr, uid, [('name', 'in', urls.keys())], context=context)
|
||||
if self.search_count(cr, uid, [('id', 'in', ids), ('state', '=', 'installed')], context=context):
|
||||
# if any to update
|
||||
cr.commit()
|
||||
openerp.service.restart_server()
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'home',
|
||||
'params': {'wait': True},
|
||||
}
|
||||
return self.button_immediate_install(cr, uid, ids, context=context)
|
||||
finally:
|
||||
shutil.rmtree(tmp)
|
||||
|
||||
def install_by_names(self, cr, uid, names, context=None):
|
||||
raise NotImplementedError('# TODO')
|
||||
|
||||
def _update_dependencies(self, cr, uid, mod_browse, depends=None):
|
||||
if depends is None:
|
||||
depends = []
|
||||
@@ -679,20 +770,20 @@ class module_dependency(osv.osv):
|
||||
|
||||
_columns = {
|
||||
# The dependency name
|
||||
'name': fields.char('Name', size=128, select=True),
|
||||
'name': fields.char('Name', size=128, select=True),
|
||||
|
||||
# The module that depends on it
|
||||
'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
|
||||
|
||||
'state': fields.function(_state, type='selection', selection=[
|
||||
('uninstallable','Uninstallable'),
|
||||
('uninstalled','Not Installed'),
|
||||
('installed','Installed'),
|
||||
('to upgrade','To be upgraded'),
|
||||
('to remove','To be removed'),
|
||||
('to install','To be installed'),
|
||||
('uninstallable', 'Uninstallable'),
|
||||
('uninstalled', 'Not Installed'),
|
||||
('installed', 'Installed'),
|
||||
('to upgrade', 'To be upgraded'),
|
||||
('to remove', 'To be removed'),
|
||||
('to install', 'To be installed'),
|
||||
('unknown', 'Unknown'),
|
||||
], string='Status', readonly=True, select=True),
|
||||
], string='Status', readonly=True, select=True),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Modules Categories -->
|
||||
|
||||
<record id="view_module_category_form" model="ir.ui.view">
|
||||
<field name="name">ir.module.category.form</field>
|
||||
<field name="model">ir.module.category</field>
|
||||
@@ -32,7 +30,6 @@
|
||||
</record>
|
||||
|
||||
<!-- Click on a category -->
|
||||
|
||||
<record id="view_module_filter" model="ir.ui.view">
|
||||
<field name="name">ir.module.module.list.select</field>
|
||||
<field name="model">ir.module.module</field>
|
||||
@@ -43,7 +40,7 @@
|
||||
<filter name="app" icon="terp-check" string="Apps" domain="[('application', '=', 1)]"/>
|
||||
<filter name="extra" icon="terp-check" string="Extra" domain="[('application', '=', 0)]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-check" string="Installed" domain="[('state', 'in', ['installed', 'to upgrade', 'to remove'])]"/>
|
||||
<filter name="installed" icon="terp-check" string="Installed" domain="[('state', 'in', ['installed', 'to upgrade', 'to remove'])]"/>
|
||||
<filter icon="terp-dialog-close" string="Not Installed" domain="[('state', 'in', ['uninstalled', 'uninstallable', 'to install'])]"/>
|
||||
<field name="category_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
@@ -53,37 +50,6 @@
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="module_view_kanban">
|
||||
<field name="name">Modules Kanban</field>
|
||||
<field name="model">ir.module.module</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban create="false">
|
||||
<field name="icon"/>
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
<field name="summary"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_module_vignette">
|
||||
<t t-set="installed" t-value="record.state.raw_value == 'installed'"/>
|
||||
<img t-attf-src="#{record.icon.value}" class="oe_module_icon"/>
|
||||
<div class="oe_module_desc">
|
||||
<h4><a type="open"><field name="shortdesc"/></a></h4>
|
||||
<p class="oe_module_name">
|
||||
<t t-if="record.summary.raw_value"><field name="summary"/><br/></t>
|
||||
<i><field name="name" groups="base.group_no_one"/></i>
|
||||
</p>
|
||||
<button type="object" name="button_immediate_install" states="uninstalled">Install</button>
|
||||
<button t-if="installed" disabled="disabled">Installed</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_module_open_categ" model="ir.actions.act_window">
|
||||
<field name="name">Modules</field>
|
||||
<field name="res_model">ir.module.module</field>
|
||||
@@ -100,7 +66,6 @@
|
||||
|
||||
|
||||
<!-- Modules -->
|
||||
|
||||
<record id="module_form" model="ir.ui.view">
|
||||
<field name="name">ir.module.module.form</field>
|
||||
<field name="model">ir.module.module</field>
|
||||
@@ -176,20 +141,74 @@
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="module_view_kanban">
|
||||
<field name="name">Modules Kanban</field>
|
||||
<field name="model">ir.module.module</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban create="false">
|
||||
<field name="icon"/>
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
<field name="summary"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_module_vignette">
|
||||
<t t-set="installed" t-value="record.state.raw_value == 'installed'"/>
|
||||
<img t-attf-src="#{record.icon.value}" class="oe_module_icon"/>
|
||||
<div class="oe_module_desc">
|
||||
<h4><a type="open"><field name="shortdesc"/></a></h4>
|
||||
<p class="oe_module_name">
|
||||
<t t-if="record.summary.raw_value"><field name="summary"/><br/></t>
|
||||
<i><field name="name" groups="base.group_no_one"/></i>
|
||||
</p>
|
||||
<button type="object" name="button_immediate_install" states="uninstalled">Install</button>
|
||||
<button t-if="installed" disabled="disabled">Installed</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
<record id="open_module_tree" model="ir.actions.act_window">
|
||||
<field name="name">Install a Module</field>
|
||||
<!-- uncomment on released
|
||||
<field name="name">Installed Modules</field>
|
||||
-->
|
||||
<field name="name">Modules</field>
|
||||
<field name="res_model">ir.module.module</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{'search_default_app':1}</field>
|
||||
<!-- uncomment on released
|
||||
<field name="context">{'search_default_installed':1}</field>
|
||||
-->
|
||||
<field name="search_view_id" ref="view_module_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p><b>No module found!</b></p>
|
||||
<p>You should try others search criteria.</p>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem id="menu_module_tree" parent="base.menu_management" name="Modules"
|
||||
sequence="1" action="open_module_tree"/>
|
||||
<menuitem id="menu_module_tree" parent="menu_management" name="Installed Modules" sequence="10" action="open_module_tree"/>
|
||||
|
||||
|
||||
<!-- Apps modules -->
|
||||
<record model="ir.actions.client" id="modules_act_cl">
|
||||
<field name="name">Apps</field>
|
||||
<field name="tag">apps</field>
|
||||
</record>
|
||||
<!-- uncomment on released
|
||||
<menuitem id="module_mi" parent="base.menu_management" sequence="3" action="modules_act_cl"/>
|
||||
-->
|
||||
<menuitem id="module_mi" parent="base.menu_management" sequence="20" action="modules_act_cl"/>
|
||||
|
||||
<record model="ir.actions.client" id="modules_updates_act_cl">
|
||||
<field name="name">Updates</field>
|
||||
<field name="tag">apps.updates</field>
|
||||
<field name="params">{}</field>
|
||||
</record>
|
||||
<!-- uncomment on released
|
||||
<menuitem id="menu_module_updates" parent="base.menu_management" sequence="7" action="modules_updates_act_cl"/>
|
||||
-->
|
||||
<menuitem id="menu_module_updates" parent="base.menu_management" sequence="30" action="modules_updates_act_cl"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<group states="choose" string="Export Settings">
|
||||
<field name="lang"/>
|
||||
<field name="format"/>
|
||||
<field name="modules"/>
|
||||
<field name="modules" widget="many2many_tags"/>
|
||||
</group>
|
||||
<div states="get">
|
||||
<h2>Export Complete</h2>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
id="menu_view_base_module_import"
|
||||
parent="menu_management"
|
||||
groups="base.group_no_one"
|
||||
sequence="1"/>
|
||||
sequence="6"/>
|
||||
-->
|
||||
|
||||
</data>
|
||||
|
||||
@@ -18,17 +18,17 @@
|
||||
</group>
|
||||
<footer>
|
||||
<div states="init">
|
||||
<button name="update_module" string="Update" type="object" class="oe_highlight"/> or
|
||||
<button name="update_module" string="Update" type="object" class="oe_highlight"/> or
|
||||
<button special="cancel" string="Cancel" class="oe_link"/>
|
||||
</div>
|
||||
<div states="done">
|
||||
<button name="action_module_open" string="Open Modules" type="object" class="oe_highlight"/> or
|
||||
<button name="action_module_open" string="Open Modules" type="object" class="oe_highlight"/> or
|
||||
<button special="cancel" string="Close" class="oe_link"/>
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</record>
|
||||
|
||||
<record id="action_view_base_module_update" model="ir.actions.act_window">
|
||||
<field name="name">Module Update</field>
|
||||
@@ -45,7 +45,7 @@
|
||||
id="menu_view_base_module_update"
|
||||
groups="base.group_no_one"
|
||||
parent="menu_management"
|
||||
sequence="2"
|
||||
sequence="40"
|
||||
icon="STOCK_CONVERT"/>
|
||||
|
||||
</data>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
groups="base.group_no_one"
|
||||
id="menu_view_base_module_upgrade"
|
||||
parent="menu_management"
|
||||
sequence="3"/>
|
||||
sequence="50"/>
|
||||
|
||||
<record id="view_base_module_upgrade_install" model="ir.ui.view">
|
||||
<field name="name">Module Upgrade Install</field>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
@@ -15,7 +15,7 @@
|
||||
# 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/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
@@ -32,6 +32,7 @@ import res_lang
|
||||
import ir_property
|
||||
|
||||
import report
|
||||
import wizard
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class res_company(osv.osv):
|
||||
'child_ids': fields.one2many('res.company', 'parent_id', 'Child Companies'),
|
||||
'partner_id': fields.many2one('res.partner', 'Partner', required=True),
|
||||
'rml_header': fields.text('RML Header', required=True),
|
||||
'rml_header1': fields.char('Company Slogan', size=200, help="Appears by default on the top right corner of your printed documents (report header)."),
|
||||
'rml_header1': fields.char('Company Tagline', size=200, help="Appears by default on the top right corner of your printed documents (report header)."),
|
||||
'rml_header2': fields.text('RML Internal Header', required=True),
|
||||
'rml_header3': fields.text('RML Internal Header for Landscape Reports', required=True),
|
||||
'rml_footer': fields.text('Report Footer', help="Footer text displayed at the bottom of all reports."),
|
||||
@@ -330,7 +330,7 @@ class res_company(osv.osv):
|
||||
<!-- left margin -->
|
||||
<rotate degrees="90"/>
|
||||
<fill color="grey"/>
|
||||
<drawString x="2.65cm" y="-0.4cm">produced by OpenERP.com</drawString>
|
||||
<drawString x="2.65cm" y="-0.4cm">generated by OpenERP.com</drawString>
|
||||
<fill color="black"/>
|
||||
<rotate degrees="-90"/>
|
||||
|
||||
|
||||
@@ -32,6 +32,11 @@
|
||||
<field name="name" class="oe_inline"/>
|
||||
</h1>
|
||||
</div>
|
||||
<div attrs="{'invisible' : [('logo','!=',False)]}" class="oe_view_nocontent oe_clear">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to set your company logo.
|
||||
</p>
|
||||
</div>
|
||||
<group col="4">
|
||||
<field name="parent_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
|
||||
@@ -466,7 +466,8 @@ class res_config_settings(osv.osv_memory):
|
||||
groups.append((name, ref(field_group), ref(field.implied_group)))
|
||||
elif name.startswith('module_') and isinstance(field, fields.boolean):
|
||||
mod_ids = ir_module.search(cr, uid, [('name', '=', name[7:])])
|
||||
modules.append((name, ir_module.browse(cr, uid, mod_ids[0], context)))
|
||||
record = ir_module.browse(cr, uid, mod_ids[0], context) if mod_ids else None
|
||||
modules.append((name, record))
|
||||
else:
|
||||
others.append(name)
|
||||
|
||||
@@ -490,7 +491,7 @@ class res_config_settings(osv.osv_memory):
|
||||
|
||||
# modules: which modules are installed/to install
|
||||
for name, module in classified['module']:
|
||||
res[name] = module.state in ('installed', 'to install', 'to upgrade')
|
||||
res[name] = module and module.state in ('installed', 'to install', 'to upgrade')
|
||||
|
||||
# other fields: call all methods that start with 'get_default_'
|
||||
for method in dir(self):
|
||||
@@ -501,9 +502,7 @@ class res_config_settings(osv.osv_memory):
|
||||
|
||||
def execute(self, cr, uid, ids, context=None):
|
||||
ir_values = self.pool.get('ir.values')
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
ir_module = self.pool.get('ir.module.module')
|
||||
res_groups = self.pool.get('res.groups')
|
||||
classified = self._get_classified_fields(cr, uid, context)
|
||||
|
||||
config = self.browse(cr, uid, ids[0], context)
|
||||
@@ -526,17 +525,26 @@ class res_config_settings(osv.osv_memory):
|
||||
getattr(self, method)(cr, uid, ids, context)
|
||||
|
||||
# module fields: install/uninstall the selected modules
|
||||
to_install_ids = []
|
||||
to_install_names = []
|
||||
to_uninstall_ids = []
|
||||
lm = len('module_')
|
||||
for name, module in classified['module']:
|
||||
if config[name]:
|
||||
if module.state == 'uninstalled': to_install_ids.append(module.id)
|
||||
if not module or module.state == 'uninstalled':
|
||||
to_install_names.append(name[lm:])
|
||||
else:
|
||||
if module.state in ('installed','upgrade'): to_uninstall_ids.append(module.id)
|
||||
if module and module.state in ('installed', 'to upgrade'):
|
||||
to_uninstall_ids.append(module.id)
|
||||
|
||||
if to_install_ids or to_uninstall_ids:
|
||||
ir_module.button_uninstall(cr, uid, to_uninstall_ids, context=context)
|
||||
ir_module.button_immediate_install(cr, uid, to_install_ids, context=context)
|
||||
if to_uninstall_ids:
|
||||
ir_module.button_immediate_uninstall(cr, uid, to_uninstall_ids, context=context)
|
||||
|
||||
if to_install_names:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'apps',
|
||||
'params': {'modules': to_install_names},
|
||||
}
|
||||
|
||||
config = self.pool.get('res.config').next(cr, uid, [], context=context) or {}
|
||||
if config.get('type') not in ('ir.actions.act_window_close',):
|
||||
|
||||
@@ -187,6 +187,12 @@ class res_partner(osv.osv, format_address):
|
||||
def _set_image(self, cr, uid, id, name, value, args, context=None):
|
||||
return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
|
||||
|
||||
def _has_image(self, cr, uid, ids, name, args, context=None):
|
||||
result = {}
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
result[obj.id] = obj.image != False
|
||||
return result
|
||||
|
||||
_order = "name"
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=128, required=True, select=True),
|
||||
@@ -253,6 +259,7 @@ class res_partner(osv.osv, format_address):
|
||||
help="Small-sized image of this contact. It is automatically "\
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "\
|
||||
"Use this field anywhere a small image is required."),
|
||||
'has_image': fields.function(_has_image, type="boolean"),
|
||||
'company_id': fields.many2one('res.company', 'Company', select=1),
|
||||
'color': fields.integer('Color Index'),
|
||||
'user_ids': fields.one2many('res.users', 'partner_id', 'Users'),
|
||||
@@ -297,7 +304,7 @@ class res_partner(osv.osv, format_address):
|
||||
'is_company': False,
|
||||
'type': 'default',
|
||||
'use_parent_address': True,
|
||||
'image': lambda self, cr, uid, ctx: self._get_default_image(cr, uid, ctx.get('default_is_company', False), ctx),
|
||||
'image': False,
|
||||
}
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
@@ -308,8 +315,7 @@ class res_partner(osv.osv, format_address):
|
||||
return super(res_partner, self).copy(cr, uid, id, default, context)
|
||||
|
||||
def onchange_type(self, cr, uid, ids, is_company, context=None):
|
||||
# get value as for an onchange on the image
|
||||
value = tools.image_get_resized_images(self._get_default_image(cr, uid, is_company, context), return_big=True, return_medium=False, return_small=False)
|
||||
value = {}
|
||||
value['title'] = False
|
||||
if is_company:
|
||||
value['parent_id'] = False
|
||||
@@ -402,6 +408,8 @@ class res_partner(osv.osv, format_address):
|
||||
name = name + "\n" + self._display_address(cr, uid, record, without_company=True, context=context)
|
||||
name = name.replace('\n\n','\n')
|
||||
name = name.replace('\n\n','\n')
|
||||
if context.get('show_email') and record.email:
|
||||
name = "%s <%s>" % (name, record.email)
|
||||
res.append((record.id, name))
|
||||
return res
|
||||
|
||||
@@ -437,18 +445,23 @@ class res_partner(osv.osv, format_address):
|
||||
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
|
||||
if not args:
|
||||
args = []
|
||||
if name and operator in ('=', 'ilike', '=ilike', 'like'):
|
||||
if name and operator in ('=', 'ilike', '=ilike', 'like', '=like'):
|
||||
# search on the name of the contacts and of its company
|
||||
name2 = operator == '=' and name or '%' + name + '%'
|
||||
search_name = name
|
||||
if operator in ('ilike', 'like'):
|
||||
search_name = '%%%s%%' % name
|
||||
if operator in ('=ilike', '=like'):
|
||||
operator = operator[1:]
|
||||
query_args = {'name': search_name}
|
||||
limit_str = ''
|
||||
query_args = [name2]
|
||||
if limit:
|
||||
limit_str = ' limit %s'
|
||||
query_args += [limit]
|
||||
limit_str = ' limit %(limit)s'
|
||||
query_args['limit'] = limit
|
||||
cr.execute('''SELECT partner.id FROM res_partner partner
|
||||
LEFT JOIN res_partner company ON partner.parent_id = company.id
|
||||
WHERE partner.name || ' (' || COALESCE(company.name,'') || ')'
|
||||
''' + operator + ''' %s ''' + limit_str, query_args)
|
||||
WHERE partner.email ''' + operator +''' %(name)s
|
||||
OR partner.name || ' (' || COALESCE(company.name,'') || ')'
|
||||
''' + operator + ' %(name)s ' + limit_str, query_args)
|
||||
ids = map(lambda x: x[0], cr.fetchall())
|
||||
if args:
|
||||
ids = self.search(cr, uid, [('id', 'in', ids)] + args, limit=limit, context=context)
|
||||
|
||||
@@ -196,12 +196,13 @@
|
||||
<field name="phone"/>
|
||||
<field name="street"/>
|
||||
<field name="street2"/>
|
||||
<field name="image"/>
|
||||
<field name="zip"/>
|
||||
<field name="city"/>
|
||||
<field name="country_id"/>
|
||||
<field name="mobile"/>
|
||||
<field name="fax"/>
|
||||
<field name="state_id"/>
|
||||
<field name="has_image"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<t t-set="color" t-value="kanban_color(record.color.raw_value)"/>
|
||||
@@ -209,7 +210,17 @@
|
||||
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
|
||||
<div class="oe_module_vignette">
|
||||
<a type="open">
|
||||
<img t-att-src="kanban_image('res.partner', 'image', record.id.value, {'preview_image': 'image_small'})" class="oe_avatar oe_kanban_avatar_smallbox"/>
|
||||
<t t-if="record.has_image.raw_value === true">
|
||||
<img t-att-src="kanban_image('res.partner', 'image', record.id.value, {'preview_image': 'image_small'})" class="oe_avatar oe_kanban_avatar_smallbox"/>
|
||||
</t>
|
||||
<t t-if="record.has_image.raw_value === false">
|
||||
<t t-if="record.is_company.raw_value === true">
|
||||
<img t-att-src='_s + "/base/static/src/img/company_image.png"' class="oe_kanban_image"/>
|
||||
</t>
|
||||
<t t-if="record.is_company.raw_value === false">
|
||||
<img t-att-src='_s + "/base/static/src/img/avatar.png"' class="oe_kanban_image"/>
|
||||
</t>
|
||||
</t>
|
||||
</a>
|
||||
<div class="oe_module_desc">
|
||||
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_color_border">
|
||||
@@ -222,6 +233,9 @@
|
||||
<div><a t-if="record.email.raw_value" title="Mail" t-att-href="'mailto:'+record.email.value">
|
||||
<field name="email"/>
|
||||
</a></div>
|
||||
<div t-if="record.phone.raw_value">Phone: <field name="phone"/></div>
|
||||
<div t-if="record.mobile.raw_value">Mobile: <field name="mobile"/></div>
|
||||
<div t-if="record.fax.raw_value">Fax: <field name="fax"/></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -285,8 +299,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Partner">
|
||||
<field name="name"
|
||||
filter_domain="['|','|',('name','ilike',self),('parent_id','ilike',self),('ref','=',self)]"
|
||||
string="Partner"/>
|
||||
filter_domain="['|','|',('name','ilike',self),('parent_id','ilike',self),('ref','=',self)]"/>
|
||||
<filter help="My Partners" icon="terp-personal+" domain="[('user_id','=',uid)]"/>
|
||||
<separator/>
|
||||
<filter string="Persons" name="type_person" icon="terp-personal" domain="[('is_company','=',0)]"/>
|
||||
@@ -328,11 +341,22 @@
|
||||
<field name="mobile"/>
|
||||
<field name="state_id"/>
|
||||
<field name="category_id"/>
|
||||
<field name="has_image"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_vignette oe_semantic_html_override">
|
||||
<a type="open">
|
||||
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.value)" class="oe_kanban_image"/>
|
||||
<t t-if="record.has_image.raw_value === true">
|
||||
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.value)" class="oe_kanban_image"/>
|
||||
</t>
|
||||
<t t-if="record.has_image.raw_value === false">
|
||||
<t t-if="record.is_company.raw_value === true">
|
||||
<img t-att-src='_s + "/base/static/src/img/company_image.png"' class="oe_kanban_image"/>
|
||||
</t>
|
||||
<t t-if="record.is_company.raw_value === false">
|
||||
<img t-att-src='_s + "/base/static/src/img/avatar.png"' class="oe_kanban_image"/>
|
||||
</t>
|
||||
</t>
|
||||
</a>
|
||||
<div class="oe_kanban_details">
|
||||
<h4 class="oe_partner_heading"><a type="open"><field name="name"/></a></h4>
|
||||
|
||||
@@ -136,6 +136,10 @@
|
||||
<form string="Users" version="7.0">
|
||||
<field name="id" invisible="1"/>
|
||||
<sheet>
|
||||
<div class="oe_right oe_button_box">
|
||||
<button string="Change Password" type="action" name="%(change_password_wizard_action)d"
|
||||
help="Change the user password."/>
|
||||
</div>
|
||||
<field name="image" widget='image' class="oe_avatar oe_left" options='{"preview_image": "image_medium"}'/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
@@ -145,7 +149,6 @@
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="new_password" password="True" attrs="{'required': [('id','=',False)]}"/>
|
||||
<field name="company_id" context="{'user_preference': 0}"/>
|
||||
</group>
|
||||
<group>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2011 OpenERP S.A (<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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import change_password_wizard
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2011 OpenERP S.A (<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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
class change_password_wizard(osv.TransientModel):
|
||||
"""
|
||||
A wizard to manage the change of users' passwords
|
||||
"""
|
||||
|
||||
_name = "change.password.wizard"
|
||||
_description = "Change Password Wizard"
|
||||
_columns = {
|
||||
'user_ids': fields.one2many('change.password.user', 'wizard_id', string='Users'),
|
||||
}
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
if context == None:
|
||||
context = {}
|
||||
user_ids = context.get('active_ids', [])
|
||||
wiz_id = context.get('active_id', None)
|
||||
res = []
|
||||
users = self.pool.get('res.users').browse(cr, uid, user_ids, context=context)
|
||||
for user in users:
|
||||
res.append((0, 0, {
|
||||
'wizard_id': wiz_id,
|
||||
'user_id': user.id,
|
||||
'user_login': user.login,
|
||||
}))
|
||||
return {'user_ids': res}
|
||||
|
||||
|
||||
def change_password_button(self, cr, uid, id, context=None):
|
||||
wizard = self.browse(cr, uid, id, context=context)[0]
|
||||
user_ids = []
|
||||
for user in wizard.user_ids:
|
||||
user_ids.append(user.id)
|
||||
self.pool.get('change.password.user').change_password_button(cr, uid, user_ids, context=context)
|
||||
return {
|
||||
'type': 'ir.actions.act_window_close',
|
||||
}
|
||||
|
||||
class change_password_user(osv.TransientModel):
|
||||
"""
|
||||
A model to configure users in the change password wizard
|
||||
"""
|
||||
|
||||
_name = 'change.password.user'
|
||||
_description = 'Change Password Wizard User'
|
||||
_columns = {
|
||||
'wizard_id': fields.many2one('change.password.wizard', string='Wizard', required=True),
|
||||
'user_id': fields.many2one('res.users', string='User', required=True),
|
||||
'user_login': fields.char('User Login', readonly=True),
|
||||
'new_passwd': fields.char('New Password'),
|
||||
}
|
||||
_defaults = {
|
||||
'new_passwd': '',
|
||||
}
|
||||
|
||||
def change_password_button(self, cr, uid, ids, context=None):
|
||||
for user in self.browse(cr, uid, ids, context=context):
|
||||
self.pool.get('res.users').write(cr, uid, user.user_id.id, {'password': user.new_passwd})
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- wizard action on res.users -->
|
||||
<act_window id="change_password_wizard_action"
|
||||
name="Change Password"
|
||||
src_model="res.users"
|
||||
res_model="change.password.wizard"
|
||||
view_type="form" view_mode="form"
|
||||
key2="client_action_multi" target="new"
|
||||
groups="base.group_erp_manager"/>
|
||||
|
||||
<!-- wizard view -->
|
||||
<record id="change_password_wizard_view" model="ir.ui.view">
|
||||
<field name="name">Change Password</field>
|
||||
<field name="model">change.password.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Change Password" version="7.0">
|
||||
<field name="user_ids"/>
|
||||
<footer>
|
||||
<button string="Change Password" name="change_password_button" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- wizard user list view -->
|
||||
<record id="change_password_wizard_user_tree_view" model="ir.ui.view">
|
||||
<field name="name">Change Password Users</field>
|
||||
<field name="model">change.password.user</field>
|
||||
<field name="arch" type="xml">
|
||||
<!-- the user list is editable, but one cannot add or delete rows -->
|
||||
<tree string="Users" editable="bottom" create="false" delete="false">
|
||||
<field name="user_login"/>
|
||||
<field name="new_passwd" required="True"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,117 @@
|
||||
openerp.base = function(instance) {
|
||||
|
||||
instance.base.apps_remote = null;
|
||||
instance.base.apps_client = null;
|
||||
|
||||
instance.base.Apps = instance.web.Widget.extend({
|
||||
template: 'EmptyComponent',
|
||||
remote_action_id: 'loempia.action_embed',
|
||||
failback_action_id: 'base.open_module_tree',
|
||||
|
||||
init: function(parent, action) {
|
||||
this._super(parent, action);
|
||||
var options = action.params || {};
|
||||
|
||||
if (options.apps_user) {
|
||||
sessionStorage.setItem('apps.login', options.apps_user);
|
||||
}
|
||||
if (options.apps_access_token) {
|
||||
sessionStorage.setItem('apps.access_token', options.apps_access_token);
|
||||
}
|
||||
|
||||
this.params = options; // NOTE read by embedded client action
|
||||
},
|
||||
|
||||
get_client: function() {
|
||||
// return the client via a deferred, resolved or rejected depending if the remote host is available or not.
|
||||
var check_client_available = function(client) {
|
||||
var d = $.Deferred();
|
||||
var i = new Image();
|
||||
i.onerror = function() {
|
||||
d.reject(client);
|
||||
};
|
||||
i.onload = function() {
|
||||
client.session.session_bind(client.origin).then(function() {
|
||||
// check if client can authenticate
|
||||
client.authenticate().then(
|
||||
function() { /* done */
|
||||
d.resolve(client);
|
||||
}, function() { /* fail */
|
||||
if (client.login === 'anonymous') {
|
||||
d.reject(client);
|
||||
} else {
|
||||
sessionStorage.removeItem('apps.login');
|
||||
sessionStorage.removeItem('apps.access_token');
|
||||
client.bind_crendentials(client.dbname, 'anonymous', 'anonymous');
|
||||
client.authenticate().then(
|
||||
function() { /* done */
|
||||
d.resolve(client);
|
||||
}, function() { /* fail */
|
||||
d.reject(client);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif', client.origin);
|
||||
return d.promise();
|
||||
};
|
||||
if (instance.base.apps_client) {
|
||||
return check_client_available(instance.base.apps_client);
|
||||
} else {
|
||||
var ICP = new instance.web.Model('ir.config_parameter');
|
||||
return ICP.call('get_param', ['apps.server', 'https://apps.openerp.com/apps']).then(function(u) {
|
||||
var link = $(_.str.sprintf('<a href="%s"></a>', u))[0];
|
||||
var host = _.str.sprintf('%s//%s', link.protocol, link.host);
|
||||
var dbname = link.pathname;
|
||||
if (dbname[0] === '/') {
|
||||
dbname = dbname.substr(1);
|
||||
}
|
||||
var login = (sessionStorage ? sessionStorage.getItem('apps.login') : null) || 'anonymous';
|
||||
var passwd = (sessionStorage ? sessionStorage.getItem('apps.access_token') : null) || 'anonymous';
|
||||
if (_.isNull(instance.base.apps_remote)) {
|
||||
instance.base.apps_remote = new openerp.init();
|
||||
}
|
||||
var client = new instance.base.apps_remote.web.EmbeddedClient(null, host, dbname, login, passwd);
|
||||
instance.base.apps_client = client;
|
||||
return check_client_available(client);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (instance.base.apps_client) {
|
||||
instance.base.apps_client.destroy();
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
|
||||
start: function() {
|
||||
var self = this;
|
||||
return self.get_client().
|
||||
done(function(client) {
|
||||
client.replace(self.$el).
|
||||
done(function() {
|
||||
client.$el.removeClass('openerp');
|
||||
client.do_action(self.remote_action_id);
|
||||
});
|
||||
}).
|
||||
fail(function(client) {
|
||||
self.do_warn('Apps Server not reachable.', 'Showing local modules.', true);
|
||||
self.rpc('/web/action/load', {action_id: self.failback_action_id}).done(function(action) {
|
||||
self.do_action(action);
|
||||
instance.webclient.menu.open_action(action.id);
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
instance.base.AppsUpdates = instance.base.Apps.extend({
|
||||
remote_action_id: 'loempia.action_embed_updates'
|
||||
});
|
||||
|
||||
instance.web.client_actions.add("apps", "instance.base.Apps");
|
||||
instance.web.client_actions.add("apps.updates", "instance.base.AppsUpdates");
|
||||
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!--
|
||||
Assert:
|
||||
-->
|
||||
|
||||
<assert model="res.request" id="res_partner_asustek_request" string="Reference field request returns a browsable object">
|
||||
<test expr="ref_doc1.name">ASUSTeK</test>
|
||||
</assert>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1,116 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2011-TODAY OpenERP S.A. <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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
import openerp
|
||||
|
||||
JOB = {
|
||||
'function': u'_0_seconds',
|
||||
'interval_type': u'minutes',
|
||||
'user_id': 1,
|
||||
'name': u'test',
|
||||
'args': False,
|
||||
'numbercall': 1,
|
||||
'nextcall': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'priority': 5,
|
||||
'doall': True,
|
||||
'active': True,
|
||||
'interval_number': 1,
|
||||
'model': u'ir.cron'
|
||||
}
|
||||
|
||||
class test_ir_cron(openerp.osv.osv.osv):
|
||||
""" Add a few handy methods to test cron jobs scheduling. """
|
||||
_inherit = "ir.cron"
|
||||
|
||||
def _0_seconds(a, b, c):
|
||||
print ">>> _0_seconds"
|
||||
|
||||
def _20_seconds(self, cr, uid):
|
||||
print ">>> in _20_seconds"
|
||||
time.sleep(20)
|
||||
print ">>> out _20_seconds"
|
||||
|
||||
def _80_seconds(self, cr, uid):
|
||||
print ">>> in _80_seconds"
|
||||
time.sleep(80)
|
||||
print ">>> out _80_seconds"
|
||||
|
||||
def test_0(self, cr, uid):
|
||||
now = datetime.now()
|
||||
t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
t2 = (now + relativedelta(minutes=1, seconds=5)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
t3 = (now + relativedelta(minutes=1, seconds=10)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.create(cr, uid, dict(JOB, name='test_0 _20_seconds A', function='_20_seconds', nextcall=t1))
|
||||
self.create(cr, uid, dict(JOB, name='test_0 _20_seconds B', function='_20_seconds', nextcall=t2))
|
||||
self.create(cr, uid, dict(JOB, name='test_0 _20_seconds C', function='_20_seconds', nextcall=t3))
|
||||
|
||||
def test_1(self, cr, uid):
|
||||
now = datetime.now()
|
||||
t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.create(cr, uid, dict(JOB, name='test_1 _20_seconds * 3', function='_20_seconds', nextcall=t1, numbercall=3))
|
||||
|
||||
def test_2(self, cr, uid):
|
||||
now = datetime.now()
|
||||
t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.create(cr, uid, dict(JOB, name='test_2 _80_seconds * 2', function='_80_seconds', nextcall=t1, numbercall=2))
|
||||
|
||||
def test_3(self, cr, uid):
|
||||
now = datetime.now()
|
||||
t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
t2 = (now + relativedelta(minutes=1, seconds=5)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
t3 = (now + relativedelta(minutes=1, seconds=10)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.create(cr, uid, dict(JOB, name='test_3 _80_seconds A', function='_80_seconds', nextcall=t1))
|
||||
self.create(cr, uid, dict(JOB, name='test_3 _20_seconds B', function='_20_seconds', nextcall=t2))
|
||||
self.create(cr, uid, dict(JOB, name='test_3 _20_seconds C', function='_20_seconds', nextcall=t3))
|
||||
|
||||
# This test assumes 4 cron threads.
|
||||
def test_00(self, cr, uid):
|
||||
self.test_00_set = set()
|
||||
now = datetime.now()
|
||||
t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
t2 = (now + relativedelta(minutes=1, seconds=5)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
t3 = (now + relativedelta(minutes=1, seconds=10)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.create(cr, uid, dict(JOB, name='test_00 _20_seconds_A', function='_20_seconds_A', nextcall=t1))
|
||||
self.create(cr, uid, dict(JOB, name='test_00 _20_seconds_B', function='_20_seconds_B', nextcall=t2))
|
||||
self.create(cr, uid, dict(JOB, name='test_00 _20_seconds_C', function='_20_seconds_C', nextcall=t3))
|
||||
|
||||
def _expect(self, cr, uid, to_add, to_sleep, to_expect_in, to_expect_out):
|
||||
assert self.test_00_set == to_expect_in
|
||||
self.test_00_set.add(to_add)
|
||||
time.sleep(to_sleep)
|
||||
self.test_00_set.discard(to_add)
|
||||
assert self.test_00_set == to_expect_out
|
||||
|
||||
def _20_seconds_A(self, cr, uid):
|
||||
self._expect(cr, uid, 'A', 20, set(), set(['B', 'C']))
|
||||
|
||||
def _20_seconds_B(self, cr, uid):
|
||||
self._expect(cr, uid, 'B', 20, set('A'), set('C'))
|
||||
|
||||
def _20_seconds_C(self, cr, uid):
|
||||
self._expect(cr, uid, 'C', 20, set(['A', 'B']), set())
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
-
|
||||
Test the cron jobs scheduling.
|
||||
-
|
||||
Disable the existing cron jobs if any during the tests.
|
||||
-
|
||||
!python {model: ir.cron }: |
|
||||
# For this test to work, as it involves multiple database cursors,
|
||||
# we have to commit changes. But YAML tests must be rollbacked, so
|
||||
# the final database state is left untouched. So we have to be a bit
|
||||
# ugly here: use our own cursor, commit, and clean after ourselves.
|
||||
# We also pass around some ids using setattr/delattr, and we have to
|
||||
# rollback the previous tests otherwise we won't be able to touch the
|
||||
# db.
|
||||
# Well, this should probably be a standalone, or regular unit test,
|
||||
# instead of using the YAML infrastructure.
|
||||
cr.rollback()
|
||||
our_cr = self.pool.db.cursor()
|
||||
try:
|
||||
ids = self.search(our_cr, uid, [], {})
|
||||
setattr(self, 'saved_ids', ids)
|
||||
self.write(our_cr, uid, ids, {'active': False}, {})
|
||||
our_cr.commit()
|
||||
finally:
|
||||
our_cr.close()
|
||||
-
|
||||
Three concurrent jobs started with a slight time gap. Assume 4 cron threads.
|
||||
This will take about 2 minutes.
|
||||
-
|
||||
!python {model: ir.cron }: |
|
||||
# Pretend initialization is already done. We the use a try/finally
|
||||
# to reset _init correctly.
|
||||
self.pool._init = False
|
||||
our_cr = self.pool.db.cursor()
|
||||
try:
|
||||
self.test_00(our_cr, uid) # this will commit using the passed cursor
|
||||
import openerp.cron
|
||||
openerp.cron._thread_slots = 4
|
||||
# Wake up this db as soon as the master cron thread starts.
|
||||
openerp.cron.schedule_wakeup(1, self.pool.db.dbname)
|
||||
# Pretend to be the master thread, for 4 iterations.
|
||||
openerp.cron.runner_body()
|
||||
openerp.cron.runner_body()
|
||||
openerp.cron.runner_body()
|
||||
openerp.cron.runner_body()
|
||||
finally:
|
||||
self.pool._init = True
|
||||
our_cr.close()
|
||||
-
|
||||
Clean after ourselves.
|
||||
-
|
||||
!python {model: ir.cron }: |
|
||||
our_cr = self.pool.db.cursor()
|
||||
try:
|
||||
ids = [x for x in self.search(our_cr, uid, ['|', ('active', '=', True), ('active', '=', False)], {}) if x not in self.saved_ids]
|
||||
self.unlink(our_cr, uid, ids, {})
|
||||
ids = self.saved_ids
|
||||
delattr(self, 'saved_ids')
|
||||
self.write(our_cr, uid, ids, {'active': True}, {})
|
||||
our_cr.commit()
|
||||
finally:
|
||||
our_cr.close()
|
||||
@@ -1,9 +1,15 @@
|
||||
import test_base, test_expression, test_search, test_ir_values, test_menu
|
||||
import test_base
|
||||
import test_expression
|
||||
import test_ir_attachment
|
||||
import test_ir_values
|
||||
import test_menu
|
||||
import test_search
|
||||
|
||||
checks = [
|
||||
test_base,
|
||||
test_expression,
|
||||
test_search,
|
||||
test_ir_attachment,
|
||||
test_ir_values,
|
||||
test_menu,
|
||||
test_search,
|
||||
]
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
import unittest2
|
||||
|
||||
import openerp
|
||||
import openerp.tests.common
|
||||
|
||||
class test_ir_attachment(openerp.tests.common.TransactionCase):
|
||||
|
||||
def test_00_attachment_flow(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
root_path = openerp.tools.config['root_path']
|
||||
ira = registry('ir.attachment')
|
||||
|
||||
# Blob1
|
||||
blob1 = 'blob1'
|
||||
blob1_b64 = blob1.encode('base64')
|
||||
blob1_hash = hashlib.sha1(blob1).hexdigest()
|
||||
blob1_fname = blob1_hash[:3] + '/' + blob1_hash
|
||||
|
||||
# Blob2
|
||||
blob2 = 'blob2'
|
||||
blob2_b64 = blob2.encode('base64')
|
||||
blob2_hash = hashlib.sha1(blob2).hexdigest()
|
||||
blob2_fname = blob2_hash[:3] + '/' + blob2_hash
|
||||
|
||||
# 'ir_attachment.location' is undefined test database storage
|
||||
a1 = ira.create(cr, uid, {'name': 'a1', 'datas': blob1_b64})
|
||||
a1_read = ira.read(cr, uid, [a1], ['datas'])
|
||||
self.assertEqual(a1_read[0]['datas'], blob1_b64)
|
||||
|
||||
cr.execute("select id,db_datas from ir_attachment where id = %s", (a1,) )
|
||||
a1_db_datas = str(cr.fetchall()[0][1])
|
||||
self.assertEqual(a1_db_datas, blob1_b64)
|
||||
|
||||
# define a location for filestore
|
||||
registry('ir.config_parameter').set_param(cr, uid, 'ir_attachment.location', 'file:///filestore')
|
||||
|
||||
# Test file storage
|
||||
a2 = ira.create(cr, uid, {'name': 'a2', 'datas': blob1_b64})
|
||||
a2_read = ira.read(cr, uid, [a2], ['datas'])
|
||||
self.assertEqual(a2_read[0]['datas'], blob1_b64)
|
||||
|
||||
cr.execute("select id,store_fname from ir_attachment where id = %s", (a2,) )
|
||||
a2_store_fname = cr.fetchall()[0][1]
|
||||
self.assertEqual(a2_store_fname, blob1_fname)
|
||||
|
||||
a2_fn = os.path.join(root_path, 'filestore', cr.dbname, blob1_hash[:3], blob1_hash)
|
||||
fc = file(a2_fn).read()
|
||||
self.assertEqual(fc, blob1)
|
||||
|
||||
# create a3 with same blob
|
||||
a3 = ira.create(cr, uid, {'name': 'a3', 'datas': blob1_b64})
|
||||
a3_read = ira.read(cr, uid, [a3], ['datas'])
|
||||
self.assertEqual(a3_read[0]['datas'], blob1_b64)
|
||||
|
||||
cr.execute("select id,store_fname from ir_attachment where id = %s", (a3,) )
|
||||
a3_store_fname = cr.fetchall()[0][1]
|
||||
self.assertEqual(a3_store_fname, a2_store_fname)
|
||||
|
||||
# create a4 blob2
|
||||
a4 = ira.create(cr, uid, {'name': 'a4', 'datas': blob2_b64})
|
||||
a4_read = ira.read(cr, uid, [a4], ['datas'])
|
||||
self.assertEqual(a4_read[0]['datas'], blob2_b64)
|
||||
|
||||
a4_fn = os.path.join(root_path, 'filestore', cr.dbname, blob2_hash[:3], blob2_hash)
|
||||
self.assertTrue(os.path.isfile(a4_fn))
|
||||
|
||||
# delete a3 but file stays
|
||||
ira.unlink(cr, uid, [a3])
|
||||
self.assertTrue(os.path.isfile(a2_fn))
|
||||
|
||||
# delete a2 it is unlinked
|
||||
ira.unlink(cr, uid, [a2])
|
||||
self.assertFalse(os.path.isfile(a2_fn))
|
||||
|
||||
# update a4 blob2 by blob1
|
||||
ira.write(cr, uid, [a4], {'datas': blob1_b64})
|
||||
a4_read = ira.read(cr, uid, [a4], ['datas'])
|
||||
self.assertEqual(a4_read[0]['datas'], blob1_b64)
|
||||
|
||||
# file of a4 disapear and a2 reappear
|
||||
self.assertFalse(os.path.isfile(a4_fn))
|
||||
self.assertTrue(os.path.isfile(a2_fn))
|
||||
|
||||
# everybody applause
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ def preload_registry(dbname):
|
||||
""" Preload a registry, and start the cron."""
|
||||
try:
|
||||
update_module = True if openerp.tools.config['init'] or openerp.tools.config['update'] else False
|
||||
db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=update_module, pooljobs=False)
|
||||
db, registry = openerp.pooler.get_db_and_pool(dbname,update_module=update_module)
|
||||
except Exception:
|
||||
_logger.exception('Failed to initialize database `%s`.', dbname)
|
||||
|
||||
@@ -103,7 +103,7 @@ def run_test_file(dbname, test_file):
|
||||
""" Preload a registry, possibly run a test file, and start the cron."""
|
||||
try:
|
||||
config = openerp.tools.config
|
||||
db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
|
||||
db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'])
|
||||
cr = db.cursor()
|
||||
_logger.info('loading test file %s', test_file)
|
||||
openerp.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'test', True)
|
||||
@@ -207,10 +207,15 @@ def quit_on_signals():
|
||||
pass
|
||||
|
||||
config = openerp.tools.config
|
||||
openerp.service.stop_services()
|
||||
|
||||
if getattr(openerp, 'phoenix', False):
|
||||
# like the phoenix, reborn from ashes...
|
||||
openerp.service._reexec()
|
||||
return
|
||||
|
||||
if config['pidfile']:
|
||||
os.unlink(config['pidfile'])
|
||||
|
||||
openerp.service.stop_services()
|
||||
sys.exit(0)
|
||||
|
||||
def configure_babel_localedata_path():
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2012 OpenERP s.a. (<http://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
|
||||
@@ -32,7 +32,8 @@ from openerp.modules.module import \
|
||||
load_information_from_description_file, \
|
||||
get_module_resource, zip_directory, \
|
||||
get_module_path, initialize_sys_path, \
|
||||
load_openerp_module, init_module_models
|
||||
load_openerp_module, init_module_models, \
|
||||
adapt_version
|
||||
|
||||
from openerp.modules.loading import load_modules
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2012 OpenERP s.a. (<http://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
|
||||
@@ -329,7 +329,7 @@ def load_information_from_description_file(module):
|
||||
'license': 'AGPL-3',
|
||||
'name': False,
|
||||
'post_load': None,
|
||||
'version': '0.0.0',
|
||||
'version': '1.0',
|
||||
'web': False,
|
||||
'website': '',
|
||||
'sequence': 100,
|
||||
@@ -349,6 +349,7 @@ def load_information_from_description_file(module):
|
||||
# 'active' has been renamed 'auto_install'
|
||||
info['auto_install'] = info['active']
|
||||
|
||||
info['version'] = adapt_version(info['version'])
|
||||
return info
|
||||
|
||||
#TODO: refactor the logger in this file to follow the logging guidelines
|
||||
@@ -447,15 +448,22 @@ def get_modules():
|
||||
|
||||
def get_modules_with_version():
|
||||
modules = get_modules()
|
||||
res = {}
|
||||
res = dict.fromkeys(modules, adapt_version('1.0'))
|
||||
for module in modules:
|
||||
try:
|
||||
info = load_information_from_description_file(module)
|
||||
res[module] = "%s.%s" % (release.major_version, info['version'])
|
||||
except Exception, e:
|
||||
res[module] = info['version']
|
||||
except Exception:
|
||||
continue
|
||||
return res
|
||||
|
||||
def adapt_version(version):
|
||||
serie = release.major_version
|
||||
if version == serie or not version.startswith(serie + '.'):
|
||||
version = '%s.%s' % (serie, version)
|
||||
return version
|
||||
|
||||
|
||||
def get_test_modules(module, submodule, explode):
|
||||
"""
|
||||
Return a list of submodules containing tests.
|
||||
|
||||
+10
-22
@@ -58,8 +58,8 @@ 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
|
||||
# Indicates that the registry is
|
||||
self.ready = False
|
||||
|
||||
# Inter-process signaling (used only when openerp.multi_process is True):
|
||||
# The `base_registry_signaling` sequence indicates the whole registry
|
||||
@@ -121,15 +121,6 @@ class Registry(object):
|
||||
models_to_load.append(model._name)
|
||||
return [self.models[m] for m in models_to_load]
|
||||
|
||||
def schedule_cron_jobs(self):
|
||||
""" Make the cron thread care about this registry/database jobs.
|
||||
This will initiate the cron thread to check for any pending jobs for
|
||||
this registry/database as soon as possible. Then it will continuously
|
||||
monitor the ir.cron model for future jobs. See openerp.cron for
|
||||
details.
|
||||
"""
|
||||
self.cron = True
|
||||
|
||||
def clear_caches(self):
|
||||
""" Clear the caches
|
||||
This clears the caches associated to methods decorated with
|
||||
@@ -192,18 +183,17 @@ class RegistryManager(object):
|
||||
registries_lock = threading.RLock()
|
||||
|
||||
@classmethod
|
||||
def get(cls, db_name, force_demo=False, status=None, update_module=False,
|
||||
pooljobs=True):
|
||||
def get(cls, db_name, force_demo=False, status=None, update_module=False):
|
||||
""" Return a registry for a given database name."""
|
||||
try:
|
||||
return cls.registries[db_name]
|
||||
except KeyError:
|
||||
return cls.new(db_name, force_demo, status,
|
||||
update_module, pooljobs)
|
||||
update_module)
|
||||
|
||||
@classmethod
|
||||
def new(cls, db_name, force_demo=False, status=None,
|
||||
update_module=False, pooljobs=True):
|
||||
update_module=False):
|
||||
""" Create and return a new registry for a given database name.
|
||||
|
||||
The (possibly) previous registry for that database name is discarded.
|
||||
@@ -235,8 +225,7 @@ class RegistryManager(object):
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
if pooljobs:
|
||||
registry.schedule_cron_jobs()
|
||||
registry.ready = True
|
||||
|
||||
return registry
|
||||
|
||||
@@ -273,7 +262,7 @@ class RegistryManager(object):
|
||||
@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)
|
||||
registry = cls.get(db_name)
|
||||
cr = registry.db.cursor()
|
||||
try:
|
||||
cr.execute("""
|
||||
@@ -285,8 +274,7 @@ class RegistryManager(object):
|
||||
# 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 = cls.new(db_name)
|
||||
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
|
||||
@@ -311,7 +299,7 @@ class RegistryManager(object):
|
||||
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)
|
||||
registry = cls.get(db_name)
|
||||
if registry.any_cache_cleared():
|
||||
_logger.info("At least one model cache has been cleared, signaling through the database.")
|
||||
cr = registry.db.cursor()
|
||||
@@ -327,7 +315,7 @@ class RegistryManager(object):
|
||||
@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)
|
||||
registry = cls.get(db_name)
|
||||
cr = registry.db.cursor()
|
||||
r = 1
|
||||
try:
|
||||
|
||||
+9
-1
@@ -4857,7 +4857,15 @@ class BaseModel(object):
|
||||
return res[0][0]
|
||||
cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
|
||||
res = cr.fetchall()
|
||||
return [x[0] for x in res]
|
||||
|
||||
# TDE note: with auto_join, we could have several lines about the same result
|
||||
# i.e. a lead with several unread messages; we uniquify the result using
|
||||
# a fast way to do it while preserving order (http://www.peterbe.com/plog/uniqifiers-benchmark)
|
||||
def _uniquify_list(seq):
|
||||
seen = set()
|
||||
return [x for x in seq if x not in seen and not seen.add(x)]
|
||||
|
||||
return _uniquify_list([x[0] for x in res])
|
||||
|
||||
# returns the different values ever entered for one field
|
||||
# this is used, for example, in the client when the user hits enter on
|
||||
|
||||
+3
-4
@@ -28,18 +28,17 @@
|
||||
from openerp.modules.registry import RegistryManager
|
||||
|
||||
|
||||
def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False, pooljobs=True):
|
||||
def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False):
|
||||
"""Create and return a database connection and a newly initialized registry."""
|
||||
registry = RegistryManager.get(db_name, force_demo, status, update_module, pooljobs)
|
||||
registry = RegistryManager.get(db_name, force_demo, status, update_module)
|
||||
return registry.db, registry
|
||||
|
||||
|
||||
def restart_pool(db_name, force_demo=False, status=None, update_module=False):
|
||||
"""Delete an existing registry and return a database connection and a newly initialized registry."""
|
||||
registry = RegistryManager.new(db_name, force_demo, status, update_module, True)
|
||||
registry = RegistryManager.new(db_name, force_demo, status, update_module)
|
||||
return registry.db, registry
|
||||
|
||||
|
||||
def get_db(db_name):
|
||||
"""Return a database connection. The corresponding registry is initialized."""
|
||||
return get_db_and_pool(db_name)[0]
|
||||
|
||||
@@ -50,4 +50,6 @@ author = 'OpenERP S.A.'
|
||||
author_email = 'info@openerp.com'
|
||||
license = 'AGPL-3'
|
||||
|
||||
nt_service_name = "openerp-server-" + serie
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -493,7 +493,7 @@ class _rml_canvas(object):
|
||||
img = ImageReader(s)
|
||||
(sx,sy) = img.getSize()
|
||||
_logger.debug("Image is %dx%d", sx, sy)
|
||||
args = { 'x': 0.0, 'y': 0.0 }
|
||||
args = { 'x': 0.0, 'y': 0.0, 'mask': 'auto'}
|
||||
for tag in ('width','height','x','y'):
|
||||
if node.get(tag):
|
||||
args[tag] = utils.unit_get(node.get(tag))
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
##############################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -33,6 +37,7 @@ import wsgi_server
|
||||
import openerp.modules
|
||||
import openerp.netsvc
|
||||
import openerp.osv
|
||||
from openerp.release import nt_service_name
|
||||
import openerp.tools
|
||||
|
||||
#.apidoc title: RPC Services
|
||||
@@ -97,27 +102,58 @@ def stop_services():
|
||||
|
||||
_logger.info("Initiating shutdown")
|
||||
_logger.info("Hit CTRL-C again or send a second signal to force the shutdown.")
|
||||
logging.shutdown()
|
||||
|
||||
# Manually join() all threads before calling sys.exit() to allow a second signal
|
||||
# to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
|
||||
# threading.Thread.join() should not mask signals (at least in python 2.5).
|
||||
me = threading.currentThread()
|
||||
_logger.debug('current thread: %r', me)
|
||||
for thread in threading.enumerate():
|
||||
_logger.debug('process %r (%r)', thread, thread.isDaemon())
|
||||
if thread != me and not thread.isDaemon() and thread.ident != main_thread_id:
|
||||
while thread.isAlive():
|
||||
_logger.debug('join and sleep')
|
||||
# Need a busyloop here as thread.join() masks signals
|
||||
# and would prevent the forced shutdown.
|
||||
thread.join(0.05)
|
||||
time.sleep(0.05)
|
||||
|
||||
_logger.debug('--')
|
||||
openerp.modules.registry.RegistryManager.delete_all()
|
||||
logging.shutdown()
|
||||
|
||||
def start_services_workers():
|
||||
import openerp.service.workers
|
||||
openerp.multi_process = True
|
||||
|
||||
openerp.service.workers.Multicorn(openerp.service.wsgi_server.application).run()
|
||||
|
||||
def _reexec():
|
||||
"""reexecute openerp-server process with (nearly) the same arguments"""
|
||||
if openerp.tools.osutil.is_running_as_nt_service():
|
||||
subprocess.call('sc stop {0} && sc start {0}'.format(nt_service_name), shell=True)
|
||||
exe = os.path.basename(sys.executable)
|
||||
strip_args = ['-d', '-u']
|
||||
a = sys.argv[:]
|
||||
args = [x for i, x in enumerate(a) if x not in strip_args and a[max(i - 1, 0)] not in strip_args]
|
||||
if not args or args[0] != exe:
|
||||
args.insert(0, exe)
|
||||
os.execv(sys.executable, args)
|
||||
|
||||
def restart_server():
|
||||
if openerp.multi_process:
|
||||
raise NotImplementedError("Multicorn is not supported (but gunicorn was)")
|
||||
pid = openerp.wsgi.core.arbiter_pid
|
||||
os.kill(pid, signal.SIGHUP)
|
||||
else:
|
||||
if os.name == 'nt':
|
||||
def reborn():
|
||||
stop_services()
|
||||
_reexec()
|
||||
|
||||
# run in a thread to let the current thread return response to the caller.
|
||||
threading.Thread(target=reborn).start()
|
||||
else:
|
||||
openerp.phoenix = True
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -43,7 +43,7 @@ def cron_runner(number):
|
||||
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:
|
||||
while True and registry.ready:
|
||||
# acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
# TODO why isnt openerp.addons.base defined ?
|
||||
import sys
|
||||
|
||||
@@ -54,7 +54,12 @@ from openerp import SUPERUSER_ID
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
RPC_VERSION_1 = {'server_version': '6.1', 'protocol_version': 1}
|
||||
RPC_VERSION_1 = {
|
||||
'server_version': release.version,
|
||||
'server_version_info': release.version_info,
|
||||
'server_serie': release.serie,
|
||||
'protocol_version': 1,
|
||||
}
|
||||
|
||||
# This should be moved to openerp.modules.db, along side initialize().
|
||||
def _initialize_db(serv, id, db_name, demo, lang, user_password):
|
||||
@@ -120,6 +125,12 @@ class db(netsvc.ExportService):
|
||||
db = sql_db.db_connect('postgres')
|
||||
cr = db.cursor()
|
||||
chosen_template = tools.config['db_template']
|
||||
cr.execute("""SELECT datname
|
||||
FROM pg_database
|
||||
WHERE datname = %s """,
|
||||
(name,))
|
||||
if cr.fetchall():
|
||||
raise openerp.exceptions.Warning(" %s database already exists!" % name )
|
||||
try:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
|
||||
|
||||
@@ -442,5 +442,6 @@ def stop_service():
|
||||
"""
|
||||
if httpd:
|
||||
httpd.shutdown()
|
||||
openerp.netsvc.close_socket(httpd.socket)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -8,12 +8,20 @@ Tests can be explicitely added to the `fast_suite` or `checks` lists or not.
|
||||
See the :ref:`test-framework` section in the :ref:`features` list.
|
||||
"""
|
||||
|
||||
from . import test_acl
|
||||
from . import test_expression, test_mail, test_ir_sequence, test_orm, \
|
||||
test_fields, test_basecase, \
|
||||
test_view_validation, test_uninstall, test_misc, test_db_cursor, \
|
||||
test_osv, test_translate
|
||||
from . import test_ir_filters
|
||||
import test_acl
|
||||
import test_basecase
|
||||
import test_db_cursor
|
||||
import test_expression
|
||||
import test_fields
|
||||
import test_ir_filters
|
||||
import test_ir_sequence
|
||||
import test_mail
|
||||
import test_misc
|
||||
import test_orm
|
||||
import test_osv
|
||||
import test_translate
|
||||
import test_uninstall
|
||||
import test_view_validation
|
||||
|
||||
fast_suite = [
|
||||
test_ir_sequence,
|
||||
@@ -33,5 +41,5 @@ checks = [
|
||||
test_osv,
|
||||
test_translate,
|
||||
]
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -177,6 +177,10 @@ class TestCleaner(unittest2.TestCase):
|
||||
new_html = html_email_clean(False)
|
||||
self.assertEqual(new_html, False, 'html_email_cleaner did change a False in an other value.')
|
||||
|
||||
# Test6: Message with xml and doctype tags don't crash
|
||||
new_html = html_email_clean(u'<?xml version="1.0" encoding="iso-8859-1"?>\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n <head>\n <title>404 - Not Found</title>\n </head>\n <body>\n <h1>404 - Not Found</h1>\n </body>\n</html>\n')
|
||||
self.assertNotIn('encoding', new_html, 'html_email_cleaner did not remove correctly encoding attributes')
|
||||
|
||||
class TestHtmlTools(unittest2.TestCase):
|
||||
""" Test some of our generic utility functions about html """
|
||||
|
||||
|
||||
@@ -484,7 +484,7 @@ class configmanager(object):
|
||||
if opt.server_wide_modules:
|
||||
openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))
|
||||
else:
|
||||
openerp.conf.server_wide_modules = ['web']
|
||||
openerp.conf.server_wide_modules = ['web','web_kanban']
|
||||
if complete:
|
||||
openerp.modules.module.initialize_sys_path()
|
||||
openerp.modules.loading.open_openerp_namespace()
|
||||
|
||||
@@ -130,11 +130,15 @@ def html_email_clean(html):
|
||||
dest += source[idx:]
|
||||
return dest
|
||||
|
||||
if not html:
|
||||
if not html or not isinstance(html, basestring):
|
||||
return html
|
||||
|
||||
html = ustr(html)
|
||||
|
||||
# 0. remove encoding attribute inside tags
|
||||
doctype = re.compile(r'(<[^>]*\s)(encoding=(["\'][^"\']*?["\']|[^\s\n\r>]+)(\s[^>]*|/)?>)', re.IGNORECASE | re.DOTALL)
|
||||
html = doctype.sub(r"", html)
|
||||
|
||||
# 1. <br[ /]> -> \n, because otherwise the tree is obfuscated
|
||||
br_tags = re.compile(r'([<]\s*[bB][rR]\s*\/?[>])')
|
||||
html = _replace_matching_regex(br_tags, html, '__BR_TAG__')
|
||||
|
||||
@@ -26,6 +26,12 @@ Some functions related to the os and os.path module
|
||||
import os
|
||||
from os.path import join as opj
|
||||
|
||||
if os.name == 'nt':
|
||||
import ctypes
|
||||
import win32service as ws
|
||||
import win32serviceutil as wsu
|
||||
|
||||
|
||||
def listdir(dir, recursive=False):
|
||||
"""Allow to recursively get the file listing"""
|
||||
dir = os.path.normpath(dir)
|
||||
@@ -56,6 +62,59 @@ def walksymlinks(top, topdown=True, onerror=None):
|
||||
yield dirpath, dirnames, filenames
|
||||
|
||||
|
||||
if os.name != 'nt':
|
||||
getppid = os.getppid
|
||||
is_running_as_nt_service = lambda: False
|
||||
else:
|
||||
# based on http://mail.python.org/pipermail/python-win32/2007-June/006174.html
|
||||
_TH32CS_SNAPPROCESS = 0x00000002
|
||||
class _PROCESSENTRY32(ctypes.Structure):
|
||||
_fields_ = [("dwSize", ctypes.c_ulong),
|
||||
("cntUsage", ctypes.c_ulong),
|
||||
("th32ProcessID", ctypes.c_ulong),
|
||||
("th32DefaultHeapID", ctypes.c_ulong),
|
||||
("th32ModuleID", ctypes.c_ulong),
|
||||
("cntThreads", ctypes.c_ulong),
|
||||
("th32ParentProcessID", ctypes.c_ulong),
|
||||
("pcPriClassBase", ctypes.c_ulong),
|
||||
("dwFlags", ctypes.c_ulong),
|
||||
("szExeFile", ctypes.c_char * 260)]
|
||||
|
||||
def getppid():
|
||||
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
||||
Process32First = ctypes.windll.kernel32.Process32First
|
||||
Process32Next = ctypes.windll.kernel32.Process32Next
|
||||
CloseHandle = ctypes.windll.kernel32.CloseHandle
|
||||
hProcessSnap = CreateToolhelp32Snapshot(_TH32CS_SNAPPROCESS, 0)
|
||||
current_pid = os.getpid()
|
||||
try:
|
||||
pe32 = _PROCESSENTRY32()
|
||||
pe32.dwSize = ctypes.sizeof(_PROCESSENTRY32)
|
||||
if not Process32First(hProcessSnap, ctypes.byref(pe32)):
|
||||
raise OSError('Failed getting first process.')
|
||||
while True:
|
||||
if pe32.th32ProcessID == current_pid:
|
||||
return pe32.th32ParentProcessID
|
||||
if not Process32Next(hProcessSnap, ctypes.byref(pe32)):
|
||||
return None
|
||||
finally:
|
||||
CloseHandle(hProcessSnap)
|
||||
|
||||
from contextlib import contextmanager
|
||||
from openerp.release import nt_service_name
|
||||
|
||||
def is_running_as_nt_service():
|
||||
@contextmanager
|
||||
def close_srv(srv):
|
||||
try:
|
||||
yield srv
|
||||
finally:
|
||||
ws.CloseServiceHandle(srv)
|
||||
|
||||
with close_srv(ws.OpenSCManager(None, None, ws.SC_MANAGER_ALL_ACCESS)) as hscm:
|
||||
with close_srv(wsu.SmartOpenService(hscm, nt_service_name, ws.SERVICE_ALL_ACCESS)) as hs:
|
||||
info = ws.QueryServiceStatusEx(hs)
|
||||
return info['ProcessId'] == getppid()
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pprint import pprint as pp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
@@ -15,14 +15,12 @@
|
||||
# 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/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Win32 python extensions modules
|
||||
import win32serviceutil
|
||||
import win32service
|
||||
import win32event
|
||||
import win32api
|
||||
import win32process
|
||||
import servicemanager
|
||||
@@ -30,35 +28,32 @@ import servicemanager
|
||||
import sys
|
||||
import subprocess
|
||||
import os
|
||||
import thread
|
||||
|
||||
try:
|
||||
import meta
|
||||
except ImportError:
|
||||
if hasattr(sys, 'frozen'):
|
||||
raise
|
||||
from setup import generate_files
|
||||
generate_files()
|
||||
import meta # noqa
|
||||
|
||||
class OpenERPServerService(win32serviceutil.ServiceFramework):
|
||||
# required info
|
||||
_svc_name_ = "openerp-server-6.1"
|
||||
_svc_display_name_ = "OpenERP Server 6.1"
|
||||
# optionnal info
|
||||
_svc_description_ = "OpenERP Server 6.1 service"
|
||||
_svc_name_ = meta.nt_service_name
|
||||
_svc_display_name_ = "%s %s" % (meta.description, meta.serie)
|
||||
|
||||
def __init__(self, args):
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
# Create an event which we will use to wait on.
|
||||
# The "service stop" request will set this event.
|
||||
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
|
||||
# a reference to the server's process
|
||||
self.terpprocess = None
|
||||
# info if the service terminates correctly or if the server crashed
|
||||
self.stopping = False
|
||||
|
||||
|
||||
def SvcStop(self):
|
||||
# Before we do anything, tell the SCM we are starting the stop process.
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
# stop the running TERP Server: say it's a normal exit
|
||||
# stop the running OpenERP Server: say it's a normal exit
|
||||
win32api.TerminateProcess(int(self.terpprocess._handle), 0)
|
||||
servicemanager.LogInfoMsg("OpenERP Server stopped correctly")
|
||||
# And set my event.
|
||||
win32event.SetEvent(self.hWaitStop)
|
||||
|
||||
|
||||
def StartTERP(self):
|
||||
# The server finds now its configuration automatically on Windows
|
||||
@@ -69,29 +64,19 @@ class OpenERPServerService(win32serviceutil.ServiceFramework):
|
||||
server_path = os.path.join(server_dir, 'server', 'openerp-server.exe')
|
||||
self.terpprocess = subprocess.Popen([server_path], cwd=server_dir, creationflags=win32process.CREATE_NO_WINDOW)
|
||||
|
||||
|
||||
def StartControl(self,ws):
|
||||
# this listens to the Service Manager's events
|
||||
win32event.WaitForSingleObject(ws, win32event.INFINITE)
|
||||
self.stopping = True
|
||||
|
||||
def SvcDoRun(self):
|
||||
# Start OpenERP Server itself
|
||||
self.StartTERP()
|
||||
# start the loop waiting for the Service Manager's stop signal
|
||||
thread.start_new_thread(self.StartControl, (self.hWaitStop,))
|
||||
# Log a info message that the server is running
|
||||
servicemanager.LogInfoMsg("OpenERP Server up and running")
|
||||
# verification if the server is really running, else quit with an error
|
||||
self.terpprocess.wait()
|
||||
if not self.stopping:
|
||||
sys.exit("OpenERP Server check: server not running, check the logfile for more info")
|
||||
# exit with same exit code as OpenERP process
|
||||
sys.exit(self.terpprocess.wait())
|
||||
|
||||
|
||||
def option_handler(opts):
|
||||
# configure the service to auto restart on failures...
|
||||
subprocess.call(['sc', 'failure', meta.nt_service_name, 'reset=', '0', 'actions=', 'restart/0/restart/0/restart/0'])
|
||||
|
||||
if __name__=='__main__':
|
||||
if __name__ == '__main__':
|
||||
# Do with the service whatever option is passed in the command line
|
||||
win32serviceutil.HandleCommandLine(OpenERPServerService)
|
||||
win32serviceutil.HandleCommandLine(OpenERPServerService, customOptionHandler=option_handler)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
||||
+41
-10
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
@@ -15,7 +15,7 @@
|
||||
# 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/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
@@ -30,15 +30,46 @@ def datas():
|
||||
r.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*')))
|
||||
return r
|
||||
|
||||
setup(service=["OpenERPServerService"],
|
||||
options={"py2exe":{"excludes":["Tkconstants","Tkinter","tcl",
|
||||
"_imagingtk","PIL._imagingtk",
|
||||
"ImageTk", "PIL.ImageTk",
|
||||
"FixTk"],
|
||||
"skip_archive": 1,
|
||||
"optimize": 2,}},
|
||||
meta = {}
|
||||
execfile(os.path.join(os.path.dirname(__file__), '..', 'openerp', 'release.py'), meta)
|
||||
|
||||
def generate_files():
|
||||
actions = {
|
||||
'start': ['stop', 'start'],
|
||||
'stop': ['stop'],
|
||||
}
|
||||
|
||||
files = []
|
||||
for action, steps in actions.items():
|
||||
fname = action + '.bat'
|
||||
files.append(fname)
|
||||
with open(fname, 'w') as fp:
|
||||
fp.write('@PATH=%WINDIR%\system32;%WINDIR%;%WINDIR%\System32\Wbem;.\n')
|
||||
for step in steps:
|
||||
fp.write('@net %s %s\n' % (step, meta['nt_service_name']))
|
||||
|
||||
files.append('meta.py')
|
||||
with open('meta.py', 'w') as fp:
|
||||
for m in 'description serie nt_service_name'.split():
|
||||
fp.write("%s = %r\n" % (m, meta[m],))
|
||||
|
||||
return files
|
||||
|
||||
excludes = "Tkconstants Tkinter tcl _imagingtk PIL._imagingtk ImageTk PIL.ImageTk FixTk".split()
|
||||
|
||||
setup(service = ["OpenERPServerService"],
|
||||
version = meta['version'],
|
||||
license = meta['license'],
|
||||
url = meta['url'],
|
||||
author = meta['author'],
|
||||
author_email = meta['author_email'],
|
||||
data_files = generate_files(),
|
||||
options = {"py2exe": {
|
||||
"excludes": excludes,
|
||||
"skip_archive": 1,
|
||||
"optimize": 2,
|
||||
}},
|
||||
data_files=datas(),
|
||||
)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
@PATH=%WINDIR%\system32;%WINDIR%;%WINDIR%\System32\Wbem;.
|
||||
|
||||
@net stop openerp-server-6.1
|
||||
|
||||
@net start openerp-server-6.1
|
||||
|
||||
cls
|
||||
@@ -1,5 +0,0 @@
|
||||
@PATH=%WINDIR%\system32;%WINDIR%;%WINDIR%\System32\Wbem;.
|
||||
|
||||
@net stop openerp-server-6.1
|
||||
|
||||
cls
|
||||
Reference in New Issue
Block a user