[MERGE] Merge with parent branch
bzr revid: fka@tinyerp.com-20120903051925-r01yx7u7nvono353 bzr revid: fka@tinyerp.com-20120903070434-on2thgronzgnk5am
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 & 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 & 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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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 & 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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 & 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 <i>CRM</i> 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 <i>CRM</i> 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>
|
||||
|
||||
Reference in New Issue
Block a user