diff --git a/addons/account/account.py b/addons/account/account.py index 31fe7dbfb12..e11479e4dc0 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -30,6 +30,8 @@ from osv import fields, osv import decimal_precision as dp from tools.translate import _ from tools.float_utils import float_round +from openerp import SUPERUSER_ID + _logger = logging.getLogger(__name__) @@ -819,7 +821,7 @@ class account_journal(osv.osv): if not 'sequence_id' in vals or not vals['sequence_id']: # if we have the right to create a journal, we should be able to # create it's sequence. - vals.update({'sequence_id': self.create_sequence(cr, 1, vals, context)}) + vals.update({'sequence_id': self.create_sequence(cr, SUPERUSER_ID, vals, context)}) return super(account_journal, self).create(cr, uid, vals, context) def name_get(self, cr, user, ids, context=None): @@ -1858,7 +1860,7 @@ class account_tax(osv.osv): def get_precision_tax(): def change_digit_tax(cr): - res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account') + res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, SUPERUSER_ID, 'Account') return (16, res+2) return change_digit_tax @@ -3347,13 +3349,11 @@ class wizard_multi_charts_accounts(osv.osv_memory): # Install all the templates objects and generate the real objects acc_template_ref, taxes_ref, tax_code_ref = self._install_template(cr, uid, obj_wizard.chart_template_id.id, company_id, code_digits=obj_wizard.code_digits, obj_wizard=obj_wizard, context=context) - # write values of default taxes for product + # write values of default taxes for product as super user if obj_wizard.sale_tax and taxes_ref: - ir_values_obj.set(cr, uid, key='default', key2=False, name="taxes_id", company=company_id, - models =[('product.product',False)], value=[taxes_ref[obj_wizard.sale_tax.id]]) + ir_values_obj.set_default(cr, SUPERUSER_ID, 'product.product', "taxes_id", [taxes_ref[obj_wizard.sale_tax.id]], for_all_users=True, company_id=company_id) if obj_wizard.purchase_tax and taxes_ref: - ir_values_obj.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=company_id, - models =[('product.product',False)], value=[taxes_ref[obj_wizard.purchase_tax.id]]) + ir_values_obj.set_default(cr, SUPERUSER_ID, 'product.product', "supplier_taxes_id", [taxes_ref[obj_wizard.purchase_tax.id]], for_all_users=True, company_id=company_id) # Create Bank journals self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context) diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 42bcb785d61..5f81cbfe857 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -1298,16 +1298,15 @@ class account_invoice(osv.osv): def create_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): - self.message_post(cr, uid, [obj.id], body=_("%s created.") % (_(self._get_document_type(obj.type))), context=context) + self.message_post(cr, uid, [obj.id],body=_("%s created.") % (self._get_document_type(obj.type)), subtype="new", context=context) def confirm_paid_send_note(self, cr, uid, ids, context=None): - for obj in self.browse(cr, uid, ids, context=context): - self.message_post(cr, uid, [obj.id], body=_("%s paid.") % (_(self._get_document_type(obj.type))), context=context) + for obj in self.browse(cr, uid, ids, context=context): + self.message_post(cr, uid, [obj.id], body=_("%s paid.") % (self._get_document_type(obj.type)), subtype="paid", context=context) def invoice_cancel_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): - self.message_post(cr, uid, [obj.id], body=_("%s cancelled.") % (_(self._get_document_type(obj.type))), context=context) - + self.message_post(cr, uid, [obj.id], body=_("%s cancelled.") % (self._get_document_type(obj.type)), subtype="cancelled", context=context) account_invoice() class account_invoice_line(osv.osv): diff --git a/addons/account/data/account_data.xml b/addons/account/data/account_data.xml index e8899d0348a..bf3b3152552 100644 --- a/addons/account/data/account_data.xml +++ b/addons/account/data/account_data.xml @@ -518,7 +518,6 @@ - Account Reconcile account.reconcile @@ -554,8 +553,6 @@ - - @@ -563,8 +560,28 @@ Invoice account.invoice - - - + + new + + + + + paid + + + + cancelled + + + + + + + + + + + + diff --git a/addons/account_analytic_plans/i18n/nb.po b/addons/account_analytic_plans/i18n/nb.po new file mode 100644 index 00000000000..27a6c0d9e24 --- /dev/null +++ b/addons/account_analytic_plans/i18n/nb.po @@ -0,0 +1,546 @@ +# Norwegian Bokmal translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-02-08 00:35+0000\n" +"PO-Revision-Date: 2012-09-02 09:46+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Norwegian Bokmal \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-09-03 05:21+0000\n" +"X-Generator: Launchpad (build 15890)\n" + +#. module: account_analytic_plans +#: view:analytic.plan.create.model:0 +msgid "" +"This distribution model has been saved.You will be able to reuse it later." +msgstr "" +"Denne fordeling modellen har vært lagret. Du vil kunne bruke den senere." + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance.line,plan_id:0 +msgid "Plan Id" +msgstr "Plan ID" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "From Date" +msgstr "Fra dato" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +#: view:account.crossovered.analytic:0 +#: model:ir.actions.act_window,name:account_analytic_plans.action_account_crossovered_analytic +#: model:ir.actions.report.xml,name:account_analytic_plans.account_analytic_account_crossovered_analytic +msgid "Crossovered Analytic" +msgstr "Krysset Analyse" + +#. module: account_analytic_plans +#: view:account.analytic.plan:0 +#: field:account.analytic.plan,name:0 +#: field:account.analytic.plan.line,plan_id:0 +#: model:ir.actions.act_window,name:account_analytic_plans.account_analytic_plan_form_action +#: model:ir.model,name:account_analytic_plans.model_account_analytic_plan +#: model:ir.ui.menu,name:account_analytic_plans.menu_account_analytic_plan_action +msgid "Analytic Plan" +msgstr "Analytisk Plan" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,journal_id:0 +#: view:account.crossovered.analytic:0 +#: field:account.crossovered.analytic,journal_ids:0 +msgid "Analytic Journal" +msgstr "Analytisk journal" + +#. module: account_analytic_plans +#: view:account.analytic.plan.line:0 +#: model:ir.model,name:account_analytic_plans.model_account_analytic_plan_line +msgid "Analytic Plan Line" +msgstr "Analytisk plan linje" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/wizard/account_crossovered_analytic.py:61 +#, python-format +msgid "User Error" +msgstr "Brukerfeil" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_analytic_plan_instance +msgid "Analytic Plan Instance" +msgstr "Analytisk Plan forekomst" + +#. module: account_analytic_plans +#: view:analytic.plan.create.model:0 +msgid "Ok" +msgstr "Ok" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,plan_id:0 +msgid "Model's Plan" +msgstr "Modell Plan" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,account2_ids:0 +msgid "Account2 Id" +msgstr "Konto2 ID" + +#. module: account_analytic_plans +#: sql_constraint:account.invoice:0 +msgid "Invoice Number must be unique per Company!" +msgstr "Fakturanummer må være unik pr. firma!" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Amount" +msgstr "Beløp" + +#. module: account_analytic_plans +#: constraint:account.journal:0 +msgid "" +"Configuration error! The currency chosen should be shared by the default " +"accounts too." +msgstr "" +"Konfigurasjon feil! Den valgte valutaen bør deles av standard kontoer også." + +#. module: account_analytic_plans +#: sql_constraint:account.move.line:0 +msgid "Wrong credit or debit value in accounting entry !" +msgstr "Feil kredit eller debet beløp i regnskaps oppføringen !" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,account6_ids:0 +msgid "Account6 Id" +msgstr "Konto6 ID" + +#. module: account_analytic_plans +#: model:ir.ui.menu,name:account_analytic_plans.menu_account_analytic_multi_plan_action +msgid "Multi Plans" +msgstr "Multi Planer" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_bank_statement_line +msgid "Bank Statement Line" +msgstr "Kontoutskriftlinje" + +#. module: account_analytic_plans +#: model:ir.actions.act_window,name:account_analytic_plans.account_analytic_plan_form_action_installer +msgid "Define your Analytic Plans" +msgstr "Definer dine analytiske planer." + +#. module: account_analytic_plans +#: constraint:account.invoice:0 +msgid "Invalid BBA Structured Communication !" +msgstr "Ugyldig BBA Strukturert Kommunikasjon!" + +#. module: account_analytic_plans +#: constraint:account.bank.statement:0 +msgid "The journal and period chosen have to belong to the same company." +msgstr "Tidsskriftet og perioden valgt å tilhøre samme selskap." + +#. module: account_analytic_plans +#: constraint:account.move.line:0 +msgid "" +"The date of your Journal Entry is not in the defined period! You should " +"change the date or remove this constraint from the journal." +msgstr "" +"Datoen for din bilagsregistrering ikke er i den definerte perioden! Du bør " +"endre datoen eller fjerne denne begrensningen fra tidsskriftet." + +#. module: account_analytic_plans +#: sql_constraint:account.journal:0 +msgid "The code of the journal must be unique per company !" +msgstr "Journalkoden må være unik pr firma!" + +#. module: account_analytic_plans +#: field:account.crossovered.analytic,ref:0 +msgid "Analytic Account Reference" +msgstr "Analytisk konto Referanse." + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_sale_order_line +msgid "Sales Order Line" +msgstr "Salgsordrelinje" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:47 +#: view:analytic.plan.create.model:0 +#, python-format +msgid "Distribution Model Saved" +msgstr "Distribusjon Model Lagret" + +#. module: account_analytic_plans +#: model:ir.actions.act_window,name:account_analytic_plans.account_analytic_plan_instance_action +msgid "Analytic Distribution's Models" +msgstr "Analytisk distribusjon modeller" + +#. module: account_analytic_plans +#: view:account.crossovered.analytic:0 +msgid "Print" +msgstr "Skriv ut" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +#: field:account.analytic.line,percentage:0 +msgid "Percentage" +msgstr "Prosentdel" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/account_analytic_plans.py:221 +#, python-format +msgid "A model having this name and code already exists !" +msgstr "" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:41 +#, python-format +msgid "No analytic plan defined !" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance.line,rate:0 +msgid "Rate (%)" +msgstr "" + +#. module: account_analytic_plans +#: view:account.analytic.plan:0 +#: field:account.analytic.plan,plan_ids:0 +#: field:account.journal,plan_id:0 +msgid "Analytic Plans" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Perc(%)" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.line,max_required:0 +msgid "Maximum Allowed (%)" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Printing date" +msgstr "" + +#. module: account_analytic_plans +#: view:account.analytic.plan.line:0 +msgid "Analytic Plan Lines" +msgstr "" + +#. module: account_analytic_plans +#: constraint:account.bank.statement.line:0 +msgid "" +"The amount of the voucher must be the same amount as the one on the " +"statement line" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_invoice_line +msgid "Invoice Line" +msgstr "" + +#. module: account_analytic_plans +#: constraint:account.move.line:0 +msgid "" +"The selected account of your Journal Entry forces to provide a secondary " +"currency. You should remove the secondary currency on the account or select " +"a multi-currency view on the journal." +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Currency" +msgstr "" + +#. module: account_analytic_plans +#: field:account.crossovered.analytic,date1:0 +msgid "Start Date" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,account_ids:0 +msgid "Account Id" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,account5_ids:0 +msgid "Account5 Id" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_analytic_plan_instance_line +msgid "Analytic Instance Line" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.line,root_analytic_id:0 +msgid "Root Account" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "To Date" +msgstr "" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/account_analytic_plans.py:341 +#: code:addons/account_analytic_plans/account_analytic_plans.py:485 +#, python-format +msgid "You have to define an analytic journal on the '%s' journal!" +msgstr "" + +#. module: account_analytic_plans +#: field:account.crossovered.analytic,empty_line:0 +msgid "Dont show empty lines" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.actions.act_window,name:account_analytic_plans.action_analytic_plan_create_model +msgid "analytic.plan.create.model.action" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_analytic_line +msgid "Analytic Line" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Analytic Account :" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Analytic Account Reference:" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.line,name:0 +msgid "Plan Name" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan,default_instance_id:0 +msgid "Default Entries" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_move_line +msgid "Journal Items" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,account1_ids:0 +msgid "Account1 Id" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.line,min_required:0 +msgid "Minimum Allowed (%)" +msgstr "" + +#. module: account_analytic_plans +#: help:account.analytic.plan.line,root_analytic_id:0 +msgid "Root account of this plan." +msgstr "" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/account_analytic_plans.py:221 +#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:38 +#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:41 +#, python-format +msgid "Error" +msgstr "" + +#. module: account_analytic_plans +#: view:analytic.plan.create.model:0 +msgid "Save This Distribution as a Model" +msgstr "" + +#. module: account_analytic_plans +#: constraint:account.move.line:0 +msgid "You can not create journal items on an account of type view." +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Quantity" +msgstr "" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:38 +#, python-format +msgid "Please put a name and a code before saving the model !" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_crossovered_analytic +msgid "Print Crossovered Analytic" +msgstr "" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/account_analytic_plans.py:341 +#: code:addons/account_analytic_plans/account_analytic_plans.py:485 +#, python-format +msgid "No Analytic Journal !" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance.line,analytic_account_id:0 +msgid "Analytic Account" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_bank_statement +msgid "Bank Statement" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,account3_ids:0 +msgid "Account3 Id" +msgstr "" + +#. module: account_analytic_plans +#: constraint:account.analytic.line:0 +msgid "You can not create analytic line on view account." +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_invoice +msgid "Invoice" +msgstr "" + +#. module: account_analytic_plans +#: view:account.crossovered.analytic:0 +#: view:analytic.plan.create.model:0 +msgid "Cancel" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,account4_ids:0 +msgid "Account4 Id" +msgstr "" + +#. module: account_analytic_plans +#: constraint:account.move.line:0 +msgid "Company must be the same for its related account and period." +msgstr "" + +#. module: account_analytic_plans +#: view:account.analytic.plan.instance.line:0 +msgid "Analytic Distribution Lines" +msgstr "" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/account_analytic_plans.py:234 +#, python-format +msgid "The Total Should be Between %s and %s" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "at" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Account Name" +msgstr "" + +#. module: account_analytic_plans +#: view:account.analytic.plan.instance.line:0 +msgid "Analytic Distribution Line" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.instance,code:0 +msgid "Distribution Code" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "%" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "100.00%" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.default,analytics_id:0 +#: view:account.analytic.plan.instance:0 +#: field:account.analytic.plan.instance,name:0 +#: field:account.bank.statement.line,analytics_id:0 +#: field:account.invoice.line,analytics_id:0 +#: field:account.move.line,analytics_id:0 +#: model:ir.model,name:account_analytic_plans.model_account_analytic_default +msgid "Analytic Distribution" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_account_journal +msgid "Journal" +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Code" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.model,name:account_analytic_plans.model_analytic_plan_create_model +msgid "analytic.plan.create.model" +msgstr "" + +#. module: account_analytic_plans +#: field:account.crossovered.analytic,date2:0 +msgid "End Date" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.actions.act_window,name:account_analytic_plans.account_analytic_instance_model_open +msgid "Distribution Models" +msgstr "" + +#. module: account_analytic_plans +#: model:ir.actions.act_window,help:account_analytic_plans.account_analytic_plan_form_action_installer +msgid "" +"To setup a multiple analytic plans environment, you must define the root " +"analytic accounts for each plan set. Then, you must attach a plan set to " +"your account journals." +msgstr "" + +#. module: account_analytic_plans +#: constraint:account.move.line:0 +msgid "You can not create journal items on closed account." +msgstr "" + +#. module: account_analytic_plans +#: report:account.analytic.account.crossovered.analytic:0 +msgid "Company" +msgstr "" + +#. module: account_analytic_plans +#: field:account.analytic.plan.line,sequence:0 +msgid "Sequence" +msgstr "" + +#. module: account_analytic_plans +#: sql_constraint:account.journal:0 +msgid "The name of the journal must be unique per company !" +msgstr "" + +#. module: account_analytic_plans +#: code:addons/account_analytic_plans/account_analytic_plans.py:234 +#, python-format +msgid "Value Error" +msgstr "" diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index f75cda8cdde..9d656c7607d 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -1293,17 +1293,17 @@ class account_voucher(osv.osv): def create_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): message = "%s created." % self._document_type[obj.type or False] - self.message_post(cr, uid, [obj.id], body=message, context=context) + self.message_post(cr, uid, [obj.id], body=message, subtype="new", context=context) def post_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): message = "%s '%s' is posted." % (self._document_type[obj.type or False], obj.move_id.name) - self.message_post(cr, uid, [obj.id], body=message, context=context) + self.message_post(cr, uid, [obj.id], body=message, subtype="post", context=context) def reconcile_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): message = "%s reconciled." % self._document_type[obj.type or False] - self.message_post(cr, uid, [obj.id], body=message, context=context) + self.message_post(cr, uid, [obj.id], body=message, subtype="reconcile", context=context) account_voucher() diff --git a/addons/account_voucher/account_voucher_data.xml b/addons/account_voucher/account_voucher_data.xml index 656d2c2cd91..a99a8735842 100644 --- a/addons/account_voucher/account_voucher_data.xml +++ b/addons/account_voucher/account_voucher_data.xml @@ -2,16 +2,38 @@ - - mail.group - - notification - eInvoicing & Payments application installed! - OpenERP's electronic invoicing accelerates the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system. + + + + OpenERP's electronic invoicing allows to ease and fasten the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system. -You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability. +You can track customer payments easily and automate the reminders. You get an overview of the discussion with your customers on each invoice to ensure a full traceability. -For advanced accounting features, you should install the "Accounting and Finance" module. +If you want to use advanced accounting features, you should install the "Accounting and Finance" module. + Module eInvoicing & Payments has been installed. + + + + + + + post + + + + + reconcile + + + + + + + + + + + diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index d90e2006a71..2e6e5089c88 100644 --- a/addons/analytic/analytic.py +++ b/addons/analytic/analytic.py @@ -297,7 +297,7 @@ class account_analytic_account(osv.osv): def create_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): - self.message_post(cr, uid, [obj.id], body=_("Contract for %s has been created.") % (obj.partner_id.name), context=context) + self.message_post(cr, uid, [obj.id], body=_("Contract for %s has been created.") % (obj.partner_id.name), subtype="new", context=context) account_analytic_account() diff --git a/addons/audittrail/audittrail.py b/addons/audittrail/audittrail.py index aee9fbd9305..3dff7c1cb00 100644 --- a/addons/audittrail/audittrail.py +++ b/addons/audittrail/audittrail.py @@ -25,6 +25,7 @@ from tools.translate import _ import pooler import time import tools +from openerp import SUPERUSER_ID class audittrail_rule(osv.osv): """ @@ -252,10 +253,10 @@ class audittrail_objects_proxy(object_proxy): pool = pooler.get_pool(cr.dbname) resource_pool = pool.get(model) model_pool = pool.get('ir.model') - model_ids = model_pool.search(cr, 1, [('model', '=', model)]) + model_ids = model_pool.search(cr, SUPERUSER_ID, [('model', '=', model)]) model_id = model_ids and model_ids[0] or False assert model_id, _("'%s' Model does not exist..." %(model)) - model = model_pool.browse(cr, 1, model_id) + model = model_pool.browse(cr, SUPERUSER_ID, model_id) # fields to log. currently only used by log on read() field_list = [] @@ -322,7 +323,7 @@ class audittrail_objects_proxy(object_proxy): data = {} resource_pool = pool.get(model.model) # read all the fields of the given resources in super admin mode - for resource in resource_pool.read(cr, 1, res_ids): + for resource in resource_pool.read(cr, SUPERUSER_ID, res_ids): values = {} values_text = {} resource_id = resource['id'] @@ -332,19 +333,19 @@ class audittrail_objects_proxy(object_proxy): continue values[field] = resource[field] # get the textual value of that field for this record - values_text[field] = self.get_value_text(cr, 1, pool, resource_pool, method, field, resource[field]) + values_text[field] = self.get_value_text(cr, SUPERUSER_ID, pool, resource_pool, method, field, resource[field]) field_obj = resource_pool._all_columns.get(field).column if field_obj._type in ('one2many','many2many'): # check if an audittrail rule apply in super admin mode - if self.check_rules(cr, 1, field_obj._obj, method): + if self.check_rules(cr, SUPERUSER_ID, field_obj._obj, method): # check if the model associated to a *2m field exists, in super admin mode - x2m_model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', field_obj._obj)]) + x2m_model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', field_obj._obj)]) x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj)) - x2m_model = pool.get('ir.model').browse(cr, 1, x2m_model_id) + x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id) #recursive call on x2m fields that need to be checked too - data.update(self.get_data(cr, 1, pool, resource[field], x2m_model, method)) + data.update(self.get_data(cr, SUPERUSER_ID, pool, resource[field], x2m_model, method)) data[(model.id, resource_id)] = {'text':values_text, 'value': values} return data @@ -389,12 +390,12 @@ class audittrail_objects_proxy(object_proxy): field_obj = field_definition.column if field_obj._type in ('one2many','many2many'): # checking if an audittrail rule apply in super admin mode - if self.check_rules(cr, 1, field_obj._obj, method): + if self.check_rules(cr, SUPERUSER_ID, field_obj._obj, method): # checking if the model associated to a *2m field exists, in super admin mode - x2m_model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', field_obj._obj)]) + x2m_model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', field_obj._obj)]) x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj)) - x2m_model = pool.get('ir.model').browse(cr, 1, x2m_model_id) + x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id) # the resource_ids that need to be checked are the sum of both old and previous values (because we # need to log also creation or deletion in those lists). x2m_old_values_ids = old_values.get(key, {'value': {}})['value'].get(field_name, []) @@ -402,7 +403,7 @@ class audittrail_objects_proxy(object_proxy): # We use list(set(...)) to remove duplicates. res_ids = list(set(x2m_old_values_ids + x2m_new_values_ids)) for res_id in res_ids: - lines.update(self.prepare_audittrail_log_line(cr, 1, pool, x2m_model, res_id, method, old_values, new_values, field_list)) + lines.update(self.prepare_audittrail_log_line(cr, SUPERUSER_ID, pool, x2m_model, res_id, method, old_values, new_values, field_list)) # if the value value is different than the old value: record the change if key not in old_values or key not in new_values or old_values[key]['value'][field_name] != new_values[key]['value'][field_name]: data = { @@ -459,9 +460,9 @@ class audittrail_objects_proxy(object_proxy): vals.update({'method': 'unlink'}) # create the audittrail log in super admin mode, only if a change has been detected if lines[(model_id, resource_id)]: - log_id = pool.get('audittrail.log').create(cr, 1, vals) + log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals) model = pool.get('ir.model').browse(cr, uid, model_id) - self.create_log_line(cr, 1, log_id, model, lines[(model_id, resource_id)]) + self.create_log_line(cr, SUPERUSER_ID, log_id, model, lines[(model_id, resource_id)]) return True def check_rules(self, cr, uid, model, method): @@ -475,11 +476,11 @@ class audittrail_objects_proxy(object_proxy): """ pool = pooler.get_pool(cr.dbname) if 'audittrail.rule' in pool.models: - model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', model)]) + model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', model)]) model_id = model_ids and model_ids[0] or False if model_id: - rule_ids = pool.get('audittrail.rule').search(cr, 1, [('object_id', '=', model_id), ('state', '=', 'subscribed')]) - for rule in pool.get('audittrail.rule').read(cr, 1, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']): + rule_ids = pool.get('audittrail.rule').search(cr, SUPERUSER_ID, [('object_id', '=', model_id), ('state', '=', 'subscribed')]) + for rule in pool.get('audittrail.rule').read(cr, SUPERUSER_ID, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']): if len(rule['user_id']) == 0 or uid in rule['user_id']: if rule.get('log_'+method,0): return True diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 2959fa45db1..5d939f19db1 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -7,6 +7,7 @@ import werkzeug.utils import openerp.modules.registry import openerp.addons.web.controllers.main import openerp.addons.web.common.http as openerpweb +from openerp import SUPERUSER_ID _logger = logging.getLogger(__name__) @@ -18,7 +19,7 @@ class OAuthController(openerpweb.Controller): registry = openerp.modules.registry.RegistryManager.get(dbname) with registry.cursor() as cr: providers = registry.get('auth.oauth.provider') - l = providers.read(cr, 1, providers.search(cr, 1, [('enabled','=',True)])) + l = providers.read(cr, SUPERUSER_ID, providers.search(cr, SUPERUSER_ID, [('enabled','=',True)])) return l @openerpweb.httprequest @@ -30,7 +31,7 @@ class OAuthController(openerpweb.Controller): with registry.cursor() as cr: try: u = registry.get('res.users') - credentials = u.auth_oauth(cr, 1, provider, kw) + credentials = u.auth_oauth(cr, SUPERUSER_ID, provider, kw) cr.commit() return openerp.addons.web.controllers.main.login_and_redirect(req, *credentials) except AttributeError: diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 3dbab849942..b4f62612746 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -6,6 +6,7 @@ import simplejson import openerp from openerp.osv import osv, fields +from openerp import SUPERUSER_ID _logger = logging.getLogger(__name__) @@ -63,7 +64,7 @@ class res_users(osv.Model): try: return super(res_users, self).check_credentials(cr, uid, password) except openerp.exceptions.AccessDenied: - res = self.search(cr, 1, [('id','=',uid),('oauth_access_token','=',password)]) + res = self.search(cr, SUPERUSER_ID, [('id','=',uid),('oauth_access_token','=',password)]) if not res: raise diff --git a/addons/auth_openid/controllers/main.py b/addons/auth_openid/controllers/main.py index eba711dae7a..ebcaab4bf25 100644 --- a/addons/auth_openid/controllers/main.py +++ b/addons/auth_openid/controllers/main.py @@ -23,6 +23,7 @@ import logging import os import tempfile import urllib +from openerp import SUPERUSER_ID import werkzeug.urls import werkzeug.exceptions @@ -176,7 +177,7 @@ class OpenIDController(openerpweb.Controller): with registry.cursor() as cr: Modules = registry.get('ir.module.module') - installed = Modules.search_count(cr, 1, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1 + installed = Modules.search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1 if installed: Users = registry.get('res.users') @@ -196,13 +197,13 @@ class OpenIDController(openerpweb.Controller): domain += [('openid_url', '=', openid_url), ('active', '=', True)] - ids = Users.search(cr, 1, domain) + ids = Users.search(cr, SUPERUSER_ID, domain) assert len(ids) < 2 if ids: user_id = ids[0] - login = Users.browse(cr, 1, user_id).login + login = Users.browse(cr, SUPERUSER_ID, user_id).login key = randomString(utils.KEY_LENGTH, '0123456789abcdef') - Users.write(cr, 1, [user_id], {'openid_key': key}) + Users.write(cr, SUPERUSER_ID, [user_id], {'openid_key': key}) # TODO fill empty fields with the ones from sreg/ax cr.commit() diff --git a/addons/auth_reset_password/auth_reset_password.py b/addons/auth_reset_password/auth_reset_password.py index 3d1bcbbfa7b..ff208c89b9c 100644 --- a/addons/auth_reset_password/auth_reset_password.py +++ b/addons/auth_reset_password/auth_reset_password.py @@ -6,6 +6,7 @@ import urlparse from openerp.tools import config from openerp.osv import osv, fields +from openerp import SUPERUSER_ID TWENTY_FOUR_HOURS = 24 * 60 * 60 @@ -76,9 +77,9 @@ class res_users(osv.osv): MailMessage.send(cr, uid, [msg_id], context=context) def send_reset_password_request(self, cr, uid, email, context=None): - ids = self.pool.get('res.users').search(cr, 1, [('user_email', '=', email)], context=context) + ids = self.pool.get('res.users').search(cr, SUPERUSER_ID, [('user_email', '=', email)], context=context) if ids: - self._auth_reset_password_send_email(cr, 1, email, 'reset_password_email', ids[0], context=context) + self._auth_reset_password_send_email(cr, SUPERUSER_ID, email, 'reset_password_email', ids[0], context=context) return True #else: # _m, company_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company') @@ -109,7 +110,7 @@ class auth_reset_password(osv.TransientModel): Users = self.pool.get('res.users') data = Users._auth_reset_password_check_token(cr, uid, values.get('token', '')) if data: - Users.write(cr, 1, data['uid'], {'password': pw}, context=context) + Users.write(cr, SUPERUSER_ID, data['uid'], {'password': pw}, context=context) else: raise osv.except_osv('Error', 'Invalid token') diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 0ed2151d12b..821ef44f459 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -5,6 +5,7 @@ import werkzeug.urls from openerp.modules.registry import RegistryManager from openerp.addons.web.controllers.main import login_and_redirect import openerp.addons.web.common.http as openerpweb +from openerp import SUPERUSER_ID _logger = logging.getLogger(__name__) @@ -18,7 +19,7 @@ class OpenIDController(openerpweb.Controller): with registry.cursor() as cr: try: Users = registry.get('res.users') - credentials = Users.auth_signup(cr, 1, name, login, password) + credentials = Users.auth_signup(cr, SUPERUSER_ID, name, login, password) cr.commit() return login_and_redirect(req, *credentials) except AttributeError: diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py index 1d77a3f9473..5f6463508b6 100644 --- a/addons/auth_signup/res_users.py +++ b/addons/auth_signup/res_users.py @@ -1,5 +1,6 @@ import openerp from openerp.osv import osv +from openerp import SUPERUSER_ID class res_users(osv.Model): _inherit = 'res.users' @@ -15,9 +16,9 @@ class res_users(osv.Model): # user_template_id = self.pool.get('ir.config_parameter').get_param(cr, uid, 'auth.signup_template_user_id', 0) if user_template_id: - self.pool.get('res.users').copy(cr, 1, user_template_id, new_user, context=context) + self.pool.get('res.users').copy(cr, SUPERUSER_ID, user_template_id, new_user, context=context) else: - self.pool.get('res.users').create(cr, 1, new_user, context=context) + self.pool.get('res.users').create(cr, SUPERUSER_ID, new_user, context=context) def auth_signup(self, cr, uid, name, login, password, context=None): r = (cr.dbname, login, password) diff --git a/addons/base_gengo/__init__.py b/addons/base_gengo/__init__.py new file mode 100644 index 00000000000..133fa90dc51 --- /dev/null +++ b/addons/base_gengo/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Openerp sa (). +# +# 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 . +# +############################################################################## + +import res_company +import ir_translation +import wizard + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_gengo/__openerp__.py b/addons/base_gengo/__openerp__.py new file mode 100644 index 00000000000..ae61ad8f13c --- /dev/null +++ b/addons/base_gengo/__openerp__.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## +{ + 'name': 'Automated Translations through Gengo API', + 'version': '0.1', + 'category': 'Tools', + 'description': """ +Automated Translations through Gengo API +---------------------------------------- + This module will install passive scheduler job for automated translations +using the Gengo API. To activate it, you must +1) Configure your Gengo authentication parameters under `Settings > Companies > Gengo Parameters` +2) Launch the wizard under `Settings > Application Terms > Gengo: Manual Request of Translation` and follow the wizard. + +This wizard will activate the CRON job and the Scheduler and will start the automatic translation via Gengo Services for all the terms where you requested it. + """, + 'author': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['base'], + 'init_xml': ['gengo_sync_schedular_data.xml'], + 'update_xml': [ + 'ir_translation.xml', + 'res_company_view.xml', + 'wizard/base_gengo_translations_view.xml', + ], + 'demo_xml': [], + 'test': [], + 'installable': True, + 'auto_install': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_gengo/gengo_sync_schedular_data.xml b/addons/base_gengo/gengo_sync_schedular_data.xml new file mode 100644 index 00000000000..823ca8f14db --- /dev/null +++ b/addons/base_gengo/gengo_sync_schedular_data.xml @@ -0,0 +1,28 @@ + + + + + + Gengo Sync Translation (Response) + + 20 + minutes + -1 + + + + + + + + Gengo Sync Translation (Request) + + 20 + minutes + -1 + + + + + + diff --git a/addons/base_gengo/ir_translation.py b/addons/base_gengo/ir_translation.py new file mode 100644 index 00000000000..7b864aba8ce --- /dev/null +++ b/addons/base_gengo/ir_translation.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (C) 2004-2012 OpenERP S.A. (). +# +# 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 . +# +############################################################################## + +from osv import fields, osv +from tools.translate import _ + +LANG_CODE_MAPPING = { + 'ar_SA': ('ar', 'Arabic'), + 'id_ID': ('id', 'Indonesian'), + 'nl_NL': ('nl', 'Dutch'), + 'fr_CA': ('fr-ca', 'French (Canada)'), + 'pl_PL': ('pl', 'Polish'), + 'zh_TW': ('zh-tw', 'Chinese (Traditional)'), + 'sv_SE': ('sv', 'Swedish'), + 'ko_KR': ('ko', 'Korean'), + 'pt_PT': ('pt', 'Portuguese (Europe)'), + 'en_US': ('en', 'English'), + 'ja_JP': ('ja', 'Japanese'), + 'es_ES': ('es', 'Spanish (Spain)'), + 'zh_CN': ('zh', 'Chinese (Simplified)'), + 'de_DE': ('de', 'German'), + 'fr_FR': ('fr', 'French'), + 'fr_BE': ('fr', 'French'), + 'ru_RU': ('ru', 'Russian'), + 'it_IT': ('it', 'Italian'), + 'pt_BR': ('pt-br', 'Portuguese (Brazil)') +} + +class ir_translation(osv.Model): + _name = "ir.translation" + _inherit = "ir.translation" + _columns = { + 'gengo_comment': fields.text("Comments & Activity Linked to Gengo"), + 'job_id': fields.char('Gengo Job ID', size=32), + "gengo_translation": fields.selection([('machine', 'Translation By Machine'), + ('standard', 'Standard'), + ('pro', 'Pro'), + ('ultra', 'Ultra')], "Gengo Translation Service Level", help='You can select here the service level you want for an automatic translation using Gengo.'), + } + + def _get_all_supported_languages(self, cr, uid, context=None): + flag, gengo = self.pool.get('base.gengo.translations').gengo_authentication(cr, uid, context=context) + if not flag: + raise osv.except_osv(_('Gengo Authentication Error'), gengo) + supported_langs = {} + lang_pair = gengo.getServiceLanguagePairs(lc_src='en') + if lang_pair['opstat'] == 'ok': + for g_lang in lang_pair['response']: + if g_lang['lc_tgt'] not in supported_langs: + supported_langs[g_lang['lc_tgt']] = [] + supported_langs[g_lang['lc_tgt']] += [g_lang['tier']] + return supported_langs + + def _get_gengo_corresponding_language(cr, lang): + return lang in LANG_CODE_MAPPING and LANG_CODE_MAPPING[lang][0] or lang + + def _check_lang_support(self, cr, uid, ids, context=None): + for term in self.browse(cr, uid, ids, context=context): + if term.gengo_translation: + supported_langs = self._get_all_supported_languages(cr, uid, context=context) + if supported_langs: + tier = "nonprofit" if term.gengo_translation == 'machine' else term.gengo_translation + language = self._get_gengo_corresponding_language(term.lang) + if tier not in supported_langs.get(language,[]): + return False + return True + + _constraints = [ + (_check_lang_support, 'The Gengo translation service selected is not supported for this language.', ['gengo_translation']) + ] diff --git a/addons/base_gengo/ir_translation.xml b/addons/base_gengo/ir_translation.xml new file mode 100644 index 00000000000..ba9667d979e --- /dev/null +++ b/addons/base_gengo/ir_translation.xml @@ -0,0 +1,31 @@ + + + + + Translations + ir.translation + + + + + + + + + + + ir.translation.form.inherit + + ir.translation + + + + + + + + + + diff --git a/addons/base_gengo/res_company.py b/addons/base_gengo/res_company.py new file mode 100644 index 00000000000..22d7b343939 --- /dev/null +++ b/addons/base_gengo/res_company.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (C) 2004-2012 OpenERP S.A. (). +# +# 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 . +# +############################################################################## + +from osv import fields, osv + + +class res_company(osv.Model): + _name = "res.company" + _inherit = "res.company" + _columns = { + "gengo_private_key": fields.text("Gengo Private Key"), + "gengo_public_key": fields.text("Gengo Public Key"), + "gengo_comment": fields.text("Comments", help="This comment will be automatically be enclosed in each an every request sent to Gengo"), + "gengo_auto_approve": fields.boolean("Auto Approve Translation ?", help="Jobs are Automatically Approved by Gengo."), + } + + _defaults = { + "gengo_auto_approve": True, + } diff --git a/addons/base_gengo/res_company_view.xml b/addons/base_gengo/res_company_view.xml new file mode 100644 index 00000000000..0f99efc8ad0 --- /dev/null +++ b/addons/base_gengo/res_company_view.xml @@ -0,0 +1,32 @@ + + + + + + res.company.form.inherit + + res.company + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/base_gengo/wizard/__init__.py b/addons/base_gengo/wizard/__init__.py new file mode 100644 index 00000000000..f5dd71be01c --- /dev/null +++ b/addons/base_gengo/wizard/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import base_gengo_translations + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_gengo/wizard/base_gengo_translations.py b/addons/base_gengo/wizard/base_gengo_translations.py new file mode 100644 index 00000000000..d8a96a2ced9 --- /dev/null +++ b/addons/base_gengo/wizard/base_gengo_translations.py @@ -0,0 +1,253 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (C) 2004-2012 OpenERP S.A. (). +# +# 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 . +# +############################################################################## + +from osv import osv, fields +from tools.translate import _ +import re +try: + from mygengo import MyGengo +except ImportError: + raise osv.except_osv(_('Gengo ImportError'), _('Please install mygengo lib from http://pypi.python.org/pypi/mygengo')) + +import logging +import tools +import time + +_logger = logging.getLogger(__name__) + +GENGO_DEFAULT_LIMIT = 20 + +DEFAULT_CRON_VALS = { + 'active': True, + 'interval_number': 20, + 'interval_type': 'minutes', + 'model': "'base.gengo.translations'", + 'args': "'(%s,)'" % (str(GENGO_DEFAULT_LIMIT)), +} + +class base_gengo_translations(osv.osv_memory): + + _name = 'base.gengo.translations' + _columns = { + 'restart_send_job': fields.boolean("Restart Sending Job"), + 'lang_id': fields.many2one('res.lang', 'Language', help="Leave empty if you don't want to restrict the request to a single language"), + } + + def gengo_authentication(self, cr, uid, context=None): + ''' + This method tries to open a connection with Gengo. For that, it uses the Public and Private + keys that are linked to the company (given by Gengo on subscription). It returns a tuple with + * as first element: a boolean depicting if the authentication was a success or not + * as second element: the connection, if it was a success, or the error message returned by + Gengo when the connection failed. + This error message can either be displayed in the server logs (if the authentication was called + by the cron) or in a dialog box (if requested by the user), thus it's important to return it + translated. + ''' + + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + if not user.company_id.gengo_public_key or not user.company_id.gengo_private_key: + return (False, _("Invalid Gengo configuration. Gengo authentication `Public Key` or `Private Key` is missing. Complete Gengo authentication parameters under `Settings > Companies > Gengo Parameters`.")) + try: + gengo = MyGengo( + public_key=user.company_id.gengo_public_key.encode('ascii'), + private_key=user.company_id.gengo_private_key.encode('ascii'), + sandbox=True, + ) + gengo.getAccountStats() + + return (True, gengo) + except Exception, e: + return (False, _("Gengo Connection Error\n%s") %e) + + def do_check_schedular(self, cr, uid, xml_id, name, fn, context=None): + """ + This function is used to reset a cron to its default values, or to recreate it if it was deleted. + """ + cron_pool = self.pool.get('ir.cron') + cron_vals = DEFAULT_CRON_VALS.copy() + cron_vals.update({'name': name, "function": fn}) + try: + res = [] + model, res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base_gengo', xml_id) + cron_pool.write(cr, uid, [res], cron_vals, context=context) + except: + #the cron job was not found, probably deleted previously, so we create it again using default values + cron_vals.update({'numbercall': -1}) + return cron_pool.create(cr, uid, cron_vals, context=context) + + def act_update(self, cr, uid, ids, context=None): + ''' + Function called by the wizard. + ''' + if context == None: + context = {} + + flag, gengo = self.gengo_authentication(cr, uid, context=context) + if not flag: + raise osv.except_osv(_('Gengo Authentication Error'), gengo) + for wizard in self.browse(cr, uid, ids, context=context): + supported_langs = self.pool.get('ir.translation')._get_all_supported_languages(cr, uid, context=context) + language = self.pool.get('ir.translation')._get_gengo_corresponding_language(wizard.lang_id.code) + if language not in supported_langs: + raise osv.except_osv(_("Warning"), _('This language is not supported by the Gengo translation services.')) + + #send immediately a new request for the selected language (if any) + ctx = context.copy() + ctx['gengo_language'] = wizard.lang_id.id + self._sync_request(cr, uid, limit=GENGO_DEFAULT_LIMIT, context=ctx) + self._sync_response( cr, uid, limit=GENGO_DEFAULT_LIMIT, context=ctx) + #check the cron jobs and eventually restart/recreate them + if wizard.restart_send_job: + self.do_check_schedular(cr, uid, 'gengo_sync_send_request_scheduler', _('Gengo Sync Translation (Request)'), '_sync_request', context=context) + self.do_check_schedular(cr, uid, 'gengo_sync_receive_request_scheduler', _('Gengo Sync Translation (Response)'), '_sync_response', context=context) + return {'type': 'ir.actions.act_window_close'} + + def _sync_response(self, cr, uid, limit=GENGO_DEFAULT_LIMIT, context=None): + """ + This method will be called by cron services to get translations from + Gengo. It will read translated terms and comments from Gengo and will + update respective ir.translation in openerp. + """ + translation_pool = self.pool.get('ir.translation') + flag, gengo = self.gengo_authentication(cr, uid, context=context) + if not flag: + _logger.warning("%s", gengo) + else: + translation_id = translation_pool.search(cr, uid, [('state', '=', 'inprogress'), ('gengo_translation', 'in', ('machine','standard','pro','ultra'))], limit=limit, context=context) + for term in translation_pool.browse(cr, uid, translation_id, context=context): + up_term = up_comment = 0 + if term.job_id: + vals={} + job_response = gengo.getTranslationJob(id=term.job_id) + if job_response['opstat'] != 'ok': + _logger.warning("Invalid Response! Skipping translation Terms with `id` %s." % (term.job_id)) + continue + if job_response['response']['job']['status'] == 'approved': + vals.update({'state': 'translated', + 'value': job_response['response']['job']['body_tgt']}) + up_term += 1 + job_comment = gengo.getTranslationJobComments(id=term.job_id) + if job_comment['opstat']=='ok': + gengo_comments="" + for comment in job_comment['response']['thread']: + gengo_comments += _('%s\n\n--\n Commented on %s by %s.') % (comment['body'], time.ctime(comment['ctime']), comment['author']) + vals.update({'gengo_comment': gengo_comments}) + up_comment += 1 + if vals: + translation_pool.write(cr, uid, term.id, vals) + _logger.info("Successfully Updated `%d` terms and %d Comments." % (up_term, up_comment )) + else: + _logger.warning("%s", 'Cannot retrieve the Gengo job ID for translation %s: %s' % (term.id, term.src)) + return True + + def _update_terms(self, cr, uid, response, context=None): + """ + Update the terms after their translation were requested to Gengo + """ + translation_pool = self.pool.get('ir.translation') + for jobs in response['jobs']: + for t_id, res in jobs.items(): + vals = {} + t_id = int(t_id) + tier = translation_pool.read(cr, uid, [t_id], ['gengo_translation'], context=context)[0]['gengo_translation'] + if tier == "machine": + vals.update({'value': res['body_tgt'], 'state': 'translated'}) + else: + vals.update({'job_id': res['job_id'], 'state': 'inprogress'}) + translation_pool.write(cr, uid, [t_id], vals, context=context) + return + + def pack_jobs_request(self, cr, uid, term_ids, context=None): + ''' prepare the terms that will be requested to gengo and returns them in a dictionary with following format + {'jobs': { + 'term1.id': {...} + 'term2.id': {...} + } + }''' + + translation_pool = self.pool.get('ir.translation') + jobs = {} + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + auto_approve = 1 if user.company_id.gengo_auto_approve else 0 + for term in translation_pool.browse(cr, uid, term_ids, context=context): + if re.search(r"\w", term.src or ""): + jobs[term.id] = {'type': 'text', + 'slug': 'single::English to ' + term.lang, + 'tier': tools.ustr(term.gengo_translation), + 'body_src': term.src, + 'lc_src': 'en', + 'lc_tgt': translation_pool._get_gengo_corresponding_language(term.lang), + 'auto_approve': auto_approve, + 'comment': user.company_id.gengo_comment, + } + return {'jobs': jobs} + + + def _send_translation_terms(self, cr, uid, term_ids, context=None): + """ + Send a request to Gengo with all the term_ids in a different job, get the response and update the terms in + database accordingly. + """ + + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + flag, gengo = self.gengo_authentication(cr, uid, context=context) + if flag: + request = self.pack_jobs_request(cr, uid, term_ids, context=context) + if request['jobs']: + result = gengo.postTranslationJobs(jobs=request) + if result['opstat'] == 'ok': + self._update_terms(cr, uid, result['response'], context=context) + else: + _logger.error(gengo) + return True + + def _sync_request(self, cr, uid, limit=GENGO_DEFAULT_LIMIT, context=None): + """ + This scheduler will send a job request to the gengo , which terms are + waiing to be translated and for which gengo_translation is enabled. + + A special key 'gengo_language' can be passed in the context in order to + request only translations of that language only. Its value is the language + ID in openerp. + """ + if context is None: + context = {} + language_pool = self.pool.get('res.lang') + translation_pool = self.pool.get('ir.translation') + try: + #by default, the request will be made for all terms that needs it, whatever the language + lang_ids = language_pool.search(cr, uid, [], context=context) + if context.get('gengo_language'): + #but if this specific key is given, then we restrict the request on terms of this language only + lang_ids = [context.get('gengo_language')] + langs = [lang.code for lang in language_pool.browse(cr, uid, lang_ids, context=context)] + #search for the n first terms to translate + term_ids = translation_pool.search(cr, uid, [('state', '=', 'to_translate'), ('gengo_translation', 'in', ('machine','standard','pro','ultra')), ('lang', 'in', langs)], limit=limit, context=context) + if term_ids: + self._send_translation_terms(cr, uid, term_ids, context=context) + _logger.info("%s Translation terms have been posted to Gengo successfully", len(term_ids)) + else: + _logger.info('No Translation terms to process.') + except Exception, e: + _logger.error("%s", e) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_gengo/wizard/base_gengo_translations_view.xml b/addons/base_gengo/wizard/base_gengo_translations_view.xml new file mode 100644 index 00000000000..09c3a77b1d5 --- /dev/null +++ b/addons/base_gengo/wizard/base_gengo_translations_view.xml @@ -0,0 +1,38 @@ + + + + + base.gengo.translation.form + base.gengo.translations + +
+ + + + + + + + +
+
+
+
+
+ + + Gengo: Manual Request of Translation + ir.actions.act_window + base.gengo.translations + form + form + new + + + +
+
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index b725a4ad988..801895885b2 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -595,18 +595,9 @@ class crm_lead(base_stage, osv.osv): for lead in self.browse(cr, uid, ids, context=context): if lead.state in ('done', 'cancel'): continue - vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context) self.write(cr, uid, [lead.id], vals, context=context) - self.convert_opportunity_send_note(cr, uid, lead, context=context) - #TOCHECK: why need to change partner details in all messages of lead ? - # TDE FIXME: I propose to delete this, makes no sense to update partner of an already-send email ... - # if lead.partner_id: - # msg_ids = [x.id for x in lead.message_ids] - # mail_message.write(cr, uid, msg_ids, { - # 'partner_id': lead.partner_id.id - # }, context=context) if user_ids or section_id: self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context) diff --git a/addons/decimal_precision/decimal_precision.py b/addons/decimal_precision/decimal_precision.py index d9f6aa7b8db..93d69eb3093 100644 --- a/addons/decimal_precision/decimal_precision.py +++ b/addons/decimal_precision/decimal_precision.py @@ -22,6 +22,7 @@ from osv import osv, fields import tools import pooler +from openerp import SUPERUSER_ID class decimal_precision(osv.osv): _name = 'decimal.precision' @@ -56,7 +57,7 @@ decimal_precision() def get_precision(application): def change_digit(cr): - res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, application) + res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, SUPERUSER_ID, application) return (16, res) return change_digit diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py index d6ceca458cd..a8fedee4fe3 100644 --- a/addons/document_webdav/nodes.py +++ b/addons/document_webdav/nodes.py @@ -25,6 +25,8 @@ from tools.safe_eval import safe_eval as eval import time import urllib import uuid +from openerp import SUPERUSER_ID + try: from tools.dict_tools import dict_filter except ImportError: @@ -233,7 +235,7 @@ class node_acl_mixin(object): if props_to_delete: # explicitly delete, as admin, any of the ids we have identified. - propobj.unlink(cr, 1, props_to_delete) + propobj.unlink(cr, SUPERUSER_ID, props_to_delete) if lock_data.get('unlock_mode', False): return lock_found and True diff --git a/addons/document_webdav/test_davclient.py b/addons/document_webdav/test_davclient.py index f1f2e58c835..0f66363c553 100755 --- a/addons/document_webdav/test_davclient.py +++ b/addons/document_webdav/test_davclient.py @@ -42,6 +42,7 @@ from tools import config from xmlrpclib import Transport, ProtocolError import StringIO import base64 +from openerp import SUPERUSER_ID _logger = logging.getLogger(__name__) @@ -361,7 +362,7 @@ class DAVClient(object): to break if "base_crypt" is used. """ ruob = obj.pool.get('res.users') - res = ruob.read(cr, 1, [uid,], ['login', 'password']) + res = ruob.read(cr, SUPERUSER_ID, [uid,], ['login', 'password']) assert res, "uid %s not found" % uid self.user = res[0]['login'] self.passwd = res[0]['password'] diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index d540971c3b4..73dc5ed2754 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -29,7 +29,7 @@ from osv import fields import tools from tools.translate import _ from tools.html_sanitize import html_sanitize -from tools import html2plaintext +from tools import append_content_to_html from urllib import quote as quote _logger = logging.getLogger(__name__) @@ -40,10 +40,8 @@ except ImportError: class email_template(osv.osv): "Templates for sending email" - _inherit = 'mail.mail' _name = "email.template" _description = 'Email Templates' - _rec_name = 'name' # override mail.message's behavior def render_template(self, cr, uid, template, model, res_id, context=None): """Render the given template text, replace mako expressions ``${expr}`` @@ -103,57 +101,44 @@ class email_template(osv.osv): mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model return {'value':{'model': mod_name}} - def name_get(self, cr, uid, ids, context=None): - """ Override name_get of mail.message: return directly the template - name, and not the generated name from mail.message.common.""" - return [(record.id, record.name) for record in self.browse(cr, uid, ids, context=context)] - _columns = { - 'name': fields.char('Name', size=250), - 'model_id': fields.many2one('ir.model', 'Related document model'), - 'lang': fields.char('Language Selection', size=250, + 'name': fields.char('Name'), + 'model_id': fields.many2one('ir.model', 'Applies to', help="The kind of document with with this template can be used"), + 'model': fields.related('model_id', 'model', type='char', string='Related Document Model', + size=128, select=True, store=True, readonly=True), + 'lang': fields.char('Language', help="Optional translation language (ISO code) to select when sending out an email. " "If not set, the english version will be used. " "This should usually be a placeholder expression " "that provides the appropriate language code, e.g. " - "${object.partner_id.lang.code}."), + "${object.partner_id.lang.code}.", + placeholder="${object.partner_id.lang.code}"), 'user_signature': fields.boolean('Add Signature', help="If checked, the user's signature will be appended to the text version " "of the message"), - 'report_name': fields.char('Report Filename', size=200, translate=True, - help="Name to use for the generated report file (may contain placeholders)\n" - "The extension can be omitted and will then come from the report type."), - 'report_template':fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'), - 'ref_ir_act_window':fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True, - help="Sidebar action to make this template available on records " - "of the related document model"), - 'ref_ir_value':fields.many2one('ir.values', 'Sidebar Button', readonly=True, - help="Sidebar button to open the sidebar action"), - 'track_campaign_item': fields.boolean('Resource Tracking', - help="Enable this is you wish to include a special tracking marker " - "in outgoing emails so you can identify replies and link " - "them back to the corresponding resource record. " - "This is useful for CRM leads for example"), - - # Overridden mail.message.common fields for technical reasons: - 'model': fields.related('model_id', 'model', type='char', string='Related Document Model', - size=128, select=True, store=True, readonly=True), - # we need a separate m2m table to avoid ID collisions with the original mail.message entries - #'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id', - # 'attachment_id', 'Files to attach', - # help="You may attach files to this template, to be added to all " - # "emails created from this template"), - - # Overridden mail.message.common fields to make tooltips more appropriate: 'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",), - 'email_from': fields.char('From', size=128, help="Sender address (placeholders may be used here)"), - 'email_to': fields.char('To', size=256, help="Comma-separated recipient addresses (placeholders may be used here)"), - 'email_cc': fields.char('Cc', size=256, help="Carbon copy recipients (placeholders may be used here)"), - 'reply_to': fields.char('Reply-To', size=250, help="Preferred response address (placeholders may be used here)"), + 'email_from': fields.char('From', help="Sender address (placeholders may be used here)"), + 'email_to': fields.char('To', help="Comma-separated recipient addresses (placeholders may be used here)"), + 'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"), + 'reply_to': fields.char('Reply-To', help="Preferred response address (placeholders may be used here)"), 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False, help="Optional preferred server for outgoing mails. If not set, the highest " "priority one will be used."), - 'body_html': fields.text('Rich-text Contents', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"), + 'body_html': fields.text('Body', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"), + 'report_name': fields.char('Report Filename', translate=True, + help="Name to use for the generated report file (may contain placeholders)\n" + "The extension can be omitted and will then come from the report type."), + 'report_template': fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'), + 'ref_ir_act_window': fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True, + help="Sidebar action to make this template available on records " + "of the related document model"), + 'ref_ir_value': fields.many2one('ir.values', 'Sidebar Button', readonly=True, + help="Sidebar button to open the sidebar action"), + 'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id', + 'attachment_id', 'Attachments', + help="You may attach files to this template, to be added to all " + "emails created from this template"), + 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"), # Fake fields used to implement the placeholder assistant 'model_object_field': fields.many2one('ir.model.fields', string="Field", @@ -167,12 +152,8 @@ class email_template(osv.osv): help="When a relationship field is selected as first field, " "this field lets you select the target field within the " "destination document model (sub-model)."), - 'null_value': fields.char('Null value', help="Optional value to use if the target field is empty", size=128), - 'copyvalue': fields.char('Expression', size=256, help="Final placeholder expression, to be copy-pasted in the desired template field."), - } - - _defaults = { - 'track_campaign_item': True + 'null_value': fields.char('Default Value', help="Optional value to use if the target field is empty"), + 'copyvalue': fields.char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field."), } def create_action(self, cr, uid, ids, context=None): @@ -217,7 +198,7 @@ class email_template(osv.osv): if template.ref_ir_value: ir_values_obj = self.pool.get('ir.values') ir_values_obj.unlink(cr, uid, template.ref_ir_value.id, context) - except: + except Exception: raise osv.except_osv(_("Warning"), _("Deletion of the action record failed.")) return True @@ -289,32 +270,14 @@ class email_template(osv.osv): :param res_id: id of the record to use for rendering the template (model is taken from template definition) :returns: a dict containing all relevant fields for creating a new - mail.message entry, with the addition one additional - special key ``attachments`` containing a list of + mail.mail entry, with one extra key ``attachments``, in the + format expected by :py:meth:`mail_thread.message_post`. """ if context is None: context = {} - values = { - 'subject': False, - 'body_html': False, - 'email_from': False, - 'email_to': False, - 'email_cc': False, - 'reply_to': False, - 'auto_delete': False, - 'model': False, - 'res_id': False, - 'mail_server_id': False, - 'attachments': False, - 'attachment_ids': False, - 'message_id': False, - 'state': 'outgoing', - } - if not template_id: - return values - report_xml_pool = self.pool.get('ir.actions.report.xml') template = self.get_email_template(cr, uid, template_id, res_id, context) + values = {'model': template.model_id.model} for field in ['subject', 'body_html', 'email_from', 'email_to', 'email_cc', 'reply_to']: @@ -322,21 +285,20 @@ class email_template(osv.osv): template.model, res_id, context=context) \ or False - if values['body_html']: - values['body'] = html_sanitize(values['body_html']) - values['body_text'] = html2plaintext(values['body_html']) - if template.user_signature: signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature - values['body'] += '\n\n' + signature + values['body_html'] = append_content_to_html(values['body_html'], signature) + + if values['body_html']: + values['body'] = html_sanitize(values['body_html']) values.update(mail_server_id = template.mail_server_id.id or False, auto_delete = template.auto_delete, model=template.model, res_id=res_id or False) - attachments = {} - # Add report as a Document + attachments = [] + # Add report in attachments if template.report_template: report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context) report_service = 'report.' + report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name @@ -352,12 +314,11 @@ class email_template(osv.osv): ext = "." + format if not report_name.endswith(ext): report_name += ext - attachments[report_name] = result + attachments.append(report_name, result) - # Add document attachments + # Add template attachments for attach in template.attachment_ids: - # keep the bytes as fetched from the db, base64 encoded - attachments[attach.datas_fname] = attach.datas + attachments.append(attach.datas_fname, attach.datas) values['attachments'] = attachments return values diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml index cd75a719197..448655a3121 100644 --- a/addons/email_template/email_template_view.xml +++ b/addons/email_template/email_template_view.xml @@ -1,95 +1,67 @@ - email.template.form email.template
- - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + - - -