diff --git a/addons/account/account.py b/addons/account/account.py index e11479e4dc0..7ec3bcde2c8 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -111,7 +111,7 @@ class account_payment_term_line(osv.osv): 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \ "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."), 'days2': fields.integer('Day of the Month', required=True, help="Day of the month, set -1 for the last day of the current month. If it's positive, it gives the day of the next month. Set 0 for net days (otherwise it's based on the beginning of the month)."), - 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True), + 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True, ondelete='cascade'), } _defaults = { 'value': 'balance', @@ -1375,7 +1375,7 @@ class account_move(osv.osv): balance = 0.0 for line in line_ids: if line[2]: - balance += (line[2]['debit'] or 0.00)- (line[2]['credit'] or 0.00) + balance += (line[2].get('debit',0.00)- (line[2].get('credit',0.00))) return {'value': {'balance': balance}} def write(self, cr, uid, ids, vals, context=None): @@ -2999,6 +2999,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): _columns = { 'company_id':fields.many2one('res.company', 'Company', required=True), + 'currency_id': fields.many2one('res.currency', 'Currency', help="Currency as per company's country."), 'only_one_chart_template': fields.boolean('Only One Chart Template Available'), 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True), 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True), @@ -3009,6 +3010,13 @@ class wizard_multi_charts_accounts(osv.osv_memory): 'purchase_tax_rate': fields.float('Purchase Tax(%)'), 'complete_tax_set': fields.boolean('Complete Set of Taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sales and purchase rates or use the usual m2o fields. This last choice assumes that the set of tax defined for the chosen template is complete'), } + + def onchange_company_id(self, cr, uid, ids, company_id, context=None): + currency_id = False + if company_id: + currency_id = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.id + return {'value': {'currency_id': currency_id}} + def onchange_tax_rate(self, cr, uid, ids, rate=False, context=None): return {'value': {'purchase_tax_rate': rate or False}} @@ -3039,6 +3047,13 @@ class wizard_multi_charts_accounts(osv.osv_memory): res.update({'bank_accounts_id': [{'acc_name': _('Cash'), 'account_type': 'cash'},{'acc_name': _('Bank'), 'account_type': 'bank'}]}) if 'company_id' in fields: res.update({'company_id': self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id}) + if 'currency_id' in fields: + company_id = res.get('company_id') or False + if company_id: + company_obj = self.pool.get('res.company') + country_id = company_obj.browse(cr, uid, company_id, context=context).country_id.id + currency_id = company_obj.on_change_country(cr, uid, company_id, country_id, context=context)['value']['currency_id'] + res.update({'currency_id': currency_id}) ids = self.pool.get('account.chart.template').search(cr, uid, [('visible', '=', True)], context=context) if ids: @@ -3343,6 +3358,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): ir_values_obj = self.pool.get('ir.values') obj_wizard = self.browse(cr, uid, ids[0]) company_id = obj_wizard.company_id.id + self.pool.get('res.company').write(cr, uid, [company_id], {'currency_id': obj_wizard.currency_id.id}) # If the floats for sale/purchase rates have been filled, create templates from them self._create_tax_templates_from_rates(cr, uid, obj_wizard, company_id, context=context) diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 8df291a98d1..095d461ff3b 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -430,7 +430,7 @@ class account_bank_statement(osv.osv): 'name': st_number, 'balance_end_real': st.balance_end }, context=context) - self.message_append_note(cr, uid, [st.id], body=_('Statement %s is confirmed, journal items are created.') % (st_number,), context=context) + self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context) return self.write(cr, uid, ids, {'state':'confirm'}, context=context) def button_cancel(self, cr, uid, ids, context=None): diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 78ef090ebbe..15027b7a8f6 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -1045,7 +1045,7 @@ class account_invoice(osv.osv): if obj_inv.type in ('out_invoice', 'out_refund'): ctx = self.get_log_context(cr, uid, context=ctx) message = _("Invoice '%s' is validated.") % name - self.message_append_note(cr, uid, [inv_id], body=message, context=context) + self.message_post(cr, uid, [inv_id], body=message, context=context) return True def action_cancel(self, cr, uid, ids, *args): @@ -1275,7 +1275,7 @@ class account_invoice(osv.osv): # TODO: use currency's formatting function msg = _("Invoice '%s' is paid partially: %s%s of %s%s (%s%s remaining).") % \ (name, pay_amount, code, invoice.amount_total, code, total, code) - self.message_append_note(cr, uid, [inv_id], body=msg, context=context) + self.message_post(cr, uid, [inv_id], body=msg, context=context) self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context) # Update the stored value (fields.function), so we write to trigger recompute @@ -1288,24 +1288,25 @@ class account_invoice(osv.osv): def _get_document_type(self, type): type_dict = { - 'out_invoice': 'Customer invoice', - 'in_invoice': 'Supplier invoice', - 'out_refund': 'Customer Refund', - 'in_refund': 'Supplier Refund', + # Translation markers will have no effect at runtime, only used to properly flag export + 'out_invoice': _('Customer invoice'), + 'in_invoice': _('Supplier invoice'), + 'out_refund': _('Customer Refund'), + 'in_refund': _('Supplier Refund'), } return type_dict.get(type, 'Invoice') def create_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): - self.message_append_note(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))), 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_append_note(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))), 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_append_note(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))), context=context) account_invoice() diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 81e683e1cfd..e3c5c211990 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -2371,8 +2371,10 @@
+
+ diff --git a/addons/account_followup/wizard/account_followup_print.py b/addons/account_followup/wizard/account_followup_print.py index d151d2171a4..1e84681b1ea 100644 --- a/addons/account_followup/wizard/account_followup_print.py +++ b/addons/account_followup/wizard/account_followup_print.py @@ -213,8 +213,6 @@ class account_followup_print_all(osv.osv_memory): mod_obj = self.pool.get('ir.model.data') move_obj = self.pool.get('account.move.line') user_obj = self.pool.get('res.users') - line_obj = self.pool.get('account_followup.stat') - mail_message = self.pool.get('mail.message') if context is None: context = {} @@ -235,13 +233,7 @@ class account_followup_print_all(osv.osv_memory): total_amt += line.debit - line.credit dest = False if partner: - if partner.type=='contact': - if adr.email: - dest = [partner.email] - if (not dest) and partner.type=='default': - if partner.email: - dest = [partner.email] - src = tools.config.options['email_from'] + dest = [partner.email] if not data.partner_lang: body = data.email_body else: @@ -281,7 +273,12 @@ class account_followup_print_all(osv.osv_memory): msg = '' if dest: try: - mail_message.schedule_with_attach(cr, uid, src, dest, sub, body, context=context) + vals = {'state': 'outgoing', + 'subject': sub, + 'body_html': '
%s
' % body, + 'email_to': dest, + 'email_from': data_user.email or tools.config.options['email_from']} + self.pool.get('mail.mail').create(cr, uid, vals, context=context) msg_sent += partner.name + '\n' except Exception, e: raise osv.except_osv('Error !', e ) diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index 223705af85e..f75cda8cdde 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_append_note(cr, uid, [obj.id], body=message, context=context) + self.message_post(cr, uid, [obj.id], body=message, 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_append_note(cr, uid, [obj.id], body=message, context=context) + self.message_post(cr, uid, [obj.id], body=message, 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_append_note(cr, uid, [obj.id], body=message, context=context) + self.message_post(cr, uid, [obj.id], body=message, context=context) account_voucher() diff --git a/addons/account_voucher/account_voucher_data.xml b/addons/account_voucher/account_voucher_data.xml index 8be2851a434..656d2c2cd91 100644 --- a/addons/account_voucher/account_voucher_data.xml +++ b/addons/account_voucher/account_voucher_data.xml @@ -2,15 +2,16 @@ - - - - Module eInvoicing & Payments has been installed. - 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. + + 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. -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. +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. -If you want to use advanced accounting features, you should install the "Accounting and Finance" module. - +For advanced accounting features, you should install the "Accounting and Finance" module. + diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index 7eeb9266e47..d90e2006a71 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_append_note(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), context=context) account_analytic_account() diff --git a/addons/auth_reset_password/auth_reset_password.xml b/addons/auth_reset_password/auth_reset_password.xml index 26ea8878c45..b7d7e0baef6 100644 --- a/addons/auth_reset_password/auth_reset_password.xml +++ b/addons/auth_reset_password/auth_reset_password.xml @@ -9,16 +9,13 @@ ]]> Password reset - A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}

-You may change your password following this link: +

You may change your password following this link, +or by copy-pasting the following URL in your browser: ${object._auth_reset_password_link()}

-${object._auth_reset_password_link()} - -If you don't have asked for password reset, you can safely ignore this email. - -]]>
+

Note: If you did not ask for a password reset, you can safely ignore this email.

]]> diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index b6eacda63c0..7e9bbacab13 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -302,33 +302,27 @@ the rule to mark CC(mail to any other person defined in actions)."), return self.format_body(body % data) def email_send(self, cr, uid, obj, emails, body, emailfrom=None, context=None): - """ send email - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param email: pass the emails - @param emailfrom: Pass name the email From else False - @param context: A standard dictionary for contextual values """ - if not emailfrom: - emailfrom = tools.config.get('email_from', False) - - if context is None: - context = {} - - mail_message = self.pool.get('mail.message') + emailfrom = tools.config.get('email_from') body = self.format_mail(obj, body) - if not emailfrom: - if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.email: - emailfrom = obj.user_id.email - - name = '[%d] %s' % (obj.id, tools.ustr(obj.name)) + if not emailfrom and hasattr(obj, 'user_id') and obj.user_id and obj.user_id.email: + emailfrom = obj.user_id.email emailfrom = tools.ustr(emailfrom) reply_to = emailfrom if not emailfrom: raise osv.except_osv(_('Error!'), - _("No email ID found for your company address.")) - return mail_message.schedule_with_attach(cr, uid, emailfrom, emails, name, body, model='base.action.rule', reply_to=reply_to, res_id=obj.id) + _("Missing default email address or missing email on responsible user")) + return self.pool.get('mail.mail').create(cr, uid, + { 'email_from': emailfrom, + 'email_to': emails.join(','), + 'reply_to': reply_to, + 'state': 'outgoing', + 'subject': '[%d] %s' % (obj.id, tools.ustr(obj.name)), + 'body_html': '
%s
' % body, + 'res_id': obj.id, + 'model': obj._table_name, + 'auto_delete': True + }, context=context) def do_check(self, cr, uid, action, obj, context=None): @@ -438,11 +432,8 @@ the rule to mark CC(mail to any other person defined in actions)."), if len(emails) and action.act_mail_body: emails = list(set(emails)) email_from = safe_eval(action.act_email_from, {}, locals_for_emails) - - def to_email(text): - return re.findall(r'([^ ,<@]+@[^> ,]+)', text or '') - emails = to_email(','.join(filter(None, emails))) - email_froms = to_email(email_from) + emails = tools.email_split(','.join(filter(None, emails))) + email_froms = tools.email_split(email_from) if email_froms: self.email_send(cr, uid, obj, emails, action.act_mail_body, emailfrom=email_froms[0]) return True diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index be3244f8a5b..a6d5f6859af 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -471,18 +471,10 @@ property or property parameter."), def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None): """ Send mail for event invitation to event attendees. - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of attendee’s IDs. @param email_from: Email address for user sending the mail - @param context: A standard dictionary for contextual values @return: True """ - if context is None: - context = {} - company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name - mail_message = self.pool.get('mail.message') for att in self.browse(cr, uid, ids, context=context): sign = att.sent_by_uid and att.sent_by_uid.signature or '' sign = '
'.join(sign and sign.split('\n') or []) @@ -508,17 +500,18 @@ property or property parameter."), } body = html_invitation % body_vals if mail_to and email_from: - attach = self.get_ics_file(cr, uid, res_obj, context=context) - mail_message.schedule_with_attach(cr, uid, - email_from, - mail_to, - sub, - body, - attachments=attach and {'invitation.ics': attach} or None, - content_subtype='html', - reply_to=email_from, - context=context - ) + ics_file = self.get_ics_file(cr, uid, res_obj, context=context) + vals = {'email_from': email_from, + 'email_to': mail_to, + 'state': 'outgoing', + 'subject': sub, + 'body_html': body, + 'auto_delete': True} + if ics_file: + vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics', + 'datas_fname': 'invitation.ics', + 'datas': str(ics_file).encode('base64')})] + self.pool.get('mail.mail').create(cr, uid, vals, context=context) return True def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv): @@ -812,7 +805,6 @@ class calendar_alarm(osv.osv): """ if context is None: context = {} - mail_message = self.pool.get('mail.message') current_datetime = datetime.now() alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context) @@ -849,36 +841,10 @@ class calendar_alarm(osv.osv): else: re_dates = [alarm.trigger_date] - for r_date in re_dates: - ref = alarm.model_id.model + ',' + str(alarm.res_id) - - # search for alreay sent requests - #if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context): - #continue - - # Deactivated because of the removing of res.request - # TODO: when cleaning calendar module, re-add this in a new mechanism - #if alarm.action == 'display': - #value = { - #'name': alarm.name, - #'act_from': alarm.user_id.id, - #'act_to': alarm.user_id.id, - #'body': alarm.description, - #'trigger_date': r_date, - #'ref_doc1': ref - #} - #request_id = request_obj.create(cr, uid, value) - #request_ids = [request_id] - #for attendee in res_obj.attendee_ids: - #if attendee.user_id: - #value['act_to'] = attendee.user_id.id - #request_id = request_obj.create(cr, uid, value) - #request_ids.append(request_id) - #request_obj.request_send(cr, uid, request_ids) - + if re_dates: if alarm.action == 'email': - sub = '[Openobject Reminder] %s' % (alarm.name) - body = """ + sub = '[OpenERP Reminder] %s' % (alarm.name) + body = """
 Event: %s
 Event Date: %s
 Description: %s
@@ -888,20 +854,21 @@ From:
 
 ----
 %s
-
+
""" % (alarm.name, alarm.trigger_date, alarm.description, \ alarm.user_id.name, alarm.user_id.signature) mail_to = [alarm.user_id.email] for att in alarm.attendee_ids: mail_to.append(att.user_id.email) if mail_to: - mail_message.schedule_with_attach(cr, uid, - tools.config.get('email_from', False), - mail_to, - sub, - body, - context=context - ) + vals = { + 'state': 'outgoing', + 'subject': sub, + 'body_html': body, + 'email_to': mail_to, + 'email_from': tools.config.get('email_from', mail_to), + } + self.pool.get('mail.mail').create(cr, uid, vals, context=context) if next_trigger_date: update_vals.update({'trigger_date': next_trigger_date}) else: @@ -1616,36 +1583,6 @@ class calendar_todo(osv.osv): calendar_todo() -class ir_attachment(osv.osv): - _name = 'ir.attachment' - _inherit = 'ir.attachment' - - def search_count(self, cr, user, args, context=None): - new_args = [] - for domain_item in args: - if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id': - new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2]))) - else: - new_args.append(domain_item) - return super(ir_attachment, self).search_count(cr, user, new_args, context) - - def create(self, cr, uid, vals, context=None): - if context: - id = context.get('default_res_id', False) - context.update({'default_res_id' : base_calendar_id2real_id(id)}) - return super(ir_attachment, self).create(cr, uid, vals, context=context) - - def search(self, cr, uid, args, offset=0, limit=None, order=None, - context=None, count=False): - new_args = [] - for domain_item in args: - if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id': - new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2]))) - else: - new_args.append(domain_item) - return super(ir_attachment, self).search(cr, uid, new_args, offset=offset, - limit=limit, order=order, context=context, count=False) -ir_attachment() class ir_values(osv.osv): _inherit = 'ir.values' diff --git a/addons/base_calendar/crm_meeting.py b/addons/base_calendar/crm_meeting.py index cb96ff2a4d0..e1c66148ae7 100644 --- a/addons/base_calendar/crm_meeting.py +++ b/addons/base_calendar/crm_meeting.py @@ -43,7 +43,7 @@ class crm_meeting(base_state, osv.Model): _name = 'crm.meeting' _description = "Meeting" _order = "id desc" - _inherit = ["calendar.event", 'ir.needaction_mixin', "mail.thread"] + _inherit = ["calendar.event", "mail.thread", 'ir.needaction_mixin'] _columns = { # base_state required fields 'create_date': fields.datetime('Creation Date', readonly=True), @@ -70,13 +70,17 @@ class crm_meeting(base_state, osv.Model): # OpenChatter # ---------------------------------------- + # shows events of the day for this user + def needaction_domain_get(self, cr, uid, domain=[], context={}): + return [('date','<=',time.strftime('%Y-%M-%D 23:59:59')), ('date_deadline','>=', time.strftime('%Y-%M-%D 00:00:00')), ('user_id','=',uid)] + def case_get_note_msg_prefix(self, cr, uid, id, context=None): return 'Meeting' def case_open_send_note(self, cr, uid, ids, context=None): - return self.message_append_note(cr, uid, ids, body=_("Meeting has been confirmed."), context=context) + return self.message_post(cr, uid, ids, body=_("Meeting confirmed."), context=context) def case_close_send_note(self, cr, uid, ids, context=None): - return self.message_append_note(cr, uid, ids, body=_("Meeting has been done."), context=context) + return self.message_post(cr, uid, ids, body=_("Meeting completed."), context=context) diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index 6009251a460..928f8d94d5c 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -170,7 +170,8 @@ - - +
    + +
@@ -56,22 +47,11 @@ Template used to display the communication history in documents form view. --> -
+
-
-
- - - -
-
-

Followers

-
    -
    -
  • - +
    @@ -147,7 +127,7 @@
    ', message.body, 'mail.message body is incorrect') + # Test: mail.message: attachments + for attach in message.attachment_ids: + self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect') + self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect') + self.assertIn((attach.name, attach.datas.decode('base64')), _attachments_test, + 'mail.message attachment name / data incorrect') + + # ---------------------------------------- + # CASE3: mass_mail on Pigs and Bird + # ---------------------------------------- + + # 1. mass_mail on pigs and bird + compose_id = mail_compose.create(cr, uid, + {'subject': _subject, 'body': '${object.description}'}, + {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': -1, + 'active_ids': [self.group_pigs_id, group_bird_id]}) + compose = mail_compose.browse(cr, uid, compose_id) + # Test: content_subtype is html + self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect') + + # 2. Post the comment, get created message for each group + mail_compose.send_mail(cr, uid, [compose_id], + context={'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]}) + group_pigs.refresh() + group_bird.refresh() + message1 = group_pigs.message_ids[0] + message2 = group_bird.message_ids[0] + # Test: Pigs and Bird did receive their message + test_msg_ids = self.mail_message.search(cr, uid, [], limit=2) + self.assertIn(message1.id, test_msg_ids, 'Pigs did not receive its mass mailing message') + self.assertIn(message2.id, test_msg_ids, 'Bird did not receive its mass mailing message') + # Test: mail.message: subject, body + self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect') + self.assertEqual(message1.body, group_pigs.description, 'mail.message body incorrect') + self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect') + self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect') + + def test_30_message_read(self): + """ Tests designed for message_read. """ + # TDE NOTE: this test is not finished, as the message_read method is not fully specified. + # It will be updated as soon as we have fixed specs ! + cr, uid = self.cr, self.uid + group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) + def _compare_structures(struct1, struct2, n=0): + # print '%scompare structure' % ('\t' * n) + self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect') + for x in range(len(struct1)): + # print '%s' % ('\t' * n), struct1[x]['id'], struct2[x]['id'], struct1[x].get('subject') or '' + self.assertEqual(struct1[x]['id'], struct2[x]['id'], 'message_read failure %s' % struct1[x].get('subject')) + _compare_structures(struct1[x]['child_ids'], struct2[x]['child_ids'], n + 1) + # print '%send compare' % ('\t' * n) + + # ---------------------------------------- + # CASE1: Flattening test + # ---------------------------------------- + + # Create dummy message structure + import copy + tree = [{'id': 2, 'child_ids': [ + {'id': 6, 'child_ids': [ + {'id': 8, 'child_ids': []}, + ]}, + ]}, + {'id': 1, 'child_ids':[ + {'id': 7, 'child_ids': [ + {'id': 9, 'child_ids': []}, + ]}, + {'id': 4, 'child_ids': [ + {'id': 10, 'child_ids': []}, + {'id': 5, 'child_ids': []}, + ]}, + {'id': 3, 'child_ids': []}, + ]}, + ] + # Test: completely flat + new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 0) + self.assertEqual(len(new_tree), 10, 'message_read_tree_flatten wrong in flat') + # Test: 1 thread level + tree_test = [{'id': 2, 'child_ids': [ + {'id': 8, 'child_ids': []}, {'id': 6, 'child_ids': []}, + ]}, + {'id': 1, 'child_ids': [ + {'id': 10, 'child_ids': []}, {'id': 9, 'child_ids': []}, + {'id': 7, 'child_ids': []}, {'id': 5, 'child_ids': []}, + {'id': 4, 'child_ids': []}, {'id': 3, 'child_ids': []}, + ]}, + ] + new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 1) + _compare_structures(new_tree, tree_test) + # Test: 2 thread levels + new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 2) + _compare_structures(new_tree, tree) + + # ---------------------------------------- + # CASE2: message_read test + # ---------------------------------------- + + # 1. Add a few messages to pigs group + msgid1 = group_pigs.message_post(body='1', subject='1', parent_id=False) + msgid2 = group_pigs.message_post(body='2', subject='1-1', parent_id=msgid1) + msgid3 = group_pigs.message_post(body='3', subject='1-2', parent_id=msgid1) + msgid4 = group_pigs.message_post(body='4', subject='2', parent_id=False) + msgid5 = group_pigs.message_post(body='5', subject='1-1-1', parent_id=msgid2) + msgid6 = group_pigs.message_post(body='6', subject='2-1', parent_id=msgid4) + + # Test: read all messages flat + tree_test = [{'id': msgid6, 'child_ids': []}, {'id': msgid5, 'child_ids': []}, + {'id': msgid4, 'child_ids': []}, {'id': msgid3, 'child_ids': []}, + {'id': msgid2, 'child_ids': []}, {'id': msgid1, 'child_ids': []}] + tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=0, limit=10) + _compare_structures(tree, tree_test) + # Test: read with 1 level of thread + tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]}, + {'id': msgid1, 'child_ids': [ + {'id': msgid5, 'child_ids': []}, {'id': msgid3, 'child_ids': []}, + {'id': msgid2, 'child_ids': []}, + ]}, + ] + tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1, limit=10) + _compare_structures(tree, tree_test) + # Test: read with 2 levels of thread + tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]}, + {'id': msgid1, 'child_ids': [ + {'id': msgid3, 'child_ids': []}, + {'id': msgid2, 'child_ids': [{'id': msgid5, 'child_ids': []}, ]}, + ]}, + ] + tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2, limit=10) + _compare_structures(tree, tree_test) + + # 2. Test expandables + # TDE FIXME: add those tests when expandables are specified and implemented + + def test_40_needaction(self): + """ Tests for mail.message needaction. """ + cr, uid = self.cr, self.uid + group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) + user_admin = self.res_users.browse(cr, uid, uid) + + # Demo values: check unread notification = needaction on mail.message + notif_ids = self.mail_notification.search(cr, uid, [ + ('partner_id', '=', user_admin.partner_id.id), + ('read', '=', False) + ]) + na_count = self.mail_message._needaction_count(cr, uid, domain=[]) + self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count') + + # Post 4 message on group_pigs + for dummy in range(4): + group_pigs.message_post(body='My Body') + + # Check there are 4 new needaction on mail.message + notif_ids = self.mail_notification.search(cr, uid, [ + ('partner_id', '=', user_admin.partner_id.id), + ('read', '=', False) + ]) + na_count = self.mail_message._needaction_count(cr, uid, domain=[]) + self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count') + + # Check there are 4 needaction on mail.message with particular domain + na_count = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]) + self.assertEqual(na_count, 4, 'posted message count does not match needaction count') + + def test_50_thread_parent_resolution(self): + """Verify parent/child relationships are correctly established when processing incoming mails""" + cr, uid = self.cr, self.uid + group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) + msg1 = group_pigs.message_post(body='My Body', subject='1') + msg2 = group_pigs.message_post(body='My Body', subject='2') + msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2]) + self.assertTrue(msg1.message_id, "New message should have a proper message_id") + + # Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms + # 1. In-Reply-To header + reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs , other@gmail.com', subject='Re: 1', + extra='In-Reply-To: %s' % msg1.message_id) + self.mail_thread.message_process(cr, uid, None, reply_msg) + # 2. References header + reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs , other@gmail.com', subject='Re: Re: 1', + extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id) + self.mail_thread.message_process(cr, uid, None, reply_msg2) + # 3. Subject contains [] + model passed to message+process -> only attached to group, not to mail + reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs , other@gmail.com', + extra='', subject='Re: [%s] 1' % self.group_pigs_id) + self.mail_thread.message_process(cr, uid, 'mail.group', reply_msg3) + group_pigs.refresh() + msg1.refresh() + self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages') + self.assertEqual(2, len(msg1.child_ids), 'msg1 should have 2 children now') diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index fe87b7584e0..3153ccc2069 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -19,417 +19,289 @@ # ############################################################################## -import ast +import base64 import re - import tools + from osv import osv from osv import fields from tools.safe_eval import safe_eval as eval from tools.translate import _ -from ..mail_message import to_email - # main mako-like expression pattern EXPRESSION_PATTERN = re.compile('(\$\{.+?\})') class mail_compose_message(osv.TransientModel): - """Generic Email composition wizard. This wizard is meant to be inherited - at model and view level to provide specific wizard features. + """ Generic message composition wizard. You may inherit from this wizard + at model and view levels to provide specific features. - The behavior of the wizard can be modified through the use of context - parameters, among which are: - - * mail.compose.message.mode: if set to 'reply', the wizard is in - reply to a previous message mode and pre-populated with the original - quote. If set to 'comment', it means you are writing a new message to - be attached to a document. If set to 'mass_mail', the wizard is in - mass mailing where the mail details can contain template placeholders - that will be merged with actual data before being sent to each - recipient. - * active_model: model name of the document to which the mail being - composed is related - * active_id: id of the document to which the mail being composed is - related, or id of the message to which user is replying, - in case ``mail.compose.message.mode == 'reply'`` - * active_ids: ids of the documents to which the mail being composed is - related, in case ``mail.compose.message.mode == 'mass_mail'``. + The behavior of the wizard depends on the composition_mode field: + - 'reply': reply to a previous message. The wizard is pre-populated + via ``get_message_data``. + - 'comment': new post on a record. The wizard is pre-populated via + ``get_record_data`` + - 'mass_mail': wizard in mass mailing mode where the mail details can + contain template placeholders that will be merged with actual data + before being sent to each recipient. """ _name = 'mail.compose.message' - _inherit = 'mail.message.common' + _inherit = 'mail.message' _description = 'Email composition wizard' + _log_access = True def default_get(self, cr, uid, fields, context=None): - """ Overridden to provide specific defaults depending on the context - parameters. - - Composition mode - - comment: default mode; active_model, active_id = model and ID of a - document we are commenting, - - reply: active_id = ID of a mail.message to which we are replying. - From this message we can find the related model and res_id, - - mass_mailing mode: active_model, active_id = model and ID of a - document we are commenting, - - :param dict context: several context values will modify the behavior - of the wizard, cfr. the class description. + """ Handle composition mode. Some details about context keys: + - comment: default mode, model and ID of a record the user comments + - default_model or active_model + - default_res_id or active_id + - reply: active_id of a message the user replies to + - default_parent_id or message_id or active_id: ID of the + mail.message we reply to + - message.res_model or default_model + - message.res_id or default_res_id + - mass_mail: model and IDs of records the user mass-mails + - active_ids: record IDs + - default_model or active_model """ if context is None: context = {} - compose_mode = context.get('mail.compose.message.mode', 'comment') - active_model = context.get('active_model') - active_id = context.get('active_id') result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context) + # get some important values from context + composition_mode = context.get('default_composition_mode', context.get('mail.compose.message.mode')) + model = context.get('default_model', context.get('active_model')) + res_id = context.get('default_res_id', context.get('active_id')) + message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id'))) + active_ids = context.get('active_ids') + # get default values according to the composition mode - vals = {} - if compose_mode in ['reply']: - vals = self.get_message_data(cr, uid, int(context['active_id']), context=context) - elif compose_mode in ['comment', 'mass_mail'] and active_model and active_id: - vals = self.get_value(cr, uid, active_model, active_id, context) + if composition_mode == 'reply': + vals = self.get_message_data(cr, uid, message_id, context=context) + elif composition_mode == 'comment' and model and res_id: + vals = self.get_record_data(cr, uid, model, res_id, context=context) + elif composition_mode == 'mass_mail' and model and active_ids: + vals = {'model': model, 'res_id': res_id, 'content_subtype': 'html'} + else: + vals = {'model': model, 'res_id': res_id} + if composition_mode: + vals['composition_mode'] = composition_mode + for field in vals: if field in fields: result[field] = vals[field] - - # link to model and record if not done yet - if not result.get('model') and active_model: - result['model'] = active_model - if not result.get('res_id') and active_id: - result['res_id'] = active_id - - # Try to provide default email_from if not specified yet - if not result.get('email_from'): - current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - result['email_from'] = current_user.email or False return result + def _get_composition_mode_selection(self, cr, uid, context=None): + return [('comment', 'Comment a document'), ('reply', 'Reply to a message'), ('mass_mail', 'Mass mailing')] + _columns = { - 'dest_partner_ids': fields.many2many('res.partner', - 'email_message_send_partner_rel', - 'wizard_id', 'partner_id', 'Destination partners', - help="When sending emails through the social network composition wizard"\ - "you may choose to send a copy of the mail to partners."), - 'attachment_ids': fields.many2many('ir.attachment','email_message_send_attachment_rel', 'wizard_id', 'attachment_id', 'Attachments'), - 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete emails after sending"), + 'composition_mode': fields.selection( + lambda s, *a, **k: s._get_composition_mode_selection(*a, **k), + string='Composition mode'), + 'partner_ids': fields.many2many('res.partner', + 'mail_compose_message_res_partner_rel', + 'wizard_id', 'partner_id', 'Additional contacts'), + 'attachment_ids': fields.many2many('ir.attachment', + 'mail_compose_message_ir_attachments_rel', + 'wizard_id', 'attachment_id', 'Attachments'), 'filter_id': fields.many2one('ir.filters', 'Filters'), + 'body_text': fields.text('Plain-text Contents'), + 'content_subtype': fields.char('Message content subtype', size=32, readonly=1, + help="Type of message, usually 'html' or 'plain', used to select "\ + "plain-text or rich-text contents accordingly"), } - def get_value(self, cr, uid, model, res_id, context=None): + _defaults = { + 'composition_mode': 'comment', + 'content_subtype': lambda self, cr, uid, ctx={}: 'plain', + 'body_text': lambda self, cr, uid, ctx={}: False, + 'body': lambda self, cr, uid, ctx={}: '', + 'subject': lambda self, cr, uid, ctx={}: False, + } + + def notify(self, cr, uid, newid, context=None): + """ Override specific notify method of mail.message, because we do + not want that feature in the wizard. """ + return + + def get_record_data(self, cr, uid, model, res_id, context=None): """ Returns a defaults-like dict with initial values for the composition wizard when sending an email related to the document record identified by ``model`` and ``res_id``. - The default implementation returns an empty dictionary, and is meant - to be overridden by subclasses. - :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 dict context: several context values will modify the behavior - of the wizard, cfr. the class description. + :param int res_id: id of the document record this mail is related to """ - result = {} - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - result.update({ - 'model': model, - 'res_id': res_id, - 'email_from': user.email or tools.config.get('email_from', False), - 'body_html': False, - 'body_text': False, - 'subject': False, - 'dest_partner_ids': [], - }) - return result - - def onchange_email_mode(self, cr, uid, ids, value, model, res_id, context=None): - """ email_mode (values: True or False). This onchange on the email mode - allows to have some specific behavior when going in email mode, or - when going out of email mode. - Basically, dest_partner_ids is reset when going out of email - mode. - This method can be overridden for models that want to have their - specific behavior. - Note that currently, this onchange is used in mail.js and called - manually on the form instantiated in the Chatter. - """ - if not value: - return {'value': {'dest_partner_ids': []}} - return {'value': {}} - - def onchange_formatting(self, cr, uid, ids, value, model, res_id, context=None): - """ onchange_formatting (values: True or False). This onchange on the - formatting allows to have some specific behavior when going in - formatting mode, or when going out of formatting. - Basically, subject is reset when going out of formatting mode. - This method can be overridden for models that want to have their - specific behavior. - Note that currently, this onchange is used in mail.js and called - manually on the form instantiated in the Chatter. - """ - if not value: - return {'value': {'subject': False}} - return {'value': {}} + return {'model': model, 'res_id': res_id} def get_message_data(self, cr, uid, message_id, context=None): """ Returns a defaults-like dict with initial values for the composition wizard when replying to the given message (e.g. including the quote - of the initial message, and the correct recipient). It should not be - called unless ``context['mail.compose.message.mode'] == 'reply'``. + of the initial message, and the correct recipients). :param int message_id: id of the mail.message to which the user is replying. - :param dict context: several context values will modify the behavior - of the wizard, cfr. the class description. """ + if not message_id: + return {} if context is None: context = {} - result = {} - if not message_id: - return result + message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context) - current_user = self.pool.get('res.users').browse(cr, uid, uid, context) - message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context) - # Form the subject - re_prefix = _("Re:") + # create subject + re_prefix = _('Re:') reply_subject = tools.ustr(message_data.subject or '') if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)): reply_subject = "%s %s" % (re_prefix, reply_subject) - # Form the bodies (text and html). We use the plain text version of the - # original mail, by default, as it is easier to quote than the HTML - # version. TODO: make it possible to switch to HTML on the fly - sent_date = _('On %(date)s, ') % {'date': message_data.date} if message_data.date else '' - sender = _('%(sender_name)s wrote:') % {'sender_name': tools.ustr(message_data.email_from or _('You'))} - body_text = message_data.body_text or '' - body_html = message_data.body_html or '' - quoted_body_text = '> %s' % tools.ustr(body_text.replace('\n', "\n> ") or '') - quoted_body_html = '
    %s
    ' % (tools.ustr(body_html)), - reply_body_text = '\n%s%s\n%s\n%s' % (sent_date, sender, quoted_body_text, current_user.signature) - reply_body_html = '

    %s%s
    %s
    %s' % (sent_date, sender, quoted_body_html, current_user.signature) - # form dest_partner_ids - dest_partner_ids = [partner.id for partner in message_data.partner_ids] - # Update header and references - reply_headers = {} - reply_references = message_data.references and tools.ustr(message_data.references) or False - reply_message_id = message_data.message_id or False - if reply_message_id: - reply_references = (reply_references or '') + " " + message_data.message_id - reply_headers['In-Reply-To'] = message_data.message_id + # create the reply in the body + reply_body = _('
    On %(date)s, %(sender_name)s wrote:
    %(body)s
    ') % { + 'date': message_data.date if message_data.date else '', + 'sender_name': message_data.author_id.name, + 'body': message_data.body, + } + # get partner_ids from original message + partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else [] + # update the result - result.update({ - 'body_text': reply_body_text, - 'body_html': reply_body_html, + result = { + 'model': message_data.model, + 'res_id': message_data.res_id, + 'parent_id': message_data.id, + 'body': reply_body, 'subject': reply_subject, - 'attachment_ids': [], - 'dest_partner_ids': dest_partner_ids, - 'model': message_data.model or False, - 'res_id': message_data.res_id or False, - 'email_from': current_user.email or message_data.email_to or False, - 'email_to': message_data.reply_to or message_data.email_from or False, - 'email_cc': message_data.email_cc or False, - 'user_id': uid, - # pass msg-id and references of mail we're replying to, to construct the - # new ones later when sending - 'message_id': reply_message_id, - 'references': reply_references, - 'headers': reply_headers, - }) + 'partner_ids': partner_ids, + 'content_subtype': 'html', + } return result - def send_mail(self, cr, uid, ids, context=None): - '''Process the wizard contents and proceed with sending the corresponding - email(s), rendering any template patterns on the fly if needed. - If the wizard is in mass-mail mode (context['mail.compose.message.mode'] is - set to ``'mass_mail'``), the resulting email(s) are scheduled for being - sent the next time the mail.message scheduler runs, or the next time - ``mail.message.process_email_queue`` is called. - Otherwise the new message is sent immediately. + def toggle_content_subtype(self, cr, uid, ids, context=None): + """ hit toggle formatting mode button: calls onchange_formatting to + emulate an on_change, then writes the value to update the form. """ + for record in self.browse(cr, uid, ids, context=context): + content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html' + onchange_res = self.onchange_content_subtype(cr, uid, ids, content_st_new_value, record.model, record.res_id, context=context) + self.write(cr, uid, [record.id], onchange_res['value'], context=context) + return True - :param dict context: several context values will modify the behavior - of the wizard, cfr. the class description. - ''' + def onchange_content_subtype(self, cr, uid, ids, value, model, res_id, context=None): + """ onchange_content_subtype (values: 'plain' or 'html'). This onchange + on the subtype allows to have some specific behavior when switching + between text or html mode. + This method can be overridden for models that want to have their + specific behavior. """ + return {'value': {'content_subtype': value}} + + def _verify_partner_email(self, cr, uid, partner_ids, context=None): + """ Verify that selected partner_ids have an email_address defined. + Otherwise throw a warning. """ + partner_wo_email_lst = [] + for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context): + if not partner.email: + partner_wo_email_lst.append(partner) + if not partner_wo_email_lst: + return {} + warning_msg = _('The following partners chosen as recipients for the email have no email address linked :') + for partner in partner_wo_email_lst: + warning_msg += '\n- %s' % (partner.name) + return {'warning': { + 'title': _('Partners email addresses not found'), + 'message': warning_msg, + } + } + + def onchange_partner_ids(self, cr, uid, ids, value, context=None): + """ onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The + basic purpose of this method is to check that destination partners + effectively have email addresses. Otherwise a warning is thrown. + """ + res = {'value': {}} + if not value or not value[0] or not value[0][0] == 6: + return + res.update(self._verify_partner_email(cr, uid, value[0][2], context=context)) + return res + + def unlink(self, cr, uid, ids, context=None): + # Cascade delete all attachments, as they are owned by the composition wizard + for wizard in self.read(cr, uid, ids, ['attachment_ids'], context=context): + self.pool.get('ir.attachment').unlink(cr, uid, wizard['attachment_ids'], context=context) + return super(mail_compose_message, self).unlink(cr, uid, ids, context=context) + + def dummy(self, cr, uid, ids, context=None): + """ TDE: defined to have buttons that do basically nothing. It is + currently impossible to have buttons that do nothing special + in views (if type not specified, considered as 'object'). """ + return True + + #------------------------------------------------------ + # Wizard validation and send + #------------------------------------------------------ + + def send_mail(self, cr, uid, ids, context=None): + """ Process the wizard content and proceed with sending the related + email(s), rendering any template patterns on the fly if needed. """ if context is None: context = {} - # composition wizard options - email_mode = context.get('email_mode') - formatting = context.get('formatting') - mass_mail_mode = context.get('mail.compose.message.mode') == 'mass_mail' + active_ids = context.get('active_ids') - mail_message_obj = self.pool.get('mail.message') - for mail_wiz in self.browse(cr, uid, ids, context=context): - # attachments - attachment = {} - for attach in mail_wiz.attachment_ids: - attachment[attach.datas_fname] = attach.datas and attach.datas.decode('base64') + for wizard in self.browse(cr, uid, ids, context=context): + mass_mail_mode = wizard.composition_mode == 'mass_mail' + active_model_pool = self.pool.get(wizard.model if wizard.model else 'mail.thread') - # default values, according to the wizard options - subject = mail_wiz.subject if formatting else False - content_subtype = 'html' if formatting else 'plain' - type = 'email' if email_mode else 'comment' - state = 'outgoing' if email_mode else False - partner_ids = [partner.id for partner in mail_wiz.dest_partner_ids] - references = None - headers = {} - body = mail_wiz.body_html if content_subtype == 'html' else mail_wiz.body_text - - # get model, active_ids, and check if model is openchatter-enabled - if mass_mail_mode and context.get('active_ids') and context.get('active_model'): - active_ids = context['active_ids'] - active_model = context['active_model'] - elif mass_mail_mode: - active_model = mail_wiz.model - active_model_pool = self.pool.get(active_model) - active_ids = active_model_pool.search(cr, uid, ast.literal_eval(mail_wiz.filter_id.domain), context=ast.literal_eval(mail_wiz.filter_id.context)) - else: - active_model = mail_wiz.model - active_ids = [mail_wiz.res_id] - active_model_pool = self.pool.get(active_model) - mail_thread_enabled = hasattr(active_model_pool, 'message_append') - - if context.get('mail.compose.message.mode') == 'mass_mail': - # Mass mailing: must render the template patterns - for active_id in active_ids: - rendered_subject = self.render_template(cr, uid, subject, active_model, active_id) - rendered_body_html = self.render_template(cr, uid, mail_wiz.body_html, active_model, active_id) - rendered_body_text = self.render_template(cr, uid, mail_wiz.body_text, active_model, active_id) - email_from = self.render_template(cr, uid, mail_wiz.email_from, active_model, active_id) - email_to = self.render_template(cr, uid, mail_wiz.email_to, active_model, active_id) - email_cc = self.render_template(cr, uid, mail_wiz.email_cc, active_model, active_id) - email_bcc = self.render_template(cr, uid, mail_wiz.email_bcc, active_model, active_id) - reply_to = self.render_template(cr, uid, mail_wiz.reply_to, active_model, active_id) - - # in mass-mailing mode we only schedule the mail for sending, it will be - # processed as soon as the mail scheduler runs. - if mail_thread_enabled: - active_model_pool.message_append(cr, uid, [active_id], rendered_subject, rendered_body_text, rendered_body_html, - type=type, content_subtype=content_subtype, state=state, partner_ids=partner_ids, - email_from=email_from, email_to=email_to, email_cc=email_cc, email_bcc=email_bcc, - reply_to=reply_to, references=references, attachments=attachment, headers=headers, context=context) - else: - mail_message_obj.schedule_with_attach(cr, uid, email_from, to_email(email_to), subject, rendered_body_text, - model=mail_wiz.model, email_cc=to_email(email_cc), email_bcc=to_email(email_bcc), reply_to=reply_to, - attachments=attachment, references=references, res_id=active_id, partner_ids=partner_ids, - content_subtype=mail_wiz.content_subtype, headers=headers, context=context) - else: - # normal mode - no mass-mailing - if mail_thread_enabled: - msg_ids = active_model_pool.message_append(cr, uid, active_ids, subject, mail_wiz.body_text, mail_wiz.body_html, - type=type, content_subtype=content_subtype, state=state, partner_ids=partner_ids, - email_from=mail_wiz.email_from, email_to=mail_wiz.email_to, email_cc=mail_wiz.email_cc, email_bcc=mail_wiz.email_bcc, - reply_to=mail_wiz.reply_to, references=references, attachments=attachment, headers=headers, context=context) - else: - msg_ids = [mail_message_obj.schedule_with_attach(cr, uid, mail_wiz.email_from, to_email(mail_wiz.email_to), subject, mail_wiz.body_text, - type=type, model=mail_wiz.model, email_cc=to_email(mail_wiz.email_cc), email_bcc=to_email(mail_wiz.email_bcc), reply_to=mail_wiz.reply_to, - attachments=attachment, references=references, res_id=int(mail_wiz.res_id), partner_ids=partner_ids, - content_subtype=mail_wiz.content_subtype, headers=headers, context=context)] - # in normal mode, we send the email immediately, as the user expects us to (delay should be sufficiently small) - if type == 'email': - mail_message_obj.send(cr, uid, msg_ids, context=context) + # wizard works in batch mode: [res_id] or active_ids + res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id] + for res_id in res_ids: + # default values, according to the wizard options + post_values = { + 'subject': wizard.subject if wizard.content_subtype == 'html' else False, + 'body': wizard.body if wizard.content_subtype == 'html' else '
    %s
    ' % tools.ustr(wizard.body_text), + 'partner_ids': [(4, partner.id) for partner in wizard.partner_ids], + 'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids], + } + # mass mailing: render and override default values + if mass_mail_mode and wizard.model: + email_dict = self.render_message(cr, uid, wizard, res_id, context=context) + new_partner_ids = email_dict.pop('partner_ids', []) + post_values['partner_ids'] += [(4, partner_id) for partner_id in new_partner_ids] + new_attachments = email_dict.pop('attachments', []) + post_values['attachments'] += new_attachments + post_values.update(email_dict) + # post the message + active_model_pool.message_post(cr, uid, [res_id], type='comment', context=context, **post_values) return {'type': 'ir.actions.act_window_close'} + def render_message(self, cr, uid, wizard, res_id, context=None): + """ Generate an email from the template for given (wizard.model, res_id) + pair. This method is meant to be inherited by email_template that + will produce a more complete dictionary. """ + return { + 'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context), + 'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context), + } + def render_template(self, cr, uid, template, model, res_id, context=None): - """Render the given template text, replace mako-like expressions ``${expr}`` - with the result of evaluating these expressions with an evaluation context - containing: + """ Render the given template text, replace mako-like expressions ``${expr}`` + with the result of evaluating these expressions with an evaluation context + containing: * ``user``: browse_record of the current user * ``object``: browse_record of the document record this mail is related to * ``context``: the context passed to the mail composition wizard - :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 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. """ if context is None: context = {} def merge(match): exp = str(match.group()[2:-1]).strip() - result = eval(exp, - { - 'user' : self.pool.get('res.users').browse(cr, uid, uid, context=context), - 'object' : self.pool.get(model).browse(cr, uid, res_id, context=context), - 'context': dict(context), # copy context to prevent side-effects of eval - }) - if result in (None, False): - return "" - return tools.ustr(result) + result = eval(exp, { + 'user': self.pool.get('res.users').browse(cr, uid, uid, context=context), + 'object': self.pool.get(model).browse(cr, uid, res_id, context=context), + 'context': dict(context), # copy context to prevent side-effects of eval + }) + return result and tools.ustr(result) or '' return template and EXPRESSION_PATTERN.sub(merge, template) - - -class mail_compose_message_extended(osv.TransientModel): - """ Extension of 'mail.compose.message' to support default field values related - to CRM-like models that follow the following conventions: - - 1. The model object must have an attribute '_mail_compose_message' equal to True. - - 2. The model should define the following fields: - - 'name' as subject of the message (required); - - 'email_from' as destination email address (required); - - 'email_cc' as cc email addresses (required); - - 'section_id.reply_to' as reply-to address (optional). - """ - _inherit = 'mail.compose.message' - - def get_value(self, cr, uid, model, res_id, context=None): - """ Overrides the default implementation to provide more default field values - related to the corresponding CRM case. - """ - result = super(mail_compose_message_extended, self).get_value(cr, uid, model, res_id, context=context) - model_obj = self.pool.get(model) - if getattr(model_obj, '_mail_compose_message', False) and res_id: - data = model_obj.browse(cr, uid , res_id, context) - result.update({ - 'email_to': data.email_from or False, - 'email_cc': tools.ustr(data.email_cc or ''), - 'subject': data.name or False, - }) - if hasattr(data, 'section_id'): - result['reply_to'] = data.section_id and data.section_id.reply_to or False - return result - - def onchange_email_mode(self, cr, uid, ids, value, model, res_id, context=None): - """ Overrides the default implementation to provide default values for - dest_partner_ids. This method checks that a partner maching the - ``email_from`` of the record exists. It it does not exist, it - creates a new partner. The found or created partner is then added - in dest_partner_ids. - Partner check/creation valid inly if the value is True, and if - the model has the ``_mail_compose_message`` attribute. - """ - result = super(mail_compose_message_extended, self).onchange_email_mode(cr, uid, ids, value, model, res_id, context=context) - model_obj = self.pool.get(model) - if not value or not (getattr(model_obj, '_mail_compose_message', False) and res_id): - return result - data = model_obj.browse(cr, uid , res_id, context=context) - partner_obj = self.pool.get('res.partner') - partner_ids = partner_obj.search(cr, uid, [('email', '=', data.email_from)], context=context) - if partner_ids: - partner_id = partner_ids[0] - else: - partner_id = partner_obj.name_create(cr, uid, data.email_from, context=context)[0] - result['value'].update({ - 'dest_partner_ids': [partner_id], - 'email_cc': tools.ustr(data.email_cc or ''), - }) - if hasattr(data, 'section_id'): - result['value']['reply_to'] = data.section_id and data.section_id.reply_to or False - return result - - def onchange_formatting(self, cr, uid, ids, value, model, res_id, context=None): - """ Overrides the default implementation to provide default values for - the subject. - Subject re-creation valid only if the value is True, and if the - model has the ``_mail_compose_message`` attribute. - """ - result = super(mail_compose_message_extended, self).onchange_formatting(cr, uid, ids, value, model, res_id, context=context) - model_obj = self.pool.get(model) - if not value or not (getattr(model_obj, '_mail_compose_message', False) and res_id): - return result - data = model_obj.browse(cr, uid , res_id, context=context) - result['value'].update({ - 'subject': data.name or False, - }) - return result - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index 73fb03cdc28..70b50c154ac 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -6,73 +6,87 @@ mail.compose.message
    + + + + + - - - - - - - - - - - + + - + - + +
    -
    - mail.compose.message.form + mail.compose.message.form.chatter mail.compose.message 18
    + + + + + + + + class="oe_mail_compose_message_subject" + attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/> - - + class="oe_mail_compose_message_body" + attrs="{'invisible':[('content_subtype', '=', 'html')]}"/> + + + + +
    +
    - - - Checklist - - - Attachment - - - Formatting - +
    @@ -97,6 +111,6 @@ target="new" key2="client_action_multi" id="base.action_partner_mass_mail" - context="{'mail.compose.message.mode':'mass_mail'}"/> + context="{'default_composition_mode': 'mass_mail'}"/> diff --git a/addons/marketing_campaign/marketing_campaign_demo.xml b/addons/marketing_campaign/marketing_campaign_demo.xml index 54897cd111a..92443fe987d 100644 --- a/addons/marketing_campaign/marketing_campaign_demo.xml +++ b/addons/marketing_campaign/marketing_campaign_demo.xml @@ -3,29 +3,29 @@ - Template for New Partner + welcome new partner info@openerp.com - Welcome in OpenERP Partner Channel! + Welcome to the OpenERP Partner Channel! ${object.email or ''} - Hello, We are very happy to send Welcome message. + Hello, you will receive your welcome pack via email shortly. - Template for Silver Partner + congrats silver partner info@openerp.com - Congratulation! You become now our Silver Partner. + Congratulations! You are now a Silver Partner! ${object.email or ''} - Hello, We are happy to announce that you now become our Silver Partner. + Hi, we are delighted to welcome you among our Silver Partners as of today! - Template for Gold Partner + congrats gold partner info@openerp.com - Congratulation! You become our Gold Partner. + Congratulations! You are now one of our Gold Partners! ${object.email or ''} - Hello, We are happy to announce that you become our Gold Partner. + Hi, we are delighted to let you know that you have entered the select circle of our Gold Partners @@ -64,7 +64,7 @@ - + diff --git a/addons/marketing_campaign/test/marketing_campaign.yml b/addons/marketing_campaign/test/marketing_campaign.yml index 04063a6fbc1..3d540b4ccb7 100644 --- a/addons/marketing_campaign/test/marketing_campaign.yml +++ b/addons/marketing_campaign/test/marketing_campaign.yml @@ -42,7 +42,7 @@ record = self.browse(cr, uid, ids[0]) assert record.state == 'todo' or record.state == 'done' , 'Marketing Workitem shoud be in draft state.' - - I check follow-up detail of first activity. + I process follow-up of first activity. - !python {model: marketing.campaign.workitem}: | ids = self.search(cr, uid, [('segment_id', '=', ref('marketing_campaign_segment0')), @@ -50,12 +50,6 @@ assert ids, 'Follow-up item is not created for first activity.' work_item_id = self.browse(cr ,uid ,ids[0] ,context) assert work_item_id.res_name, 'Resource Name is not defined.' -- - I process follow-up of first activity. -- - !python {model: marketing.campaign.workitem}: | - ids = self.search(cr, uid, [('segment_id', '=', ref('marketing_campaign_segment0')), - ('campaign_id', '=', ref('marketing_campaign_openerppartnerchannel')), ('activity_id', '=', ref('marketing_campaign_activity_0'))]) self.process(cr, uid, ids) record = self.browse(cr, uid, ids)[0] assert record.state == "done", "Follow-up item should be closed after process." diff --git a/addons/marketing_campaign_crm_demo/marketing_campaign_demo.xml b/addons/marketing_campaign_crm_demo/marketing_campaign_demo.xml index 0946ff6e129..141981a21d6 100644 --- a/addons/marketing_campaign_crm_demo/marketing_campaign_demo.xml +++ b/addons/marketing_campaign_crm_demo/marketing_campaign_demo.xml @@ -40,7 +40,10 @@ - Hello,Thanks for generous interest you have shown in the openERP.Regards,OpenERP Team, + Hello,

    +

    Thanks for the genuine interest you have shown in OpenERP.

    +

    If any further information is required, do not hesitate to reply to this message.

    +

    Regards,OpenERP Team,

    ]]>
    For OpenERP OnDemand Free Trial 2010
    @@ -50,11 +53,11 @@ - Hello,We have very good offer that might suit you. - We propose you to subscribe to the OpenERP Discovery Day on May 2010. - If any further information required kindly revert back. - We really appreciate your co-operation on this. - Regards,OpenERP Team, + Hello,

    +

    We have very good offer that might suit you. + We suggest you subscribe to the OpenERP Discovery Day on May 2010.

    +

    If any further information is required, do not hesitate to reply to this message.

    +

    Regards,OpenERP Team,

    ]]>
    For OpenERP Discovery Day on May 2010
    @@ -65,10 +68,10 @@ - Hello,Thanks for showing intrest and for subscribing to the OpenERP Discovery Day. - If any further information required kindly revert back. - I really appreciate your co-operation on this. - Regards,OpenERP Team, + Hello,

    +

    Thanks for showing interest and for subscribing to the OpenERP Discovery Day.

    +

    If any further information is required, do not hesitate to reply to this message.

    +

    Regards,OpenERP Team,

    ]]>
    For OpenERP Discovery Day @@ -79,10 +82,10 @@ - Hello,Thanks for showing intrest and buying the OpenERP book. + Hello,

    +

    Thanks for showing interest and buying the OpenERP book.

    If any further information required kindly revert back. - I really appreciate your co-operation on this. - Regards,OpenERP Team,
    +

    Regards,OpenERP Team,

    ]]>
    For OpenERP book @@ -92,12 +95,12 @@ - Hello, We have very good offer that might suit you. - For our gold partners,We are arranging free technical training on june,2010. - If any further information required kindly revert back. - I really appreciate your co-operation on this. - Regards,OpenERP Team, - For technical training to Gold partners + Hello,

    +

    We have very good offer that might suit you. + For our gold partners,We are arranging free technical training on june,2010.

    +

    If any further information is required, do not hesitate to reply to this message.

    +

    Regards,OpenERP Team,

    ]]>
    + technical training to gold partners @@ -107,12 +110,12 @@ - Hello, We have very good offer that might suit you. - For our silver partners,We are paid technical training on june,2010. - If any further information required kindly revert back. - I really appreciate your co-operation on this. - Regards,OpenERP Team, - For training to Silver partners + Hello,

    +

    We have very good offer that might suit you. + For our silver partners,We are paid technical training on june,2010.

    +

    If any further information is required, do not hesitate to reply to this message.

    +

    Regards,OpenERP Team,

    ]]>
    + training to silver partners
    @@ -122,12 +125,12 @@ - Hello, We have very good offer that might suit you. - For our silver partners,We are offering Gold partnership. - If any further information required kindly revert back. - I really appreciate your co-operation on this. - Regards,OpenERP Team, - For gold partnership to silver partners + Hello,

    +

    We have very good offer that might suit you. + For our silver partners, we are offering Gold partnership.

    +

    If any further information is required, do not hesitate to reply to this message.

    +

    Regards,OpenERP Team,

    ]]>
    + gold partnership to silver partners
    @@ -137,9 +140,12 @@ - Hello, Thanks for showing intrest and for subscribing to technical training.If any further information required kindly revert back.I really appreciate your co-operation on this. - Regards,OpenERP Team, - For subscribing to technical training + Hello,

    +

    Thanks for showing interest and for subscribing to technical training.

    + If any further information required kindly revert back.I really appreciate your co-operation on this.

    +

    If any further information is required, do not hesitate to reply to this message.

    +

    Regards,OpenERP Team,

    ]]>
    + subscribing to technical training
    diff --git a/addons/mrp/board_manufacturing_view.xml b/addons/mrp/board_manufacturing_view.xml index e7668517a70..cae1fea07dc 100644 --- a/addons/mrp/board_manufacturing_view.xml +++ b/addons/mrp/board_manufacturing_view.xml @@ -9,7 +9,6 @@ - diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 948c204ed6a..fcb420a475e 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -368,7 +368,7 @@ class mrp_bom(osv.osv): prod_obj = self.pool.get('product.product') for obj in self.browse(cr, uid, ids, context=context): for prod in prod_obj.browse(cr, uid, [obj.product_id], context=context): - self.message_append_note(cr, uid, [obj.id], body=_("Bill of Material has been created for %s product.") % (prod.id.name_template), context=context) + self.message_post(cr, uid, [obj.id], body=_("Bill of Material has been created for %s product.") % (prod.id.name_template), context=context) return True mrp_bom() @@ -386,7 +386,7 @@ class mrp_production(osv.osv): _name = 'mrp.production' _description = 'Manufacturing Order' _date_name = 'date_planned' - _inherit = ['ir.needaction_mixin', 'mail.thread'] + _inherit = ['mail.thread', 'ir.needaction_mixin'] def _production_calc(self, cr, uid, ids, prop, unknow_none, context=None): """ Calculates total hours and total no. of cycles for a production order. @@ -1025,33 +1025,28 @@ class mrp_production(osv.osv): # OpenChatter methods and notifications # --------------------------------------------------- - def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): - """ Add 'user_id' to the monitored fields """ - res = super(mrp_production, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) - return res + ['user_id'] - def create_send_note(self, cr, uid, ids, context=None): - self.message_append_note(cr, uid, ids, body=_("Manufacturing order has been created."), context=context) + self.message_post(cr, uid, ids, body=_("Manufacturing order has been created."), context=context) return True def action_cancel_send_note(self, cr, uid, ids, context=None): message = _("Manufacturing order has been canceled.") - self.message_append_note(cr, uid, ids, body=message, context=context) + self.message_post(cr, uid, ids, body=message, context=context) return True def action_ready_send_note(self, cr, uid, ids, context=None): message = _("Manufacturing order is ready to produce.") - self.message_append_note(cr, uid, ids, body=message, context=context) + self.message_post(cr, uid, ids, body=message, context=context) return True def action_in_production_send_note(self, cr, uid, ids, context=None): message = _("Manufacturing order is in production.") - self.message_append_note(cr, uid, ids, body=message, context=context) + self.message_post(cr, uid, ids, body=message, context=context) return True def action_done_send_note(self, cr, uid, ids, context=None): message = _("Manufacturing order has been done.") - self.message_append_note(cr, uid, ids, body=message, context=context) + self.message_post(cr, uid, ids, body=message, context=context) return True def action_confirm_send_note(self, cr, uid, ids, context=None): @@ -1061,7 +1056,7 @@ class mrp_production(osv.osv): obj_datetime = fields.DT.datetime.strptime(obj.date_planned, DEFAULT_SERVER_DATETIME_FORMAT) obj_date_str = fields.datetime.context_timestamp(cr, uid, obj_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)") message = _("Manufacturing order has been confirmed and is scheduled for the %s.") % (obj_date_str) - self.message_append_note(cr, uid, [obj.id], body=message, context=context) + self.message_post(cr, uid, [obj.id], body=message, context=context) return True diff --git a/addons/mrp/mrp_data.xml b/addons/mrp/mrp_data.xml index e5d5380f96a..0bbf2d3b64c 100644 --- a/addons/mrp/mrp_data.xml +++ b/addons/mrp/mrp_data.xml @@ -2,14 +2,16 @@ - - - - Module MRP has been installed - Manage your manufacturing process in OpenERP by defining bill of materials (BoM), routings and work centers. This module supports complete integration and planification of stockable goods, consumable, and services. + + mail.group + + notification + MRP application installed! + Manage your manufacturing process with OpenERP by defining your bills of materials (BoM), routings and work centers. +This application supports complete integration and production scheduling for stockable goods, consumables, and services. -From the Manufacturing Settings, you can choose to compute production schedules periodically or just-in-time. - +From the Manufacturing Settings, you can choose to compute production schedules periodically or just-in-time.
    + Production order diff --git a/addons/mrp/mrp_demo.xml b/addons/mrp/mrp_demo.xml index d0f05a2aaff..952a5e0e3de 100644 --- a/addons/mrp/mrp_demo.xml +++ b/addons/mrp/mrp_demo.xml @@ -49,7 +49,7 @@ 0.05 - + diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index 8811fcf5611..6d2a486514c 100644 --- a/addons/mrp/mrp_view.xml +++ b/addons/mrp/mrp_view.xml @@ -552,8 +552,8 @@ mrp.production.tree mrp.production - - + + diff --git a/addons/mrp/procurement.py b/addons/mrp/procurement.py index 363f98e35d1..7f417dc18b1 100644 --- a/addons/mrp/procurement.py +++ b/addons/mrp/procurement.py @@ -43,7 +43,7 @@ class procurement_order(osv.osv): cr.execute('update procurement_order set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id)) for (id, name) in self.name_get(cr, uid, procurement.id): message = _("Procurement '%s' has an exception: 'No BoM defined for this product !'") % name - self.message_append_note(cr, uid, [procurement.id], body=message, context=context) + self.message_post(cr, uid, [procurement.id], body=message, context=context) return False return True diff --git a/addons/mrp_operations/mrp_operations.py b/addons/mrp_operations/mrp_operations.py index 8692dab5c87..ee2547bc7df 100644 --- a/addons/mrp_operations/mrp_operations.py +++ b/addons/mrp_operations/mrp_operations.py @@ -224,7 +224,7 @@ class mrp_production_workcenter_line(osv.osv): for workorder in self.browse(cr, uid, ids): for prod in prod_obj.browse(cr, uid, [workorder.production_id]): message = _("Work order has been created for production order %s.") % (prod.id.name) - self.message_append_note(cr, uid, [workorder.id], body=message, context=context) + self.message_post(cr, uid, [workorder.id], body=message, context=context) return True def action_start_send_note(self, cr, uid, ids, context=None): @@ -232,7 +232,7 @@ class mrp_production_workcenter_line(osv.osv): for workorder in self.browse(cr, uid, ids): for prod in prod_obj.browse(cr, uid, [workorder.production_id]): message = _("Work order has been started for production order %s.") % (prod.id.name) - self.message_append_note(cr, uid, [workorder.id], body=message, context=context) + self.message_post(cr, uid, [workorder.id], body=message, context=context) return True def action_done_send_note(self, cr, uid, ids, context=None): @@ -240,7 +240,7 @@ class mrp_production_workcenter_line(osv.osv): for workorder in self.browse(cr, uid, ids): for prod in prod_obj.browse(cr, uid, [workorder.production_id]): message = _("Work order has been done for production order %s.") % (prod.id.name) - self.message_append_note(cr, uid, [workorder.id], body=message, context=context) + self.message_post(cr, uid, [workorder.id], body=message, context=context) return True def action_pending_send_note(self, cr, uid, ids, context=None): @@ -248,7 +248,7 @@ class mrp_production_workcenter_line(osv.osv): for workorder in self.browse(cr, uid, ids): for prod in prod_obj.browse(cr, uid, [workorder.production_id]): message = _("Work order is pending for production order %s.") % (prod.id.name) - self.message_append_note(cr, uid, [workorder.id], body=message, context=context) + self.message_post(cr, uid, [workorder.id], body=message, context=context) return True def action_cancel_send_note(self, cr, uid, ids, context=None): @@ -256,7 +256,7 @@ class mrp_production_workcenter_line(osv.osv): for workorder in self.browse(cr, uid, ids): for prod in prod_obj.browse(cr, uid, [workorder.production_id]): message = _("Work order has been cancelled for production order %s.") % (prod.id.name) - self.message_append_note(cr, uid, [workorder.id], body=message, context=context) + self.message_post(cr, uid, [workorder.id], body=message, context=context) return True mrp_production_workcenter_line() diff --git a/addons/mrp_repair/mrp_repair.py b/addons/mrp_repair/mrp_repair.py index f41abb58c08..c1ac8f809ce 100644 --- a/addons/mrp_repair/mrp_repair.py +++ b/addons/mrp_repair/mrp_repair.py @@ -571,40 +571,40 @@ class mrp_repair(osv.osv): def create_send_note(self, cr, uid, ids, context=None): for repair in self.browse(cr, uid, ids, context): message = _("Repair Order for %s has been created." % (repair.product_id.name)) - self.message_append_note(cr, uid, [repair.id], body=message, context=context) + self.message_post(cr, uid, [repair.id], body=message, context=context) return True def set_start_send_note(self, cr, uid, ids, context=None): for repair in self.browse(cr, uid, ids, context): message = _("Repair Order for %s has been started." % (repair.product_id.name)) - self.message_append_note(cr, uid, [repair.id], body=message, context=context) + self.message_post(cr, uid, [repair.id], body=message, context=context) return True def set_toinvoiced_send_note(self, cr, uid, ids, context=None): for repair in self.browse(cr, uid, ids, context): message = _("Draft Invoice of %s %s waiting for validation.") % (repair.invoice_id.amount_total, repair.invoice_id.currency_id.symbol) - self.message_append_note(cr, uid, [repair.id], body=message, context=context) + self.message_post(cr, uid, [repair.id], body=message, context=context) return True def set_confirm_send_note(self, cr, uid, ids, context=None): for repair in self.browse(cr, uid, ids, context): message = _( "Repair Order for %s has been accepted." % (repair.product_id.name)) - self.message_append_note(cr, uid, [repair.id], body=message, context=context) + self.message_post(cr, uid, [repair.id], body=message, context=context) return True def set_cancel_send_note(self, cr, uid, ids, context=None): message = _("Repair has been cancelled.") - self.message_append_note(cr, uid, ids, body=message, context=context) + self.message_post(cr, uid, ids, body=message, context=context) return True def set_ready_send_note(self, cr, uid, ids, context=None): message = _("Repair Order is now ready to repair.") - self.message_append_note(cr, uid, ids, body=message, context=context) + self.message_post(cr, uid, ids, body=message, context=context) return True def set_done_send_note(self, cr, uid, ids, context=None): message = _("Repair Order is closed.") - self.message_append_note(cr, uid, ids, body=message, context=context) + self.message_post(cr, uid, ids, body=message, context=context) return True mrp_repair() diff --git a/addons/note/note.py b/addons/note/note.py index bce54566e64..abc7dca297e 100644 --- a/addons/note/note.py +++ b/addons/note/note.py @@ -64,11 +64,15 @@ class note_note(osv.osv): stage_obj = self.pool.get('note.stage') # only show stage groups not folded and owned by user - search_domain = [('fold', '=', False),('user_id', '=', uid)] + search_domain = [('user_id', '=', uid)] stage_ids = stage_obj._search(cr, uid, search_domain, order=self._order, access_rights_uid=access_rights_uid, context=context) result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) - return result + + fold = {} + for stage in stage_obj.browse(cr, access_rights_uid, stage_ids, context=context): + fold[stage.id] = stage.fold or False + return result, fold _columns = { 'name': fields.function(_get_note_first_line, fnct_inv=_set_note_first_line, string='Note Summary', type="text"), diff --git a/addons/note/note_demo.xml b/addons/note/note_demo.xml index e322f362ef5..e6ab25198a6 100644 --- a/addons/note/note_demo.xml +++ b/addons/note/note_demo.xml @@ -58,7 +58,7 @@ @@ -93,7 +93,7 @@ @@ -104,7 +104,7 @@ @@ -115,7 +115,7 @@ @@ -127,7 +127,7 @@ @@ -139,7 +139,7 @@ diff --git a/addons/pad_project/i18n/tr.po b/addons/pad_project/i18n/tr.po new file mode 100644 index 00000000000..a19889de838 --- /dev/null +++ b/addons/pad_project/i18n/tr.po @@ -0,0 +1,38 @@ +# Turkish 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:36+0000\n" +"PO-Revision-Date: 2012-09-07 20:06+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Turkish \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-08 04:54+0000\n" +"X-Generator: Launchpad (build 15914)\n" + +#. module: pad_project +#: constraint:project.task:0 +msgid "Error ! Task end-date must be greater then task start-date" +msgstr "Hata! görev bitiş tarihi başlangıç tarihinden sonra olmalı" + +#. module: pad_project +#: model:ir.model,name:pad_project.model_project_task +msgid "Task" +msgstr "Görev" + +#. module: pad_project +#: view:project.task:0 +msgid "Pad" +msgstr "" + +#. module: pad_project +#: constraint:project.task:0 +msgid "Error ! You cannot create recursive tasks." +msgstr "Hata ! Yinelenen görevler oluşturamazsınız." diff --git a/addons/plugin/plugin_handler.py b/addons/plugin/plugin_handler.py index e497f902dc2..9c582a8e3bf 100644 --- a/addons/plugin/plugin_handler.py +++ b/addons/plugin/plugin_handler.py @@ -44,7 +44,7 @@ class plugin_handler(osv.osv_memory): res_id = 0 url = "" name = "" - msg = mail_message_obj.parse_message(email) + msg = self.pool.get('mail.thread').parse_message(cr, uid, email) references = [msg.get('message-id')] refs = msg.get('references',False) if refs: @@ -91,7 +91,7 @@ class plugin_handler(osv.osv_memory): """ mail_message = self.pool.get('mail.message') model_obj = self.pool.get(model) - msg = mail_message.parse_message(email) + msg = self.pool.get('mail.thread').parse_message(cr, uid, email) message_id = msg.get('message-id') mail_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)]) @@ -101,16 +101,15 @@ class plugin_handler(osv.osv_memory): notify = "Email already pushed" elif res_id == 0: if model == 'res.partner': - notify = 'User the button Partner to create a new partner' + notify = 'User the Partner button to create a new partner' else: res_id = model_obj.message_new(cr, uid, msg) - notify = "Mail succefully pushed, a new %s has been created " % model + notify = "Mail succesfully pushed, a new %s has been created " % model else: if model == 'res.partner': model_obj = self.pool.get('mail.thread') - res = self.pool.get(model).browse(cr, uid, [res_id]) - model_obj.message_append_dict(cr, uid, res, msg) - notify = "Mail succefully pushed" + model_obj.message_post(cr, uid, [res_id], body=msg) + notify = "Mail succesfully pushed" url = self._make_url(cr, uid, res_id, model) return (model, res_id, url, notify) @@ -133,7 +132,7 @@ class plugin_handler(osv.osv_memory): return ('res.partner', partner_id, url) # Specific to outlook rfc822 is not available so we split in arguments headerd,body,attachemnts - def push_message_outlook(self, cr, uid, model, headers,res_id=0 ,body_text=False, body_html=False, attachments=False): + def push_message_outlook(self, cr, uid, model, headers,res_id=0 ,body=False, body_html=False, attachments=False): # ---------------------------------------- # solution 1 # construct a fake rfc822 from the separated arguement @@ -147,7 +146,7 @@ class plugin_handler(osv.osv_memory): mail_message = self.pool.get('mail.message') ir_attachment_obj = self.pool.get('ir.attachment') attach_ids = [] - msg = mail_message.parse_message(headers) + msg = self.pool.get('mail.thread').parse_message(cr, uid, headers) message_id = msg.get('message-id') push_mail = self.push_message(cr, uid, model, headers, res_id) res_id = push_mail[1] @@ -161,6 +160,6 @@ class plugin_handler(osv.osv_memory): attach_ids.append(ir_attachment_obj.create(cr, uid, vals)) mail_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)]) if mail_ids: - ids = mail_message.write(cr, uid,mail_ids[0],{ 'attachment_ids': [(6, 0, attach_ids)],'body_text':body_text,'body_html':body_html}) + ids = mail_message.write(cr, uid,mail_ids[0],{ 'attachment_ids': [(6, 0, attach_ids)],'body':body,'body_html':body_html}) url = self._make_url(cr, uid, res_id, model) return (model, res_id, url) diff --git a/addons/plugin_outlook/plugin_outlook.xml b/addons/plugin_outlook/plugin_outlook.xml index c2c035f532d..e99fbf72b6d 100644 --- a/addons/plugin_outlook/plugin_outlook.xml +++ b/addons/plugin_outlook/plugin_outlook.xml @@ -19,7 +19,7 @@

    Click on the link above to download the installer for either 32 or 64 bits, and execute it.

    System requirements:

    -