[MERGE] Merge with parent branch

bzr revid: fka@tinyerp.com-20120903051925-r01yx7u7nvono353
bzr revid: fka@tinyerp.com-20120903070434-on2thgronzgnk5am
This commit is contained in:
Foram Katharotiya (OpenERP)
2012-09-03 12:34:34 +05:30
38 changed files with 740 additions and 729 deletions
+4 -5
View File
@@ -1298,16 +1298,15 @@ class account_invoice(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>created</b>.") % (_(self._get_document_type(obj.type))), context=context)
self.message_post(cr, uid, [obj.id],body=_("%s <b>created</b>.") % (self._get_document_type(obj.type)), subtype="new", context=context)
def confirm_paid_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (_(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 <b>paid</b>.") % (self._get_document_type(obj.type)), subtype="paid", context=context)
def invoice_cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (_(self._get_document_type(obj.type))), context=context)
self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (self._get_document_type(obj.type)), subtype="cancelled", context=context)
account_invoice()
class account_invoice_line(osv.osv):
+23 -6
View File
@@ -518,7 +518,6 @@
<!--
Account Statement Sequences
-->
<record id="sequence_reconcile" model="ir.sequence.type">
<field name="name">Account Reconcile</field>
<field name="code">account.reconcile</field>
@@ -554,8 +553,6 @@
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
</record>
<!--
Invoice requests (deprecated)
-->
@@ -563,8 +560,28 @@
<field name="name">Invoice</field>
<field name="object">account.invoice</field>
</record>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('analytic.model_account_analytic_account')),(4,ref('account.model_account_invoice'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_paid" model="mail.message.subtype">
<field name="name">paid</field>
<field name="model_ids" eval="[(4,ref('account.model_account_invoice'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('account.model_account_invoice'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account.model_account_invoice')), (4,ref('analytic.model_account_analytic_account'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account.model_account_invoice')), (4,ref('analytic.model_account_analytic_account'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account.model_account_invoice')), (4,ref('analytic.model_account_analytic_account'))]"/>
</record>
</data>
</openerp>
@@ -23,7 +23,6 @@
<label for="quantity_max"/>
<div>
<field name="quantity_max" class="oe_inline"/>
<field name="company_uom_id" readonly="1" class="oe_inline"/>
</div>
</xpath>
<xpath expr='//group[@name="invoice_on_timesheets"]' position="replace">
+3 -3
View File
@@ -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 <b>created</b>." % self._document_type[obj.type or False]
self.message_post(cr, uid, [obj.id], body=message, context=context)
self.message_post(cr, uid, [obj.id], body=message, subtype="new", context=context)
def post_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
self.message_post(cr, uid, [obj.id], body=message, context=context)
self.message_post(cr, uid, [obj.id], body=message, subtype="post", context=context)
def reconcile_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
self.message_post(cr, uid, [obj.id], body=message, context=context)
self.message_post(cr, uid, [obj.id], body=message, subtype="reconcile", context=context)
account_voucher()
@@ -2,16 +2,38 @@
<openerp>
<data noupdate="1">
<!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification">
<field name="model">mail.group</field>
<field name="res_id" ref="mail.group_all_employees"/>
<field name="type">notification</field>
<field name="subject">eInvoicing &amp; Payments application installed!</field>
<field name="body">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.
<function model="mail.group" name="message_post">
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
<value eval="[ref('mail.group_all_employees')]"/>
<value>OpenERP's electronic invoicing allows to ease and fasten the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system.
You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability.
You can track customer payments easily and automate the reminders. You get an overview of the discussion with your customers on each invoice to ensure a full traceability.
For advanced accounting features, you should install the "Accounting and Finance" module.</field>
If you want to use advanced accounting features, you should install the "Accounting and Finance" module.</value>
<value>Module eInvoicing &amp; Payments has been installed.</value>
</function>
<!-- Message Sub Type -->
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
</record>
<record id="mail.mail_subtype_post" model="mail.message.subtype">
<field name="name">post</field>
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_reconcile" model="mail.message.subtype">
<field name="name">reconcile</field>
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
</record>
</data>
</openerp>
+1 -1
View File
@@ -297,7 +297,7 @@ class account_analytic_account(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), subtype="new", context=context)
account_analytic_account()
-9
View File
@@ -595,18 +595,9 @@ class crm_lead(base_stage, osv.osv):
for lead in self.browse(cr, uid, ids, context=context):
if lead.state in ('done', 'cancel'):
continue
vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
self.write(cr, uid, [lead.id], vals, context=context)
self.convert_opportunity_send_note(cr, uid, lead, context=context)
#TOCHECK: why need to change partner details in all messages of lead ?
# TDE FIXME: I propose to delete this, makes no sense to update partner of an already-send email ...
# if lead.partner_id:
# msg_ids = [x.id for x in lead.message_ids]
# mail_message.write(cr, uid, msg_ids, {
# 'partner_id': lead.partner_id.id
# }, context=context)
if user_ids or section_id:
self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context)
+42 -81
View File
@@ -29,7 +29,7 @@ from osv import fields
import tools
from tools.translate import _
from tools.html_sanitize import html_sanitize
from tools import html2plaintext
from tools import append_content_to_html
from urllib import quote as quote
_logger = logging.getLogger(__name__)
@@ -40,10 +40,8 @@ except ImportError:
class email_template(osv.osv):
"Templates for sending email"
_inherit = 'mail.mail'
_name = "email.template"
_description = 'Email Templates'
_rec_name = 'name' # override mail.message's behavior
def render_template(self, cr, uid, template, model, res_id, context=None):
"""Render the given template text, replace mako expressions ``${expr}``
@@ -103,57 +101,44 @@ class email_template(osv.osv):
mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
return {'value':{'model': mod_name}}
def name_get(self, cr, uid, ids, context=None):
""" Override name_get of mail.message: return directly the template
name, and not the generated name from mail.message.common."""
return [(record.id, record.name) for record in self.browse(cr, uid, ids, context=context)]
_columns = {
'name': fields.char('Name', size=250),
'model_id': fields.many2one('ir.model', 'Related document model'),
'lang': fields.char('Language Selection', size=250,
'name': fields.char('Name'),
'model_id': fields.many2one('ir.model', 'Applies to', help="The kind of document with with this template can be used"),
'model': fields.related('model_id', 'model', type='char', string='Related Document Model',
size=128, select=True, store=True, readonly=True),
'lang': fields.char('Language',
help="Optional translation language (ISO code) to select when sending out an email. "
"If not set, the english version will be used. "
"This should usually be a placeholder expression "
"that provides the appropriate language code, e.g. "
"${object.partner_id.lang.code}."),
"${object.partner_id.lang.code}.",
placeholder="${object.partner_id.lang.code}"),
'user_signature': fields.boolean('Add Signature',
help="If checked, the user's signature will be appended to the text version "
"of the message"),
'report_name': fields.char('Report Filename', size=200, translate=True,
help="Name to use for the generated report file (may contain placeholders)\n"
"The extension can be omitted and will then come from the report type."),
'report_template':fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
'ref_ir_act_window':fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
help="Sidebar action to make this template available on records "
"of the related document model"),
'ref_ir_value':fields.many2one('ir.values', 'Sidebar Button', readonly=True,
help="Sidebar button to open the sidebar action"),
'track_campaign_item': fields.boolean('Resource Tracking',
help="Enable this is you wish to include a special tracking marker "
"in outgoing emails so you can identify replies and link "
"them back to the corresponding resource record. "
"This is useful for CRM leads for example"),
# Overridden mail.message.common fields for technical reasons:
'model': fields.related('model_id', 'model', type='char', string='Related Document Model',
size=128, select=True, store=True, readonly=True),
# we need a separate m2m table to avoid ID collisions with the original mail.message entries
#'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
# 'attachment_id', 'Files to attach',
# help="You may attach files to this template, to be added to all "
# "emails created from this template"),
# Overridden mail.message.common fields to make tooltips more appropriate:
'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",),
'email_from': fields.char('From', size=128, help="Sender address (placeholders may be used here)"),
'email_to': fields.char('To', size=256, help="Comma-separated recipient addresses (placeholders may be used here)"),
'email_cc': fields.char('Cc', size=256, help="Carbon copy recipients (placeholders may be used here)"),
'reply_to': fields.char('Reply-To', size=250, help="Preferred response address (placeholders may be used here)"),
'email_from': fields.char('From', help="Sender address (placeholders may be used here)"),
'email_to': fields.char('To', help="Comma-separated recipient addresses (placeholders may be used here)"),
'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"),
'reply_to': fields.char('Reply-To', help="Preferred response address (placeholders may be used here)"),
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
help="Optional preferred server for outgoing mails. If not set, the highest "
"priority one will be used."),
'body_html': fields.text('Rich-text Contents', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
'body_html': fields.text('Body', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
'report_name': fields.char('Report Filename', translate=True,
help="Name to use for the generated report file (may contain placeholders)\n"
"The extension can be omitted and will then come from the report type."),
'report_template': fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
'ref_ir_act_window': fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
help="Sidebar action to make this template available on records "
"of the related document model"),
'ref_ir_value': fields.many2one('ir.values', 'Sidebar Button', readonly=True,
help="Sidebar button to open the sidebar action"),
'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
'attachment_id', 'Attachments',
help="You may attach files to this template, to be added to all "
"emails created from this template"),
'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"),
# Fake fields used to implement the placeholder assistant
'model_object_field': fields.many2one('ir.model.fields', string="Field",
@@ -167,12 +152,8 @@ class email_template(osv.osv):
help="When a relationship field is selected as first field, "
"this field lets you select the target field within the "
"destination document model (sub-model)."),
'null_value': fields.char('Null value', help="Optional value to use if the target field is empty", size=128),
'copyvalue': fields.char('Expression', size=256, help="Final placeholder expression, to be copy-pasted in the desired template field."),
}
_defaults = {
'track_campaign_item': True
'null_value': fields.char('Default Value', help="Optional value to use if the target field is empty"),
'copyvalue': fields.char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field."),
}
def create_action(self, cr, uid, ids, context=None):
@@ -217,7 +198,7 @@ class email_template(osv.osv):
if template.ref_ir_value:
ir_values_obj = self.pool.get('ir.values')
ir_values_obj.unlink(cr, uid, template.ref_ir_value.id, context)
except:
except Exception:
raise osv.except_osv(_("Warning"), _("Deletion of the action record failed."))
return True
@@ -289,32 +270,14 @@ class email_template(osv.osv):
:param res_id: id of the record to use for rendering the template (model
is taken from template definition)
:returns: a dict containing all relevant fields for creating a new
mail.message entry, with the addition one additional
special key ``attachments`` containing a list of
mail.mail entry, with one extra key ``attachments``, in the
format expected by :py:meth:`mail_thread.message_post`.
"""
if context is None:
context = {}
values = {
'subject': False,
'body_html': False,
'email_from': False,
'email_to': False,
'email_cc': False,
'reply_to': False,
'auto_delete': False,
'model': False,
'res_id': False,
'mail_server_id': False,
'attachments': False,
'attachment_ids': False,
'message_id': False,
'state': 'outgoing',
}
if not template_id:
return values
report_xml_pool = self.pool.get('ir.actions.report.xml')
template = self.get_email_template(cr, uid, template_id, res_id, context)
values = {'model': template.model_id.model}
for field in ['subject', 'body_html', 'email_from',
'email_to', 'email_cc', 'reply_to']:
@@ -322,21 +285,20 @@ class email_template(osv.osv):
template.model, res_id, context=context) \
or False
if values['body_html']:
values['body'] = html_sanitize(values['body_html'])
values['body_text'] = html2plaintext(values['body_html'])
if template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body'] += '\n\n' + signature
values['body_html'] = append_content_to_html(values['body_html'], signature)
if values['body_html']:
values['body'] = html_sanitize(values['body_html'])
values.update(mail_server_id = template.mail_server_id.id or False,
auto_delete = template.auto_delete,
model=template.model,
res_id=res_id or False)
attachments = {}
# Add report as a Document
attachments = []
# Add report in attachments
if template.report_template:
report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
report_service = 'report.' + report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
@@ -352,12 +314,11 @@ class email_template(osv.osv):
ext = "." + format
if not report_name.endswith(ext):
report_name += ext
attachments[report_name] = result
attachments.append(report_name, result)
# Add document attachments
# Add template attachments
for attach in template.attachment_ids:
# keep the bytes as fetched from the db, base64 encoded
attachments[attach.datas_fname] = attach.datas
attachments.append(attach.datas_fname, attach.datas)
values['attachments'] = attachments
return values
+53 -81
View File
@@ -1,95 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="email_template_form">
<field name="name">email.template.form</field>
<field name="model">email.template</field>
<field name="arch" type="xml">
<form string="Templates" version="7.0">
<sheet>
<group col="4">
<field name="name" required="1"/>
<field name="model_id" required="1" on_change="onchange_model_id(model_id)"/>
<field name="model" invisible="1"/>
</group>
<notebook>
<page string="Email Details">
<group>
<field name="email_from" required="1"/>
<field name="email_to" required="1"/>
<field name="email_cc"/>
<field name="reply_to"/>
<field name="subject" required="1"/>
<notebook colspan="4">
<page string="Body (HTML)">
<field name="body_html" colspan="4" width="250" height="250" nolabel="1"/>
<label string="Note: This is Raw HTML." colspan="4"/>
</page>
</notebook>
<field name="user_signature"/>
<notebook colspan="4">
<page string="Dynamic Values Builder">
<group>
<field name="model_object_field"
domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
<field name="sub_object" readonly="1"/>
<field name="sub_model_object_field"
domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]"
attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}"
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
<field name="null_value" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
<field name="copyvalue"/>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/><h1><field name="name" required="1"/></h1>
<h3><label for="model_id"/><field name="model_id" required="1" on_change="onchange_model_id(model_id)" class="oe_inline"/></h3>
<field name="model" invisible="1"/>
</div>
<div class="oe_right oe_button_box" name="buttons">
<field name="ref_ir_act_window" invisible="1"/>
<button name="create_action" string="Add context action" type="object"
attrs="{'invisible':[('ref_ir_act_window','!=',False)]}"
help="Display an option on related documents to open a composition wizard with this template"/>
<button name="unlink_action" string="Remove context action" type="object"
attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
help="Remove the contextual action to use this template on related documents"/>
<button name="%(wizard_email_template_preview)d" string="Preview"
type="action" target="new"
context="{'template_id':active_id}"/>
</div>
<notebook>
<page string="Email Details">
<group>
<group string="Addressing">
<field name="email_from" required="1"/>
<field name="email_to" required="1"/>
<field name="email_cc"/>
<field name="reply_to"/>
<field name="user_signature"/>
</group>
<group string="Dynamic Value Builder" class="oe_edit_only">
<field name="model_object_field" domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]" on_change="onchange_sub_model_object_value_field(model_object_field)"/>
<field name="sub_object" readonly="1"/>
<field name="sub_model_object_field" domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]" attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
<field name="null_value" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
<field name="copyvalue"/>
</group>
<group string="Contents" colspan="2">
<field name="subject" required="1"/>
<field name="body_html" width="250" height="450" nolabel="1" colspan="2" placeholder="Email contents (in raw HTML format)"/>
</group>
</page>
</notebook>
<button name="%(wizard_email_template_preview)d" string="Preview Template"
type="action" colspan="4" target="new" icon="gtk-zoom-fit" context="{'template_id':active_id}"/>
</group>
</page>
<page string="Advanced">
<group colspan="2" col="2">
<group colspan="2" col="2">
<separator string="Sidebar Button" colspan="2"/>
<button name="create_action" string="Add sidebar button" type="object" icon="gtk-execute"
colspan="2" attrs="{'invisible':[('ref_ir_act_window','!=',False)]}"
help="Display a button in the sidebar of related documents to open a composition wizard with this template"
/>
<field name="ref_ir_act_window" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"/>
<field name="ref_ir_value" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"/>
<button name="unlink_action" string="Remove sidebar button" type="object" icon="gtk-delete"
colspan="2" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
help="Remove the sidebar button currently displayed on related documents"
/>
</group>
<group colspan="2" col="2">
<separator string="Advanced Options" colspan="2"/>
<field name="mail_server_id"/>
<field name="track_campaign_item"/>
<field name="message_id"/>
<field name="auto_delete"/>
</page>
<page string="Advanced">
<group>
<field name="lang"/>
<field name="mail_server_id"/>
<field name="auto_delete"/>
<field name="report_template" domain="[('model','=',model)]"/>
<field name="report_name" class="oe_inline"
attrs="{'invisible':[('report_template','=',False)]}"/>
<field name="attachment_ids">
<tree><field name="name"/></tree>
</field>
</group>
</group>
<group colspan="2" col="2">
<separator string="Attachments" colspan="2"/>
<notebook colspan="2">
<page string="Attach Report">
<group>
<field name="report_template" colspan="4"
domain="[('model','=',model)]"/>
<field name="report_name" colspan="4" />
</group>
</page>
<page string="Attach existing files">
<field name="attachment_ids" colspan="4" nolabel="1" height="350"/>
</page>
</notebook>
</group>
</page>
</notebook>
</sheet>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
@@ -99,9 +71,9 @@
<field name="model">email.template</field>
<field name="arch" type="xml">
<tree string="Templates">
<field name="model_id"/>
<field name="mail_server_id" invisible="1"/>
<field name="name"/>
<field name="model_id"/>
<field name="subject"/>
<field name="email_from"/>
<field name="email_to"/>
@@ -1,5 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_email_template,email.template,model_email_template,,1,0,0,0
access_email_template,email.template,model_email_template,,1,1,1,0
access_email_template_system,email.template system,model_email_template,base.group_system,1,1,1,1
access_email_template_manager,email.template,model_email_template,,1,1,1,1
access_email_template_preview_system,email.template.preview system,model_email_template_preview,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_email_template email.template model_email_template 1 0 1 0 1 0
3 access_email_template_system email.template system model_email_template base.group_system 1 1 1 1
access_email_template_manager email.template model_email_template 1 1 1 1
access_email_template_preview_system email.template.preview system model_email_template_preview base.group_system 1 1 1 1
+35 -27
View File
@@ -53,51 +53,59 @@ class test_message_compose(common.TransactionCase):
""" Tests designed for the mail.compose.message wizard updated by email_template. """
cr, uid = self.cr, self.uid
mail_compose = self.registry('mail.compose.message')
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a.a'})
user_admin = self.res_users.browse(cr, uid, uid)
group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0]
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'I am angry !'})
group_bird = self.mail_group.browse(cr, uid, group_bird_id)
# Create template on mail.group
group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0]
email_template = self.registry('email.template')
email_template_id = email_template.create(cr, uid, {'model_id': group_model_id,
'name': 'Pigs Template', 'subject': '${record.name}',
'name': 'Pigs Template', 'subject': '${object.name}',
'body_html': '${object.description}', 'user_signature': True,
'email_to': 'b@b c@c', 'email_cc': 'd@d'})
# import pdb
# pdb.set_trace()
'email_to': 'b@b.b c@c.c', 'email_cc': 'd@d.d'})
# Mail data
_subject = 'Pigs'
_body_text = 'Pigs rules'
_subject1 = 'Pigs'
_subject2 = 'Bird'
_body_text1 = 'Pigs rules'
_body_text_html1 = 'Pigs rules<pre>Admin</pre>'
_body_text2 = 'I am angry !'
_body_text_html2 = 'I am angry !<pre>Admin</pre>'
# 3 - Create in mass_mail composition mode that should work with or without email_template installed
# CASE1: create in mass_mail composition mode that should work with email_template installed
compose_id = mail_compose.create(cr, uid,
{'subject': _subject, 'body': _body_text},
{'subject': 'Forget me subject', 'body': 'Dummy body'},
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group',
'default_res_id': -1, 'default_use_template': True,
'active_ids': [self.group_pigs_id], 'default_template_id': email_template_id})
'active_ids': [self.group_pigs_id, group_bird_id], 'default_template_id': email_template_id})
compose = mail_compose.browse(cr, uid, compose_id)
# print compose.subject
# Try the 'onchange_template_id'
values = mail_compose.onchange_template_id(cr, uid, [], compose.use_template, compose.template_id, compose.composition_mode, compose.res_id)
# print values
values = mail_compose.onchange_use_template(cr, uid, [], not compose.use_template, compose.template_id, compose.composition_mode, compose.res_id)
# print values
values = mail_compose.onchange_template_id(cr, uid, [], compose.use_template, compose.template_id, compose.composition_mode, compose.model, compose.res_id)
print values
# compose.write(values['value'])
values = mail_compose.onchange_use_template(cr, uid, [], not compose.use_template, compose.template_id, compose.composition_mode, compose.model, compose.res_id)
print values
compose.refresh()
# # Post the comment, get created message
# mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id]})
# group_pigs.refresh()
# msg = group_pigs.message_ids[0]
# # Test: last message on Pigs = last created message
# test_msg = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [], limit=1))[0]
# self.assertEqual(msg.id, test_msg.id, 'Pigs did not receive its mass mailing message')
# # Test: mail.message: subject, body
# self.assertEqual(msg.subject, _subject, 'mail.message subject is incorrect')
# self.assertEqual(msg.body, group_pigs.description, 'mail.message body is incorrect')
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]})
group_pigs.refresh()
group_bird.refresh()
message_pigs = group_pigs.message_ids[0]
message_bird = group_bird.message_ids[0]
# Test: subject, body
self.assertEqual(message_pigs.subject, _subject1, 'mail.message subject on Pigs incorrect')
self.assertEqual(message_bird.subject, _subject2, 'mail.message subject on Bird incorrect')
self.assertEqual(message_pigs.body, _body_text_html1, 'mail.message body on Pigs incorrect')
self.assertEqual(message_bird.body, _body_text_html2, 'mail.message body on Bird incorrect')
# Test: partner_ids
print message_pigs.partner_ids
print message_pigs.partner_ids
self.assertEqual(len(message_pigs.partner_ids), 6, 'mail.message partner_ids incorrect')
@@ -65,29 +65,19 @@ class email_template_preview(osv.osv_memory):
return result
_columns = {
'res_id':fields.selection(_get_records, 'Sample Document'),
'res_id': fields.selection(_get_records, 'Sample Document'),
}
def on_change_res_id(self, cr, uid, ids, res_id, context=None):
if not res_id:
return {}
if not res_id: return {}
vals = {}
email_template = self.pool.get('email.template')
template_id = context and context.get('template_id')
template = email_template.get_email_template(cr, uid, template_id=template_id, record_id=res_id, context=context)
model = template.model
vals['email_to'] = self.render_template(cr, uid, template.email_to, model, res_id, context)
vals['email_cc'] = self.render_template(cr, uid, template.email_cc, model, res_id, context)
vals['reply_to'] = self.render_template(cr, uid, template.reply_to, model, res_id, context)
vals['subject'] = self.render_template(cr, uid, template.subject, model, res_id, context)
description = self.render_template(cr, uid, template.body, model, res_id, context) or ''
if template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
description += '\n' + signature
vals['body'] = description
if template.body_html:
vals['body_html'] = self.render_template(cr, uid, template.body_html, model, res_id, context) or ''
vals['report_name'] = self.render_template(cr, uid, template.report_name, model, res_id, context)
template = email_template.browse(cr, uid, template_id, context=context)
vals['name'] = template.name
mail_values = email_template.generate_email(cr, uid, template_id, res_id, context=context)
for k in ('email_from','email_to','email_cc','reply_to','subject','body_html'):
vals[k] = mail_values[k]
return {'value': vals}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -6,26 +6,22 @@
<field name="name">email_template.preview.form</field>
<field name="model">email_template.preview</field>
<field name="arch" type="xml">
<form string="Email Preview">
<field name="model_id" invisible="1"/>
<field name="res_id" on_change="on_change_res_id(res_id, context)"/>
<group col="2" colspan="4">
<field name="email_to" readonly="1"/>
<field name="email_cc" readonly="1" attrs="{'invisible': [('email_cc','=',False)]}"/>
<field name="reply_to" readonly="1" attrs="{'invisible': [('reply_to','=',False)]}"/>
<field name="subject" readonly="1"/>
<form string="Email Preview" version="7.0">
<div>
<h2>Preview of <field name="name" readonly="1" class="oe_inline"/></h2>
<field name="model_id" invisible="1"/>
<h3>Using sample document
<field name="res_id" on_change="on_change_res_id(res_id, context)" class="oe_inline"/>
</h3>
</div>
<group>
<field name="email_from" readonly="1"/>
<field name="email_to" readonly="1"/>
<field name="email_cc" readonly="1" attrs="{'invisible':[('email_cc','=',False)]}"/>
<field name="reply_to" readonly="1" attrs="{'invisible':[('reply_to','=',False)]}"/>
<field name="subject" readonly="1"/>
<field name="body_html" widget="html" readonly="1"/>
</group>
<group col="4" colspan="4">
<notebook>
<page string="Body (Text)">
<field name="body" nolabel="1" colspan="4" height="350" width="350" readonly="1"/>
</page>
<page string="Body (Rich/HTML)" attrs="{'invisible': [('body_html','=', False)]}">
<field name="body_html" nolabel="1" colspan="4" height="350" width="350" readonly="1"/>
</page>
</notebook>
</group>
<field name="report_name" colspan="4" readonly="1"/>
</form>
</field>
</record>
@@ -27,13 +27,9 @@ from osv import fields
from tools.translate import _
class mail_compose_message(osv.osv_memory):
""" Inherit mail_compose_message to add email template feature in the
message composer. """
_inherit = 'mail.compose.message'
def _get_templates(self, cr, uid, context=None):
""" Return Email Template of particular Model. """
if context is None:
context = {}
model = False
@@ -52,7 +48,6 @@ class mail_compose_message(osv.osv_memory):
return []
def default_get(self, cr, uid, fields, context=None):
""" Override to handle templates. """
if context is None:
context = {}
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
@@ -65,43 +60,36 @@ class mail_compose_message(osv.osv_memory):
'template_id': fields.selection(_get_templates, 'Template', size=-1),
}
def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, res_id, context=None):
""" onchange_template_id: read or render the template if set, get back
to default values if not. """
def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
""" - use_template not set: return default_get
- use_template set:
- mass_mailing: we cannot render, so return the template values
- else: return rendered values """
fields = ['body', 'body_html', 'subject', 'partner_ids', 'email_to', 'email_cc']
if use_template and template_id:
# use the original template values, to be rendered when actually sent
if composition_mode == 'mass_mail':
values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
# render the mail as one-shot
else:
values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
# retrofit generated attachments in the expected field format
if values['attachments']:
attachment = values.pop('attachments')
attachment_obj = self.pool.get('ir.attachment')
att_ids = []
for fname, fcontent in attachment.iteritems():
data_attach = {
'name': fname,
'datas': fcontent,
'datas_fname': fname,
'description': fname,
'res_model' : self._name,
'res_id' : ids[0] if ids else False
}
att_ids.append(attachment_obj.create(cr, uid, data_attach))
values['attachment_ids'] = att_ids
else: # restore defaults
if use_template and template_id and composition_mode == 'mass_mail':
values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
elif use_template and template_id:
values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
values['attachment_ids'] = []
attachment_obj = self.pool.get('ir.attachment')
for attach_fname, attach_datas in values.get('attachments', []):
data_attach = {
'name': attach_fname,
'datas': attach_datas,
'datas_fname': attach_fname,
'description': fname,
'res_model': model,
'res_id': res_id,
}
values['attachment_ids'].append(attachment_obj.create(cr, uid, data_attach), context=context)
else:
values = self.default_get(cr, uid, fields, context=context)
if values.get('body_html') and not values.get('body'):
values['body'] = values.get('body_html')
values.update(use_template=use_template, template_id=template_id)
print 'returning ', values
return {'value': values}
def toggle_template(self, cr, uid, ids, context=None):
@@ -109,16 +97,15 @@ class mail_compose_message(osv.osv_memory):
emulate an on_change, then writes the value to update the form. """
for record in self.browse(cr, uid, ids, context=context):
onchange_res = self.onchange_use_template(cr, uid, ids, not record.use_template,
record.template_id, record.composition_mode, record.res_id, context=context)['value']
record.template_id, record.composition_mode, record.model, record.res_id, context=context)['value']
record.write(onchange_res)
return True
def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, res_id, context=None):
def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
""" onchange_use_template (values: True or False). If use_template is
False, we do like an onchange with template_id False for values """
onchange_template_values = self.onchange_template_id(cr, uid, ids, use_template,
template_id, composition_mode, res_id, context=context)
return onchange_template_values
return self.onchange_template_id(cr, uid, ids, use_template,
template_id, composition_mode, model, res_id, context=context)
def save_as_template(self, cr, uid, ids, context=None):
""" hit save as template button: current form value will be a new
@@ -144,9 +131,36 @@ class mail_compose_message(osv.osv_memory):
record.write({'template_id': template_id, 'use_template': True})
return True
#------------------------------------------------------
# Wizard validation and send
#------------------------------------------------------
def render_message(self, cr, uid, wizard, model, res_id, context=None):
""" Generate an email from the template for given (model, res_id) pair.
This method is meant to be inherited by email_template that will
produce a more complete dictionary, with email_to, ...
"""
# render the template to get the email
template_values = self.pool.get('email.template').generate_email(cr, uid, wizard.template_id, res_id, context=context)
# TDE TODO: handle fields to update / not to update (mail_server_id, auto_delete, ...)
# transform email_to, email_cc into partner_ids
partner_ids = []
mails = tools.email_split(template_values.pop('email_to', '') + ' ' + template_values.pop('email_cc', ''))
for mail in mails:
partner_search_ids = self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', mail)], context=context)
if partner_search_ids:
partner_ids.append((4, partner_search_ids[0]))
else:
partner_id = self.pool.get('res.partner').name_create(cr, uid, mail, context=context)[0]
partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
partner_ids.append((4, partner_id))
# get values to return
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, model, res_id, context)
email_dict.update(template_values)
email_dict.update(partner_ids=partner_ids, attachments=template_values.get('attachments'))
return email_dict
def render_template(self, cr, uid, template, model, res_id, context=None):
""" Override of mail.compose.message behavior: use the power of
templates ! """
return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -19,7 +19,7 @@
<group attrs="{'invisible':[('use_template','=',False)]}" colspan="4" col="4">
<field name="use_template" invisible="1"/>
<field name="template_id" colspan="3"
on_change="onchange_template_id(use_template, template_id, composition_mode, res_id, context)"/>
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
</group>
</xpath>
</data>
@@ -34,10 +34,10 @@
<data>
<xpath expr="//field[@name='partner_ids']" position="after">
<field name="use_template" colspan="2" nolabel="1" invisible="1"
on_change="onchange_use_template(use_template, template_id, composition_mode, res_id, context)"/>
on_change="onchange_use_template(use_template, template_id, composition_mode, model, res_id, context)"/>
<field name="template_id" colspan="2" nolabel="1"
attrs="{'invisible':[('use_template','=',False)]}"
on_change="onchange_template_id(use_template, template_id, composition_mode, res_id, context)"/>
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
</xpath>
<xpath expr="//button[@class='oe_mail_compose_message_attachment']" position="before">
<button icon="/email_template/static/src/img/email_template.png"
+1
View File
@@ -48,6 +48,7 @@ Note that:
'security/ir.model.access.csv',
'wizard/event_confirm_view.xml',
'event_view.xml',
'event_data.xml',
'report/report_event_registration_view.xml',
'board_association_view.xml',
'res_partner_view.xml',
+10 -10
View File
@@ -264,27 +264,27 @@ class event_event(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
message = _("Event has been <b>created</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="new", context=context)
return True
def button_cancel_send_note(self, cr, uid, ids, context=None):
message = _("Event has been <b>cancelled</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="cancelled", context=context)
return True
def button_draft_send_note(self, cr, uid, ids, context=None):
message = _("Event has been set to <b>draft</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="new", context=context)
return True
def button_done_send_note(self, cr, uid, ids, context=None):
message = _("Event has been <b>done</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="closed", context=context)
return True
def button_confirm_send_note(self, cr, uid, ids, context=None):
message = _("Event has been <b>confirmed</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="confirmed", context=context)
return True
event_event()
@@ -331,7 +331,7 @@ class event_registration(osv.osv):
def confirm_registration(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_('State set to open'), context=context)
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
return self.write(cr, uid, ids, {'state': 'open'},context=context)
def create(self, cr, uid, vals, context=None):
obj_id = super(event_registration, self).create(cr, uid, vals, context)
@@ -360,13 +360,13 @@ class event_registration(osv.osv):
if today >= registration.event_id.date_begin:
values = {'state': 'done', 'date_closed': today}
self.write(cr, uid, ids, values)
self.message_post(cr, uid, ids, body=_('State set to Done'), context=context)
self.message_post(cr, uid, ids, body=_('State set to Done'), subtype="closed", context=context)
else:
raise osv.except_osv(_('Error!'),_("You must wait for the starting day of the event to do this action.") )
return True
def button_reg_cancel(self, cr, uid, ids, context=None, *args):
self.message_post(cr, uid, ids, body=_('State set to Cancel'), context=context)
self.message_post(cr, uid, ids, body=_('State set to Cancel'), subtype="cancelled", context=context)
return self.write(cr, uid, ids, {'state': 'cancel'})
def mail_user(self, cr, uid, ids, context=None):
@@ -436,12 +436,12 @@ class event_registration(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
message = _("Registration has been <b>created</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="new", context=context)
return True
def do_draft_send_note(self, cr, uid, ids, context=None):
message = _("Registration has been set as <b>draft</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="new", context=context)
return True
event_registration()
+34 -8
View File
@@ -12,15 +12,41 @@
<field name="type">automatic</field>
<field name="sequence">100</field>
</record>
<!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification">
<field name="model">mail.group</field>
<field name="res_id" ref="mail.group_all_employees"/>
<field name="type">notification</field>
<field name="subject">Events Organisation application installed!</field>
<field name="body">From the top Events menu, you can organize events, manage registrations, automate communication around your event and sell events through your quotations.</field>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('event.model_event_event')),(4,ref('event.model_event_registration'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="model_ids" eval="[(4,ref('event.model_event_event')),(4,ref('event.model_event_registration'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('event.model_event_event')),(4,ref('event.model_event_registration'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_confirmed" model="mail.message.subtype">
<field name="name">confirmed</field>
<field name="model_ids" eval="[(4,ref('event.model_event_event')),(4,ref('event.model_event_registration'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('event.model_event_event')),(4,ref('event.model_event_registration'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('event.model_event_event')),(4,ref('event.model_event_registration'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('event.model_event_event')),(4,ref('event.model_event_registration'))]"/>
</record>
<!-- notify all employees of module installation -->
<function model="mail.group" name="message_post">
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
<value eval="[ref('mail.group_all_employees')]"/>
<value>From the top menu Events, you can organize events, manage registrations, automate communication around your event and sell events through your quotations.</value>
<value>Module Events Organisation has been installed</value>
</function>
</data>
</openerp>
+1 -1
View File
@@ -101,7 +101,7 @@
<field name="arch" type="xml">
<form string="Events" version="7.0">
<header>
<span groups="base.user_group">
<span groups="base.group_user">
<button string="Confirm Event" name="button_confirm" states="draft" type="object" class="oe_highlight"/>
<button string="Event Ended" name="button_done" states="confirm" type="object" class="oe_highlight"/>
<button string="Set To Draft" name="button_draft" states="cancel,done" type="object" />
+4 -4
View File
@@ -67,21 +67,21 @@ class idea_idea(osv.osv):
def idea_cancel(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, { 'state': 'cancel' })
self.message_post(cr, uid, ids, body=_('Idea canceled.'), context=context)
self.message_post(cr, uid, ids, body=_('Idea cancelled.'), subtype="cancelled", context=context)
return True
def idea_open(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, { 'state': 'open'})
self.message_post(cr, uid, ids, body=_('Idea accepted.'), context=context)
self.message_post(cr, uid, ids, body=_('Idea accepted.'), subtype="open", context=context)
return True
def idea_close(self, cr, uid, ids, context={}):
self.message_post(cr, uid, ids, body=_('Idea done.'), context=context)
self.message_post(cr, uid, ids, body=_('Idea closed.'), subtype="closed", context=context)
self.write(cr, uid, ids, { 'state': 'close' })
return True
def idea_draft(self, cr, uid, ids, context={}):
self.message_post(cr, uid, ids, body=_('Idea reset to draft.'), context=context)
self.message_post(cr, uid, ids, body=_('Idea reset to draft.'), subtype="new", context=context)
self.write(cr, uid, ids, { 'state': 'draft' })
return True
idea_idea()
+29
View File
@@ -15,6 +15,35 @@
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4,ref('base.group_tool_user'))]"/>
</record>
<!-- SUBTYPE -->
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('idea.model_idea_idea'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_open" model="mail.message.subtype">
<field name="name">open</field>
<field name="model_ids" eval="[(4,ref('idea.model_idea_idea'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('idea.model_idea_idea'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="model_ids" eval="[(4,ref('idea.model_idea_idea'))]"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('idea.model_idea_idea'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('idea.model_idea_idea'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('idea.model_idea_idea'))]"/>
</record>
</data>
</openerp>
+14 -7
View File
@@ -61,14 +61,21 @@ class mail_notification(osv.Model):
_columns = {
'partner_id': fields.many2one('res.partner', string='Contact',
ondelete='cascade', required=True, select=1),
ondelete='cascade', required=True),
'read': fields.boolean('Read'),
'message_id': fields.many2one('mail.message', string='Message',
ondelete='cascade', required=True),
}
_defaults = {
'read': False,
}
def init(self, cr):
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_message_id',))
if not cr.fetchone():
cr.execute('CREATE INDEX mail_notification_partner_id_read_message_id ON mail_notification (partner_id, read, message_id)')
def create(self, cr, uid, vals, context=None):
""" Override of create to check that we can not create a notification
for a message the user can not read. """
@@ -76,6 +83,11 @@ class mail_notification(osv.Model):
return super(mail_notification, self).create(cr, uid, vals, context=context)
return False
def set_message_read(self, cr, uid, msg_id, context=None):
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', '=', msg_id)], context=context)
return self.write(cr, uid, notif_ids, {'read': True}, context=context)
def notify(self, cr, uid, partner_ids, msg_id, context=None):
""" Send by email the notification depending on the user preferences """
context = context or {}
@@ -90,12 +102,7 @@ class mail_notification(osv.Model):
body_html = msg.body
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
if signature:
signature_block = u'\n<pre>%s</pre>\n' % signature
insertion_point = body_html.find('</html>')
if insertion_point > -1:
body_html = body_html[:insertion_point] + signature_block + body_html[insertion_point:]
else:
body_html += signature_block
body_html = tools.append_content_to_html(body_html, signature)
towrite = {
'mail_message_id': msg.id,
+41 -47
View File
@@ -31,11 +31,8 @@ from tools.translate import _
_logger = logging.getLogger(__name__)
class mail_mail(osv.Model):
"""
Model holding RFC2822 email messages to send. This model also provides
facilities to queue and send new email messages.
"""
""" Model holding RFC2822 email messages to send. This model also provides
facilities to queue and send new email messages. """
_name = 'mail.mail'
_description = 'Outgoing Mails'
_inherits = {'mail.message': 'mail_message_id'}
@@ -64,9 +61,7 @@ class mail_mail(osv.Model):
def _get_default_from(self, cr, uid, context=None):
cur = self.pool.get('res.users').browse(cr, uid, uid, context=context)
if not cur.alias_domain:
# TDE temp: impossible to install social module, because mail.group tries to send emails ...
# raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
pass
raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
return cur.alias_name + '@' + cur.alias_domain
_defaults = {
@@ -127,69 +122,68 @@ class mail_mail(osv.Model):
message.unlink()
return True
def send(self, cr, uid, ids, auto_commit=False, context=None):
"""Sends the selected emails immediately, ignoring their current
state (mails that have already been sent should not be passed
unless they should actually be re-sent).
Emails successfully delivered are marked as 'sent', and those
that fail to be deliver are marked as 'exception', and the
corresponding error message is output in the server logs.
def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
""" if void and related document: '<Author> posted on <Resource>'
:param mail: mail.mail browse_record """
if force or (not mail.subject and mail.model and mail.res_id):
return '%s posted on %s' % (mail.author_id.name, mail.record_name)
return mail.subject
:param bool auto_commit: whether to force a commit of the message
status after sending each message (meant
only for processing by the scheduler),
should never be True during normal
transactions (default: False)
:return: True
def send(self, cr, uid, ids, auto_commit=False, context=None):
""" Sends the selected emails immediately, ignoring their current
state (mails that have already been sent should not be passed
unless they should actually be re-sent).
Emails successfully delivered are marked as 'sent', and those
that fail to be deliver are marked as 'exception', and the
corresponding error mail is output in the server logs.
:param bool auto_commit: whether to force a commit of the mail status
after sending each mail (meant only for scheduler processing);
should never be True during normal transactions (default: False)
:return: True
"""
ir_mail_server = self.pool.get('ir.mail_server')
for message in self.browse(cr, uid, ids, context=context):
for mail in self.browse(cr, uid, ids, context=context):
try:
body = message.body_html
body = mail.body_html
subject = self._send_get_mail_subject(cr, uid, mail, context=context)
# handle attachments
attachments = []
for attach in message.attachment_ids:
for attach in mail.attachment_ids:
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
# no subject, res_id, model: '<Author> posted on <Resource>'
if not message.subject and message.model and message.res_id:
subject = '%s posted on %s' % (message.author_id.name, message.record_name)
else:
subject = message.subject
# use only sanitized html and set its plaintexted version as alternative
body_alternative = tools.html2plaintext(body)
content_subtype_alternative = 'plain'
# build an RFC2822 email.message.Message object and send it
# without queuing
# build an RFC2822 email.message.Message object and send it without queuing
msg = ir_mail_server.build_email(
email_from = message.email_from,
email_to = tools.email_split(message.email_to),
email_from = mail.email_from,
email_to = tools.email_split(mail.email_to),
subject = subject,
body = body,
body_alternative = body_alternative,
email_cc = tools.email_split(message.email_cc),
reply_to = message.reply_to,
email_cc = tools.email_split(mail.email_cc),
reply_to = mail.reply_to,
attachments = attachments,
message_id = message.message_id,
references = message.references,
object_id = message.res_id and ('%s-%s' % (message.res_id,message.model)),
message_id = mail.message_id,
references = mail.references,
object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
subtype = 'html',
subtype_alternative = content_subtype_alternative)
res = ir_mail_server.send_email(cr, uid, msg,
mail_server_id=message.mail_server_id.id, context=context)
mail_server_id=mail.mail_server_id.id, context=context)
if res:
message.write({'state':'sent', 'message_id': res})
mail.write({'state':'sent', 'message_id': res})
else:
message.write({'state':'exception'})
message.refresh()
if message.state == 'sent':
self._postprocess_sent_message(cr, uid, message, context=context)
mail.write({'state':'exception'})
mail.refresh()
if mail.state == 'sent':
self._postprocess_sent_message(cr, uid, mail, context=context)
except Exception:
_logger.exception('failed sending mail.mail %s', message.id)
message.write({'state':'exception'})
_logger.exception('failed sending mail.mail %s', mail.id)
mail.write({'state':'exception'})
if auto_commit == True:
cr.commit()
+4 -4
View File
@@ -59,7 +59,7 @@
</record>
<record model="ir.ui.view" id="view_mail_tree">
<field name="name">mail.message.tree</field>
<field name="name">mail.mail.tree</field>
<field name="model">mail.mail</field>
<field name="arch" type="xml">
<tree string="Emails" colors="grey:state in ('sent', 'cancel');blue:state=='outgoing';red:state=='exception';black:state=='received'">
@@ -113,15 +113,15 @@
</record>
<record id="action_view_mail_mail" model="ir.actions.act_window">
<field name="name">Messages</field>
<field name="name">Emails</field>
<field name="res_model">mail.mail</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_received': 1, 'search_default_type_email': 1}</field>
<field name="context">{'search_default_outgoing': 1, 'search_default_type_email': 1}</field>
<field name="search_view_id" ref="view_mail_search"/>
</record>
<!-- Add menu entry in Settings/Email -->
<menuitem name="Mails" id="menu_mail_mail" parent="base.menu_email" action="action_view_mail_mail" />
<menuitem name="Emails" id="menu_mail_mail" parent="base.menu_email" action="action_view_mail_mail" />
</data>
</openerp>
+54 -113
View File
@@ -34,11 +34,8 @@ def decode(text):
return ''.join([tools.ustr(x[0], x[1]) for x in text])
class mail_message(osv.Model):
"""Model holding messages: system notification (replacing res.log
notifications), comments (for OpenChatter feature). This model also
provides facilities to parse new email messages. Type of messages are
differentiated using the 'type' column. """
""" Messages model: system notification (replacing res.log notifications),
comments (OpenChatter discussion) and incoming emails. """
_name = 'mail.message'
_description = 'Message'
_inherit = ['ir.needaction_mixin']
@@ -76,21 +73,17 @@ class mail_message(osv.Model):
return res
def _search_unread(self, cr, uid, obj, name, domain, context=None):
""" Search for messages unread by the current user. """
read_value = not domain[0][2]
read_cond = '' if read_value else '!= true'
""" Search for messages unread by the current user. Condition is
inversed because we search unread message on a read column. """
if domain[0][2]:
read_cond = '(read = false or read is null)'
else:
read_cond = 'read = true'
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
cr.execute(""" SELECT mail_message.id \
FROM mail_message \
JOIN mail_notification ON ( \
mail_notification.message_id = mail_message.id ) \
WHERE mail_notification.partner_id = %%s AND \
mail_notification.read %s \
""" % read_cond, (partner_id,) )
res = cr.fetchall()
message_ids = [r[0] for r in res]
return [('id', 'in', message_ids)]
cr.execute("SELECT message_id FROM mail_notification "\
"WHERE partner_id = %%s AND %s" % read_cond,
(partner_id,))
return [('id', 'in', [r[0] for r in cr.fetchall()])]
def name_get(self, cr, uid, ids, context=None):
# name_get may receive int id instead of an id list
@@ -103,9 +96,8 @@ class mail_message(osv.Model):
return res
_columns = {
# should we keep a distinction between email and comment ?
'type': fields.selection([
('email', 'email'),
('email', 'Email'),
('comment', 'Comment'),
('notification', 'System notification'),
], 'Type',
@@ -147,7 +139,6 @@ class mail_message(osv.Model):
'author_id': _get_default_author
}
#------------------------------------------------------
# Message loading for web interface
#------------------------------------------------------
@@ -176,12 +167,10 @@ class mail_message(osv.Model):
def message_read_tree_flatten(self, cr, uid, messages, current_level, level, context=None):
""" Given a tree with several roots of following structure :
[
{'id': 1, 'child_ids':[
{'id': 11, 'child_ids': [...] },
] },
{...}
]
[ {'id': 1, 'child_ids': [
{'id': 11, 'child_ids': [...] },],
},
{...} ]
Flatten it to have a maximum number of level, with 0 being
completely flat.
Perform the flattening at leafs if above the maximum depth, then get
@@ -196,40 +185,29 @@ class mail_message(osv.Model):
return [msg_dict] + child_ids
# Depth-first flattening
for message in messages:
if message.get('type') == 'expandable':
continue
message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level+1, level, context=context)
# Flatten if above maximum depth
if current_level < level:
return messages
new_list = []
for x in range(0, len(messages)):
flatenned = _flatten(messages[x])
for flat in flatenned:
new_list.append(flat)
messages = new_list
return messages
def _debug_print_tree(self, tree, prefix=''):
for elem in tree:
print '%s%s (%s childs: %s)' % (prefix, elem['id'], len(elem['child_ids']), [xelem['id'] for xelem in elem['child_ids']])
if elem['child_ids']:
self._debug_print_tree(elem['child_ids'], prefix+'--')
for message in messages:
for flat_message in _flatten(message):
new_list.append(flat_message)
return new_list
def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None):
"""
If IDS are provided, fetch these records, otherwise use the domain to
fetch the matching records. After having fetched the records provided
by IDS, it will fetch children (according to thread_level).
Return [
]
""" If IDs are provided, fetch these records. Otherwise use the domain
to fetch the matching records.
After having fetched the records provided by IDs, it will fetch the
parents to have well-formed threads.
:return list: list of trees of messages
"""
limit = limit or self._message_read_limit
context = context or {}
if ids is False:
if not ids:
ids = self.search(cr, uid, domain, context=context, limit=limit)
# FP Todo: flatten to max X level of mail_thread
messages = self.browse(cr, uid, ids, context=context)
result = []
@@ -262,12 +240,10 @@ class mail_message(osv.Model):
break
# Flatten the result
# if thread_level > 0:
# result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
if thread_level > 0:
result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
return result
#------------------------------------------------------
# Email api
#------------------------------------------------------
@@ -277,52 +253,47 @@ class mail_message(osv.Model):
if not cr.fetchone():
cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
def check(self, cr, uid, ids, mode, context=None):
"""
You can read/write a message if:
- you received it (a notification exists) or
- you can read the related document (res_model, res_id)
If a message is not attached to a document, normal access rights on
the mail.message object apply.
"""
if not ids:
return
def check_access_rule(self, cr, uid, ids, operation, context=None):
""" mail.message access rule check
- message received (a notification exists) -> ok
- check rules of related document if exists
- fallback on normal mail.message check """
if isinstance(ids, (int, long)):
ids = [ids]
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
# check messages for which you have a notification
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
not_obj = self.pool.get('mail.notification')
not_ids = not_obj.search(cr, uid, [
('partner_id', '=', partner_id),
('message_id', 'in', ids),
], context=context)
for notification in not_obj.browse(cr, uid, not_ids, context=context):
if notification.message_id.id in ids:
pass
# FP Note: we should put this again !!!
#ids.remove(notification.message_id.id)
notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, uid, not_ids, context=context)
if notification.message_id.id in ids]
# check messages according to related documents
res_ids = {}
cr.execute('SELECT DISTINCT model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,))
for rmod, rid in cr.fetchall():
# check messages linked to an existing document
model_record_ids = {}
document_ids = []
cr.execute('SELECT DISTINCT id, model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,))
for id, rmod, rid in cr.fetchall():
if not (rmod and rid):
continue
res_ids.setdefault(rmod,set()).add(rid)
document_ids.append(id)
model_record_ids.setdefault(rmod,set()).add(rid)
for model, mids in model_record_ids.items():
model_obj = self.pool.get(model)
mids = model_obj.exists(cr, uid, mids)
model_obj.check_access_rights(cr, uid, operation)
model_obj.check_access_rule(cr, uid, mids, operation, context=context)
ima_obj = self.pool.get('ir.model.access')
for model, mids in res_ids.items():
mids = self.pool.get(model).exists(cr, uid, mids)
ima_obj.check(cr, uid, model, mode)
self.pool.get(model).check_access_rule(cr, uid, mids, mode, context=context)
# fall back on classic operation for other ids
other_ids = set(ids).difference(set(notified_ids), set(document_ids))
super(mail_message, self).check_access_rule(cr, uid, other_ids, operation, context=None)
def create(self, cr, uid, values, context=None):
if not values.get('message_id') and values.get('res_id') and values.get('model'):
values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s'% values)
newid = super(mail_message, self).create(cr, uid, values, context)
self.check(cr, uid, [newid], mode='create', context=context)
self.notify(cr, uid, newid, context=context)
return newid
@@ -351,39 +322,9 @@ class mail_message(osv.Model):
partners_to_notify |= extra_notified
self.pool.get('mail.notification').notify(cr, uid, list(partners_to_notify), newid, context=context)
def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
self.check(cr, uid, ids, 'read', context=context)
return super(mail_message, self).read(cr, uid, ids, fields_to_read, context, load)
def copy(self, cr, uid, id, default=None, context=None):
"""Overridden to avoid duplicating fields that are unique to each email"""
if default is None:
default = {}
self.check(cr, uid, [id], 'read', context=context)
default.update(message_id=False, headers=False)
return super(mail_message,self).copy(cr, uid, id, default=default, context=context)
def write(self, cr, uid, ids, vals, context=None):
result = super(mail_message, self).write(cr, uid, ids, vals, context)
self.check(cr, uid, ids, 'write', context=context)
return result
def unlink(self, cr, uid, ids, context=None):
self.check(cr, uid, ids, 'unlink', context=context)
return super(mail_message, self).unlink(cr, uid, ids, context)
class mail_notification(osv.Model):
""" mail_notification is a relational table modeling messages pushed to partners.
"""
_inherit = 'mail.notification'
_columns = {
'message_id': fields.many2one('mail.message', string='Message',
ondelete='cascade', required=True, select=1),
}
def set_message_read(self, cr, uid, msg_id, context=None):
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', '=', msg_id)], context=context)
return self.write(cr, uid, notif_ids, {'read': True}, context=context)
+5 -18
View File
@@ -208,10 +208,11 @@ class mail_thread(osv.Model):
# mail.message wrappers and tools
#------------------------------------------------------
def _needaction_domain_get(self, cr, uid, context={}):
def _needaction_domain_get(self, cr, uid, context=None):
if self._needaction:
return [('message_unread', '=', True)]
return []
#------------------------------------------------------
# Mail gateway
#------------------------------------------------------
@@ -460,29 +461,15 @@ class mail_thread(osv.Model):
continue
# 2) text/plain -> <pre/>
if part.get_content_type() == 'text/plain' and (not alternative or not body):
insertion_point = body.find('</html>')
# plain text is wrapped in <pre/> to preserve formatting
text = u'<pre>%s</pre>' % tools.ustr(part.get_payload(decode=True),
encoding, errors='replace')
if insertion_point != -1:
body = body[:insertion_point] + text + body[insertion_point:]
else:
body += text
body = tools.append_content_to_html(body, tools.ustr(part.get_payload(decode=True),
encoding, errors='replace'))
# 3) text/html -> raw
elif part.get_content_type() == 'text/html':
html = tools.ustr(part.get_payload(decode=True), encoding, errors='replace')
if alternative:
body = html
# force </html> tag to lowercase, for easier matching
body = re.sub(r'(?i)</html>', r'</html>', body)
else:
# strip enclosing html/body tags and append to existing body
html = re.sub(r'(?i)(</?html>|</?body>)', '', html)
insertion_point = body.find('</html>')
if insertion_point != -1:
body = body[:insertion_point] + html + body[insertion_point:]
else:
body += html
body = tools.append_content_to_html(body, html, plaintext=False)
# 4) Anything else -> attachment
else:
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
+90 -84
View File
@@ -19,9 +19,11 @@
#
##############################################################################
from openerp.tests import common
import base64
import tools
from openerp.tests import common
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
To: {to}
Received: by mail1.openerp.com (Postfix, from userid 10002)
@@ -203,17 +205,19 @@ class test_mail(common.TransactionCase):
user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'})
user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
# Subscribe Raoul twice (niak niak) through message_subscribe_users
# Subscribe Raoul three times (niak niak) through message_subscribe_users
group_pigs.message_subscribe_users([user_raoul_id, user_raoul_id])
group_pigs.message_subscribe_users([user_raoul_id])
group_pigs.refresh()
follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
self.assertEqual(follower_ids, set([user_raoul.partner_id.id, user_admin.partner_id.id]), 'Admin and Raoul should be the only 2 Pigs group followers')
follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
self.assertEqual(len(follower_ids), 2, 'There should be 2 Pigs fans')
self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]), 'Admin and Raoul should be the only 2 Pigs fans')
# Unsubscribe Raoul twice through message_unsubscribe_users
group_pigs.message_unsubscribe_users([user_raoul_id, user_raoul_id])
group_pigs.refresh()
follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin must be the only Pigs fan')
follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
self.assertEqual(follower_ids, [user_admin.partner_id.id], 'Admin must be the only Pigs fan')
def test_20_message_post(self):
""" Tests designed for message_post. """
@@ -243,10 +247,10 @@ class test_mail(common.TransactionCase):
_body2 = '<html>Pigs rules</html>'
_mail_body2 = '<html>Pigs rules\n<pre>Admin</pre>\n</html>'
_mail_bodyalt2 = 'Pigs rules\nAdmin\n'
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
# Post comment with body and subject, comment preference
# CASE1: post comment, body and subject specified
msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, msg_type='comment')
# Fetch: created mail_message, mail_mail, sent_email
message = self.mail_message.browse(cr, uid, msg_id)
mail_ids = self.mail_mail.search(cr, uid, [], limit=1)
mail = self.mail_mail.browse(cr, uid, mail_ids[0])
@@ -259,7 +263,7 @@ class test_mail(common.TransactionCase):
self.assertEqual(mail.subject, _subject, 'mail.mail subject incorrect')
self.assertEqual(mail.body_html, _mail_body1, 'mail.mail body_html incorrect')
self.assertEqual(mail.mail_message_id.id, msg_id, 'mail_mail.mail_message_id is not the id of its related mail_message)')
# Test: sent_email: email send by server: correct subject and body
# Test: sent_email: email send by server: correct subject, body; body_alternative
self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
self.assertEqual(sent_email['body'], _mail_body1, 'sent_email body incorrect')
self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1, 'sent_email body_alternative is incorrect')
@@ -274,23 +278,24 @@ class test_mail(common.TransactionCase):
# Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
# New post: test automatic subject, signature in html, add a partner, email preference, parent_id previous message
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, msg_type='email', partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id)
# Fetch: created mail_message, mail_mail, sent_email
# CASE2: post an email with attachments, parent_id, partner_ids
# TESTS: automatic subject, signature in body_html, attachments propagation
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, msg_type='email',
partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
message = self.mail_message.browse(cr, uid, msg_id2)
mail_ids = self.mail_mail.search(cr, uid, [], limit=1)
mail = self.mail_mail.browse(cr, uid, mail_ids[0])
sent_email = self._build_email_kwargs
# Test: mail_message: subject is False, body is _body1 (no formatting done), parent_id is msg_id
# Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
self.assertEqual(message.subject, False, 'mail.message subject incorrect')
self.assertEqual(message.body, _body2, 'mail.message body incorrect')
self.assertEqual(message.parent_id.id, msg_id, 'mail.message parent_id incorrect')
# Test: mail_mail: subject is False, body_html is _mail_body1 (signature appended)
# Test: mail_mail: subject is False, body_html is _mail_body2 (signature appended)
self.assertEqual(mail.subject, False, 'mail.mail subject is incorrect')
self.assertEqual(mail.body_html, _mail_body2, 'mail.mail body_html incorrect')
self.assertEqual(mail.mail_message_id.id, msg_id2, 'mail_mail.mail_message_id incorrect')
# Test: sent_email: email send by server: correct subject and body
# Test: sent_email: email send by server: correct subject, body, body_alternative
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
self.assertEqual(sent_email['body'], _mail_body2, 'sent_email body incorrect')
self.assertEqual(sent_email['body_alternative'], _mail_bodyalt2, 'sent_email body_alternative incorrect')
@@ -298,17 +303,20 @@ class test_mail(common.TransactionCase):
message_pids = set([partner.id for partner in message.partner_ids])
test_pids = set([p_a_id, p_b_id, p_c_id, p_d_id])
self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
# Test: notification linked to this message = group followers = partner_ids
# Test: notifications linked to this message = group followers = partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
# Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
self.assertEqual(set(sent_email['email_to']), set(['b@b', 'c@c']), 'sent_email email_to incorrect')
# Test: attachments
for i in range(len(message.attachment_ids)):
self.assertEqual(message.attachment_ids[i].name, _attachments[i][0], 'mail.message attachment name incorrect')
self.assertEqual(message.attachment_ids[i].res_model, 'mail.group', 'mail.message attachment res_model incorrect')
self.assertEqual(message.attachment_ids[i].res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertEqual(base64.b64decode(message.attachment_ids[i].datas), _attachments[i][1], 'mail.message attachment data incorrect')
def test_21_message_post_attachments(self):
""" Tests designed for attachments. """
def test_22_message_compose_wizard(self):
def test_21_message_compose_wizard(self):
""" Tests designed for the mail.compose.message wizard. """
cr, uid = self.cr, self.uid
mail_compose = self.registry('mail.compose.message')
@@ -318,11 +326,16 @@ class test_mail(common.TransactionCase):
# Mail data
_subject = 'Pigs'
_subject_reply = 'Re: Pigs'
_mail_subject = '%s posted on %s' % (user_admin.name, group_pigs.name)
_body_text = 'Pigs rules'
_msg_body1 = '<pre>Pigs rules</pre>'
_body_html = '<html>Pigs rules</html>'
_msg_body2 = '<html>Pigs rules</html>'
_attachments = [
{'name': 'First', 'datas': base64.b64encode('My first attachment')},
{'name': 'Second', 'datas': base64.b64encode('My second attachment')}
]
# Create partners
# 0 - Admin
@@ -337,46 +350,59 @@ class test_mail(common.TransactionCase):
# Subscribe #1
group_pigs.message_subscribe([p_b_id])
# Comment group_pigs: body_text
# CASE1: comment group_pigs with body_text and subject
compose_id = mail_compose.create(cr, uid,
{'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
{'mail.compose.message.mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id})
compose = mail_compose.browse(cr, uid, compose_id)
# Test: model, res_id
self.assertTrue(compose.model == 'mail.group' and compose.res_id == self.group_pigs_id,
'mail.compose.message has model %s and res_id %s; should be mail.group and %s' % (compose.model, compose.res_id, self.group_pigs_id))
# Test: mail.compose.message: model, res_id
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id])
group_pigs.refresh()
msg = group_pigs.message_ids[0]
message = group_pigs.message_ids[0]
# Test: mail.message: subject, body inside pre
self.assertTrue(msg.subject == False and msg.body == _msg_body1,
'mail.message subject is %s, body is %s; should be %s and %s' % (msg.subject, msg.body, False, _msg_body1))
# Test: mail.message partners = notified people: group_pigs followers (a, b) + mail.compose.message partner_ids (c, d)
msg_pids = [partner.id for partner in msg.partner_ids]
self.assertEqual(message.subject, False, 'mail.message incorrect subject')
self.assertEqual(message.body, _msg_body1, 'mail.message incorrect body')
# Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
msg_pids = [partner.id for partner in message.partner_ids]
test_pids = [p_a_id, p_b_id, p_c_id, p_d_id]
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg.id)])
self.assertTrue(len(msg_pids) == 4, 'There are %s partners linked to the newly posted comment; should be 4' % (len(msg_pids)))
self.assertTrue(len(notif_ids) == 4, 'There are %s entries in mail_notification: should be 4' % (len(notif_ids)))
self.assertTrue(all(id in msg_pids for id in test_pids) and len(msg_pids) == len(test_pids),
'Admin, Bert Raoul and Roger should be the 4 partners of the newly created message')
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
self.assertEqual(len(notif_ids), 4, 'mail.message: too much notifications created')
self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect')
# Create a reply to the last comment
# CASE2: reply to last comment (update its subject) with attachments
message.write({'subject': _subject})
compose_id = mail_compose.create(cr, uid,
{}, {'mail.compose.message.mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id,
'active_id': msg.id})
{'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
{'mail.compose.message.mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'active_id': message.id})
compose = mail_compose.browse(cr, uid, compose_id)
# Test: model, res_id, parent_id
self.assertTrue(compose.model == 'mail.group' and compose.res_id == self.group_pigs_id,
'Wizard message has model: %s and res_id:%s; should be mail.group and %s' % (compose.model, compose.res_id, self.group_pigs_id))
self.assertEqual(compose.parent_id.id, msg.id,
'Wizard parent_id is %d; should be %d' % (compose.parent_id.id, msg.id))
# Test: form view methods
# Test: model, res_id, parent_id, content_subtype
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
# 3 - Create in mass_mail composition mode that should work with or without email_template installed
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id])
group_pigs.refresh()
message = group_pigs.message_ids[0]
# Test: subject as Re:.., body in html
self.assertEqual(message.subject, _subject_reply, 'mail.message incorrect subject')
self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote></div>', message.body, 'mail.message body is incorrect')
# Test: attachments
for i in range(len(message.attachment_ids)):
self.assertEqual(message.attachment_ids[i].name, _attachments[i]['name'], 'mail.message attachment name incorrect')
self.assertEqual(message.attachment_ids[i].res_model, 'mail.group', 'mail.message attachment res_model incorrect')
self.assertEqual(message.attachment_ids[i].res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertEqual(base64.b64decode(message.attachment_ids[i].datas), base64.b64decode(_attachments[i]['datas']), 'mail.message attachment data incorrect')
# CASE3 - Create in mass_mail composition mode that should work with or without email_template installed
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,
@@ -386,27 +412,16 @@ class test_mail(common.TransactionCase):
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id]})
group_pigs.refresh()
msg = group_pigs.message_ids[0]
message = group_pigs.message_ids[0]
# Test: last message on Pigs = last created message
test_msg = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [], limit=1))[0]
self.assertEqual(msg.id, test_msg.id, 'Pigs did not receive its mass mailing message')
self.assertEqual(message.id, test_msg.id, 'Pigs did not receive its mass mailing message')
# Test: mail.message: subject, body
self.assertEqual(msg.subject, _subject, 'mail.message subject is incorrect')
self.assertEqual(msg.body, group_pigs.description, 'mail.message body is incorrect')
self.assertEqual(message.subject, _subject, 'mail.message subject is incorrect')
self.assertEqual(message.body, group_pigs.description, 'mail.message body is incorrect')
def test_30_message_read(self):
""" Tests designed for message_read. """
def _simplify_struct(read_dict):
res = []
for val in read_dict:
current = {'_id': val['id']}
if val.get('child_ids'):
current['child_ids'] = _simplify_struct(val.get('child_ids'))
res.append(current)
return res
# TDE NOTE: this test is not finished, as the message_read method is not fully specified.
# It wil be updated as soon as we have fixed specs !
cr, uid = self.cr, self.uid
@@ -414,38 +429,31 @@ class test_mail(common.TransactionCase):
# Test message_read_tree_flatten that flattens a thread according to a given thread_level
import copy
tree = [
{'id': 1, 'child_ids':[
{'id': 3, 'child_ids': [] },
{'id': 4, 'child_ids': [
{'id': 5, 'child_ids': []},
{'id': 12, 'child_ids': []},
tree = [{'id': 1, 'child_ids':[
{'id': 3, 'child_ids': [] },
{'id': 4, 'child_ids': [
{'id': 5, 'child_ids': []},
{'id': 12, 'child_ids': []},
] },
{'id': 8, 'child_ids': [
{'id': 10, 'child_ids': []},
] },
] },
{'id': 8, 'child_ids': [
{'id': 10, 'child_ids': []},
{'id': 2, 'child_ids': [
{'id': 7, 'child_ids': [
{'id': 9, 'child_ids': []},
] },
] },
] },
{'id': 2, 'child_ids': [
{'id': 7, 'child_ids': [
{'id': 9, 'child_ids': []},
{'id': 6, 'child_ids': [
{'id': 11, 'child_ids': [] },
] },
] },
{'id': 6, 'child_ids': [
{'id': 11, 'child_ids': [] },
] },
]
]
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 0)
# self.mail_message._debug_print_tree(new_tree)
# print '-------------------'
self.assertTrue(len(new_tree) == 12, 'Flattening wrongly produced')
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 1)
# self.mail_message._debug_print_tree(new_tree)
# print '-------------------'
self.assertTrue(len(new_tree) == 3 and len(new_tree[0]['child_ids']) == 6 and len(new_tree[1]['child_ids']) == 2 and len(new_tree[2]['child_ids']) == 1,
'Flattening wrongly produced')
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 2)
# self.mail_message._debug_print_tree(new_tree)
# print '-------------------'
self.assertTrue(len(new_tree) == 3 and len(new_tree[0]['child_ids']) == 3 and len(new_tree[0]['child_ids'][1]) == 2,
'Flattening wrongly produced')
@@ -465,12 +473,10 @@ class test_mail(common.TransactionCase):
# Second try: read with thread_level 1
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1)
# self.mail_message._debug_print_tree(tree)
self.assertTrue(len(tree) == 2 and len(tree[1]['child_ids']) == 3, 'Incorrect number of child in message_read')
# Third try: read with thread_level 2
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2)
# self.mail_message._debug_print_tree(tree)
self.assertTrue(len(tree) == 2 and len(tree[1]['child_ids']) == 2 and len(tree[1]['child_ids'][0]['child_ids']) == 1, 'Incorrect number of child in message_read')
def test_40_needaction(self):
+34 -44
View File
@@ -19,6 +19,7 @@
#
##############################################################################
import base64
import re
import tools
@@ -71,12 +72,12 @@ class mail_compose_message(osv.TransientModel):
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'))
active_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
if composition_mode == 'reply':
vals = self.get_message_data(cr, uid, active_id, context=context)
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:
@@ -154,7 +155,7 @@ class mail_compose_message(osv.TransientModel):
message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context)
# create subject
re_prefix = _("Re:")
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)
@@ -162,7 +163,7 @@ class mail_compose_message(osv.TransientModel):
reply_header = _('On %(date)s, %(sender_name)s wrote:') % {
'date': message_data.date if message_data.date else '',
'sender_name': message_data.author_id.name }
reply_body = '<div>%s<blockquote>%s</blockquote></div>%s' % (reply_header, message_data.body, current_user.signature)
reply_body = '<div>%s<blockquote>%s</blockquote></div>' % (reply_header, 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 []
@@ -223,6 +224,22 @@ class mail_compose_message(osv.TransientModel):
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. """
@@ -242,19 +259,18 @@ class mail_compose_message(osv.TransientModel):
'subject': wizard.subject if wizard.content_subtype == 'html' else False,
'body': wizard.body if wizard.content_subtype == 'html' else '<pre>%s</pre>' % tools.ustr(wizard.body_text),
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
'attachments': [(attach.datas_fname, attach.datas) for attach in wizard.attachment_ids],
'attachments': [(attach.name or attach.datas_fname, 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, wizard.model, res_id, context=context)
post_values['subject'] = email_dict.get('subject', False)
post_values['body'] = email_dict.get('body', '')
if email_dict.get('partner_ids'):
post_values['partner_ids'] += [(4, id) for id in email_dict.get('partner_ids')]
if email_dict.get('attachments'):
post_values['attachments'] += [(attach.datas_fname, attach.datas) for attach in email_dict.get('attachments')]
new_partner_ids = email_dict.pop('partner_ids', [])
post_values['partner_ids'] += 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_ids, msg_type='comment', context=context, **post_values)
active_model_pool.message_post(cr, uid, [res_id], msg_type='comment', context=context, **post_values)
return {'type': 'ir.actions.act_window_close'}
@@ -263,20 +279,9 @@ class mail_compose_message(osv.TransientModel):
This method is meant to be inherited by email_template that will
produce a more complete dictionary, with email_to, ...
"""
# TDE: TODO: to move into mail.compose.message in email template
# mails = tools.email_split(email_dict.get('email_to', '')) + tools.email_split(email_dict.get('email_cc', ''))
# for mail in mails:
# partner_search_ids = self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', email)], context=context)
# if partner_search_ids:
# partner_ids.append((4, 0, partner_search_ids[0]))
# else:
# partner_id = self.pool.get('res.partner').name_create(cr, uid, values['email_to'], context=context)
# partner_ids.append((4, 0, partner_id))
return {
'subject': self.render_template(cr, uid, wizard.subject, model, res_id, context),
'body': self.render_template(cr, uid, wizard.body, model, res_id, context),
'partner_ids': [],
'attachment_ids': [],
}
def render_template(self, cr, uid, template, model, res_id, context=None):
@@ -297,25 +302,10 @@ class mail_compose_message(osv.TransientModel):
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)
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
+23 -33
View File
@@ -985,43 +985,14 @@
</data>
</field>
</record>
<record id="product_normal_form_supply_view" model="ir.ui.view">
<record id="product_normal_form_supply_view" model="ir.ui.view">
<field name="name">product.normal.form.mrp.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='procure_method']" position="after">
<field name="supply_method" groups="base.group_user"/>
</xpath>
</field>
</record>
<record id="product_normal_form_view" model="ir.ui.view">
<field name="name">product.normal.form.mrp.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form"/>
<field name="arch" type="xml">
<page string="Procurement &amp; Locations" position="inside">
<separator string="Bill of Materials"/>
<field name="bom_ids" widget="one2many_list" mode="tree" context="{'default_product_id': active_id}">
<tree string="Bill of Materials">
<field name="name" string="Component Name"/>
<field name="bom_id"/>
<field name="product_qty" string="Quantity of Product"/>
<field name="type"/>
<field name="date_start"/>
<field name="date_stop"/>
</tree>
<form string="Bill of Material">
<field name="name" string="Component Name"/>
<field name="bom_id"/>
<field name="product_qty" string="Quantity of Product"/>
<field name="type"/>
<field name="date_start"/>
<field name="date_stop"/>
</form>
</field>
</page>
<xpath expr="//field[@name='procure_method']" position="after">
<field name="supply_method" groups="base.group_user"/>
</xpath>
</field>
</record>
@@ -1079,5 +1050,24 @@
id="menu_mrp_production_order_action" name="Orders Planning"
parent="menu_mrp_planning" sequence="1"/>
<record model="ir.actions.act_window" id="product_open_bom">
<field name="context">{'default_product_id': active_id, 'search_default_product_id': active_id}</field>
<field name="name">Bill of Materials</field>
<field name="res_model">mrp.bom</field>
</record>
<record model="ir.ui.view" id="product_form_view_bom_button">
<field name="name">product.product.procurement</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button string="Bill of Materials" name="%(product_open_bom)d" type="action"/>
</xpath>
</field>
</record>
</data>
</openerp>
+1 -1
View File
@@ -26,7 +26,7 @@ from tools.translate import _
class product_product(osv.osv):
_inherit = "product.product"
_columns = {
"bom_ids": fields.one2many('mrp.bom', 'product_id','Bill of Materials'),
"bom_ids": fields.one2many('mrp.bom', 'product_id','Bill of Materials', domain=[('bom_id','=',False)]),
}
def copy(self, cr, uid, id, default=None, context=None):
if not default:
+14 -27
View File
@@ -261,12 +261,12 @@
</record>
<act_window
context="{'search_default_warehouse_id': active_id, 'default_warehouse_id': active_id}"
id="act_stock_warehouse_2_stock_warehouse_orderpoint"
name="Reordering Rules"
res_model="stock.warehouse.orderpoint"
src_model="stock.warehouse"
groups="stock.group_stock_user"/>
context="{'search_default_warehouse_id': active_id, 'default_warehouse_id': active_id}"
id="act_stock_warehouse_2_stock_warehouse_orderpoint"
name="Reordering Rules"
res_model="stock.warehouse.orderpoint"
src_model="stock.warehouse"
groups="stock.group_stock_user"/>
<act_window
context="{'product_uom': locals().has_key('uom_id') and uom_id, 'default_procurement_id': active_id}"
@@ -283,23 +283,11 @@
<menuitem id="menu_stock_procurement" name="Automatic Procurements" parent="stock.menu_stock_configuration" sequence="5"/>
<menuitem action="action_orderpoint_form" id="menu_stock_order_points" parent="stock.menu_stock_configuration" sequence="10"/>
<record id="product_normal_form_view" model="ir.ui.view">
<field name="name">product.normal.form.orderpoint.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form"/>
<field name="arch" type="xml">
<page string="Procurement &amp; Locations" position="inside">
<separator string="Minimum Stock Rules"/>
<field name="orderpoint_ids" widget="one2many_list" mode="tree" context="{'default_product_id': active_id}">
<tree string="Reordering Rules">
<field name="name"/>
<field name="warehouse_id"/>
<field name="product_min_qty"/>
<field name="product_max_qty"/>
</tree>
</field>
</page>
</field>
<record model="ir.actions.act_window" id="product_open_orderpoint">
<field name="context">{'default_product_id': active_id, 'search_default_product_id': active_id}</field>
<field name="name">Orderpoints</field>
<field name="res_model">stock.warehouse.orderpoint</field>
</record>
<record model="ir.ui.view" id="product_form_view_procurement_button">
@@ -307,10 +295,9 @@
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='image_medium']" position="after">
<div class='oe_right oe_button_box'>
<button string="Procurement Request" name="%(act_make_procurement)d" type="action"/>
</div>
<xpath expr="//div[@name='buttons']" position="inside">
<button string="Request Procurement" name="%(act_make_procurement)d" type="action"/>
<button string="Orderpoints" name="%(product_open_orderpoint)d" type="action"/>
</xpath>
</field>
</record>
+2
View File
@@ -72,6 +72,8 @@
<label for="categ_id" class="oe_edit_only"/>
<h2><field name="categ_id"/></h2>
</div>
<div class="oe_right oe_button_box" name="buttons">
</div>
<notebook>
<page string="Information">
<group>
+5 -5
View File
@@ -735,7 +735,7 @@ class purchase_order(osv.osv):
return [('state','=','draft')]
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), subtype="new", context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
@@ -753,20 +753,20 @@ class purchase_order(osv.osv):
def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
for order in self.browse(cr, uid, ids, context=context):
for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), subtype="pending", context=context)
def shipment_done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), subtype="received", context=context)
def invoice_done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), subtype="paid", context=context)
def draft_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), subtype="cancelled", context=context)
purchase_order()
+32
View File
@@ -49,5 +49,37 @@ You can also manage purchase requisitions, see also the Purchase Settings.</fiel
model="ir.values"
name="set"/>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_paid" model="mail.message.subtype">
<field name="name">paid</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
<record id="mail.mail_subtype_pending" model="mail.message.subtype">
<field name="name">pending</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_received" model="mail.message.subtype">
<field name="name">received</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
</data>
</openerp>
@@ -94,13 +94,13 @@ class purchase_requisition(osv.osv):
self.message_post(cr, uid, ids, body=_("Draft Requisition has been <b>sent to suppliers</b>."), context=context)
def reset_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), subtype="new", context=context)
def done_to_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), subtype="closed", context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), subtype="cancelled", context=context)
def _planned_date(self, requisition, delay=0.0):
company = requisition.company_id
@@ -184,7 +184,7 @@ class purchase_requisition(osv.osv):
return res
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>created</b>."), context=context)
return self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>created</b>."), subtype="new", context=context)
def create(self, cr, uid, vals, context=None):
requisition = super(purchase_requisition, self).create(cr, uid, vals, context=context)
@@ -1,10 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<function
<function
eval="('default',False,'warehouse_id', [('purchase.requisition', False)], ref('stock.warehouse0'), True, False, False, False, True)"
id="purchase_default_set"
model="ir.values"
name="set"/>
<!-- SUBTYPE -->
<record id="mail.mail_subtype_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
</data>
</openerp>
+5 -5
View File
@@ -1027,7 +1027,7 @@ class sale_order(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>created</b>.") % (obj.partner_id.name), context=context)
self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), subtype="new", context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
@@ -1035,7 +1035,7 @@ class sale_order(osv.osv):
def cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Sale Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
self.message_post(cr, uid, [obj.id], body=_("Sale Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), subtype="cancelled", context=context)
def delivery_send_note(self, cr, uid, ids, picking_id, context=None):
for order in self.browse(cr, uid, ids, context=context):
@@ -1047,15 +1047,15 @@ class sale_order(osv.osv):
self.message_post(cr, uid, [order.id], body=_("Delivery Order <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
def delivery_end_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Order <b>delivered</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Order <b>delivered</b>."), subtype="delivered", context=context)
def invoice_paid_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), subtype="paid", context=context)
def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
for order in self.browse(cr, uid, ids, context=context):
for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), subtype="pending", context=context)
def action_cancel_draft_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_('Sale order set to draft.'), context=context)
+39 -7
View File
@@ -41,14 +41,46 @@
<function eval="('default',False,'shop_id', [('sale.order', False)], sale_shop_1, True, False, False, False, True)" id="sale_default_set" model="ir.values" name="set"/>
<!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification">
<field name="model">mail.group</field>
<field name="res_id" ref="mail.group_all_employees"/>
<field name="type">notification</field>
<field name="subject">Sales Management application installed!</field>
<field name="body">This application lets you create and send quotations and process your sales orders; from delivery to invoicing.
<function model="mail.group" name="message_post">
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
<value eval="[ref('mail.group_all_employees')]"/>
<value>The Sales Management application has been installed.</value>
<value>This modules allows you to create and send easily quotations and process your sales orders; from the delivery to the invoicing.
If you need to manage your sales pipeline (leads, opportunities, phonecalls), the &lt;i&gt;CRM&lt;/i&gt; application may be useful. Use the Settings menu to install it.</field>
If you need to manage your sales pipeline (leads, opportunities, phonecalls), you can install the module &lt;i&gt;CRM&lt;/i&gt; from the top menu Settings.</value>
</function>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_paid" model="mail.message.subtype">
<field name="name">paid</field>
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
</record>
<record id="mail.mail_subtype_pending" model="mail.message.subtype">
<field name="name">pending</field>
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_delivered" model="mail.message.subtype">
<field name="name">delivered</field>
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
</record>
</data>
</openerp>