[MERGE]:trunk-mail-cleaning-fp

bzr revid: apa@tinyerp.com-20120906061835-gb317yvqnd5vaiei
This commit is contained in:
Amit Patel (OpenERP) 2012-09-06 11:48:35 +05:30
commit 1334eb7a0d
47 changed files with 583 additions and 406 deletions

View File

@ -383,7 +383,7 @@
<field name="type">cash</field>
<field name="profit_account_id" model="account.account" ref="rsa" />
<field name="loss_account_id" model="account.account" ref="rsa" />
<field name="internal_account_id" model="account.account" ref="chart0" />
<field name="internal_account_id" model="account.account" ref="rsa" />
<field name="with_last_closing_balance" eval="True" />
<field name="cash_control" eval="True" />
<field name="view_id" ref="account_journal_bank_view"/>

View File

@ -40,6 +40,10 @@ class CashBox(osv.osv_memory):
return {}
def _create_bank_statement_line(self, cr, uid, box, record, context=None):
values = self._compute_values_for_statement_line(cr, uid, box, record, context=context)
return self.pool.get('account.bank.statement.line').create(cr, uid, values, context=context)
class CashBoxIn(CashBox):
_name = 'cash.box.in'
@ -49,30 +53,24 @@ class CashBoxIn(CashBox):
'ref' : fields.char('Reference', size=32),
})
def _create_bank_statement_line(self, cr, uid, box, record, context=None):
absl_proxy = self.pool.get('account.bank.statement.line')
values = {
def _compute_values_for_statement_line(self, cr, uid, box, record, context=None):
return {
'statement_id' : record.id,
'journal_id' : record.journal_id.id,
'account_id' : record.journal_id.internal_account_id.id,
'amount' : box.amount or 0.0,
'ref' : "%s" % (box.ref or ''),
'ref' : '%s' % (box.ref or ''),
'name' : box.name,
}
return absl_proxy.create(cr, uid, values, context=context)
CashBoxIn()
class CashBoxOut(CashBox):
_name = 'cash.box.out'
def _create_bank_statement_line(self, cr, uid, box, record, context=None):
absl_proxy = self.pool.get('account.bank.statement.line')
def _compute_values_for_statement_line(self, cr, uid, box, record, context=None):
amount = box.amount or 0.0
values = {
return {
'statement_id' : record.id,
'journal_id' : record.journal_id.id,
'account_id' : record.journal_id.internal_account_id.id,
@ -80,6 +78,4 @@ class CashBoxOut(CashBox):
'name' : box.name,
}
return absl_proxy.create(cr, uid, values, context=context)
CashBoxOut()

View File

@ -233,12 +233,7 @@ class account_followup_print_all(osv.osv_memory):
total_amt += line.debit - line.credit
dest = False
if partner:
if partner.type=='contact':
if adr.email:
dest = [partner.email]
if (not dest) and partner.type=='default':
if partner.email:
dest = [partner.email]
dest = [partner.email]
if not data.partner_lang:
body = data.email_body
else:

View File

@ -4,7 +4,7 @@
<t t-extend="Login">
<t t-jquery=".oe_login .oe_login_logo" t-operation="after">
<ul class="openid_providers">
<ul class="openid_providers oe_semantic_html_override">
<li><a href="#login,password" title="Password" data-url="" id="btn_password">Password</a></li>
<li><a href="#google" title="Google" data-url="https://www.google.com/accounts/o8/id">Google</a></li>
<li><a href="#googleapps" title="Google Apps" data-url="https://www.google.com/accounts/o8/site-xrds?hd={id}">Google</a></li>

View File

@ -36,7 +36,7 @@
</div>
</t>
<t t-name="DashBoard.layouts">
<div class="oe_dashboard_layout_selector">
<div class="oe_dashboard_layout_selector oe_semantic_html_override">
<p>
<strong>Choose dashboard layout</strong>
</p>

View File

@ -298,7 +298,7 @@
<field name="message_unread"/>
<templates>
<t t-name="lead_details">
<ul class="oe_kanban_tooltip">
<ul class="oe_kanban_tooltip oe_semantic_html_override">
<li t-if="record.phone.raw_value"><b>Phone:</b> <field name="phone"/></li>
<li><b>Probability:</b> <field name="probability"/>%%</li>
<li><b>Creation date:</b> <field name="create_date"/></li>
@ -307,11 +307,11 @@
</t>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
<div class="oe_dropdown_toggle oe_dropdown_kanban">
<div class="oe_dropdown_toggle oe_dropdown_kanban oe_semantic_html_override">
<span class="oe_e">í</span>
<ul class="oe_dropdown_menu">
<li><a type="edit" >Edit...</a></li>
<li><a type="delete">Delete</a></li>
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
<t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
<li><a name="%(mail.action_email_compose_message_wizard)d" type="action">Send Email</a></li>
<li><a name="%(opportunity2phonecall_act)d" type="action">Log Call</a></li>
<li><a name="action_makeMeeting" type="object">Schedule Meeting</a></li>

View File

@ -6,7 +6,7 @@
<t t-call="WebClient"/>
</t>
<t t-name="EdiView">
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" id="oe_app" class="oe-application oe_forms">
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" id="oe_app" class="oe-application oe_forms oe_semantic_html_override">
<tr>
<td colspan="2" valign="top" id="oe_header" class="header">
<div> <a href="/" class="company_logo_link">

View File

@ -151,7 +151,7 @@
reference <strong><t t-esc="doc.internal_number"/></strong> on the transfer:
<br/><br/>
</p>
<ul class="oe_edi_nested_block_pay oe_edi_pay_wire_nested">
<ul class="oe_edi_nested_block_pay oe_edi_pay_wire_nested oe_semantic_html_override">
<t t-foreach="doc.company_address.bank_ids" t-as="bank_info">
<li><t t-esc="bank_info[1]"/></li>
</t>

View File

@ -151,7 +151,7 @@
reference <strong><t t-esc="doc.name"/></strong> on the transfer:
<br/><br/>
</p>
<ul class="oe_edi_nested_block_pay oe_edi_pay_wire_nested">
<ul class="oe_edi_nested_block_pay oe_edi_pay_wire_nested oe_semantic_html_override">
<t t-foreach="doc.company_address.bank_ids" t-as="bank_info">
<li><t t-esc="bank_info[1]"/></li>
</t>

View File

@ -99,7 +99,7 @@ class email_template(osv.osv):
mod_name = False
if model_id:
mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
return {'value':{'model': mod_name}}
return {'value': {'model': mod_name}}
_columns = {
'name': fields.char('Name'),
@ -156,6 +156,10 @@ class email_template(osv.osv):
'copyvalue': fields.char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field."),
}
_defaults = {
'auto_delete': True,
}
def create_action(self, cr, uid, ids, context=None):
vals = {}
action_obj = self.pool.get('ir.actions.act_window')
@ -277,14 +281,12 @@ class email_template(osv.osv):
context = {}
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}
values = {}
for field in ['subject', 'body_html', 'email_from',
'email_to', 'email_cc', 'reply_to']:
values[field] = self.render_template(cr, uid, getattr(template, field),
template.model, res_id, context=context) \
or False
if template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body_html'] = append_content_to_html(values['body_html'], signature)
@ -292,8 +294,8 @@ class email_template(osv.osv):
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,
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)
@ -318,7 +320,7 @@ class email_template(osv.osv):
# Add template attachments
for attach in template.attachment_ids:
attachments.append(attach.datas_fname, attach.datas)
attachments.append((attach.datas_fname, attach.datas))
values['attachments'] = attachments
return values

View File

@ -19,8 +19,8 @@
#
##############################################################################
import base64
from openerp.tests import common
import tools
class test_message_compose(common.TransactionCase):
@ -44,10 +44,12 @@ class test_message_compose(common.TransactionCase):
self.build_email_real = self.registry('ir.mail_server').build_email
self.registry('ir.mail_server').build_email = self._mock_build_email
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
# create a 'pigs' group that will be used through the various tests
# create a 'pigs' and 'bird' groups that will be used through the various tests
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
self.group_bird_id = self.mail_group.create(self.cr, self.uid,
{'name': 'Bird', 'description': 'I am angry !'})
def test_00_message_compose_wizard(self):
""" Tests designed for the mail.compose.message wizard updated by email_template. """
@ -55,74 +57,116 @@ class test_message_compose(common.TransactionCase):
mail_compose = self.registry('mail.compose.message')
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a.a'})
user_admin = self.res_users.browse(cr, uid, uid)
p_a_id = user_admin.partner_id.id
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)
group_bird = self.mail_group.browse(cr, uid, self.group_bird_id)
# Create template on mail.group
# Mail data
_subject1 = 'Pigs'
_subject2 = 'Bird'
_body_html1 = 'Fans of Pigs, unite !\n<pre>Admin</pre>\n'
_body_html2 = 'I am angry !\n<pre>Admin</pre>\n'
_attachments = [
{'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment')},
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment')}
]
_attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
# Create template on mail.group, with attachments
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': '${object.name}',
'body_html': '${object.description}', 'user_signature': True,
'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])],
'email_to': 'b@b.b c@c.c', 'email_cc': 'd@d.d'})
# Mail data
_subject1 = 'Pigs'
_subject2 = 'Bird'
_body_text1 = 'Pigs rules'
_body_text_html1 = 'Fans of Pigs, unite !\n<pre>Admin</pre>\n'
_body_text2 = 'I am angry !'
_body_text_html2 = 'I am angry !<pre>Admin</pre>'
# ----------------------------------------
# CASE1: comment and save as template
# ----------------------------------------
# CASE1: create in comment
# 1. Comment on pigs
compose_id = mail_compose.create(cr, uid,
{'subject': 'Forget me subject', 'body': '<p>Dummy body</p>'},
{'default_composition_mode': 'comment', 'default_model': 'mail.group',
'default_res_id': self.group_pigs_id,
'default_template_id': email_template_id,
'active_ids': [self.group_pigs_id, self.group_bird_id]})
compose = mail_compose.browse(cr, uid, compose_id)
# 2. Save current composition form as a template
mail_compose.save_as_template(cr, uid, [compose_id], context={'default_model': 'mail.group'})
# Test: email_template subject, body_html, model
last_template_id = email_template.search(cr, uid, [('model', '=', 'mail.group'), ('subject', '=', 'Forget me subject')], limit=1)[0]
self.assertTrue(last_template_id, 'email_template not found for model mail.group, subject Forget me subject')
last_template = email_template.browse(cr, uid, last_template_id)
self.assertEqual(last_template.body_html, '<p>Dummy body</p>', 'email_template incorrect body_html')
# ----------------------------------------
# CASE2: comment with template, save as template
# ----------------------------------------
# 1. Comment on pigs
compose_id = mail_compose.create(cr, uid,
{'subject': 'Forget me subject', 'body': 'Dummy body'},
{'default_composition_mode': 'comment', 'default_model': 'mail.group',
'default_res_id': self.group_pigs_id, 'default_use_template': True,
'active_ids': [self.group_pigs_id, group_bird_id] })
'default_res_id': self.group_pigs_id,
'default_template_id': email_template_id,
'active_ids': [self.group_pigs_id, self.group_bird_id]})
compose = mail_compose.browse(cr, uid, compose_id)
# Perform 'onchange_template_id' with 'use_template' set
values = mail_compose.onchange_template_id(cr, uid, [], compose.use_template, email_template_id, compose.composition_mode, compose.model, compose.res_id)
compose.write(values.get('value', {}), {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
# 2. Perform 'toggle_template', to set use_template and use template_id
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
compose.refresh()
message_pids = [partner.id for partner in compose.partner_ids]
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
partners = self.res_partner.browse(cr, uid, partner_ids)
# Test: subject, body, partner_ids
# Test: mail.compose.message: subject, body, content_subtype, partner_ids
self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect')
self.assertEqual(compose.body, _body_text_html1, 'mail.compose.message body incorrect')
self.assertEqual(compose.body, _body_html1, 'mail.compose.message body incorrect')
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
# Test: mail.compose.message: attachments
# Test: mail.message: attachments
for attach in compose.attachment_ids:
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test,
'mail.message attachment name / data incorrect')
# Perform 'onchange_use_template': use_template is not set anymore
values = mail_compose.onchange_use_template(cr, uid, [], not compose.use_template, compose.template_id, compose.composition_mode, compose.model, compose.res_id)
compose.write(values.get('value', {}), {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
# 3. Perform 'toggle_template': template is not set anymore
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
compose.refresh()
# Test: subject, body, partner_ids
self.assertEqual(compose.subject, False, 'mail.compose.message subject incorrect')
self.assertEqual(compose.body, '', 'mail.compose.message body incorrect')
# CASE12 create in mass_mail composition
# ----------------------------------------
# CASE3: mass_mail with template
# ----------------------------------------
# 1. Mass_mail on pigs and bird, with a default_partner_ids set to check he is correctly added
compose_id = mail_compose.create(cr, uid,
{'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, group_bird_id] })
'default_res_id': self.group_pigs_id,
'default_template_id': email_template_id,
'default_partner_ids': [p_a_id],
'active_ids': [self.group_pigs_id, self.group_bird_id]})
compose = mail_compose.browse(cr, uid, compose_id)
# Test 'onchange_template_id' with 'use_template' set
values = mail_compose.onchange_template_id(cr, uid, [], compose.use_template, email_template_id, compose.composition_mode, compose.model, compose.res_id)
print values
# self.assertEqual()
# 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
# 2. Perform 'toggle_template', to set use_template and use template_id
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
compose.refresh()
message_pids = [partner.id for partner in compose.partner_ids]
partner_ids = [p_a_id]
# Test: mail.compose.message: subject, body, content_subtype, partner_ids
self.assertEqual(compose.subject, '${object.name}', 'mail.compose.message subject incorrect')
self.assertEqual(compose.body, '${object.description}', 'mail.compose.message body incorrect')
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids 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]})
# 3. Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id, self.group_bird_id]})
group_pigs.refresh()
group_bird.refresh()
message_pigs = group_pigs.message_ids[0]
@ -130,9 +174,13 @@ class test_message_compose(common.TransactionCase):
# 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')
self.assertEqual(message_pigs.body, _body_html1, 'mail.message body on Pigs incorrect')
self.assertEqual(message_bird.body, _body_html2, 'mail.message body on Bird incorrect')
# Test: partner_ids: p_a_id (default) + 3 newly created partners
message_pigs_pids = [partner.id for partner in message_pigs.partner_ids]
message_bird_pids = [partner.id for partner in message_bird.partner_ids]
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])]) + [p_a_id]
self.assertEqual(len(message_pigs_pids), len(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
self.assertEqual(len(message_bird_pids), len(partner_ids), 'mail.message on bird partner_ids incorrect')
self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird partner_ids incorrect')

View File

@ -19,22 +19,19 @@
#
##############################################################################
import base64
import tools
from osv import osv
from osv import fields
from tools.translate import _
class mail_compose_message(osv.osv_memory):
_inherit = 'mail.compose.message'
_compose_fields = ['body', 'body_html', 'subject', 'partner_ids', 'attachment_ids']
def _get_templates(self, cr, uid, context=None):
if context is None:
context = {}
model = False
email_template_obj= self.pool.get('email.template')
email_template_obj = self.pool.get('email.template')
message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id')))
if context.get('default_composition_mode') == 'reply' and message_id:
@ -54,6 +51,9 @@ class mail_compose_message(osv.osv_memory):
context = {}
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
result['template_id'] = context.get('default_template_id', context.get('mail.compose.template_id', False))
# force html when using templates
if result.get('use_template'):
result['content_subtype'] = 'html'
return result
_columns = {
@ -67,34 +67,50 @@ class mail_compose_message(osv.osv_memory):
- use_template set in mass_mailing: we cannot render, so return the template values
- use_template set: return rendered values """
if use_template and template_id and composition_mode == 'mass_mail':
values = self.pool.get('email.template').read(cr, uid, template_id, self._compose_fields, context)
values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context)
values.pop('id')
elif use_template and template_id:
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
# transform attachments into attachment_ids
values['attachment_ids'] = []
ir_attach_obj = self.pool.get('ir.attachment')
for attach_fname, attach_datas in values.pop('attachments', []):
data_attach = {
'name': attach_fname,
'datas': attach_datas,
'datas_fname': attach_fname,
'res_model': model,
'res_id': res_id,
}
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
else:
values = self.default_get(cr, uid, self._compose_fields, context=context)
values = self.default_get(cr, uid, ['body', 'body_html', 'subject', 'partner_ids', 'attachment_ids'], context=context)
if values.get('body_html'):
values['body'] = values.pop('body_html')
values.update(use_template=use_template, template_id=template_id)
return {'value': values}
def toggle_template(self, cr, uid, ids, context=None):
""" hit toggle template mode button: calls onchange_use_template to
emulate an on_change, then writes the value to update the form. """
""" hit toggle template mode button: calls onchange_use_template to
emulate an on_change, then writes the values 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.model, record.res_id, context=context)['value']
record.write(onchange_res.get('value', {}))
record.template_id, record.composition_mode, record.model, record.res_id, context=context).get('value', {})
# update partner_ids and attachment_ids
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
record.write(onchange_res)
return True
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 """
False, we do as an onchange with template_id False for values """
values = self.onchange_template_id(cr, uid, ids, use_template,
template_id, composition_mode, model, res_id, context=context)
# force html when using templates
if use_template:
values['content_subtype'] = 'html'
values['value']['content_subtype'] = 'html'
return values
def save_as_template(self, cr, uid, ids, context=None):
@ -112,7 +128,7 @@ class mail_compose_message(osv.osv_memory):
values = {
'name': template_name,
'subject': record.subject or False,
'body': record.body or False,
'body_html': record.body or False,
'model_id': model_id or False,
'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])]
}
@ -127,61 +143,29 @@ class mail_compose_message(osv.osv_memory):
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
""" Call email_template.generate_email(), get fields relevant for
mail.compose.message, transform email_cc and email_to into partner_ids """
fields = ['body', 'body_html', 'subject', 'email_to', 'email_cc', 'attachment_ids']
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
values = {field: template_values[field] for field in fields if template_values.get(field)}
# filter template values
fields = ['body', 'body_html', 'subject', 'email_to', 'email_cc', 'attachments']
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
values['body'] = values.pop('body_html', '')
# transform email_to, email_cc into partner_ids
values['partner_ids'] = []
mails = tools.email_split(values.pop('email_to', '') + ' ' + 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:
values['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)
values['partner_ids'].append((4, partner_id))
# transform attachments into attachment_ids
values['attachment_ids'] = []
for attach_fname, attach_datas in template_values.get('attachments', []):
data_attach = {
'name': attach_fname,
'datas': attach_datas,
'datas_fname': attach_fname,
'res_model': model,
'res_id': res_id,
}
values['attachment_ids'].append((0, 0, data_attach))
partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
values['partner_ids'].append(partner_id)
return values
def render_message(self, cr, uid, wizard, model, res_id, context=None):
def render_message(self, cr, uid, wizard, 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
fields = ['body', 'body_html', 'subject', 'email_to', 'email_cc', 'partner_ids', 'attachment_ids']
template_values = self.pool.get('email.template').generate_email(cr, uid, wizard.template_id, res_id, context=context)
template_values = {field: template_values[field] for field in fields if template_values.get(field)}
# 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))
# generate the composer email
values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context)
# 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, partner_ids=partner_ids)
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
email_dict.update(values)
return email_dict
def render_template(self, cr, uid, template, model, res_id, context=None):

View File

@ -42,10 +42,12 @@
<xpath expr="//button[@class='oe_mail_compose_message_attachment']" position="before">
<button icon="/email_template/static/src/img/email_template.png"
type="object" name="toggle_template" string=""
help="Use a message template"/>
help="Use a message template"
attrs="{'invisible':[('content_subtype','!=','html')]}"/>
<button icon="/email_template/static/src/img/email_template_save.png"
type="object" name="save_as_template" string=""
help="Save as a new template"/>
help="Save as a new template"
attrs="{'invisible':[('content_subtype','!=','html')]}"/>
</xpath>
</data>
</field>

View File

@ -258,6 +258,25 @@ class event_event(osv.osv):
}
return {'value': dic}
def on_change_address_id(self, cr, uid, ids, address_id, context=None):
values = {
'street' : False,
'city' : False,
'zip' : False,
}
if isinstance(address_id, (long, int)):
address = self.pool.get('res.partner').browse(cr, uid, address_id, context=context)
values.update({
'street' : address.street,
'city' : address.city,
'zip' : address.zip,
})
return {'value' : values}
# ----------------------------------------
# OpenChatter methods and notifications
# ----------------------------------------

View File

@ -124,7 +124,7 @@
<group>
<label for="address_id" string="Location Address"/>
<div>
<field name="address_id" widget="many2one_address_google_map" widget_option="{'placeholder':'.oe_google_map'}"/>
<field name="address_id" widget="many2one_address_google_map" widget_option="{'placeholder':'.oe_google_map'}" on_change="on_change_address_id(address_id)" />
<field name="street" placeholder="Street..."/>
<div>
<field name="zip" class="oe_inline" placeholder="Zip"/>
@ -143,7 +143,7 @@
</group>
</group>
</div>
<!-- <div class="oe_right" style="height: 200px"></div> -->
<div class="oe_google_map" style="height: 200px"></div>
<notebook>
<page string="Event Description">
<field name="note" colspan="4" nolabel="1"/>

View File

@ -128,7 +128,7 @@
<field name="last_login"/>
<templates>
<t t-name="kanban-box">
<div class="oe_employee_vignette">
<div class="oe_employee_vignette oe_semantic_html_override">
<div class="oe_employee_image">
<a type="open"><img t-att-src="kanban_image('hr.employee', 'image_medium', record.id.value)" class="oe_employee_picture"/></a>
</div>

View File

@ -271,19 +271,19 @@
<field name="categ_ids"/>
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
<ul class="oe_kanban_tooltip oe_semantic_html_override">
<li t-if="record.type_id.raw_value"><b>Degree:</b> <field name="type_id"/></li>
<li t-if="record.partner_id.raw_value"><b>Contact:</b> <field name="partner_id"/></li>
<li t-if="record.department_id.raw_value"><b>Departement:</b> <field name="department_id"/></li>
</ul>
</t>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_semantic_html_override">
<div class="oe_dropdown_toggle oe_dropdown_kanban">
<span class="oe_e">i</span>
<ul class="oe_dropdown_menu">
<li><a type="edit">Edit...</a></li>
<li><a type="delete">Delete</a></li>
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
<t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
<li><a name="action_makeMeeting" type="object">Schedule Interview</a></li>
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>

View File

@ -104,11 +104,12 @@ class mail_notification(osv.Model):
if signature:
body_html = tools.append_content_to_html(body_html, signature)
towrite = {
mail_values = {
'mail_message_id': msg.id,
'email_to': [],
'auto_delete': False,
'auto_delete': True,
'body_html': body_html,
'state': 'outgoing',
}
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
@ -127,10 +128,10 @@ class mail_notification(osv.Model):
# Partner wants to receive only emails
if partner.notification_email_send == 'email' and msg.type != 'email':
continue
if partner.email not in towrite['email_to']:
towrite['email_to'].append(partner.email)
if towrite['email_to']:
towrite['email_to'] = ', '.join(towrite['email_to'])
email_notif_id = mail_mail.create(cr, uid, towrite, context=context)
if partner.email not in mail_values['email_to']:
mail_values['email_to'].append(partner.email)
if mail_values['email_to']:
mail_values['email_to'] = ', '.join(mail_values['email_to'])
email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
mail_mail.send(cr, uid, [email_notif_id], context=context)
return True

View File

@ -25,7 +25,7 @@
</div>
</t>
<t t-name="kanban-box">
<div t-attf-class="{record.message_is_follower.raw_value} oe_group_vignette">
<div t-attf-class="{record.message_is_follower.raw_value} oe_group_vignette oe_semantic_html_override">
<div class="oe_group_image">
<a type="open"><img t-att-src="kanban_image('mail.group', 'image_medium', record.id.value)" class="oe_group_photo" tooltip="kanban-description"/></a>
</div>

View File

@ -54,8 +54,12 @@ class mail_mail(osv.Model):
'email_from': fields.char('From', help='Message sender, taken from user preferences.'),
'email_to': fields.text('To', help='Message recipients'),
'email_cc': fields.char('Cc', help='Carbon copy message recipients'),
'reply_to':fields.char('Reply-To', help='Preferred response address for the message'),
'reply_to': fields.char('Reply-To', help='Preferred response address for the message'),
'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
# Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
# and during unlink() we will cascade delete the parent and its attachments
'notification': fields.boolean('Is Notification')
}
def _get_default_from(self, cr, uid, context=None):
@ -69,6 +73,19 @@ class mail_mail(osv.Model):
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
}
def create(self, cr, uid, values, context=None):
if 'notification' not in values and values.get('mail_message_id'):
values['notification'] = True
return super(mail_mail,self).create(cr, uid, values, context=context)
def unlink(self, cr, uid, ids, context=None):
# cascade-delete the parent message for all mails that are not created for a notification
ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
return res
def mark_outgoing(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
@ -106,20 +123,17 @@ class mail_mail(osv.Model):
_logger.exception("Failed processing mail queue")
return res
def _postprocess_sent_message(self, cr, uid, message, context=None):
"""Perform any post-processing necessary after sending ``message``
def _postprocess_sent_message(self, cr, uid, mail, context=None):
"""Perform any post-processing necessary after sending ``mail``
successfully, including deleting it completely along with its
attachment if the ``auto_delete`` flag of the message was set.
attachment if the ``auto_delete`` flag of the mail was set.
Overridden by subclasses for extra post-processing behaviors.
:param browse_record message: the message that was just sent
:param browse_record mail: the mail that was just sent
:return: True
"""
if message.auto_delete:
self.pool.get('ir.attachment').unlink(cr, uid,
[x.id for x in message.attachment_ids],
context=context)
message.unlink()
if mail.auto_delete:
mail.unlink()
return True
def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):

View File

@ -20,10 +20,12 @@
##############################################################################
import logging
from email.header import decode_header
from osv import osv, fields
import tools
from email.header import decode_header
from operator import itemgetter
from osv import osv, fields
_logger = logging.getLogger(__name__)
""" Some tools for parsing / creating email fields """
@ -45,7 +47,7 @@ class mail_message(osv.Model):
_message_record_name_length = 18
def _shorten_name(self, name):
if len(name) <= (self._message_record_name_length+3):
if len(name) <= (self._message_record_name_length + 3):
return name
return name[:self._message_record_name_length] + '...'
@ -73,7 +75,7 @@ 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. Condition is
""" 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)'
@ -136,7 +138,7 @@ class mail_message(osv.Model):
_defaults = {
'type': 'email',
'date': lambda *a: fields.datetime.now(),
'author_id': lambda self,cr,uid,ctx: self._get_default_author(cr, uid, ctx),
'author_id': lambda self, cr, uid, ctx={}: self._get_default_author(cr, uid, ctx),
'body': '',
}
@ -170,12 +172,13 @@ class mail_message(osv.Model):
""" Given a tree with several roots of following structure :
[ {'id': 1, 'child_ids': [
{'id': 11, 'child_ids': [...] },],
},
{...} ]
Flatten it to have a maximum number of level, with 0 being
completely flat.
Flatten it to have a maximum number of levels, 0 being flat and
sort messages in a level according to a key of the messages.
Perform the flattening at leafs if above the maximum depth, then get
back in the tree.
:param context: ``sort_key``: key for sorting (id by default)
:param context: ``sort_reverse``: reverser order for sorting (True by default)
"""
def _flatten(msg_dict):
""" from {'id': x, 'child_ids': [{child1}, {child2}]}
@ -184,19 +187,22 @@ class mail_message(osv.Model):
child_ids = msg_dict.pop('child_ids', [])
msg_dict['child_ids'] = []
return [msg_dict] + child_ids
# return sorted([msg_dict] + child_ids, key=itemgetter('id'), reverse=True)
context = context or {}
# 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)
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 message in messages:
for flat_message in _flatten(message):
new_list.append(flat_message)
return new_list
return_list = messages
else:
return_list = []
for message in messages:
for flat_message in _flatten(message):
return_list.append(flat_message)
return sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True))
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
@ -214,7 +220,7 @@ class mail_message(osv.Model):
result = []
tree = {} # key: ID, value: record
for msg in messages:
if len(result)<(limit-1):
if len(result) < (limit - 1):
record = self._message_dict_get(cr, uid, msg, context=context)
if thread_level and msg.parent_id:
while msg.parent_id:
@ -234,9 +240,10 @@ class mail_message(osv.Model):
else:
result.append({
'type': 'expandable',
'domain': [('id','<=', msg.id)]+domain,
'domain': [('id', '<=', msg.id)] + domain,
'context': context,
'thread_level': thread_level # should be improve accodting to level of records
'thread_level': thread_level, # should be improve accodting to level of records
'id': -1,
})
break
@ -280,7 +287,7 @@ class mail_message(osv.Model):
if not (rmod and rid):
continue
document_ids.append(id)
model_record_ids.setdefault(rmod,set()).add(rid)
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)
@ -293,11 +300,23 @@ class mail_message(osv.Model):
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)
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.notify(cr, uid, newid, context=context)
return newid
def unlink(self, cr, uid, ids, context=None):
# cascade-delete attachments that are directly attached to the message (should only happen
# for mail.messages that act as parent for a standalone mail.mail record.
attachments_to_delete = []
for mail in self.browse(cr, uid, ids, context=context):
for attach in mail.attachment_ids:
if attach.res_model == 'mail.message' and attach.res_id == mail.id:
attachments_to_delete.append(attach.id)
if attachments_to_delete:
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
return super(mail_message,self).unlink(cr, uid, ids, context=context)
def notify(self, cr, uid, newid, context=None):
""" Add the related record followers to the destination partner_ids.
Call mail_notification.notify to manage the email sending
@ -328,4 +347,4 @@ class mail_message(osv.Model):
if default is None:
default = {}
default.update(message_id=False, headers=False)
return super(mail_message,self).copy(cr, uid, id, default=default, context=context)
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)

View File

@ -24,7 +24,6 @@ import dateutil
import email
import logging
import pytz
import re
import time
import tools
import xmlrpclib
@ -32,20 +31,19 @@ import xmlrpclib
from email.message import Message
from mail_message import decode
from osv import osv, fields
from tools.translate import _
from tools.safe_eval import safe_eval as eval
_logger = logging.getLogger(__name__)
def decode_header(message, header, separator=' '):
return separator.join(map(decode,message.get_all(header, [])))
return separator.join(map(decode, message.get_all(header, [])))
class many2many_reference(fields.many2many):
""" many2many_reference manages many2many fields where one id is found
by a reference-like key (a char column in addition to the foreign id).
The reference_column attribute on the many2many fields is used;
if not defined, ``res_model`` is used. """
def _get_query_and_where_params(self, cr, model, ids, values, where_params):
""" Add in where condition like mail_followers.res_model = 'crm.lead' """
reference_column = self.reference_column if self.reference_column else 'res_model'
@ -119,7 +117,7 @@ class mail_thread(osv.AbstractModel):
_description = 'Email Thread'
def _get_message_data(self, cr, uid, ids, name, args, context=None):
res = dict( (id, dict(message_unread=False, message_summary='')) for id in ids)
res = dict((id, dict(message_unread=False, message_summary='')) for id in ids)
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
notif_obj = self.pool.get('mail.notification')
@ -149,7 +147,7 @@ class mail_thread(osv.AbstractModel):
], context=context)
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
res[notif.message_id.res_id] = True
return [('id','in',res.keys())]
return [('id', 'in', res.keys())]
_columns = {
'message_is_follower': fields.function(_get_message_data,
@ -159,13 +157,13 @@ class mail_thread(osv.AbstractModel):
reference_column='res_model', string='Followers'),
'message_comment_ids': fields.one2many('mail.message', 'res_id',
domain=lambda self: [('model', '=', self._name), ('type', 'in', ('comment', 'email'))],
string='Comments and emails',
string='Comments and emails',
help="Comments and emails"),
'message_ids': fields.one2many('mail.message', 'res_id',
domain=lambda self: [('model','=',self._name)],
string='Messages',
domain=lambda self: [('model', '=', self._name)],
string='Messages',
help="Messages and communication history"),
'message_unread': fields.function(_get_message_data, fnct_search=_search_unread,
'message_unread': fields.function(_get_message_data, fnct_search=_search_unread,
type='boolean', string='Unread Messages', multi="_get_message_data",
help="If checked new messages require your attention."),
'message_summary': fields.function(_get_message_data, method=True,
@ -229,7 +227,7 @@ class mail_thread(osv.AbstractModel):
def _message_find_user_id(self, cr, uid, message, context=None):
from_local_part = tools.email_split(decode(message.get('From')))[0]
# FP Note: canonification required, the minimu: .lower()
user_ids = self.pool.get('res.users').search(cr, uid, ['|',
user_ids = self.pool.get('res.users').search(cr, uid, ['|',
('login', '=', from_local_part),
('email', '=', from_local_part)], context=context)
return user_ids[0] if user_ids else uid
@ -445,6 +443,9 @@ class mail_thread(osv.AbstractModel):
encoding = message.get_content_charset()
body = message.get_payload(decode=True)
body = tools.ustr(body, encoding, errors='replace')
if message.get_content_type() == 'text/plain':
# text/plain -> <pre/>
body = tools.append_content_to_html(u'', body)
else:
alternative = (message.get_content_type() == 'multipart/alternative')
for part in message.walk():
@ -453,7 +454,7 @@ class mail_thread(osv.AbstractModel):
filename = part.get_filename() # None if normal part
encoding = part.get_content_charset() # None if attachment
# 1) Explicit Attachments -> attachments
if filename or part.get('content-disposition','').strip().startswith('attachment'):
if filename or part.get('content-disposition', '').strip().startswith('attachment'):
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
continue
# 2) text/plain -> <pre/>
@ -515,7 +516,7 @@ class mail_thread(osv.AbstractModel):
if 'Subject' in message:
msg_dict['subject'] = decode(message.get('Subject'))
# Envelope fields not stored in mail.message but made available for message_new()
# Envelope fields not stored in mail.message but made available for message_new()
msg_dict['from'] = decode(message.get('from'))
msg_dict['to'] = decode(message.get('to'))
msg_dict['cc'] = decode(message.get('cc'))
@ -524,7 +525,7 @@ class mail_thread(osv.AbstractModel):
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
if author_ids:
msg_dict['author_id'] = author_ids[0]
partner_ids = self._message_find_partners(cr, uid, message, ['From','To','Cc'], context=context)
partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
msg_dict['partner_ids'] = partner_ids
if 'Date' in message:
@ -535,16 +536,16 @@ class mail_thread(osv.AbstractModel):
msg_dict['date'] = date_server_datetime_str
if 'In-Reply-To' in message:
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id','=',decode(message['In-Reply-To']))])
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))])
if parent_ids:
msg_dict['parent_id'] = parent_ids[0]
if 'References' in message and 'parent_id' not in msg_dict:
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id','in',
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in',
[x.strip() for x in decode(message['References']).split()])])
if parent_ids:
msg_dict['parent_id'] = parent_ids[0]
msg_dict['body'], msg_dict['attachments'] = self._message_extract_payload(message)
return msg_dict
@ -573,7 +574,7 @@ class mail_thread(osv.AbstractModel):
:param int parent_id: optional ID of parent message in this thread
:param tuple(str,str) attachments: list of attachment tuples in the form
``(name,content)``, where content is NOT base64 encoded
:return: ID of newly created mail.message
:return: ID of newly created mail.message
"""
context = context or {}
attachments = attachments or []
@ -594,7 +595,7 @@ class mail_thread(osv.AbstractModel):
'res_model': context.get('thread_model') or self._name,
'res_id': thread_id,
}
attachment_ids.append((0,0, data_attach))
attachment_ids.append((0, 0, data_attach))
values = kwargs
subtype_obj = self.pool.get('mail.message.subtype')
@ -604,7 +605,7 @@ class mail_thread(osv.AbstractModel):
subtype_browse = subtype_obj.browse(cr, uid, subtypes[0][0])
if self._name in [model.model for model in subtype_browse.model_ids]:
values['subtype_id']=subtype_browse.id
values.update( {
values.update({
'model': context.get('thread_model', self._name) if thread_id else False,
'res_id': thread_id or False,
'body': body,
@ -613,7 +614,7 @@ class mail_thread(osv.AbstractModel):
'parent_id': parent_id,
'attachment_ids': attachment_ids,
})
for x in ('from','to','cc'): values.pop(x, None) # Avoid warnings
for x in ('from', 'to', 'cc'): values.pop(x, None) # Avoid warnings
return self.pool.get('mail.message').create(cr, uid, values, context=context)
#------------------------------------------------------
@ -641,7 +642,6 @@ class mail_thread(osv.AbstractModel):
if context and context.get('read_back'):
return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
return []
def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not
@ -666,10 +666,9 @@ class mail_thread(osv.AbstractModel):
def message_mark_as_unread(self, cr, uid, ids, context=None):
""" Set as unread. """
notobj = self.pool.get('mail.notification')
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
cr.execute('''
UPDATE mail_notification SET
UPDATE mail_notification SET
read=false
WHERE
message_id IN (SELECT id from mail_message where res_id=any(%s) and model=%s limit 1) and
@ -679,10 +678,9 @@ class mail_thread(osv.AbstractModel):
def message_mark_as_read(self, cr, uid, ids, context=None):
""" Set as read. """
notobj = self.pool.get('mail.notification')
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
cr.execute('''
UPDATE mail_notification SET
UPDATE mail_notification SET
read=true
WHERE
message_id IN (SELECT id FROM mail_message WHERE res_id=ANY(%s) AND model=%s) AND

View File

@ -79,7 +79,7 @@
container, holding the composition form. Then come the various
messages. Then comes the 'more' button.
-->
<ul t-name="mail.thread" class="oe_mail oe_mail_thread">
<ul t-name="mail.thread" class="oe_mail oe_mail_thread oe_semantic_html_override">
<div class="oe_mail_thread_action">
<!-- contains the composition box (form + image) -->
<t t-call="mail.compose_message"/>
@ -94,7 +94,7 @@
<!-- default layout -->
<li t-name="mail.thread.message" class="oe_mail oe_mail_thread_msg">
<div t-attf-class="oe_mail_msg_#{record.type}">
<div t-attf-class="oe_mail_msg_#{record.type} oe_semantic_html_override">
<img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="record.avatar"/>
<div class="oe_mail_msg_content">
<!-- dropdown menu with message options and actions -->

View File

@ -5,7 +5,7 @@
followers main template
Template used to display the followers, the actions and the subtypes in a record.
-->
<div t-name="mail.followers" class="oe_mail_recthread_aside">
<div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
<div class="oe_mail_recthread_actions">
<button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button>
<button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button>

View File

@ -19,10 +19,8 @@
#
##############################################################################
import base64
import tools
from openerp.tests import common
from openerp.tools.html_sanitize import html_sanitize
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
To: {to}
@ -65,6 +63,25 @@ Content-Transfer-Encoding: quoted-printable
------=_Part_4200734_24778174.1344608186754--
"""
MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
To: {to}
Received: by mail1.openerp.com (Postfix, from userid 10002)
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
Subject: {subject}
MIME-Version: 1.0
Content-Type: text/plain
Date: Fri, 10 Aug 2012 14:16:26 +0000
Message-ID: {msg_id}
{extra}
Please call me as soon as possible this afternoon!
--
Sylvie
"""
class test_mail(common.TransactionCase):
def _mock_smtp_gateway(self, *args, **kwargs):
@ -95,10 +112,9 @@ class test_mail(common.TransactionCase):
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
# groups@.. will cause the creation of new mail groups
self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model','=', 'mail.group')])[0]
self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups',
'alias_model_id': self.mail_group_model_id})
# create a 'pigs' group that will be used through the various tests
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
@ -106,10 +122,10 @@ class test_mail(common.TransactionCase):
def test_00_message_process(self):
cr, uid = self.cr, self.uid
# Incoming mail creates a new mail_group "frogs"
self.assertEqual(self.mail_group.search(cr, uid, [('name','=','frogs')]), [])
self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), [])
mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='')
self.mail_thread.message_process(cr, uid, None, mail_frogs)
frog_groups = self.mail_group.search(cr, uid, [('name','=','frogs')])
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'frogs')])
self.assertTrue(len(frog_groups) == 1)
# Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
@ -123,17 +139,25 @@ class test_mail(common.TransactionCase):
# Even with a wrong destination, a reply should end up in the correct thread
mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n'%frog_group.id)
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
self.mail_thread.message_process(cr, uid, None, mail_reply)
frog_group.refresh()
self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
# No model passed and no matching alias must raise
mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='')
self.assertRaises(Exception,
self.mail_thread.message_process,
cr, uid, None, mail_spam)
# plain text content should be wrapped and stored as html
test_msg_id = '<deadcafe.1337@smtp.agrolait.com>'
mail_text = MAIL_TEMPLATE_PLAINTEXT.format(to='groups@example.com', subject='frogs', extra='', msg_id=test_msg_id)
self.mail_thread.message_process(cr, uid, None, mail_text)
new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id','=',test_msg_id)])[0])
self.assertEqual(new_mail.body, '\n<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>\n',
'plaintext mail incorrectly parsed')
def test_10_many2many_reference_field(self):
""" Tests designed for the many2many_reference field (follower_ids).
We will test to perform writes using the many2many commands 0, 3, 4,
@ -194,7 +218,7 @@ class test_mail(common.TransactionCase):
self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the follower of dummy mail.thread data')
fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', group_dummy_id)])
follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
self.assertEqual(follower_ids,set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data')
self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data')
def test_11_message_followers(self):
""" Tests designed for the subscriber API. """
@ -246,24 +270,19 @@ class test_mail(common.TransactionCase):
_mail_body1 = 'Pigs rules\n<pre>Admin</pre>\n'
_mail_bodyalt1 = 'Pigs rules\nAdmin'
_body2 = '<html>Pigs rules</html>'
_mail_body2 = '<html>Pigs rules\n<pre>Admin</pre>\n</html>'
_mail_bodyalt2 = 'Pigs rules\nAdmin\n'
_mail_body2 = html_sanitize('<html>Pigs rules\n<pre>Admin</pre>\n</html>')
_mail_bodyalt2 = 'Pigs rules\nAdmin'
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
# CASE1: post comment, body and subject specified
msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment')
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])
sent_email = self._build_email_kwargs
# Test: notifications have been deleted
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id)]), 'mail.mail notifications should have been auto-deleted!')
# Test: mail_message: subject is _subject, body is _body1 (no formatting done)
self.assertEqual(message.subject, _subject, 'mail.message subject incorrect')
self.assertEqual(message.body, _body1, 'mail.message body incorrect')
# Test: mail_mail: subject is _subject, body_html is _mail_body1 (signature appended)
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, body; body_alternative
self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
self.assertEqual(sent_email['body'], _mail_body1, 'sent_email body incorrect')
@ -284,18 +303,13 @@ class test_mail(common.TransactionCase):
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, 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
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id2)]), 'mail.mail notifications should have been auto-deleted!')
# 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.body, html_sanitize(_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_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, 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')
@ -311,11 +325,11 @@ class test_mail(common.TransactionCase):
# 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')
for attach in message.attachment_ids:
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
'mail.message attachment name / data incorrect')
def test_21_message_compose_wizard(self):
""" Tests designed for the mail.compose.message wizard. """
@ -324,19 +338,19 @@ class test_mail(common.TransactionCase):
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
user_admin = self.res_users.browse(cr, uid, uid)
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'Bird resistance'})
group_bird = self.mail_group.browse(cr, uid, group_bird_id)
# 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>'
_msg_reply = 'Re: Pigs'
_msg_body = '<pre>Pigs rules</pre>'
_attachments = [
{'name': 'First', 'datas': base64.b64encode('My first attachment')},
{'name': 'Second', 'datas': base64.b64encode('My second attachment')}
{'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
]
_attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
# Create partners
# 0 - Admin
@ -351,23 +365,27 @@ class test_mail(common.TransactionCase):
# Subscribe #1
group_pigs.message_subscribe([p_b_id])
# CASE1: comment group_pigs with body_text and subject
# ----------------------------------------
# CASE1: comment on group_pigs
# ----------------------------------------
# 1. 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)]},
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id})
compose = mail_compose.browse(cr, uid, compose_id)
# Test: mail.compose.message: model, res_id
# Test: mail.compose.message: composition_mode, model, res_id
self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode')
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
# 2. Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id])
group_pigs.refresh()
message = group_pigs.message_ids[0]
# Test: mail.message: subject, body inside pre
self.assertEqual(message.subject, False, 'mail.message incorrect subject')
self.assertEqual(message.body, _msg_body1, 'mail.message incorrect body')
self.assertEqual(message.body, _msg_body, '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]
@ -375,114 +393,165 @@ class test_mail(common.TransactionCase):
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')
# CASE2: reply to last comment (update its subject) with attachments
# ----------------------------------------
# CASE2: reply to last comment with attachments
# ----------------------------------------
# 1. Update last comment subject, reply with attachments
message.write({'subject': _subject})
compose_id = mail_compose.create(cr, uid,
{'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
{'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
compose = mail_compose.browse(cr, uid, compose_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')
# Post the comment, get created message
# 2. 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')
# Test: mail.message: subject as Re:.., body in html
self.assertEqual(message.subject, _msg_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')
# Test: mail.message: attachments
for attach in message.attachment_ids:
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments_test,
'mail.message attachment name / data incorrect')
# CASE3 - Create in mass_mail composition mode that should work with or without email_template installed
# ----------------------------------------
# CASE3: mass_mail on Pigs and Bird
# ----------------------------------------
# 1. mass_mail on pigs and bird
compose_id = mail_compose.create(cr, uid,
{'subject': _subject, 'body': '${object.description}'},
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': -1,
'active_ids': [self.group_pigs_id]})
'active_ids': [self.group_pigs_id, group_bird_id]})
compose = mail_compose.browse(cr, uid, compose_id)
# Test: content_subtype is html
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id]})
# 2. Post the comment, get created message for each group
mail_compose.send_mail(cr, uid, [compose_id],
context={'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]})
group_pigs.refresh()
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(message.id, test_msg.id, 'Pigs did not receive its mass mailing message')
group_bird.refresh()
message1 = group_pigs.message_ids[0]
message2 = group_bird.message_ids[0]
# Test: Pigs and Bird did receive their message
test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
self.assertIn(message1.id, test_msg_ids, 'Pigs did not receive its mass mailing message')
self.assertIn(message2.id, test_msg_ids, 'Bird did not receive its mass mailing message')
# Test: mail.message: subject, body
self.assertEqual(message.subject, _subject, 'mail.message subject is incorrect')
self.assertEqual(message.body, group_pigs.description, 'mail.message body is incorrect')
self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect')
self.assertEqual(message1.body, group_pigs.description, 'mail.message body incorrect')
self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect')
self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect')
def test_30_message_read(self):
""" Tests designed for message_read. """
# TDE NOTE: this test is not finished, as the message_read method is not fully specified.
# It wil be updated as soon as we have fixed specs !
cr, uid = self.cr, self.uid
# It will be updated as soon as we have fixed specs !
cr, uid = self.cr, self.uid
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
def _compare_structures(struct1, struct2, n=0):
# print '%scompare structure' % ('\t' * n)
self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect')
for x in range(len(struct1)):
# print '%s' % ('\t' * n), struct1[x]['id'], struct2[x]['id'], struct1[x].get('subject') or ''
self.assertEqual(struct1[x]['id'], struct2[x]['id'], 'message_read failure %s' % struct1[x].get('subject'))
_compare_structures(struct1[x]['child_ids'], struct2[x]['child_ids'], n + 1)
# print '%send compare' % ('\t' * n)
# Test message_read_tree_flatten that flattens a thread according to a given thread_level
# ----------------------------------------
# CASE1: Flattening test
# ----------------------------------------
# Create dummy message structure
import copy
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': 2, 'child_ids': [
tree = [{'id': 2, 'child_ids': [
{'id': 6, 'child_ids': [
{'id': 8, 'child_ids': []},
]},
]},
{'id': 1, 'child_ids':[
{'id': 7, 'child_ids': [
{'id': 9, 'child_ids': []},
] },
] },
{'id': 6, 'child_ids': [
{'id': 11, 'child_ids': [] },
] },
]},
{'id': 4, 'child_ids': [
{'id': 10, 'child_ids': []},
{'id': 5, 'child_ids': []},
]},
{'id': 3, 'child_ids': []},
]},
]
# Test: completely flat
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 0)
self.assertTrue(len(new_tree) == 12, 'Flattening wrongly produced')
self.assertEqual(len(new_tree), 10, 'message_read_tree_flatten wrong in flat')
# Test: 1 thread level
tree_test = [{'id': 2, 'child_ids': [
{'id': 8, 'child_ids': []}, {'id': 6, 'child_ids': []},
]},
{'id': 1, 'child_ids': [
{'id': 10, 'child_ids': []}, {'id': 9, 'child_ids': []},
{'id': 7, 'child_ids': []}, {'id': 5, 'child_ids': []},
{'id': 4, 'child_ids': []}, {'id': 3, 'child_ids': []},
]},
]
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 1)
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')
_compare_structures(new_tree, tree_test)
# Test: 2 thread levels
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 2)
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')
_compare_structures(new_tree, tree)
# Add a few messages to pigs group
msgid1 = group_pigs.message_post(body='My Body', subject='1', parent_id=False)
msgid2 = group_pigs.message_post(body='My Body', subject='1-1', parent_id=msgid1)
msgid3 = group_pigs.message_post(body='My Body', subject='1-2', parent_id=msgid1)
msgid4 = group_pigs.message_post(body='My Body', subject='2', parent_id=False)
msgid5 = group_pigs.message_post(body='My Body', subject='1-1-1', parent_id=msgid2)
msgid6 = group_pigs.message_post(body='My Body', subject='2-1', parent_id=msgid4)
# ----------------------------------------
# CASE2: message_read test
# ----------------------------------------
# First try: read flat
first_try_ids = [msgid6, msgid5, msgid4, msgid3, msgid2, msgid1]
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=0)
self.assertTrue(all(elem['id'] in first_try_ids for elem in tree) and len(tree) == 6,
'Incorrect structure and/or number of childs in purely flat message_read')
# 1. Add a few messages to pigs group
msgid1 = group_pigs.message_post(body='1', subject='1', parent_id=False)
msgid2 = group_pigs.message_post(body='2', subject='1-1', parent_id=msgid1)
msgid3 = group_pigs.message_post(body='3', subject='1-2', parent_id=msgid1)
msgid4 = group_pigs.message_post(body='4', subject='2', parent_id=False)
msgid5 = group_pigs.message_post(body='5', subject='1-1-1', parent_id=msgid2)
msgid6 = group_pigs.message_post(body='6', subject='2-1', parent_id=msgid4)
# 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.assertTrue(len(tree) == 2 and len(tree[1]['child_ids']) == 3, 'Incorrect number of child in message_read')
# Test: read all messages flat
tree_test = [{'id': msgid6, 'child_ids': []}, {'id': msgid5, 'child_ids': []},
{'id': msgid4, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
{'id': msgid2, 'child_ids': []}, {'id': msgid1, 'child_ids': []}]
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=0, limit=10)
_compare_structures(tree, tree_test)
# Test: read with 1 level of thread
tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
{'id': msgid1, 'child_ids': [
{'id': msgid5, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
{'id': msgid2, 'child_ids': []},
]},
]
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1, limit=10)
_compare_structures(tree, tree_test)
# Test: read with 2 levels of thread
tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
{'id': msgid1, 'child_ids': [
{'id': msgid3, 'child_ids': []},
{'id': msgid2, 'child_ids': [{'id': msgid5, 'child_ids': []}, ]},
]},
]
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2, limit=10)
_compare_structures(tree, tree_test)
# 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.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')
# 2. Test expandables
# TDE FIXME: add those tests when expandables are specified and implemented
def test_40_needaction(self):
""" Tests for mail.message needaction. """
cr, uid = self.cr, self.uid
cr, uid = self.cr, self.uid
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
user_admin = self.res_users.browse(cr, uid, uid)
@ -491,9 +560,8 @@ class test_mail(common.TransactionCase):
('partner_id', '=', user_admin.partner_id.id),
('read', '=', False)
])
na_count = self.mail_message._needaction_count(cr, uid, domain = [])
self.assertEqual(len(notif_ids), na_count,
'Number of unread notifications (%s) does not match the needaction count (%s)' % (len(notif_ids), na_count))
na_count = self.mail_message._needaction_count(cr, uid, domain=[])
self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count')
# Post 4 message on group_pigs
for dummy in range(4):
@ -504,14 +572,12 @@ class test_mail(common.TransactionCase):
('partner_id', '=', user_admin.partner_id.id),
('read', '=', False)
])
na_count = self.mail_message._needaction_count(cr, uid, domain = [])
self.assertEqual(len(notif_ids), na_count,
'Number of unread notifications after posting messages (%s) does not match the needaction count (%s)' % (len(notif_ids), na_count))
na_count = self.mail_message._needaction_count(cr, uid, domain=[])
self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count')
# Check there are 4 needaction on mail.message with particular domain
na_count = self.mail_message._needaction_count(cr, uid, domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_count, 4,
'Number of posted message (4) does not match the needaction count with domain mail.group - group pigs (%s)' % (na_count))
na_count = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_count, 4, 'posted message count does not match needaction count')
def test_50_thread_parent_resolution(self):
"""Verify parent/child relationships are correctly established when processing incoming mails"""
@ -519,7 +585,7 @@ class test_mail(common.TransactionCase):
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
msg1 = group_pigs.message_post(body='My Body', subject='1')
msg2 = group_pigs.message_post(body='My Body', subject='2')
msg1, msg2 = self.mail_message.browse(cr, uid, [msg1,msg2])
msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
self.assertTrue(msg1.message_id, "New message should have a proper message_id")
# Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms

View File

@ -36,7 +36,7 @@ class mail_compose_message(osv.TransientModel):
at model and view levels to provide specific features.
The behavior of the wizard depends on the composition_mode field:
- 'reply': reply to a previous message. The wizard is pre-populated
- 'reply': reply to a previous message. The wizard is pre-populated
via ``get_message_data``.
- 'comment': new post on a record. The wizard is pre-populated via
``get_record_data``
@ -63,7 +63,6 @@ class mail_compose_message(osv.TransientModel):
- active_ids: record IDs
- default_model or active_model
"""
# get some important values from context
if context is None:
context = {}
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
@ -81,7 +80,7 @@ class mail_compose_message(osv.TransientModel):
elif composition_mode == 'comment' and model and res_id:
vals = self.get_record_data(cr, uid, model, res_id, context=context)
elif composition_mode == 'mass_mail' and model and active_ids:
vals = {'model': model, 'res_id': res_id}
vals = {'model': model, 'res_id': res_id, 'content_subtype': 'html'}
else:
vals = {'model': model, 'res_id': res_id}
if composition_mode:
@ -114,10 +113,10 @@ class mail_compose_message(osv.TransientModel):
_defaults = {
'composition_mode': 'comment',
'content_subtype': lambda self,cr, uid, context={}: 'plain',
'body_text': lambda self,cr, uid, context={}: False,
'body': lambda self,cr, uid, context={}: '',
'subject': lambda self,cr, uid, context={}: False,
'content_subtype': lambda self, cr, uid, ctx={}: 'plain',
'body_text': lambda self, cr, uid, ctx={}: False,
'body': lambda self, cr, uid, ctx={}: '',
'subject': lambda self, cr, uid, ctx={}: False,
}
def notify(self, cr, uid, newid, context=None):
@ -144,13 +143,10 @@ class mail_compose_message(osv.TransientModel):
:param int message_id: id of the mail.message to which the user
is replying.
"""
if not message_id:
return {}
if context is None:
context = {}
result = {}
if not message_id:
return result
current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context)
# create subject
@ -159,15 +155,16 @@ class mail_compose_message(osv.TransientModel):
if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)):
reply_subject = "%s %s" % (re_prefix, reply_subject)
# create the reply in the body
reply_header = _('On %(date)s, %(sender_name)s wrote:') % {
reply_body = _('<div>On %(date)s, %(sender_name)s wrote:<blockquote>%(body)s</blockquote></div>') % {
'date': message_data.date if message_data.date else '',
'sender_name': message_data.author_id.name }
reply_body = '<div>%s<blockquote>%s</blockquote></div>' % (reply_header, message_data.body)
'sender_name': message_data.author_id.name,
'body': message_data.body,
}
# get partner_ids from original message
partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
# update the result
result.update({
result = {
'model': message_data.model,
'res_id': message_data.res_id,
'parent_id': message_data.id,
@ -175,11 +172,11 @@ class mail_compose_message(osv.TransientModel):
'subject': reply_subject,
'partner_ids': partner_ids,
'content_subtype': 'html',
})
}
return result
def toggle_content_subtype(self, cr, uid, ids, context=None):
""" hit toggle formatting mode button: calls onchange_formatting to
""" hit toggle formatting mode button: calls onchange_formatting to
emulate an on_change, then writes the value to update the form. """
for record in self.browse(cr, uid, ids, context=context):
content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html'
@ -209,17 +206,18 @@ class mail_compose_message(osv.TransientModel):
warning_msg += '\n- %s' % (partner.name)
return {'warning': {
'title': _('Partners email addresses not found'),
'message': warning_msg }
'message': warning_msg,
}
}
def onchange_partner_ids(self, cr, uid, ids, value, context=None):
""" onchange_partner_ids (value format: [[6, False, [3, 4]]]). The
""" onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The
basic purpose of this method is to check that destination partners
effectively have email addresses. Otherwise a warning is thrown.
"""
res = {'value': {}}
if not value or not value[0] or not value[0][0] == 6:
return
return
res.update(self._verify_partner_email(cr, uid, value[0][2], context=context))
return res
@ -227,7 +225,7 @@ class mail_compose_message(osv.TransientModel):
# 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)
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
@ -258,13 +256,13 @@ 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.name or attach.datas_fname, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
}
# mass mailing: render and override default values
if mass_mail_mode and wizard.model:
email_dict = self.render_message(cr, uid, wizard, wizard.model, res_id, context=context)
email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
new_partner_ids = email_dict.pop('partner_ids', [])
post_values['partner_ids'] += new_partner_ids
post_values['partner_ids'] += [(4, partner_id) for partner_id in new_partner_ids]
new_attachments = email_dict.pop('attachments', [])
post_values['attachments'] += new_attachments
post_values.update(email_dict)
@ -273,14 +271,13 @@ class mail_compose_message(osv.TransientModel):
return {'type': 'ir.actions.act_window_close'}
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, ...
"""
def render_message(self, cr, uid, wizard, res_id, context=None):
""" Generate an email from the template for given (wizard.model, res_id)
pair. This method is meant to be inherited by email_template that
will produce a more complete dictionary. """
return {
'subject': self.render_template(cr, uid, wizard.subject, model, res_id, context),
'body': self.render_template(cr, uid, wizard.body, model, res_id, context),
'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context),
'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context),
}
def render_template(self, cr, uid, template, model, res_id, context=None):
@ -302,8 +299,8 @@ class mail_compose_message(osv.TransientModel):
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),
'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 ''

View File

@ -46,6 +46,9 @@ class note_note(osv.osv):
_pad_fields = ['note_pad']
_description = "Note"
def _set_note_first_line(self, cr, uid, id, name, value, args, context=None):
return self.write(cr, uid, [id], {'note': value}, context=context)
def _get_note_first_line(self, cr, uid, ids, name, args, context=None):
res = {}
for note in self.browse(cr, uid, ids, context=context):
@ -68,7 +71,7 @@ class note_note(osv.osv):
return result
_columns = {
'name': fields.function(_get_note_first_line, string='Note Summary', type="text", store=True),
'name': fields.function(_get_note_first_line, fnct_inv=_set_note_first_line, string='Note Summary', type="text"),
'note': fields.text('Pad Content'),
'note_pad_url': fields.char('Pad Url', size=250),
'sequence': fields.integer('Sequence'),

View File

@ -57,7 +57,7 @@
<field name="follower_ids"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_semantic_html_override">
<!-- dropdown menu -->
<div class="oe_dropdown_toggle oe_dropdown_kanban">
<span class="oe_e">í</span>

View File

@ -14,6 +14,7 @@ pads (by default, http://ietherpad.com/).
'website': 'http://openerp.com',
'depends': ['base'],
'data': ['res_company.xml'],
'demo': ['pad_demo.xml'],
'installable': True,
'auto_install': False,
'web': True,

9
addons/pad/pad_demo.xml Normal file
View File

@ -0,0 +1,9 @@
<openerp>
<data noupdate="1">
<record id="base.main_company" model="res.company">
<field name="pad_server">pad.openerp.com</field>
</record>
</data>
</openerp>

View File

@ -7,11 +7,12 @@
<field name="arch" type="xml">
<xpath expr="//group[@name='account_grp']" position="after">
<group string="Pads">
<field name="pad_server" placeholder="e.g. beta.primarypad.org"/>
<field name="pad_server" placeholder="e.g. beta.primarypad.com"/>
<field name="pad_key"/>
</group>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -11,7 +11,6 @@
</div>
</t>
<t t-name="FieldPad.unconfigured">
Please configure your etherpad server.<br/>
OpenERP Entreprise customers may safely use pad.openerp.com<br/>
You must configure the etherpad through the menu Setting > Companies > Companies, in the configuration tab of your company.<br/>
</t>
</templates>

View File

@ -19,7 +19,7 @@
<separator string="Installation and Configuration Steps"/>
<p>Click on the link above to download the installer for either 32 or 64 bits, and execute it.</p>
<p>System requirements:</p>
<ul>
<ul class="oe_semantic_html_override">
<li>1. MS Outlook 2005 or above.</li>
<li>2. MS .Net Framework 3.5 or above.</li>
</ul>

View File

@ -20,7 +20,7 @@
</group>
<separator string="Installation and Configuration Steps"/>
<p>Thunderbird plugin installation:</p>
<ul>
<ul class="oe_semantic_html_override">
<li>1. Save the Thunderbird plug-in.</li>
<li>2. From the Thunderbird menubar: Tools ­> Add-ons -> Screwdriver/Wrench Icon -> Install add-on from file...</li>
<li>3. Select the plug-in (the file named openerp_plugin.xpi).</li>

View File

@ -422,7 +422,7 @@ class pos_session(osv.osv):
self.pool.get('pos.order')._create_account_move_line(cr, uid, order_ids, session, move_id, context=context)
for order in session.order_ids:
if order.state != 'paid':
if order.state not in ('paid', 'invoiced'):
raise osv.except_osv(
_('Error!'),
_("You cannot confirm all orders of this session, because they have not the 'paid' status"))

View File

@ -897,7 +897,7 @@
</group>
<newline/>
<group string="Opening Cash Control" attrs="{'invisible' : ['|', ('cash_control', '=', False),('state', '=', 'closed')]}">
<group string="Opening Cash Control" attrs="{'invisible' : ['|', ('cash_control', '=', False),('state', 'in', ('opened', 'closing_control'))]}">
<field name="opening_details_ids" nolabel="1" colspan="2" attrs="{'readonly' : [('state', 'not in', ('opening_control',))]}">
<tree string="Opening Cashbox Lines" editable="bottom">
<field name="pieces" readonly="1" />
@ -906,7 +906,7 @@
</tree>
</field>
</group>
<group string="Closing Cash Control" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', '!=', 'closing_control')]}">
<group string="Closing Cash Control" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', 'in', ('opening_control', 'opened'))]}">
<field name="details_ids" nolabel="1" colspan="2">
<tree string="Cashbox Lines" editable="bottom">
<field name="pieces" readonly="1" />
@ -916,14 +916,14 @@
</field>
</group>
<div attrs="{'invisible': [('state', '!=', 'closed')]}">
<div attrs="{'invisible' : ['|', ('cash_control', '=', False),('state', 'in', ('opened', 'closing_control'))]}">
<group class="oe_subtotal_footer oe_right">
<field name="cash_register_balance_start" readonly="1" string="Opening Balance" class="oe_subtotal_footer_separator"/>
<field name="cash_register_total_entry_encoding" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="+ Transactions"/>
<field name="cash_register_balance_end" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="= Theorical Balance"/>
</group>
<div class="oe_clear"/>
<div attrs="{'invisible' : [('cash_journal_id', '=', False)]}" class="oe_view_nocontent" groups="point_of_sale.group_pos_manager">
<div attrs="{'invisible' : ['|', ('cash_journal_id', '=', False), ('state', '!=', 'opening_control')]}" class="oe_view_nocontent" groups="point_of_sale.group_pos_manager">
<p class="oe_view_nocontent_create">
You can define another list of available currencies on the
<i>Cash Registers</i> tab of the <b><field name="cash_journal_id" class="oe_inline"/></b>
@ -933,7 +933,7 @@
</div>
<group class="oe_subtotal_footer oe_right" attrs="{'invisible' : [('state', '!=', 'closed')]}">
<group class="oe_subtotal_footer oe_right" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', 'in', ('opening_control', 'opened'))]}">
<field name="cash_register_balance_end_real" class="oe_subtotal_footer_separator"/>
<field name="cash_register_difference" class="oe_subtotal_footer_separator"/>
</group>

View File

@ -4,7 +4,7 @@
<templates id="template" xml:space="preserve">
<t t-name="PosWidget">
<div class="point-of-sale">
<div class="point-of-sale oe_semantic_html_override">
<div id="topheader">
<div id="branding">
<img src="/point_of_sale/static/src/img/logo.png" />

View File

@ -15,8 +15,7 @@ class PosBox(CashBox):
active_ids = context.get('active_ids', []) or []
if active_model == 'pos.session':
records = self.pool.get(active_model).browse(cr, uid, context.get('active_ids', []) or [], context=context)
records = self.pool.get(active_model).browse(cr, uid, active_ids, context=context)
bank_statements = [record.cash_register_id for record in records if record.cash_register_id]
if not bank_statements:
@ -30,6 +29,30 @@ class PosBox(CashBox):
class PosBoxIn(PosBox):
_inherit = 'cash.box.in'
def _compute_values_for_statement_line(self, cr, uid, box, record, context=None):
values = super(PosBoxIn, self)._compute_values_for_statement_line(cr, uid, box, record, context=context)
active_model = context.get('active_model', False) or False
active_ids = context.get('active_ids', []) or []
if active_model == 'pos.session':
session = self.pool.get(active_model).browse(cr, uid, active_ids, context=context)[0]
values['ref'] = session.name
return values
class PosBoxOut(PosBox):
_inherit = 'cash.box.out'
def _compute_values_for_statement_line(self, cr, uid, box, record, context=None):
values = super(PosBoxOut, self)._compute_values_for_statement_line(cr, uid, box, record, context=context)
active_model = context.get('active_model', False) or False
active_ids = context.get('active_ids', []) or []
if active_model == 'pos.session':
session = self.pool.get(active_model).browse(cr, uid, active_ids, context=context)[0]
values['ref'] = session.name
return values

View File

@ -40,14 +40,14 @@
<templates>
<t t-name="kanban-box">
<h4><field name="name"/></h4>
<ul class="oe_portal_crm_address">
<ul class="oe_portal_crm_address oe_semantic_html_override">
<li t-if="record.street"><field name="street"/></li>
<li t-if="record.street2"><field name="street2"/></li>
<li t-if="record.zip"><field name="zip"/></li>
<li t-if="record.city"><field name="city"/></li>
<li t-if="record.country_id"><field name="country_id"/></li>
</ul>
<ul class="oe_portal_crm_contact_info">
<ul class="oe_portal_crm_contact_info oe_semantic_html_override">
<li t-if="record.phone"><field name="phone"/></li>
<li t-if="record.email.raw_value">
<a title="Mail" t-att-href="'mailto:'+record.email.value">

View File

@ -32,7 +32,7 @@
<templates>
<t t-name="kanban-box">
<div class="oe_employee_vignette">
<div class="oe_employee_vignette oe_semantic_html_override">
<div class="oe_employee_image">
<img t-att-src="kanban_image('hr.employee', 'photo', record.id.value)" class="oe_employee_picture"/>
</div>

View File

@ -200,7 +200,7 @@
<field name="list_price"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_vignette">
<div class="oe_kanban_vignette oe_semantic_html_override">
<a type="open"><img t-att-src="kanban_image('product.product', 'image_small', record.id.value)" class="oe_kanban_image"/></a>
<div class="oe_kanban_details">
<h4>

View File

@ -232,12 +232,12 @@
<field name="alias_domain"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_project oe_kanban_global_click">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_project oe_kanban_global_click oe_semantic_html_override">
<div class="oe_dropdown_toggle oe_dropdown_kanban">
<span class="oe_e">í</span>
<ul class="oe_dropdown_menu">
<li><a type="edit">Edit...</a></li>
<li><a type="delete">Delete</a></li>
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
<t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>
</div>
@ -533,12 +533,12 @@
<field name="categ_ids"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_semantic_html_override">
<div class="oe_dropdown_toggle oe_dropdown_kanban">
<span class="oe_e">í</span>
<ul class="oe_dropdown_menu">
<li><a type="edit" >Edit...</a></li>
<li><a type="delete">Delete</a></li>
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
<t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
<li>
<ul class="oe_kanban_project_times">
<li><a name="set_remaining_time_1" type="object" class="oe_kanban_button">1</a></li>

View File

@ -247,18 +247,18 @@
<field name="date_deadline"/>
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
<ul class="oe_kanban_tooltip oe_semantic_html_override">
<li><b>Project:</b> <field name="project_id"/></li>
<li><b>Category:</b> <field name="categ_ids"/></li>
</ul>
</t>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_semantic_html_override">
<div class="oe_dropdown_toggle oe_dropdown_kanban">
<span class="oe_e">i</span>
<ul class="oe_dropdown_menu">
<li><a type="edit" >Edit...</a></li>
<li><a type="delete">Delete</a></li>
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
<t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>
</div>

View File

@ -269,18 +269,18 @@
groups="base.group_user"
on_change="product_id_change(parent.pricelist_id, product_id, product_uom_qty, product_uom, product_uos_qty, product_uos, name, parent.partner_id, False, True, parent.date_order, False, parent.fiscal_position, False, context)"/>
<field name="name"/>
<field name="type"/>
<field name="type" invisible="1"/>
<field name="product_uom_qty"
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'shop':parent.shop_id, 'uom':product_uom}"
on_change="product_id_change(parent.pricelist_id, product_id, product_uom_qty, product_uom, product_uos_qty, product_uos, name, parent.partner_id, False, False, parent.date_order, False, parent.fiscal_position, True, context)"/>
<field name="product_uom"
on_change="product_uom_change(parent.pricelist_id, product_id, product_uom_qty, product_uom, product_uos_qty, product_uos, name, parent.partner_id, False, False, parent.date_order, context)"
groups="product.group_uom" options='{"no_open": true}'/>
<field name="product_uos_qty" groups="product.group_uos"/>
<field name="product_uos" string="UoS" groups="product.group_uos"/>
<field name="discount" groups="sale.group_discount_per_so_line"/>
<field name="price_unit"/>
<field name="product_uos_qty" groups="product.group_uos" invisible="1"/>
<field name="product_uos" string="UoS" groups="product.group_uos" invisible="1"/>
<field name="tax_id" widget="many2many_tags" domain="[('parent_id','=',False),('type_tax_use','&lt;&gt;','purchase')]"/>
<field name="price_unit"/>
<field name="discount" groups="sale.group_discount_per_so_line"/>
<field name="price_subtotal"/>
</tree>
</field>

View File

@ -200,7 +200,7 @@
<t t-esc="record.delivery_count.value"/> Deliveries
</a>
</xpath>
<ul position="inside">
<ul position="inside" class="oe_semantic_html_override">
<li t-if="record.type.raw_value != 'service'">On hand: <field name="qty_available"/> <field name="uom_id"/></li>
<li t-if="record.type.raw_value != 'service'">Available: <field name="virtual_available"/> <field name="uom_id"/></li>
</ul>

View File

@ -298,12 +298,12 @@
<field name="color"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_semantic_html_override">
<div class="oe_dropdown_toggle oe_dropdown_kanban">
<span class="oe_e">i</span>
<ul class="oe_dropdown_menu">
<li><a type="edit">Edit...</a></li>
<li><a type="delete">Delete</a></li>
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
<t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>
</div>

View File

@ -3,7 +3,7 @@
-->
<templates id="template" xml:space="preserve">
<t t-name="Systray.Shortcuts">
<div class="oe_systray_shortcuts oe_topbar_item oe_dropdown_toggle">
<div class="oe_systray_shortcuts oe_topbar_item oe_dropdown_toggle oe_semantic_html_override">
<span class="oe_e oe_star_off">7</span>
<ul class="oe_systray_shortcuts_items oe_dropdown_menu"/>
</div>