diff --git a/addons/account/account.py b/addons/account/account.py index df623f32174..21a5ce992d7 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -1416,14 +1416,17 @@ class account_move(osv.osv): l[2]['period_id'] = default_period context['period_id'] = default_period - if 'line_id' in vals: + if vals.get('line_id', False): c = context.copy() c['novalidate'] = True c['period_id'] = vals['period_id'] if 'period_id' in vals else self._get_period(cr, uid, context) c['journal_id'] = vals['journal_id'] if 'date' in vals: c['date'] = vals['date'] result = super(account_move, self).create(cr, uid, vals, c) - self.validate(cr, uid, [result], context) + tmp = self.validate(cr, uid, [result], context) + journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'], context) + if journal.entry_posted and tmp: + self.button_validate(cr,uid, [result], context) else: result = super(account_move, self).create(cr, uid, vals, context) return result diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index d56fe27759c..1b7bd658faf 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -349,6 +349,7 @@ + diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 7f5fa9707c1..0fc9d9e2227 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -1283,7 +1283,7 @@ class account_move_line(osv.osv): self.create(cr, uid, data, context) del vals['account_tax_id'] - if check and ((not context.get('no_store_function')) or journal.entry_posted): + if check and not context.get('novalidate') and ((not context.get('no_store_function')) or journal.entry_posted): tmp = move_obj.validate(cr, uid, [vals['move_id']], context) if journal.entry_posted and tmp: move_obj.button_validate(cr,uid, [vals['move_id']], context) diff --git a/addons/account/res_config.py b/addons/account/res_config.py index 8ab93c2fcbb..09df82e38fd 100644 --- a/addons/account/res_config.py +++ b/addons/account/res_config.py @@ -81,31 +81,31 @@ class account_config_settings(osv.osv_memory): 'purchase_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier credit note number'), 'module_account_check_writing': fields.boolean('Pay your suppliers by check', - help="""This allows you to check writing and printing. - This installs the module account_check_writing."""), + help='This allows you to check writing and printing.\n' + '-This installs the module account_check_writing.'), 'module_account_accountant': fields.boolean('Full accounting features: journals, legal statements, chart of accounts, etc.', help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of Accounts, ...)"""), 'module_account_asset': fields.boolean('Assets management', - help="""This allows you to manage the assets owned by a company or a person. - It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines. - This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments, - but not accounting (Journal Items, Chart of Accounts, ...)"""), + help='This allows you to manage the assets owned by a company or a person.\n' + 'It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.\n' + '-This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments, ' + 'but not accounting (Journal Items, Chart of Accounts, ...)'), 'module_account_budget': fields.boolean('Budget management', - help="""This allows accountants to manage analytic and crossovered budgets. - Once the master budgets and the budgets are defined, - the project managers can set the planned amount on each analytic account. - This installs the module account_budget."""), + help='This allows accountants to manage analytic and crossovered budgets. ' + 'Once the master budgets and the budgets are defined, ' + 'the project managers can set the planned amount on each analytic account.\n' + '-This installs the module account_budget.'), 'module_account_payment': fields.boolean('Manage payment orders', - help="""This allows you to create and manage your payment orders, with purposes to - * serve as base for an easy plug-in of various automated payment mechanisms, and - * provide a more efficient way to manage invoice payments. - This installs the module account_payment."""), + help='This allows you to create and manage your payment orders, with purposes to \n' + '* serve as base for an easy plug-in of various automated payment mechanisms, and \n' + '* provide a more efficient way to manage invoice payments.\n' + '-This installs the module account_payment.' ), 'module_account_voucher': fields.boolean('Manage customer payments', - help="""This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc. - This installs the module account_voucher."""), + help='This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.\n' + '-This installs the module account_voucher.'), 'module_account_followup': fields.boolean('Manage customer payment follow-ups', - help="""This allows to automate letters for unpaid invoices, with multi-level recalls. - This installs the module account_followup."""), + help='This allows to automate letters for unpaid invoices, with multi-level recalls.\n' + '-This installs the module account_followup.'), 'group_proforma_invoices': fields.boolean('Allow pro-forma invoices', implied_group='account.group_proforma_invoices', help="Allows you to put invoices in pro-forma state."), diff --git a/addons/account/test/account_test_users.yml b/addons/account/test/account_test_users.yml index 143e8bb5473..c0002ac8b67 100644 --- a/addons/account/test/account_test_users.yml +++ b/addons/account/test/account_test_users.yml @@ -1,7 +1,7 @@ - Create a user as 'Accountant' - - !record {model: res.users, id: res_users_account_user}: + !record {model: res.users, id: res_users_account_user, view: False}: company_id: base.main_company name: Accountant login: acc @@ -17,7 +17,7 @@ - Create a user as 'Financial Manager' - - !record {model: res.users, id: res_users_account_manager}: + !record {model: res.users, id: res_users_account_manager, view: False}: company_id: base.main_company name: Financial Manager login: fm diff --git a/addons/account/wizard/account_report_partner_ledger_view.xml b/addons/account/wizard/account_report_partner_ledger_view.xml index 34c5d55e4c1..61b57f87d7e 100644 --- a/addons/account/wizard/account_report_partner_ledger_view.xml +++ b/addons/account/wizard/account_report_partner_ledger_view.xml @@ -15,7 +15,7 @@ - + diff --git a/addons/account_accountant/i18n/es_AR.po b/addons/account_accountant/i18n/es_AR.po new file mode 100644 index 00000000000..0e2684b0431 --- /dev/null +++ b/addons/account_accountant/i18n/es_AR.po @@ -0,0 +1,23 @@ +# Spanish (Argentina) translation for openobject-addons +# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:04+0000\n" +"PO-Revision-Date: 2013-10-07 21:16+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Spanish (Argentina) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-10-08 05:42+0000\n" +"X-Generator: Launchpad (build 16799)\n" + +#. module: account_accountant +#: model:ir.actions.client,name:account_accountant.action_client_account_menu +msgid "Open Accounting Menu" +msgstr "" diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 7b21a4f1855..65d87ca3086 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -655,7 +655,7 @@ class account_analytic_account(osv.osv): if not contract.partner_id: raise osv.except_osv(_('No Customer Defined!'),_("You must first select a Customer for Contract %s!") % contract.name ) - fpos = contract.partner_id.property_account_position.id or False + fpos = contract.partner_id.property_account_position or False journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', contract.company_id.id or False)], limit=1) if not journal_ids: raise osv.except_osv(_('Error!'), @@ -673,7 +673,7 @@ class account_analytic_account(osv.osv): 'journal_id': len(journal_ids) and journal_ids[0] or False, 'date_invoice': contract.recurring_next_date, 'origin': contract.name, - 'fiscal_position': fpos, + 'fiscal_position': fpos and fpos.id, 'payment_term': partner_payment_term, 'company_id': contract.company_id.id or False, } @@ -687,7 +687,7 @@ class account_analytic_account(osv.osv): account_id = res.categ_id.property_account_income_categ.id account_id = fpos_obj.map_account(cr, uid, fpos, account_id) - taxes = res.taxes_id and res.taxes_id or False + taxes = res.taxes_id or False tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes) invoice_line_vals = { diff --git a/addons/account_cancel/i18n/da.po b/addons/account_cancel/i18n/da.po index db0de0e5bae..2f0269b2d93 100644 --- a/addons/account_cancel/i18n/da.po +++ b/addons/account_cancel/i18n/da.po @@ -8,19 +8,19 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-21 17:05+0000\n" -"PO-Revision-Date: 2011-11-08 10:42+0000\n" -"Last-Translator: OpenERP Danmark / Mikhael Saxtorph \n" +"PO-Revision-Date: 2013-09-26 21:19+0000\n" +"Last-Translator: Morten Schou \n" "Language-Team: Danish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2013-09-12 06:17+0000\n" -"X-Generator: Launchpad (build 16761)\n" +"X-Launchpad-Export-Date: 2013-09-27 05:49+0000\n" +"X-Generator: Launchpad (build 16774)\n" #. module: account_cancel #: view:account.invoice:0 msgid "Cancel" -msgstr "" +msgstr "Annuller" #~ msgid "Account Cancel" #~ msgstr "Annuller post" diff --git a/addons/account_test/i18n/zh_CN.po b/addons/account_test/i18n/zh_CN.po index 71d962610b7..67aeab3353e 100644 --- a/addons/account_test/i18n/zh_CN.po +++ b/addons/account_test/i18n/zh_CN.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-21 17:05+0000\n" -"PO-Revision-Date: 2013-03-21 19:22+0000\n" -"Last-Translator: Liuming \n" +"PO-Revision-Date: 2013-09-24 02:46+0000\n" +"Last-Translator: DWXXX \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2013-09-12 06:40+0000\n" -"X-Generator: Launchpad (build 16761)\n" +"X-Launchpad-Export-Date: 2013-09-25 05:28+0000\n" +"X-Generator: Launchpad (build 16771)\n" #. module: account_test #: view:accounting.assert.test:0 @@ -106,7 +106,7 @@ msgstr "" #. module: account_test #: view:accounting.assert.test:0 msgid "Description" -msgstr "" +msgstr "描述" #. module: account_test #: model:accounting.assert.test,desc:account_test.account_test_06_1 @@ -123,13 +123,13 @@ msgstr "" #: model:ir.actions.report.xml,name:account_test.account_assert_test_report #: model:ir.ui.menu,name:account_test.menu_action_license msgid "Accounting Tests" -msgstr "" +msgstr "帐户测试" #. module: account_test #: code:addons/account_test/report/account_test_report.py:74 #, python-format msgid "The test was passed successfully" -msgstr "" +msgstr "测试通过" #. module: account_test #: field:accounting.assert.test,active:0 @@ -155,7 +155,7 @@ msgstr "" #. module: account_test #: field:accounting.assert.test,code_exec:0 msgid "Python code" -msgstr "" +msgstr "Python代码" #. module: account_test #: model:accounting.assert.test,desc:account_test.account_test_07 @@ -209,7 +209,7 @@ msgstr "" #. module: account_test #: view:accounting.assert.test:0 msgid "Python Code" -msgstr "" +msgstr "Python代码" #. module: account_test #: model:ir.actions.act_window,help:account_test.action_accounting_assert diff --git a/addons/account_voucher/test/account_voucher_users.yml b/addons/account_voucher/test/account_voucher_users.yml index c96645f0190..cb822ae4cef 100644 --- a/addons/account_voucher/test/account_voucher_users.yml +++ b/addons/account_voucher/test/account_voucher_users.yml @@ -1,7 +1,7 @@ - Create a user as 'Accountant for account voucher' - - !record {model: res.users, id: res_users_account_voucher_user}: + !record {model: res.users, id: res_users_account_voucher_user, view: False}: company_id: base.main_company name: Voucher Accountant login: vacc @@ -17,7 +17,7 @@ - Create a user as 'Financial Manager for account voucher' - - !record {model: res.users, id: res_users_account_voucher_manager}: + !record {model: res.users, id: res_users_account_voucher_manager, view: False}: company_id: base.main_company name: Financial Manager for voucher login: fmv diff --git a/addons/auth_signup/i18n/zh_TW.po b/addons/auth_signup/i18n/zh_TW.po new file mode 100644 index 00000000000..b16d2be5abf --- /dev/null +++ b/addons/auth_signup/i18n/zh_TW.po @@ -0,0 +1,279 @@ +# Chinese (Traditional) translation for openobject-addons +# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:05+0000\n" +"PO-Revision-Date: 2013-09-19 09:40+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Chinese (Traditional) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-09-20 05:38+0000\n" +"X-Generator: Launchpad (build 16765)\n" + +#. module: auth_signup +#: field:res.partner,signup_type:0 +msgid "Signup Token Type" +msgstr "" + +#. module: auth_signup +#: field:base.config.settings,auth_signup_uninvited:0 +msgid "Allow external users to sign up" +msgstr "允許外部使用者註冊" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:19 +#, python-format +msgid "Confirm Password" +msgstr "確認密碼" + +#. module: auth_signup +#: help:base.config.settings,auth_signup_uninvited:0 +msgid "If unchecked, only invited users may sign up." +msgstr "如果未勾選,只有被邀請的使用者可以註冊" + +#. module: auth_signup +#: model:ir.model,name:auth_signup.model_base_config_settings +msgid "base.config.settings" +msgstr "" + +#. module: auth_signup +#: code:addons/auth_signup/res_users.py:266 +#, python-format +msgid "Cannot send email: user has no email address." +msgstr "無法送出email:使用者沒有email 地址" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:27 +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:31 +#, python-format +msgid "Reset password" +msgstr "重設密碼" + +#. module: auth_signup +#: field:base.config.settings,auth_signup_template_user_id:0 +msgid "Template user for new users created through signup" +msgstr "" + +#. module: auth_signup +#: model:email.template,subject:auth_signup.reset_password_email +msgid "Password reset" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/js/auth_signup.js:120 +#, python-format +msgid "Please enter a password and confirm it." +msgstr "" + +#. module: auth_signup +#: view:res.users:0 +msgid "Send an email to the user to (re)set their password." +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:26 +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:29 +#, python-format +msgid "Sign Up" +msgstr "" + +#. module: auth_signup +#: selection:res.users,state:0 +msgid "New" +msgstr "" + +#. module: auth_signup +#: code:addons/auth_signup/res_users.py:258 +#, python-format +msgid "Mail sent to:" +msgstr "" + +#. module: auth_signup +#: field:res.users,state:0 +msgid "Status" +msgstr "" + +#. module: auth_signup +#: model:email.template,body_html:auth_signup.reset_password_email +msgid "" +"\n" +"

A password reset was requested for the OpenERP account linked to this " +"email.

\n" +"\n" +"

You may change your password by following this link.

\n" +"\n" +"

Note: If you do not expect this, you can safely ignore this email.

" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/js/auth_signup.js:114 +#, python-format +msgid "Please enter a name." +msgstr "" + +#. module: auth_signup +#: model:ir.model,name:auth_signup.model_res_users +msgid "Users" +msgstr "" + +#. module: auth_signup +#: field:res.partner,signup_url:0 +msgid "Signup URL" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/js/auth_signup.js:117 +#, python-format +msgid "Please enter a username." +msgstr "" + +#. module: auth_signup +#: selection:res.users,state:0 +msgid "Active" +msgstr "" + +#. module: auth_signup +#: code:addons/auth_signup/res_users.py:270 +#, python-format +msgid "" +"Cannot send email: no outgoing email server configured.\n" +"You can configure it under Settings/General Settings." +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:12 +#, python-format +msgid "Username" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:8 +#, python-format +msgid "Name" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/js/auth_signup.js:173 +#, python-format +msgid "Please enter a username or email address." +msgstr "" + +#. module: auth_signup +#: selection:res.users,state:0 +msgid "Resetting Password" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:13 +#, python-format +msgid "Username (Email)" +msgstr "" + +#. module: auth_signup +#: field:res.partner,signup_expiration:0 +msgid "Signup Expiration" +msgstr "" + +#. module: auth_signup +#: help:base.config.settings,auth_signup_reset_password:0 +msgid "This allows users to trigger a password reset from the Login page." +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:25 +#, python-format +msgid "Log in" +msgstr "" + +#. module: auth_signup +#: field:res.partner,signup_valid:0 +msgid "Signup Token is Valid" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/js/auth_signup.js:111 +#: code:addons/auth_signup/static/src/js/auth_signup.js:114 +#: code:addons/auth_signup/static/src/js/auth_signup.js:117 +#: code:addons/auth_signup/static/src/js/auth_signup.js:120 +#: code:addons/auth_signup/static/src/js/auth_signup.js:123 +#: code:addons/auth_signup/static/src/js/auth_signup.js:170 +#: code:addons/auth_signup/static/src/js/auth_signup.js:173 +#, python-format +msgid "Login" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/js/auth_signup.js:97 +#, python-format +msgid "Invalid signup token" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/js/auth_signup.js:123 +#, python-format +msgid "Passwords do not match; please retype them." +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/js/auth_signup.js:111 +#: code:addons/auth_signup/static/src/js/auth_signup.js:170 +#, python-format +msgid "No database selected !" +msgstr "" + +#. module: auth_signup +#: view:res.users:0 +msgid "Reset Password" +msgstr "" + +#. module: auth_signup +#: field:base.config.settings,auth_signup_reset_password:0 +msgid "Enable password reset from Login page" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:30 +#, python-format +msgid "Back to Login" +msgstr "" + +#. module: auth_signup +#. openerp-web +#: code:addons/auth_signup/static/src/xml/auth_signup.xml:22 +#, python-format +msgid "Sign up" +msgstr "" + +#. module: auth_signup +#: model:ir.model,name:auth_signup.model_res_partner +msgid "Partner" +msgstr "" + +#. module: auth_signup +#: field:res.partner,signup_token:0 +msgid "Signup Token" +msgstr "" diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index d4cd1e083b4..397bd193757 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -114,19 +114,6 @@ def real_id2base_calendar_id(real_id, recurrent_date): return '%d-%s' % (real_id, recurrent_date) return real_id -def _links_get(self, cr, uid, context=None): - """ - Get request link. - @param cr: the current row, from the database cursor - @param uid: the current user's ID for security checks - @param context: a standard dictionary for contextual values - @return: list of dictionary which contain object and name and id - """ - obj = self.pool.get('res.request.link') - ids = obj.search(cr, uid, []) - res = obj.read(cr, uid, ids, ['object', 'name'], context=context) - return [(r['object'], r['name']) for r in res] - html_invitation = """ @@ -307,19 +294,6 @@ class calendar_attendee(osv.osv): return result - def _links_get(self, cr, uid, context=None): - """ - Get request link for ref field in calendar attendee. - @param cr: the current row, from the database cursor - @param uid: the current user's id for security checks - @param context: A standard dictionary for contextual values - @return: list of dictionary which contain object and name and id - """ - obj = self.pool.get('res.request.link') - ids = obj.search(cr, uid, []) - res = obj.read(cr, uid, ids, ['object', 'name'], context=context) - return [(r['object'], r['name']) for r in res] - def _lang_get(self, cr, uid, context=None): """ Get language for language selection field. @@ -385,7 +359,7 @@ property or property parameter."), 'event_end_date': fields.function(_compute_data, \ string='Event End Date', type="datetime", \ multi='event_end_date'), - 'ref': fields.reference('Event Ref', selection=_links_get, size=128), + 'ref': fields.reference('Event Ref', selection=openerp.addons.base.res.res_request.referencable_models, size=128), 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"), } _defaults = { @@ -1208,20 +1182,44 @@ rule or repeating pattern of time to exclude from the recurring rule."), new_rrule_str = ';'.join(new_rrule_str) rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule']) for r_date in rdates: - ok = True + # fix domain evaluation + # step 1: check date and replace expression by True or False, replace other expressions by True + # step 2: evaluation of & and | + # check if there are one False + pile = [] for arg in domain: - if arg[0] in ('date', 'date_deadline'): - if (arg[1]=='='): - ok = ok and r_date.strftime('%Y-%m-%d')==arg[2] - if (arg[1]=='>'): - ok = ok and r_date.strftime('%Y-%m-%d')>arg[2] - if (arg[1]=='<'): - ok = ok and r_date.strftime('%Y-%m-%d')='): - ok = ok and r_date.strftime('%Y-%m-%d')>=arg[2] - if (arg[1]=='<='): - ok = ok and r_date.strftime('%Y-%m-%d')<=arg[2] - if not ok: + if str(arg[0]) in (str('date'), str('date_deadline')): + if (arg[1] == '='): + ok = r_date.strftime('%Y-%m-%d')==arg[2] + if (arg[1] == '>'): + ok = r_date.strftime('%Y-%m-%d')>arg[2] + if (arg[1] == '<'): + ok = r_date.strftime('%Y-%m-%d')='): + ok = r_date.strftime('%Y-%m-%d')>=arg[2] + if (arg[1] == '<='): + ok = r_date.strftime('%Y-%m-%d')<=arg[2] + pile.append(ok) + elif str(arg) == str('&') or str(arg) == str('|'): + pile.append(arg) + else: + pile.append(True) + pile.reverse() + new_pile = [] + for item in pile: + if not isinstance(item, basestring): + res = item + elif str(item) == str('&'): + first = new_pile.pop() + second = new_pile.pop() + res = first and second + elif str(item) == str('|'): + first = new_pile.pop() + second = new_pile.pop() + res = first or second + new_pile.append(res) + + if [True for item in new_pile if not item]: continue idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S")) result.append(idval) @@ -1346,18 +1344,17 @@ rule or repeating pattern of time to exclude from the recurring rule."), for arg in args: new_arg = arg - if arg[0] in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')): + if arg[0] in ('date_deadline', unicode('date_deadline')): if context.get('virtual_id', True): - new_args += ['|','&',('recurrency','=',1),('recurrent_id_date', arg[1], arg[2])] + new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])] elif arg[0] == "id": new_id = get_real_ids(arg[2]) new_arg = (arg[0], arg[1], new_id) new_args.append(new_arg) - #offset, limit and count must be treated separately as we may need to deal with virtual ids res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=order, context=context, count=False) if context.get('virtual_id', True): - res = self.get_recurrent_ids(cr, uid, res, new_args, limit, context=context) + res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context) if count: return len(res) elif limit: @@ -1436,6 +1433,14 @@ rule or repeating pattern of time to exclude from the recurring rule."), vals['vtimezone'] = vals['vtimezone'][40:] res = super(calendar_event, self).write(cr, uid, ids, vals, context=context) + + # set end_date for calendar searching + if vals.get('recurrency', True) and vals.get('end_type', 'count') in ('count', unicode('count')) and \ + (vals.get('rrule_type') or vals.get('count') or vals.get('date') or vals.get('date_deadline')): + for data in self.read(cr, uid, ids, ['date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context): + end_date = self._set_recurrency_end_date(data, context=context) + super(calendar_event, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context) + if vals.get('partner_ids', False): self.create_attendees(cr, uid, ids, context) @@ -1554,6 +1559,21 @@ rule or repeating pattern of time to exclude from the recurring rule."), self.unlink_events(cr, uid, ids, context=context) return res + def _set_recurrency_end_date(self, data, context=None): + end_date = data.get('end_date') + if data.get('recurrency') and data.get('end_type') in ('count', unicode('count')): + data_date_deadline = datetime.strptime(data.get('date_deadline'), '%Y-%m-%d %H:%M:%S') + if data.get('rrule_type') in ('daily', unicode('count')): + rel_date = relativedelta(days=data.get('count')+1) + elif data.get('rrule_type') in ('weekly', unicode('weekly')): + rel_date = relativedelta(days=(data.get('count')+1)*7) + elif data.get('rrule_type') in ('monthly', unicode('monthly')): + rel_date = relativedelta(months=data.get('count')+1) + elif data.get('rrule_type') in ('yearly', unicode('yearly')): + rel_date = relativedelta(years=data.get('count')+1) + end_date = data_date_deadline + rel_date + return end_date + def create(self, cr, uid, vals, context=None): if context is None: context = {} @@ -1561,7 +1581,9 @@ rule or repeating pattern of time to exclude from the recurring rule."), if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'): vals['vtimezone'] = vals['vtimezone'][40:] + vals['end_date'] = self._set_recurrency_end_date(vals, context=context) res = super(calendar_event, self).create(cr, uid, vals, context) + alarm_obj = self.pool.get('res.alarm') alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context) self.create_attendees(cr, uid, [res], context) diff --git a/addons/base_setup/res_config.py b/addons/base_setup/res_config.py index 6b8578bbf47..b059584d2db 100644 --- a/addons/base_setup/res_config.py +++ b/addons/base_setup/res_config.py @@ -20,14 +20,20 @@ ############################################################################## from openerp.osv import fields, osv +import re +from openerp.report.render.rml2pdf import customfonts class base_config_settings(osv.osv_memory): _name = 'base.config.settings' _inherit = 'res.config.settings' + + def _get_font(self, cr, uid, context=None): + return sorted(customfonts.RegisterCustomFonts()) + _columns = { 'module_multi_company': fields.boolean('Manage multiple companies', - help="""Work in multi-company environments, with appropriate security access between companies. - This installs the module multi_company."""), + help='Work in multi-company environments, with appropriate security access between companies.\n' + '-This installs the module multi_company.'), 'module_share': fields.boolean('Allow documents sharing', help="""Share or embbed any screen of openerp."""), 'module_portal': fields.boolean('Activate the customer portal', @@ -38,8 +44,13 @@ class base_config_settings(osv.osv_memory): 'module_base_import': fields.boolean("Allow users to import data from CSV files"), 'module_google_drive': fields.boolean('Attach Google documents to any record', help="""This installs the module google_docs."""), + 'font': fields.selection(_get_font, "Select Font", help="Set the font into the report header, will be used for every RML report of the user company"), } - + + _defaults= { + 'font': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.font or 'Helvetica', + } + def open_company(self, cr, uid, ids, context=None): user = self.pool.get('res.users').browse(cr, uid, uid, context) return { @@ -52,6 +63,20 @@ class base_config_settings(osv.osv_memory): 'target': 'current', } + def _change_header(self, header,font): + """ Replace default fontname use in header and setfont tag """ + + default_para = re.sub('fontName.?=.?".*"', 'fontName="%s"'% font,header) + return re.sub('("%s"\g<3>'% font,default_para) + + def set_base_defaults(self, cr, uid, ids, context=None): + ir_model_data = self.pool.get('ir.model.data') + wizard = self.browse(cr, uid, ids)[0] + if wizard.font: + user = self.pool.get('res.users').browse(cr, uid, uid, context) + user.company_id.write({'font':wizard.font,'rml_header': self._change_header(user.company_id.rml_header,wizard.font), 'rml_header2': self._change_header(user.company_id.rml_header2,wizard.font), 'rml_header3': self._change_header(user.company_id.rml_header3,wizard.font)}) + return {} + # Preferences wizard for Sales & CRM. # It is defined here because it is inherited independently in modules sale, crm, # plugin_outlook and plugin_thunderbird. @@ -64,18 +89,20 @@ class sale_config_settings(osv.osv_memory): 'module_crm': fields.boolean('CRM'), 'module_sale' : fields.boolean('SALE'), 'module_plugin_thunderbird': fields.boolean('Enable Thunderbird plug-in', - help="""The plugin allows you archive email and its attachments to the selected - OpenERP objects. You can select a partner, or a lead and - attach the selected mail as a .eml file in - the attachment of a selected record. You can create documents for CRM Lead, - Partner from the selected emails. - This installs the module plugin_thunderbird."""), + help='The plugin allows you archive email and its attachments to the selected ' + 'OpenERP objects. You can select a partner, or a lead and ' + 'attach the selected mail as a .eml file in ' + 'the attachment of a selected record. You can create documents for CRM Lead, ' + 'Partner from the selected emails.\n' + '-This installs the module plugin_thunderbird.'), 'module_plugin_outlook': fields.boolean('Enable Outlook plug-in', - help="""The Outlook plugin allows you to select an object that you would like to add - to your email and its attachments from MS Outlook. You can select a partner, - or a lead object and archive a selected - email into an OpenERP mail message with attachments. - This installs the module plugin_outlook."""), + help='The Outlook plugin allows you to select an object that you would like to add ' + 'to your email and its attachments from MS Outlook. You can select a partner, ' + 'or a lead object and archive a selected email into an OpenERP mail message with attachments.\n' + '-This installs the module plugin_outlook.'), + 'module_mass_mailing': fields.boolean( + 'Manage mass mailing campaigns', + help='Get access to statistics with your mass mailing, manage campaigns.'), } -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/base_setup/res_config_view.xml b/addons/base_setup/res_config_view.xml index 73685be95df..e0434480a37 100644 --- a/addons/base_setup/res_config_view.xml +++ b/addons/base_setup/res_config_view.xml @@ -90,6 +90,14 @@ + +
@@ -123,6 +131,10 @@
- - - - - - - - + + + + + + + + + diff --git a/addons/crm/res_config.py b/addons/crm/res_config.py index ea37c24fe06..5f3ffe272e7 100644 --- a/addons/crm/res_config.py +++ b/addons/crm/res_config.py @@ -58,10 +58,11 @@ class crm_configuration(osv.TransientModel): implied_group='crm.group_fund_raising', help="""Allows you to trace and manage your activities for fund raising."""), 'module_crm_claim': fields.boolean("Manage Customer Claims", - help="""Allows you to track your customers/suppliers claims and grievances. - This installs the module crm_claim."""), + help='Allows you to track your customers/suppliers claims and grievances.\n' + '-This installs the module crm_claim.'), 'module_crm_helpdesk': fields.boolean("Manage Helpdesk and Support", - help="""Allows you to communicate with Customer, process Customer query, and provide better help and support. This installs the module crm_helpdesk."""), + help='Allows you to communicate with Customer, process Customer query, and provide better help and support.\n' + '-This installs the module crm_helpdesk.'), 'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams", implied_group='base.group_multi_salesteams', help="""Allows you to use Sales Teams to manage your leads and opportunities."""), diff --git a/addons/crm/test/crm_access_group_users.yml b/addons/crm/test/crm_access_group_users.yml index 51cf52c0abc..ffde96a18f3 100644 --- a/addons/crm/test/crm_access_group_users.yml +++ b/addons/crm/test/crm_access_group_users.yml @@ -1,7 +1,7 @@ - Create a user as 'Crm Salesmanager' - - !record {model: res.users, id: crm_res_users_salesmanager}: + !record {model: res.users, id: crm_res_users_salesmanager, view: False}: company_id: base.main_company name: Crm Sales manager login: csm @@ -16,7 +16,7 @@ - Create a user as 'Crm Salesman' - - !record {model: res.users, id: crm_res_users_salesman}: + !record {model: res.users, id: crm_res_users_salesman, view: False}: company_id: base.main_company name: Crm Salesman login: csu diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index d954e0486cc..48097944ec4 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -63,7 +63,7 @@ class crm_lead2opportunity_partner(osv.osv_memory): for id in ids: tomerge.add(id) if email: - ids = lead_obj.search(cr, uid, [('email_from', 'ilike', email[0]), ('probability', '<', '100')]) + ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), ('probability', '<', '100')]) for id in ids: tomerge.add(id) diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py index c655ce2c12b..1b9cb0b0c50 100644 --- a/addons/crm_claim/crm_claim.py +++ b/addons/crm_claim/crm_claim.py @@ -19,6 +19,7 @@ # ############################################################################## +import openerp from openerp.addons.crm import crm from openerp.osv import fields, osv from openerp import tools @@ -83,7 +84,7 @@ class crm_claim(osv.osv): 'date_deadline': fields.date('Deadline'), 'date_closed': fields.datetime('Closed', readonly=True), 'date': fields.datetime('Claim Date', select=True), - 'ref' : fields.reference('Reference', selection=crm._links_get, size=128), + 'ref': fields.reference('Reference', selection=openerp.addons.base.res.res_request.referencable_models), 'categ_id': fields.many2one('crm.case.categ', 'Category', \ domain="[('section_id','=',section_id),\ ('object_id.model', '=', 'crm.claim')]"), @@ -108,7 +109,7 @@ class crm_claim(osv.osv): _defaults = { 'user_id': lambda s, cr, uid, c: uid, 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), - 'date': fields.datetime.now(), + 'date': fields.datetime.now, 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c), 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], 'active': lambda *a: 1, diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py index 03e6a243851..5f84d45f974 100644 --- a/addons/crm_helpdesk/crm_helpdesk.py +++ b/addons/crm_helpdesk/crm_helpdesk.py @@ -19,6 +19,7 @@ # ############################################################################## +import openerp from openerp.addons.crm import crm from openerp.osv import fields, osv from openerp import tools @@ -53,8 +54,8 @@ class crm_helpdesk(osv.osv): 'email_cc': fields.text('Watchers Emails', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"), 'email_from': fields.char('Email', size=128, help="Destination email for email gateway"), 'date': fields.datetime('Date'), - 'ref' : fields.reference('Reference', selection=crm._links_get, size=128), - 'ref2' : fields.reference('Reference 2', selection=crm._links_get, size=128), + 'ref': fields.reference('Reference', selection=openerp.addons.base.res.res_request.referencable_models), + 'ref2': fields.reference('Reference 2', selection=openerp.addons.base.res.res_request.referencable_models), 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel."), 'planned_revenue': fields.float('Planned Revenue'), 'planned_cost': fields.float('Planned Costs'), @@ -80,7 +81,7 @@ class crm_helpdesk(osv.osv): 'active': lambda *a: 1, 'user_id': lambda s, cr, uid, c: uid, 'state': lambda *a: 'draft', - 'date': lambda *a: fields.datetime.now(), + 'date': fields.datetime.now, 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c), 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], } diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 5f264cbbc7c..ed35349f420 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -75,7 +75,7 @@ class email_template(osv.osv): _description = 'Email Templates' _order = 'name' - def render_template(self, cr, uid, template, model, res_id, context=None): + def render_template_batch(self, cr, uid, template, model, res_ids, context=None): """Render the given template text, replace mako expressions ``${expr}`` with the result of evaluating these expressions with an evaluation context containing: @@ -87,46 +87,60 @@ class email_template(osv.osv): :param str template: the template text to render :param str model: model name of the document record this mail is related to. - :param int res_id: id of the document record this mail is related to. + :param int res_ids: list of ids of document records those mails are related to. """ - if not template: - return u"" if context is None: context = {} - try: - template = tools.ustr(template) - record = None - if res_id: - record = self.pool[model].browse(cr, uid, res_id, context=context) - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - variables = { - 'object': record, - 'user': user, - 'ctx': context, # context kw would clash with mako internals - } - result = mako_template_env.from_string(template).render(variables) - if result == u"False": - result = u"" - return result - except Exception: - _logger.exception("failed to render mako template value %r", template) - return u"" + results = dict.fromkeys(res_ids, u"") - def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None): + # try to load the template + try: + template = mako_template_env.from_string(tools.ustr(template)) + except Exception: + _logger.exception("Failed to load template %r", template) + return results + + # prepare template variables + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + records = self.pool[model].browse(cr, uid, res_ids, context=context) or [None] + variables = { + 'user': user, + 'ctx': context, # context kw would clash with mako internals + } + for record in records: + res_id = record.id if record else None + variables['object'] = record + try: + render_result = template.render(variables) + except Exception: + _logger.exception("Failed to render template %r using values %r" % (template, variables)) + render_result = u"" + if render_result == u"False": + render_result = u"" + results[res_id] = render_result + return results + + def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None): if context is None: context = {} + if res_ids is None: + res_ids = [None] + results = dict.fromkeys(res_ids, False) + if not template_id: - return False + return results template = self.browse(cr, uid, template_id, context) - lang = self.render_template(cr, uid, template.lang, template.model, record_id, context) - if lang: - # Use translated template if necessary - ctx = context.copy() - ctx['lang'] = lang - template = self.browse(cr, uid, template.id, ctx) - else: - template = self.browse(cr, uid, int(template_id), context) - return template + langs = self.render_template_batch(cr, uid, template.lang, template.model, res_ids, context) + for res_id, lang in langs.iteritems(): + if lang: + # Use translated template if necessary + ctx = context.copy() + ctx['lang'] = lang + template = self.browse(cr, uid, template.id, ctx) + else: + template = self.browse(cr, uid, int(template_id), context) + results[res_id] = template + return results def onchange_model_id(self, cr, uid, ids, model_id, context=None): mod_name = False @@ -308,64 +322,75 @@ class email_template(osv.osv): }) return {'value': result} - def generate_email(self, cr, uid, template_id, res_id, context=None): - """Generates an email from the template for given (model, res_id) pair. + def generate_email_batch(self, cr, uid, template_id, res_ids, context=None, fields=None): + """Generates an email from the template for given the given model based on + records given by res_ids. - :param template_id: id of the template to render. - :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.mail entry, with one extra key ``attachments``, in the - format expected by :py:meth:`mail_thread.message_post`. + :param template_id: id of the template to render. + :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.mail entry, with one extra key ``attachments``, in the + format expected by :py:meth:`mail_thread.message_post`. """ if context is None: context = {} + if fields is None: + fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to'] + report_xml_pool = self.pool.get('ir.actions.report.xml') - template = self.get_email_template(cr, uid, template_id, res_id, context) - values = {} - for field in ['subject', 'body_html', 'email_from', - 'email_to', 'partner_to', 'email_cc', 'reply_to']: - values[field] = self.render_template(cr, uid, getattr(template, field), - template.model, res_id, context=context) \ - or False - if template.user_signature: - signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature - values['body_html'] = tools.append_content_to_html(values['body_html'], signature) + res_ids_to_templates = self.get_email_template_batch(cr, uid, template_id, res_ids, context) - if values['body_html']: - values['body'] = tools.html_sanitize(values['body_html']) + # templates: res_id -> template; template -> res_ids + templates_to_res_ids = {} + for res_id, template in res_ids_to_templates.iteritems(): + templates_to_res_ids.setdefault(template, []).append(res_id) - 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) + results = dict() + for template, template_res_ids in templates_to_res_ids.iteritems(): + # generate fields value for all res_ids linked to the current template + for field in ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']: + generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context) + for res_id, field_value in generated_field_values.iteritems(): + results.setdefault(res_id, dict())[field] = field_value + # update values for all res_ids + for res_id in template_res_ids: + values = results[res_id] + if template.user_signature: + signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature + values['body_html'] = tools.append_content_to_html(values['body_html'], signature) + if values['body_html']: + values['body'] = tools.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, + attachment_ids=[attach.id for attach in template.attachment_ids], + ) - 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_xml_pool.browse(cr, uid, template.report_template.id, context).report_name - # Ensure report is rendered using template's language - ctx = context.copy() - if template.lang: - ctx['lang'] = self.render_template(cr, uid, template.lang, template.model, res_id, context) - result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx) - result = base64.b64encode(result) - if not report_name: - report_name = 'report.' + report_service - ext = "." + format - if not report_name.endswith(ext): - report_name += ext - attachments.append((report_name, result)) + # Add report in attachments + if template.report_template: + for res_id in template_res_ids: + attachments = [] + report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context) + report_service = report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name + # Ensure report is rendered using template's language + ctx = context.copy() + if template.lang: + ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, res_id, context) # take 0 ? + result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx) + result = base64.b64encode(result) + if not report_name: + report_name = 'report.' + report_service + ext = "." + format + if not report_name.endswith(ext): + report_name += ext + attachments.append((report_name, result)) - attachment_ids = [] - # Add template attachments - for attach in template.attachment_ids: - attachment_ids.append(attach.id) + values['attachments'] = attachments - values['attachments'] = attachments - values['attachment_ids'] = attachment_ids - return values + return results def send_mail(self, cr, uid, template_id, res_id, force_send=False, raise_exception=False, context=None): """Generates a new mail message for the given template and record, @@ -412,4 +437,14 @@ class email_template(osv.osv): mail_mail.send(cr, uid, [msg_id], raise_exception=raise_exception, context=context) return msg_id + # Compatibility method + def render_template(self, cr, uid, template, model, res_id, context=None): + return self.render_template_batch(cr, uid, template, model, [res_id], context)[res_id] + + def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None): + return self.get_email_template_batch(cr, uid, template_id, [record_id], context)[record_id] + + def generate_email(self, cr, uid, template_id, res_id, context=None): + return self.generate_email_batch(cr, uid, template_id, [res_id], context)[res_id] + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/email_template/ir_actions.py b/addons/email_template/ir_actions.py index bd860785da4..ae2395e3b81 100644 --- a/addons/email_template/ir_actions.py +++ b/addons/email_template/ir_actions.py @@ -33,48 +33,45 @@ class actions_server(osv.Model): return res _columns = { - 'email_from': fields.char('From', - help="Sender address; define the template to see its value. If not set, the default " - "value will be the author's email alias if configured, or email address."), - 'email_to': fields.char('To (Emails)', - help="Comma-separated recipient addresses; define the template to see its value"), - 'partner_to': fields.char('To (Partners)', - help="Comma-separated ids of recipient partners; define the template to see its value"), - 'subject': fields.char('Subject', - help="Email subject; define the template to see its value"), - 'body_html': fields.text('Body', - help="Rich-text/HTML version of the message; define the template to see its value"), - 'template_id': fields.many2one('email.template', 'Email Template', ondelete='set null', - help="Define the email template to use for the email to send.") + 'email_from': fields.related( + 'template_id', 'email_from', type='char', + readonly=True, string='From' + ), + 'email_to': fields.related( + 'template_id', 'email_to', type='char', + readonly=True, string='To (Emails)' + ), + 'partner_to': fields.related( + 'template_id', 'partner_to', type='char', + readonly=True, string='To (Partners)' + ), + 'subject': fields.related( + 'template_id', 'subject', type='char', + readonly=True, string='Subject' + ), + 'body_html': fields.related( + 'template_id', 'body_html', type='text', + readonly=True, string='Body' + ), + 'template_id': fields.many2one( + 'email.template', 'Email Template', ondelete='set null', + domain="[('model_id', '=', model_id)]", + ), } def on_change_template_id(self, cr, uid, ids, template_id, context=None): """ Render the raw template in the server action fields. """ + fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to'] if template_id: - fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids'] template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context) values = dict((field, template_values[field]) for field in fields if template_values.get(field)) if not values.get('email_from'): return {'warning': {'title': 'Incomplete template', 'message': 'Your template should define email_from'}, 'value': values} else: - values = self.default_get(cr, uid, ['subject', 'body_html', 'email_from', 'email_to', 'partner_to'], context=context) + values = dict.fromkeys(fields, False) return {'value': values} - def create(self, cr, uid, values, context=None): - if values.get('template_id'): - fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids'] - template_values = self.pool.get('email.template').read(cr, uid, values.get('template_id'), fields, context) - values.update(dict((field, template_values[field]) for field in fields if template_values.get(field))) - return super(actions_server, self).create(cr, uid, values, context=context) - - def write(self, cr, uid, ids, values, context=None): - if values.get('template_id'): - fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids'] - template_values = self.pool.get('email.template').read(cr, uid, values.get('template_id'), fields, context) - values.update(dict((field, template_values[field]) for field in fields if template_values.get(field))) - return super(actions_server, self).write(cr, uid, ids, values, context=context) - def run_action_email(self, cr, uid, action, eval_context=None, context=None): if not action.template_id or not context.get('active_id'): return False diff --git a/addons/email_template/ir_actions_view.xml b/addons/email_template/ir_actions_view.xml index fae9c364312..8f5875257aa 100644 --- a/addons/email_template/ir_actions_view.xml +++ b/addons/email_template/ir_actions_view.xml @@ -16,7 +16,6 @@

Choose a template to display its values. diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index d5feea9ebc4..ccc8a49b875 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -20,10 +20,10 @@ ############################################################################## import base64 -from openerp.addons.mail.tests.test_mail_base import TestMailBase +from openerp.addons.mail.tests.common import TestMail -class test_message_compose(TestMailBase): +class test_message_compose(TestMail): def setUp(self): super(test_message_compose, self).setUp() @@ -73,7 +73,7 @@ class test_message_compose(TestMailBase): # 1. Comment on pigs compose_id = mail_compose.create(cr, uid, - {'subject': 'Forget me subject', 'body': '

Dummy body

'}, + {'subject': 'Forget me subject', 'body': '

Dummy body

', 'post': True}, {'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id, @@ -101,7 +101,7 @@ class test_message_compose(TestMailBase): 'default_template_id': email_template_id, 'active_ids': [self.group_pigs_id, self.group_bird_id] } - compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context) + compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body', 'post': True}, context) compose = mail_compose.browse(cr, uid, compose_id, context) onchange_res = compose.onchange_template_id(email_template_id, 'comment', 'mail.group', self.group_pigs_id)['value'] onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])] @@ -145,7 +145,7 @@ class test_message_compose(TestMailBase): 'default_partner_ids': [p_a_id], 'active_ids': [self.group_pigs_id, self.group_bird_id] } - compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context) + compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body', 'post': True}, context) compose = mail_compose.browse(cr, uid, compose_id, context) onchange_res = compose.onchange_template_id(email_template_id, 'mass_mail', 'mail.group', self.group_pigs_id)['value'] onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])] diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 0aab765efbf..bae7a0d8f9c 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -62,6 +62,7 @@ class mail_compose_message(osv.TransientModel): for wizard in self.browse(cr, uid, ids, context=context): if wizard.template_id: wizard_context['mail_notify_user_signature'] = False # template user_signature is added when generating body_html + wizard_context['mail_auto_delete'] = wizard.template_id.auto_delete # mass mailing: use template auto_delete value -> note, for emails mass mailing only if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id: continue new_attachment_ids = [] @@ -81,7 +82,7 @@ class mail_compose_message(osv.TransientModel): template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context) values = dict((field, template_values[field]) for field in fields if template_values.get(field)) elif template_id: - values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context) + values = self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context=context)[res_id] # transform attachments into attachment_ids; not attached to the document because this will # be done further in the posting process, allowing to clean database if email not send values['attachment_ids'] = values.pop('attachment_ids', []) @@ -147,45 +148,55 @@ class mail_compose_message(osv.TransientModel): partner_ids.append(int(partner_id)) return partner_ids - def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None): + def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None): """ Call email_template.generate_email(), get fields relevant for mail.compose.message, transform email_cc and email_to into partner_ids """ - template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context) # filter template values fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id'] - values = dict((field, template_values[field]) for field in fields if template_values.get(field)) - values['body'] = values.pop('body_html', '') + values = dict.fromkeys(res_ids, False) - # transform email_to, email_cc into partner_ids - ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_')) - partner_ids = self._get_or_create_partners_from_values(cr, uid, values, context=ctx) - # legacy template behavior: void values do not erase existing values and the - # related key is removed from the values dict - if partner_ids: - values['partner_ids'] = list(partner_ids) + template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context) + for res_id in res_ids: + res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field)) + res_id_values['body'] = res_id_values.pop('body_html', '') + # transform email_to, email_cc into partner_ids + ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_')) + partner_ids = self._get_or_create_partners_from_values(cr, uid, res_id_values, context=ctx) + # legacy template behavior: void values do not erase existing values and the + # related key is removed from the values dict + if partner_ids: + res_id_values['partner_ids'] = list(partner_ids) + + values[res_id] = res_id_values return values - def render_message(self, cr, uid, wizard, res_id, context=None): + def render_message_batch(self, cr, uid, wizard, res_ids, context=None): """ Override to handle templates. """ - # generate the composer email + # generate template-based values if wizard.template_id: - values = self.generate_email_for_composer(cr, uid, wizard.template_id.id, res_id, context=context) + template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context) else: - values = {} - # remove attachments as they should not be rendered - values.pop('attachment_ids', None) - # get values to return - email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context) - # those values are not managed; they are readonly - email_dict.pop('email_to', None) - email_dict.pop('email_cc', None) - email_dict.pop('partner_to', None) - # update template values by wizard values - values.update(email_dict) - return values + template_values = dict.fromkeys(res_ids, dict()) + # generate composer values + composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context) - def render_template(self, cr, uid, template, model, res_id, context=None): - return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context) + for res_id in res_ids: + # remove attachments from template values as they should not be rendered + template_values[res_id].pop('attachment_ids', None) + # remove some keys from composer that are readonly + composer_values[res_id].pop('email_to', None) + composer_values[res_id].pop('email_cc', None) + composer_values[res_id].pop('partner_to', None) + # update template values by composer values + template_values[res_id].update(composer_values[res_id]) + return template_values + + def render_template_batch(self, cr, uid, template, model, res_ids, context=None): + return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context) + + # Compatibility methods + def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None): + return self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context)[res_id] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/email_template/wizard/mail_compose_message_view.xml b/addons/email_template/wizard/mail_compose_message_view.xml index 3cb83896ca2..b7c57af2679 100644 --- a/addons/email_template/wizard/mail_compose_message_view.xml +++ b/addons/email_template/wizard/mail_compose_message_view.xml @@ -11,7 +11,7 @@