Merge lp:~openerp-dev/openobject-addons/trunk-fetchmail-inbox-tde into lp:openobject-addons

Proposed by Thibault Delavallée (OpenERP)
Status: Superseded
Proposed branch: lp:~openerp-dev/openobject-addons/trunk-fetchmail-inbox-tde
Merge into: lp:openobject-addons
Diff against target: 899 lines (+264/-149)
23 files modified
email_template/email_template.py (+2/-4)
fetchmail/fetchmail.py (+5/-5)
mail/data/mail_demo.xml (+10/-6)
mail/data/mail_group_data.xml (+3/-5)
mail/mail_followers.py (+15/-12)
mail/mail_message.py (+5/-2)
mail/mail_thread.py (+79/-32)
mail/res_partner.py (+18/-0)
mail/security/ir.model.access.csv (+4/-3)
mail/security/mail_security.xml (+9/-1)
mail/static/src/css/mail.css (+4/-0)
mail/static/src/js/mail.js (+1/-2)
mail/tests/__init__.py (+1/-0)
mail/tests/test_mail.py (+8/-40)
mail/tests/test_mail_access_rights.py (+2/-2)
mail/tests/test_mail_mockup.py (+54/-0)
mail/wizard/mail_compose_message_view.xml (+5/-5)
note/note.py (+1/-3)
pad/pad.py (+1/-1)
portal/mail_mail.py (+1/-1)
portal/portal_demo.xml (+33/-20)
portal/tests/test_portal.py (+2/-3)
portal/wizard/portal_wizard.py (+1/-2)
To merge this branch: bzr merge lp:~openerp-dev/openobject-addons/trunk-fetchmail-inbox-tde
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+131936@code.launchpad.net

This proposal has been superseded by a proposal from 2012-11-14.

Commit message

Mail: improve incoming/outgoing emails behavior
Main:
- updated overall code to match the new mail openerp tools
- message_route: after checking the headers to find if the incoming email is a reply in an existing discussion, it checks whether the email is a reply to a private discussion (without model, res_id), based on the message_id of the mail; it then relies on the aliases then fetchmail, as before
- incoming emails and quick posting through Chatter now calls message_post_user_api (based on old message_post_api), that performs some things the composer do on its side (adding recipients of a replied message, cleaning the content)
Misc:
- mail: improved set_message_read, fixed a bug with message_id
- fetchmail: fixed a bug in fetchmail module about non-existing variable
- portal: updated demo data

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'email_template/email_template.py'
2--- email_template/email_template.py 2012-11-07 11:39:25 +0000
3+++ email_template/email_template.py 2012-11-14 09:23:22 +0000
4@@ -28,8 +28,6 @@
5 from osv import fields
6 import tools
7 from tools.translate import _
8-from tools.html_sanitize import html_sanitize
9-from tools import append_content_to_html
10 from urllib import quote as quote
11 _logger = logging.getLogger(__name__)
12
13@@ -293,10 +291,10 @@
14 or False
15 if template.user_signature:
16 signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
17- values['body_html'] = append_content_to_html(values['body_html'], signature)
18+ values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
19
20 if values['body_html']:
21- values['body'] = html_sanitize(values['body_html'])
22+ values['body'] = tools.html_sanitize(values['body_html'])
23
24 values.update(mail_server_id=template.mail_server_id.id or False,
25 auto_delete=template.auto_delete,
26
27=== modified file 'fetchmail/fetchmail.py'
28--- fetchmail/fetchmail.py 2012-10-25 13:09:30 +0000
29+++ fetchmail/fetchmail.py 2012-11-14 09:23:22 +0000
30@@ -243,20 +243,20 @@
31
32 def create(self, cr, uid, values, context=None):
33 if context is None:
34- context={}
35+ context = {}
36 fetchmail_server_id = context.get('fetchmail_server_id')
37 if fetchmail_server_id:
38 values['fetchmail_server_id'] = fetchmail_server_id
39- res = super(mail_mail,self).create(cr, uid, values, context=context)
40+ res = super(mail_mail, self).create(cr, uid, values, context=context)
41 return res
42
43 def write(self, cr, uid, ids, values, context=None):
44 if context is None:
45- context={}
46+ context = {}
47 fetchmail_server_id = context.get('fetchmail_server_id')
48 if fetchmail_server_id:
49- values['fetchmail_server_id'] = server_id
50- res = super(mail_mail,self).write(cr, uid, ids, values, context=context)
51+ values['fetchmail_server_id'] = fetchmail_server_id
52+ res = super(mail_mail, self).write(cr, uid, ids, values, context=context)
53 return res
54
55
56
57=== modified file 'mail/data/mail_demo.xml'
58--- mail/data/mail_demo.xml 2012-11-12 15:27:53 +0000
59+++ mail/data/mail_demo.xml 2012-11-14 09:23:22 +0000
60@@ -11,44 +11,48 @@
61 <record id="message_blogpost0" model="mail.message">
62 <field name="model">mail.group</field>
63 <field name="res_id" ref="mail.group_all_employees"/>
64- <field name="body">Your monthly meal vouchers arrived. You can get them at Christine's office.
65-This month you also get 250 EUR of eco-vouchers if you have been in the company for more than a year.</field>
66+ <field name="body"><![CDATA[<p>Your monthly meal vouchers arrived. You can get them at Christine's office.</p>]]></field>
67 <field name="type">comment</field>
68 <field name="subtype_id" ref="mt_comment"/>
69+ <field name="author_id" ref="base.partner_root"/>
70 </record>
71 <record id="message_blogpost0_comment0" model="mail.message">
72 <field name="model">mail.group</field>
73 <field name="res_id" ref="group_all_employees"/>
74- <field name="body"><![CDATA[Great.]]></field>
75+ <field name="body"><![CDATA[<p>Oh, I had forgotten. This month you also get 250 EUR of eco-vouchers if you have been in the company for more than a year.</p>]]></field>
76 <field name="parent_id" ref="message_blogpost0"/>
77 <field name="type">comment</field>
78 <field name="subtype_id" ref="mt_comment"/>
79+ <field name="author_id" ref="base.partner_root"/>
80 </record>
81 <record id="message_blogpost0_comment1" model="mail.message">
82 <field name="model">mail.group</field>
83 <field name="res_id" ref="group_all_employees"/>
84- <field name="body">Thanks, but where is Christine's office, if I may ask? (I'm new here)</field>
85+ <field name="body"><![CDATA[<p>Thanks! Could you please remind me where is Christine's office, if I may ask? I'm new here!</p>]]></field>
86 <field name="parent_id" ref="message_blogpost0"/>
87 <field name="type">comment</field>
88 <field name="subtype_id" ref="mt_comment"/>
89+ <field name="author_id" ref="base.partner_demo"/>
90 </record>
91 <!-- This one is starred for having mailboxes with demo data -->
92 <record id="message_blogpost0_comment2" model="mail.message">
93 <field name="model">mail.group</field>
94 <field name="res_id" ref="group_all_employees"/>
95- <field name="body">Building B3, second floor on the right :-)</field>
96+ <field name="body"><![CDATA[<p>Building B3, second floor on the right :-).</p>]]></field>
97 <field name="parent_id" ref="message_blogpost0"/>
98 <field name="type">comment</field>
99 <field name="subtype_id" ref="mt_comment"/>
100+ <field name="author_id" ref="base.partner_root"/>
101 <field name="favorite_user_ids" eval="[(6, 0, [ref('base.user_root'), ref('base.user_demo')])]"/>
102 </record>
103 <record id="message_blogpost0_comment3" model="mail.message">
104 <field name="model">mail.group</field>
105 <field name="res_id" ref="group_all_employees"/>
106- <field name="body">Great news, I need to buy a new fridge, I think I can pay it with the eco-vouchers!</field>
107+ <field name="body"><![CDATA[<p>Many thanks. Actually that's good news, next year I'll have to buy a new fridge, I think I will pay it with the eco-vouchers!</p>]]></field>
108 <field name="parent_id" ref="message_blogpost0"/>
109 <field name="type">comment</field>
110 <field name="subtype_id" ref="mt_comment"/>
111+ <field name="author_id" ref="base.partner_demo"/>
112 </record>
113
114 <!-- Demo user and admin conversation -->
115
116=== modified file 'mail/data/mail_group_data.xml'
117--- mail/data/mail_group_data.xml 2012-11-12 23:04:18 +0000
118+++ mail/data/mail_group_data.xml 2012-11-14 09:23:22 +0000
119@@ -18,11 +18,9 @@
120 <field name="res_id" ref="mail.group_all_employees"/>
121 <field name="type">notification</field>
122 <field name="subject">Welcome to OpenERP!</field>
123- <field name="body">Your homepage is a summary of messages you received and key information about documents you follow.
124-
125-The top menu bar contains all applications you installed. You can use this &lt;i&gt;Settings&lt;/i&gt; menu to install more applications, activate others features or give access to new users.
126-
127-To setup your preferences (name, email signature, avatar), click on the top right corner.</field>
128+ <field name="body"><![CDATA[<p>Your homepage is a summary of messages you received and key information about documents you follow.<br />
129+The top menu bar contains all applications you installed. You can use this &lt;i&gt;Settings&lt;/i&gt; menu to install more applications, activate others features or give access to new users.<br />
130+To setup your preferences (name, email signature, avatar), click on the top right corner.</p>]]></field>
131 </record>
132 </data>
133 </openerp>
134
135=== modified file 'mail/mail_followers.py'
136--- mail/mail_followers.py 2012-11-12 13:17:59 +0000
137+++ mail/mail_followers.py 2012-11-14 09:23:22 +0000
138@@ -84,11 +84,13 @@
139 return False
140
141 def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
142- """ Set a message and its child messages as (un)read for uid.
143+ """ Set messages as (un)read. Technically, the notifications related
144+ to uid are set to (un)read. If for some msg_ids there are missing
145+ notifications (i.e. due to load more or thread parent fetching),
146+ they are created.
147
148- :param bool read: read / unread
149+ :param bool read: (un)read notification
150 """
151- # TDE note: use child_of or front-end send correct values ?
152 user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
153 notif_ids = self.search(cr, uid, [
154 ('partner_id', '=', user_pid),
155@@ -100,10 +102,9 @@
156 return self.write(cr, uid, notif_ids, {'read': read}, context=context)
157
158 # some messages do not have notifications: find which one, create notification, update read status
159- exist_notification = dict.fromkeys(msg_ids, False)
160- for notification in self.browse(cr, uid, notif_ids, context=context):
161- exist_notification[notification.message_id.id] = True
162- for msg_id in exist_notification.keys():
163+ notified_msg_ids = [notification.message_id.id for notification in self.browse(cr, uid, notif_ids, context=context)]
164+ to_create_msg_ids = list(set(msg_ids) - set(notified_msg_ids))
165+ for msg_id in to_create_msg_ids:
166 self.create(cr, uid, {'partner_id': user_pid, 'read': read, 'message_id': msg_id}, context=context)
167 return self.write(cr, uid, notif_ids, {'read': read}, context=context)
168
169@@ -150,16 +151,18 @@
170
171 # add the context in the email
172 # TDE FIXME: commented, to be improved in a future branch
173- # quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
174+ quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
175
176 mail_mail = self.pool.get('mail.mail')
177 # add signature
178 body_html = msg.body
179- # if quote_context:
180- # body_html = tools.append_content_to_html(body_html, quote_context, plaintext=False)
181- signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
182+ if quote_context:
183+ pass
184+ # print quote_context
185+ # body_html = tools.append_content_to_html(body_html, quote_context, plaintext=False)
186+ signature = msg.author_id and msg.author_id.user_id and msg.author_id.user_ids[0].signature or ''
187 if signature:
188- body_html = tools.append_content_to_html(body_html, signature)
189+ body_html = tools.append_content_to_html(body_html, tools.text2html(signature), plaintext=False)
190
191 mail_values = {
192 'mail_message_id': msg.id,
193
194=== modified file 'mail/mail_message.py'
195--- mail/mail_message.py 2012-11-12 14:04:08 +0000
196+++ mail/mail_message.py 2012-11-14 09:23:22 +0000
197@@ -25,6 +25,7 @@
198 from email.header import decode_header
199 from openerp import SUPERUSER_ID
200 from openerp.osv import osv, orm, fields
201+from openerp.tools import html_email_clean
202 from openerp.tools.translate import _
203
204 _logger = logging.getLogger(__name__)
205@@ -280,7 +281,7 @@
206
207 return {'id': message.id,
208 'type': message.type,
209- 'body': message.body,
210+ 'body': html_email_clean(message.body),
211 'model': message.model,
212 'res_id': message.res_id,
213 'record_name': message.record_name,
214@@ -633,6 +634,8 @@
215 def create(self, cr, uid, values, context=None):
216 if not values.get('message_id') and values.get('res_id') and values.get('model'):
217 values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
218+ elif not values.get('message_id'):
219+ values['message_id'] = tools.generate_tracking_message_id('private')
220 newid = super(mail_message, self).create(cr, uid, values, context)
221 self._notify(cr, SUPERUSER_ID, newid, context=context)
222 return newid
223@@ -763,7 +766,7 @@
224 ], context=context)
225 fol_objs = fol_obj.read(cr, uid, fol_ids, ['partner_id'], context=context)
226 partners_to_notify |= set(fol['partner_id'][0] for fol in fol_objs)
227- # when writing to a wall
228+ # remove me from notified partners, unless the message is written on my own wall
229 if message.get('author_id') and message.get('model') == "res.partner" and message.get('res_id') == message.get('author_id')[0]:
230 partners_to_notify |= set([message.get('author_id')[0]])
231 elif message.get('author_id'):
232
233=== modified file 'mail/mail_thread.py'
234--- mail/mail_thread.py 2012-11-12 13:10:51 +0000
235+++ mail/mail_thread.py 2012-11-14 09:23:22 +0000
236@@ -319,10 +319,12 @@
237 """
238 assert isinstance(message, Message), 'message must be an email.message.Message at this point'
239 message_id = message.get('Message-Id')
240+ references = decode_header(message, 'References')
241+ in_reply_to = decode_header(message, 'In-Reply-To')
242
243 # 1. Verify if this is a reply to an existing thread
244- references = decode_header(message, 'References') or decode_header(message, 'In-Reply-To')
245- ref_match = references and tools.reference_re.search(references)
246+ thread_references = references or in_reply_to
247+ ref_match = thread_references and tools.reference_re.search(thread_references)
248 if ref_match:
249 thread_id = int(ref_match.group(1))
250 model = ref_match.group(2) or model
251@@ -333,6 +335,14 @@
252 message_id, model, thread_id, custom_values, uid)
253 return [(model, thread_id, custom_values, uid)]
254
255+ # Verify this is a reply to a private message
256+ message_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', in_reply_to)], limit=1, context=context)
257+ if message_ids:
258+ message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context)
259+ _logger.debug('Routing mail with Message-Id %s: reply to a private message: %s, custom_values: %s, uid: %s',
260+ message_id, message.id, custom_values, uid)
261+ return [(False, 0, custom_values, uid)]
262+
263 # 2. Look for a matching mail.alias entry
264 # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
265 # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
266@@ -376,14 +386,19 @@
267 def message_process(self, cr, uid, model, message, custom_values=None,
268 save_original=False, strip_attachments=False,
269 thread_id=None, context=None):
270- """Process an incoming RFC2822 email message, relying on
271- ``mail.message.parse()`` for the parsing operation,
272- and ``message_route()`` to figure out the target model.
273+ """ Process an incoming RFC2822 email message, relying on
274+ ``mail.message.parse()`` for the parsing operation,
275+ and ``message_route()`` to figure out the target model.
276
277- Once the target model is known, its ``message_new`` method
278- is called with the new message (if the thread record did not exist)
279+ Once the target model is known, its ``message_new`` method
280+ is called with the new message (if the thread record did not exist)
281 or its ``message_update`` method (if it did).
282
283+ There is a special case where the target model is False: a reply
284+ to a private message. In this case, we skip the message_new /
285+ message_update step, to just post a new message using mail_thread
286+ message_post.
287+
288 :param string model: the fallback model to use if the message
289 does not match any of the currently configured mail aliases
290 (may be None if a matching alias is supposed to be present)
291@@ -425,15 +440,18 @@
292 for model, thread_id, custom_values, user_id in routes:
293 if self._name != model:
294 context.update({'thread_model': model})
295- model_pool = self.pool.get(model)
296- assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
297- "Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
298- (msg['message_id'], model)
299- if thread_id and hasattr(model_pool, 'message_update'):
300- model_pool.message_update(cr, user_id, [thread_id], msg, context=context)
301+ if model:
302+ model_pool = self.pool.get(model)
303+ assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
304+ "Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
305+ (msg['message_id'], model)
306+ if thread_id and hasattr(model_pool, 'message_update'):
307+ model_pool.message_update(cr, user_id, [thread_id], msg, context=context)
308+ else:
309+ thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context)
310 else:
311- thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context)
312- model_pool.message_post(cr, uid, [thread_id], context=context, **msg)
313+ model_pool = self.pool.get('mail.thread')
314+ model_pool.message_post_user_api(cr, uid, [thread_id], context=context, content_subtype='html', **msg)
315 return thread_id
316
317 def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
318@@ -556,7 +574,6 @@
319 """
320 msg_dict = {
321 'type': 'email',
322- 'subtype': 'mail.mt_comment',
323 'author_id': False,
324 }
325 if not isinstance(message, Message):
326@@ -588,7 +605,7 @@
327 else:
328 msg_dict['email_from'] = message.get('from')
329 partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
330- msg_dict['partner_ids'] = partner_ids
331+ msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids]
332
333 if 'Date' in message:
334 date_hdr = decode(message.get('Date'))
335@@ -629,7 +646,8 @@
336 mail.message ID. Extra keyword arguments will be used as default
337 column values for the new mail.message record.
338 Auto link messages for same id and object
339- :param int thread_id: thread ID to post into, or list with one ID
340+ :param int thread_id: thread ID to post into, or list with one ID;
341+ if False/0, mail.message model will also be set as False
342 :param str body: body of the message, usually raw HTML that will
343 be sanitized
344 :param str subject: optional subject
345@@ -639,10 +657,13 @@
346 ``(name,content)``, where content is NOT base64 encoded
347 :return: ID of newly created mail.message
348 """
349- context = context or {}
350- attachments = attachments or []
351+ if context is None:
352+ context = {}
353+ if attachments is None:
354+ attachments = {}
355+
356 assert (not thread_id) or isinstance(thread_id, (int, long)) or \
357- (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id"
358+ (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id; should be 0, False, an ID or a list with one ID"
359 if isinstance(thread_id, (list, tuple)):
360 thread_id = thread_id and thread_id[0]
361 mail_message = self.pool.get('mail.message')
362@@ -682,7 +703,6 @@
363 # avoid loops when finding ancestors
364 processed_list = []
365 if message_ids:
366- _counter, _counter_max = 0, 200
367 message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context)
368 while (message.parent_id and message.parent_id.id not in processed_list):
369 processed_list.append(message.parent_id.id)
370@@ -707,18 +727,45 @@
371
372 return mail_message.create(cr, uid, values, context=context)
373
374- def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
375- """ Wrapper on message_post, used only in Chatter (JS). The purpose is
376- to handle attachments.
377- # TDE FIXME: body is plaintext: convert it into html
378+ def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False,
379+ attachment_ids=None, context=None, content_subtype='plaintext', **kwargs):
380+ """ Wrapper on message_post, used for user input :
381+ - mail gateway
382+ - quick reply in Chatter (refer to mail.js), not
383+ the mail.compose.message wizard
384+ The purpose is to perform some pre- and post-processing:
385+ - if body is plaintext: convert it into html
386+ - if parent_id: handle reply to a previous message by adding the
387+ parent partners to the message
388+ - type and subtype: comment and mail.mt_comment by default
389+ - attachment_ids: supposed not attached to any document; attach them
390+ to the related document. Should only be set by Chatter.
391 """
392- new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type='comment',
393- subtype='mail.mt_comment', parent_id=parent_id, context=context)
394-
395- # HACK FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
396+ ir_attachment = self.pool.get('ir.attachment')
397+ mail_message = self.pool.get('mail.message')
398+
399+ # 1. Pre-processing: body, partner_ids, type and subtype
400+ if content_subtype == 'plaintext':
401+ body = tools.text2html(body)
402+
403+ partner_ids = kwargs.pop('partner_ids', [])
404+ if parent_id:
405+ parent_message = self.pool.get('mail.message').browse(cr, uid, parent_id, context=context)
406+ partner_ids += [(4, partner.id) for partner in parent_message.partner_ids]
407+ # TDE FIXME HACK: mail.thread -> private message
408+ if self._name == 'mail.thread' and parent_message.author_id.id:
409+ partner_ids.append((4, parent_message.author_id.id))
410+
411+ message_type = kwargs.pop('type', 'comment')
412+ message_subtype = kwargs.pop('type', 'mail.mt_comment')
413+
414+ # 2. Post message
415+ new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=message_type,
416+ subtype=message_subtype, parent_id=parent_id, context=context, partner_ids=partner_ids, **kwargs)
417+
418+ # 3. Post-processing
419+ # HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
420 if attachment_ids:
421- ir_attachment = self.pool.get('ir.attachment')
422- mail_message = self.pool.get('mail.message')
423 filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
424 ('res_model', '=', 'mail.compose.message'),
425 ('res_id', '=', 0),
426
427=== modified file 'mail/res_partner.py'
428--- mail/res_partner.py 2012-10-15 13:23:13 +0000
429+++ mail/res_partner.py 2012-11-14 09:23:22 +0000
430@@ -42,4 +42,22 @@
431 'notification_email_send': lambda *args: 'comment'
432 }
433
434+ def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
435+ subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
436+ """ Override related to res.partner. In case of email message, set it as
437+ private:
438+ - add the target partner in the message partner_ids
439+ - set thread_id as None, because this will trigger the 'private'
440+ aspect of the message (model=False, res_id=False)
441+ """
442+ if isinstance(thread_id, (list, tuple)):
443+ thread_id = thread_id[0]
444+ if type == 'email':
445+ partner_ids = kwargs.get('partner_ids', [])
446+ if thread_id not in partner_ids:
447+ partner_ids.append(thread_id)
448+ kwargs['partner_ids'] = partner_ids
449+ return super(res_partner_mail, self).message_post(cr, uid, False, body=body, subject=subject,
450+ type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)
451+
452 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
453
454=== modified file 'mail/security/ir.model.access.csv'
455--- mail/security/ir.model.access.csv 2012-11-06 15:04:31 +0000
456+++ mail/security/ir.model.access.csv 2012-11-14 09:23:22 +0000
457@@ -7,12 +7,13 @@
458 access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
459 access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
460 access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0
461-access_mail_notification_aystem,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1
462+access_mail_notification_group_user,mail.notification.user,model_mail_notification,base.group_user,1,1,1,0
463+access_mail_notification_system,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1
464 access_mail_group_all,mail.group.all,model_mail_group,,1,0,0,0
465 access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
466 access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
467-access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0
468-access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1
469+access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,0
470+access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
471 access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
472 access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1
473 access_mail_favorite_all,mail.favorite.all,model_mail_favorite,,1,1,1,1
474
475=== modified file 'mail/security/mail_security.xml'
476--- mail/security/mail_security.xml 2012-10-19 09:59:19 +0000
477+++ mail/security/mail_security.xml 2012-11-14 09:23:22 +0000
478@@ -10,7 +10,7 @@
479 <field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id]), '&amp;', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])]</field>
480 </record>
481
482- <record id="mail_followers_read_own" model="ir.rule">
483+ <record id="mail_followers_read_write_own" model="ir.rule">
484 <field name="name">mail.followers: read and write its own entries</field>
485 <field name="model_id" ref="model_mail_followers"/>
486 <field name="domain_force">[('partner_id', '=', user.partner_id.id)]</field>
487@@ -18,6 +18,14 @@
488 <field name="perm_unlink" eval="False"/>
489 </record>
490
491+ <record id="mail_notification_read_write_own" model="ir.rule">
492+ <field name="name">mail.notification: read and write its own entries</field>
493+ <field name="model_id" ref="model_mail_notification"/>
494+ <field name="domain_force">[('partner_id', '=', user.partner_id.id)]</field>
495+ <field name="perm_create" eval="False"/>
496+ <field name="perm_unlink" eval="False"/>
497+ </record>
498+
499 <!--
500 This rule can not be uncommented, because we have a more wide method in mail.message. When we implement a many2one_variable field, we will be able to uncomment this.
501 <record id="mail_message_read_partner_or_author" model="ir.rule">
502
503=== modified file 'mail/static/src/css/mail.css'
504--- mail/static/src/css/mail.css 2012-11-13 10:35:30 +0000
505+++ mail/static/src/css/mail.css 2012-11-14 09:23:22 +0000
506@@ -87,6 +87,10 @@
507 margin-bottom: 0px;
508 margin-top: 2px;
509 }
510+.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_body p{
511+ margin-top: 2px;
512+ margin-bottom: 2px;
513+}
514
515 /* a) Indented Messages */
516
517
518=== modified file 'mail/static/src/js/mail.js'
519--- mail/static/src/js/mail.js 2012-11-13 14:57:22 +0000
520+++ mail/static/src/js/mail.js 2012-11-14 09:23:22 +0000
521@@ -590,7 +590,7 @@
522
523 if (body.match(/\S+/)) {
524 //session.web.blockUI();
525- this.parent_thread.ds_thread.call('message_post_api', [
526+ this.parent_thread.ds_thread.call('message_post_user_api', [
527 this.context.default_res_id,
528 mail.ChatterUtils.get_text2html(body),
529 false,
530@@ -728,7 +728,6 @@
531
532 mail.ThreadMessage = mail.MessageCommon.extend({
533 template: 'mail.thread.message',
534-
535
536 start: function () {
537 this._super.apply(this, arguments);
538
539=== modified file 'mail/tests/__init__.py'
540--- mail/tests/__init__.py 2012-09-14 11:58:15 +0000
541+++ mail/tests/__init__.py 2012-11-14 09:23:22 +0000
542@@ -18,6 +18,7 @@
543 # along with this program. If not, see <http://www.gnu.org/licenses/>.
544 #
545 ##############################################################################
546+
547 from . import test_mail, test_mail_access_rights
548
549 checks = [
550
551=== modified file 'mail/tests/test_mail.py'
552--- mail/tests/test_mail.py 2012-11-09 15:11:22 +0000
553+++ mail/tests/test_mail.py 2012-11-14 09:23:22 +0000
554@@ -21,8 +21,8 @@
555
556 import tools
557
558-from openerp.tests import common
559-from openerp.tools.html_sanitize import html_sanitize
560+from openerp.addons.mail.tests import test_mail_mockup
561+from openerp.tools.mail import html_sanitize
562
563 MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
564 To: {to}
565@@ -84,43 +84,11 @@
566 """
567
568
569-class TestMailMockups(common.TransactionCase):
570-
571- def _mock_smtp_gateway(self, *args, **kwargs):
572- return True
573-
574- def _init_mock_build_email(self):
575- self._build_email_args_list = []
576- self._build_email_kwargs_list = []
577-
578- def _mock_build_email(self, *args, **kwargs):
579- """ Mock build_email to be able to test its values. Store them into
580- some internal variable for latter processing. """
581- self._build_email_args_list.append(args)
582- self._build_email_kwargs_list.append(kwargs)
583- return self._build_email(*args, **kwargs)
584-
585- def setUp(self):
586- super(TestMailMockups, self).setUp()
587- # Install mock SMTP gateway
588- self._init_mock_build_email()
589- self._build_email = self.registry('ir.mail_server').build_email
590- self.registry('ir.mail_server').build_email = self._mock_build_email
591- self._send_email = self.registry('ir.mail_server').send_email
592- self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
593-
594- def tearDown(self):
595- # Remove mocks
596- self.registry('ir.mail_server').build_email = self._build_email
597- self.registry('ir.mail_server').send_email = self._send_email
598- super(TestMailMockups, self).tearDown()
599-
600-
601-class test_mail(TestMailMockups):
602+class test_mail(test_mail_mockup.TestMailMockups):
603
604 def _mock_send_get_mail_body(self, *args, **kwargs):
605 # def _send_get_mail_body(self, cr, uid, mail, partner=None, context=None)
606- body = tools.append_content_to_html(args[2].body_html, kwargs.get('partner').name if kwargs.get('partner') else 'No specific partner')
607+ body = tools.append_content_to_html(args[2].body_html, kwargs.get('partner').name if kwargs.get('partner') else 'No specific partner', plaintext=False)
608 return body
609
610 def setUp(self):
611@@ -375,10 +343,10 @@
612 _subject = 'Pigs'
613 _mail_subject = '%s posted on %s' % (user_admin.name, group_pigs.name)
614 _body1 = 'Pigs rules'
615- _mail_body1 = 'Pigs rules\n<pre>Admin</pre>\n'
616- _mail_bodyalt1 = 'Pigs rules\nAdmin'
617+ _mail_body1 = 'Pigs rules\n<div><p>Admin</p></div>\n'
618+ _mail_bodyalt1 = 'Pigs rules\nAdmin\n'
619 _body2 = '<html>Pigs rules</html>'
620- _mail_body2 = html_sanitize('<html>Pigs rules\n<pre>Admin</pre>\n</html>')
621+ _mail_body2 = html_sanitize('<html>Pigs rules\n<div><p>Admin</p></div>\n</html>')
622 _mail_bodyalt2 = 'Pigs rules\nAdmin'
623 _attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
624
625@@ -399,7 +367,7 @@
626 # Test: sent_email: email send by server: correct subject, body, body_alternative
627 for sent_email in sent_emails:
628 self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
629- self.assertEqual(sent_email['body'], _mail_body1 + '\n<pre>Bert Tartopoils</pre>\n', 'sent_email body incorrect')
630+ self.assertEqual(sent_email['body'], _mail_body1 + '\nBert Tartopoils\n', 'sent_email body incorrect')
631 # the html2plaintext uses etree or beautiful soup, so the result may be slighly different
632 # depending if you have installed beautiful soup.
633 self.assertIn(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils\n', 'sent_email body_alternative is incorrect')
634
635=== modified file 'mail/tests/test_mail_access_rights.py'
636--- mail/tests/test_mail_access_rights.py 2012-10-25 11:30:48 +0000
637+++ mail/tests/test_mail_access_rights.py 2012-11-14 09:23:22 +0000
638@@ -19,11 +19,11 @@
639 #
640 ##############################################################################
641
642-from openerp.addons.mail.tests import test_mail
643+from openerp.addons.mail.tests import test_mail_mockup
644 from osv.orm import except_orm
645
646
647-class test_mail_access_rights(test_mail.TestMailMockups):
648+class test_mail_access_rights(test_mail_mockup.TestMailMockups):
649
650 def setUp(self):
651 super(test_mail_access_rights, self).setUp()
652
653=== added file 'mail/tests/test_mail_mockup.py'
654--- mail/tests/test_mail_mockup.py 1970-01-01 00:00:00 +0000
655+++ mail/tests/test_mail_mockup.py 2012-11-14 09:23:22 +0000
656@@ -0,0 +1,54 @@
657+# -*- coding: utf-8 -*-
658+##############################################################################
659+#
660+# OpenERP, Open Source Business Applications
661+# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
662+#
663+# This program is free software: you can redistribute it and/or modify
664+# it under the terms of the GNU Affero General Public License as
665+# published by the Free Software Foundation, either version 3 of the
666+# License, or (at your option) any later version.
667+#
668+# This program is distributed in the hope that it will be useful,
669+# but WITHOUT ANY WARRANTY; without even the implied warranty of
670+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
671+# GNU Affero General Public License for more details.
672+#
673+# You should have received a copy of the GNU Affero General Public License
674+# along with this program. If not, see <http://www.gnu.org/licenses/>.
675+#
676+##############################################################################
677+
678+from openerp.tests import common
679+
680+
681+class TestMailMockups(common.TransactionCase):
682+
683+ def _mock_smtp_gateway(self, *args, **kwargs):
684+ return True
685+
686+ def _init_mock_build_email(self):
687+ self._build_email_args_list = []
688+ self._build_email_kwargs_list = []
689+
690+ def _mock_build_email(self, *args, **kwargs):
691+ """ Mock build_email to be able to test its values. Store them into
692+ some internal variable for latter processing. """
693+ self._build_email_args_list.append(args)
694+ self._build_email_kwargs_list.append(kwargs)
695+ return self._build_email(*args, **kwargs)
696+
697+ def setUp(self):
698+ super(TestMailMockups, self).setUp()
699+ # Install mock SMTP gateway
700+ self._init_mock_build_email()
701+ self._build_email = self.registry('ir.mail_server').build_email
702+ self.registry('ir.mail_server').build_email = self._mock_build_email
703+ self._send_email = self.registry('ir.mail_server').send_email
704+ self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
705+
706+ def tearDown(self):
707+ # Remove mocks
708+ self.registry('ir.mail_server').build_email = self._build_email
709+ self.registry('ir.mail_server').send_email = self._send_email
710+ super(TestMailMockups, self).tearDown()
711
712=== modified file 'mail/wizard/mail_compose_message_view.xml'
713--- mail/wizard/mail_compose_message_view.xml 2012-11-13 08:41:53 +0000
714+++ mail/wizard/mail_compose_message_view.xml 2012-11-14 09:23:22 +0000
715@@ -8,11 +8,11 @@
716 <form string="Compose Email" version="7.0">
717 <group>
718 <!-- truly invisible fields for control and options -->
719- <field name="composition_mode" invisible="1"/>
720- <field name="model" invisible="1"/>
721- <field name="res_id" invisible="1"/>
722- <field name="parent_id" invisible="1"/>
723- <field name="content_subtype" invisible="1"/>
724+ <field name="composition_mode" invisible="0"/>
725+ <field name="model" invisible="0"/>
726+ <field name="res_id" invisible="0"/>
727+ <field name="parent_id" invisible="0"/>
728+ <field name="content_subtype" invisible="0"/>
729 <!-- visible wizard -->
730 <label for="partner_ids" string="Recipients"/>
731 <div>
732
733=== modified file 'note/note.py'
734--- note/note.py 2012-11-02 13:17:59 +0000
735+++ note/note.py 2012-11-14 09:23:22 +0000
736@@ -20,9 +20,7 @@
737 ##############################################################################
738
739 from openerp.osv import osv, fields
740-from tools.translate import _
741-import re
742-from openerp.tools.misc import html2plaintext
743+from openerp.tools import html2plaintext
744
745 class note_stage(osv.osv):
746 """ Category of Note """
747
748=== modified file 'pad/pad.py'
749--- pad/pad.py 2012-11-09 06:30:02 +0000
750+++ pad/pad.py 2012-11-14 09:23:22 +0000
751@@ -6,7 +6,7 @@
752 import urllib2
753 import logging
754 from tools.translate import _
755-from openerp.tools.misc import html2plaintext
756+from openerp.tools import html2plaintext
757 from py_etherpad import EtherpadLiteClient
758
759 _logger = logging.getLogger(__name__)
760
761=== modified file 'portal/mail_mail.py'
762--- portal/mail_mail.py 2012-10-01 09:14:41 +0000
763+++ portal/mail_mail.py 2012-11-14 09:23:22 +0000
764@@ -36,5 +36,5 @@
765 if partner:
766 context = dict(context or {}, signup_valid=True)
767 partner = self.pool.get('res.partner').browse(cr, uid, partner.id, context)
768- body = tools.append_content_to_html(body, "Log in our portal at: %s" % partner.signup_url)
769+ body = tools.append_content_to_html(body, ("<div><p>Log in our portal at: %s</p></div>" % partner.signup_url), plaintext=False)
770 return body
771
772=== modified file 'portal/portal_demo.xml'
773--- portal/portal_demo.xml 2012-09-20 14:57:53 +0000
774+++ portal/portal_demo.xml 2012-11-14 09:23:22 +0000
775@@ -1,22 +1,27 @@
776 <?xml version="1.0"?>
777 <openerp>
778- <data>
779+ <data noupdate="1">
780
781- <!-- Create a portal member attached to a partner -->
782- <record id="demo_user0" model="res.users">
783+ <!-- Create a partner, that is also a portal user -->
784+ <record id="partner_demo_portal" model="res.partner">
785 <field name="name">Demo Portal User</field>
786+ <field name="email">demo@portal.example.com</field>
787+ <field name="supplier" eval="False"/>
788+ <field name="customer" eval="True"/>
789+ </record>
790+ <record id="user_demo_portal" model="res.users">
791+ <field name="partner_id" ref="partner_demo_portal"/>
792 <field name="login">portal</field>
793 <field name="password">portal</field>
794+ <field name="signature">--
795+Mr Demo Portal</field>
796 <!-- Avoid auto-including this user in any default group -->
797 <field name="groups_id" eval="[(5,)]"/>
798- <field name="supplier" eval="False"/>
799- <field name="customer" eval="True"/>
800- <field name="email">demo@portal.wrong.address</field>
801 </record>
802
803 <!-- Add the demo user to the portal (and therefore to the portal member group) -->
804 <record id="group_portal" model="res.groups">
805- <field name="users" eval="[(4,ref('demo_user0'))]"/>
806+ <field name="users" eval="[(4,ref('user_demo_portal'))]"/>
807 </record>
808
809 <!-- Company news and comments -->
810@@ -24,33 +29,41 @@
811 <field name="subject">Our company's first blog-post !</field>
812 <field name="model">mail.group</field>
813 <field name="res_id" ref="company_news_feed"/>
814- <field name="body"><![CDATA[Hello, and welcome to our company's portal !
815-
816-Lorem ipsum <b>sit amet</b>, consectetur <em>adipiscing elit</em>. Pellentesque et quam sapien, in sagittis tellus.
817-Praesent vel massa sed massa consequat egestas in tristique orci. Praesent iaculis libero et neque vehicula iaculis. Vivamus placerat tincidunt orci ac ornare. Proin ut dolor fringilla velit ultricies consequat. Maecenas sit amet ipsum non leo interdum imperdiet. Donec sapien mi.
818-
819-Fusce tempus elit volutpat mi auctor adipiscing. Nam congue luctus suscipit. Sed tellus libero, venenatis ut mollis ut, luctus quis dui. Sed rhoncus pulvinar orci in consectetur.
820-
821-Nulla turpis leo, rhoncus ut egestas sit amet, consectetur vitae urna. Mauris in dolor in sapien tempus vehicula.]]></field>
822+ <field name="body"><![CDATA[<p>Hello, and welcome to our company's portal !</p>
823+<p>It is a great pleasure to announce you the creation of our portal by writing this first news! As you may have seen, a new discussion group is now present under your 'My groups' menu: <b>Company's News</b>. We will post news about the company and its employees in this discussion group. Moreover, we will be able to communicate with our partners that are given the opportunity to join us in our portal.</p>
824+<p>A new era of communication has begun! <b>Feel free to post your feelings about our portal by replying on this message!</b></p>]]></field>
825 <field name="type">comment</field>
826+ <field name="subtype_id" ref="mail.mt_comment"/>
827 <field name="author_id" ref="base.partner_root"/>
828 </record>
829
830 <record id="message_company_news0_comment0" model="mail.message">
831 <field name="model">mail.group</field>
832 <field name="res_id" ref="company_news_feed"/>
833- <field name="body"><![CDATA[Great first blogpost ! (first comment)]]></field>
834- <field name="parent_id" ref="message_company_news0"/>
835- <field name="type">comment</field>
836+ <field name="body"><![CDATA[<p>As your first portal member, I am very pleased to be able to be able to communicate directly with you. Be sure I'll read all news carefully!</p>]]></field>
837+ <field name="parent_id" ref="message_company_news0"/>
838+ <field name="type">comment</field>
839+ <field name="subtype_id" ref="mail.mt_comment"/>
840+ <field name="author_id" ref="partner_demo_portal"/>
841+ </record>
842+
843+ <record id="message_company_news0_comment1" model="mail.message">
844+ <field name="model">mail.group</field>
845+ <field name="res_id" ref="company_news_feed"/>
846+ <field name="body"><![CDATA[<p>That's good news! As said by <i>Demo Portal User</i> in the previous post, I'm looking forward to hearing from you!</p>]]></field>
847+ <field name="parent_id" ref="message_company_news0"/>
848+ <field name="type">comment</field>
849+ <field name="subtype_id" ref="mail.mt_comment"/>
850 <field name="author_id" ref="base.res_partner_1"/>
851 </record>
852
853- <record id="message_company_news0_comment1" model="mail.message">
854+ <record id="message_company_news0_comment2" model="mail.message">
855 <field name="model">mail.group</field>
856 <field name="res_id" ref="company_news_feed"/>
857- <field name="body"><![CDATA[Thanks ! (second comment)]]></field>
858+ <field name="body"><![CDATA[<p>This feature is realy great! We will be able to communicate directly to our partners!</p>]]></field>
859 <field name="parent_id" ref="message_company_news0"/>
860 <field name="type">comment</field>
861+ <field name="subtype_id" ref="mail.mt_comment"/>
862 <field name="author_id" ref="base.partner_demo"/>
863 </record>
864
865
866=== modified file 'portal/tests/test_portal.py'
867--- portal/tests/test_portal.py 2012-10-09 15:54:56 +0000
868+++ portal/tests/test_portal.py 2012-11-14 09:23:22 +0000
869@@ -19,12 +19,11 @@
870 #
871 ##############################################################################
872
873-from openerp.addons.mail.tests import test_mail
874-from openerp.tools import append_content_to_html
875+from openerp.addons.mail.tests import test_mail_mockup
876 from osv.orm import except_orm
877
878
879-class test_portal(test_mail.TestMailMockups):
880+class test_portal(test_mail_mockup.TestMailMockups):
881
882 def setUp(self):
883 super(test_portal, self).setUp()
884
885=== modified file 'portal/wizard/portal_wizard.py'
886--- portal/wizard/portal_wizard.py 2012-10-25 13:09:30 +0000
887+++ portal/wizard/portal_wizard.py 2012-11-14 09:23:22 +0000
888@@ -24,10 +24,9 @@
889
890 from osv import osv, fields
891 from tools.translate import _
892-from tools.misc import email_re
893+from tools import email_re
894 from openerp import SUPERUSER_ID
895
896-from base.res.res_partner import _lang_get
897 _logger = logging.getLogger(__name__)
898
899 # welcome email sent to portal users

Subscribers

People subscribed via source and target branches

to all changes: