From b16935efdc608361db0ad50f3ddffaabbf86d1e3 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 Jun 2013 13:13:54 +0200 Subject: [PATCH 01/23] [IMP] orm: include forbidden IDs when logging ir.rules violations, to make troubleshooting easier bzr revid: odo@openerp.com-20130604111354-1x5yc305okhx5ujk --- openerp/osv/orm.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index a451fbf6582..765ab434f9b 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -3853,11 +3853,12 @@ class BaseModel(object): # Attempt to distinguish record rule restriction vs deleted records, # to provide a more specific error message - check if the missinf cr.execute('SELECT id FROM ' + self._table + ' WHERE id IN %s', (tuple(missing_ids),)) - if cr.rowcount: + forbidden_ids = [x[0] for x in cr.fetchall()] + if forbidden_ids: # the missing ids are (at least partially) hidden by access rules if uid == SUPERUSER_ID: return - _logger.warning('Access Denied by record rules for operation: %s, uid: %s, model: %s', operation, uid, self._name) + _logger.warning('Access Denied by record rules for operation: %s on record ids: %r, uid: %s, model: %s', operation, forbidden_ids, uid, self._name) raise except_orm(_('Access Denied'), _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ (self._description, operation)) @@ -4817,8 +4818,8 @@ class BaseModel(object): order_field = order_split[0].strip() order_direction = order_split[1].strip() if len(order_split) == 2 else '' inner_clause = None - if order_field == 'id': - order_by_elements.append('"%s"."id" %s' % (self._table, order_direction)) + if order_field == 'id' or (self._log_access and order_field in LOG_ACCESS_COLUMNS.keys()): + order_by_elements.append('"%s"."%s" %s' % (self._table, order_field, order_direction)) elif order_field in self._columns: order_column = self._columns[order_field] if order_column._classic_read: @@ -4836,6 +4837,8 @@ class BaseModel(object): inner_clause = self._generate_m2o_order_by(order_field, query) else: continue # ignore non-readable or "non-joinable" fields + else: + raise ValueError( _("Sorting field %s not found on model %s") %( order_field, self._name)) if inner_clause: if isinstance(inner_clause, list): for clause in inner_clause: @@ -5092,20 +5095,18 @@ class BaseModel(object): :param parent: optional parent field name (default: ``self._parent_name = parent_id``) :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected. """ - if not parent: parent = self._parent_name - ids_parent = ids[:] - query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table) - while ids_parent: - ids_parent2 = [] - for i in range(0, len(ids), cr.IN_MAX): - sub_ids_parent = ids_parent[i:i+cr.IN_MAX] - cr.execute(query, (tuple(sub_ids_parent),)) - ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall()))) - ids_parent = ids_parent2 - for i in ids_parent: - if i in ids: + + # must ignore 'active' flag, ir.rules, etc. => direct SQL query + query = 'SELECT "%s" FROM "%s" WHERE id = %%s' % (parent, self._table) + for id in ids: + current_id = id + while current_id is not None: + cr.execute(query, (current_id,)) + result = cr.fetchone() + current_id = result[0] if result else None + if current_id == id: return False return True From 12e3ce123f476304319298a0a80dfa15ab3a8a9c Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 5 Jun 2013 11:39:15 +0200 Subject: [PATCH 02/23] detail bzr revid: nicolas.vanhoren@openerp.com-20130605093915-kmwtw7cq578lb230 --- addons/web/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/http.py b/addons/web/http.py index 5ada5d4a68c..9377ab02df8 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -366,7 +366,7 @@ class RequestProxy(object): return delattr(_thlocal.stack[-1], name) @classmethod def set_request(cls, request): - class with_obj: + class with_obj(object): def __enter__(self): if getattr(_thlocal, "stack", None) is None: _thlocal.stack = [] From 9fd2e7297ffb0fe9cb96767bcb8ad686a66d064b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 5 Jun 2013 12:13:00 +0200 Subject: [PATCH 03/23] [IMP] Chatter: better display of date bzr revid: tde@openerp.com-20130605101300-t1lwykg3d6lp7au2 --- addons/mail/static/src/js/mail.js | 1 + addons/mail/static/src/xml/mail.xml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 2cd9c7c077d..70e93e2957c 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -259,6 +259,7 @@ openerp.mail = function (session) { format_data: function () { //formating and add some fields for render this.date = this.date ? session.web.str_to_datetime(this.date) : false; + this.display_date = this.date.toString('ddd MMM dd yyyy HH:mm'); if (this.date && new Date().getTime()-this.date.getTime() < 7*24*60*60*1000) { this.timerelative = $.timeago(this.date); } diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index f5004941a8d..b3b79bcfc43 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -295,7 +295,10 @@ notified • - + + + + • From 9c57f6e1cdaf8a80a403486b51cd9e661c11e3dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 12:24:37 +0200 Subject: [PATCH 04/23] [FIX] mail: various fixes Mass mailing: emails due to mass mailing are not queued instead of sent immediately. As most of cases go through mail_followers._notify(), a new argument is added that allows to control the force_send (like what is done in email_template). Chatter: better display of recipients when having a message_post due to mass mailing (not always displaying logged a note). Notified people: when having no subtype, just avoid notifying the followers but take into account specified recipients (partner_ids). bzr revid: tde@openerp.com-20130606102437-43s4xp96b5wa3r1q --- addons/mail/mail_followers.py | 17 +++++---- addons/mail/mail_mail.py | 16 ++++----- addons/mail/mail_message.py | 42 ++++++++++++++-------- addons/mail/static/src/xml/mail.xml | 6 ++-- addons/mail/wizard/mail_compose_message.py | 12 +++---- 5 files changed, 49 insertions(+), 44 deletions(-) diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index b85410a7a1c..93196976e90 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -150,11 +150,14 @@ class mail_notification(osv.Model): return footer - def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None): + def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None, force_send=False): """ Send by email the notification depending on the user preferences :param list partners_to_notify: optional list of partner ids restricting the notifications to process + :param bool force_send: if True, the generated mail.mail is + immediately sent after being created, as if the scheduler + was executed for this message only. """ if context is None: context = {} @@ -203,13 +206,9 @@ class mail_notification(osv.Model): 'recipient_ids': [(4, id) for id in notify_partner_ids], 'references': references, } - if msg.email_from: - mail_values['email_from'] = msg.email_from - if msg.reply_to: - mail_values['reply_to'] = msg.reply_to mail_mail = self.pool.get('mail.mail') email_notif_id = mail_mail.create(cr, uid, mail_values, context=context) - try: - return mail_mail.send(cr, uid, [email_notif_id], context=context) - except Exception: - return False + + if force_send: + mail_mail.send(cr, uid, [email_notif_id], context=context) + return True diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index a36ec85b27b..4da3b27bd7d 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -55,7 +55,6 @@ class mail_mail(osv.Model): 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"), 'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1), - 'email_from': fields.char('From', help='Message sender, taken from user preferences.'), 'email_to': fields.text('To', help='Message recipients (emails)'), 'recipient_ids': fields.many2many('res.partner', string='To (Partners)'), 'email_cc': fields.char('Cc', help='Carbon copy message recipients'), @@ -67,16 +66,10 @@ class mail_mail(osv.Model): } def _get_default_from(self, cr, uid, context=None): - this = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context) - if this.alias_domain: - return '%s <%s@%s>' % (this.name, this.alias_name, this.alias_domain) - elif this.email: - return '%s <%s>' % (this.name, this.email) - raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias.")) + return self.pool['mail.message']._get_default_from(cr, uid, context=context) _defaults = { 'state': 'outgoing', - 'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx), } def default_get(self, cr, uid, fields, context=None): @@ -97,19 +90,22 @@ class mail_mail(osv.Model): # model, res_id: comes from values OR related message model = values.get('model') res_id = values.get('res_id') + email_from = values.get('email_from') if values.get('mail_message_id') and (not model or not res_id): message = self.pool.get('mail.message').browse(cr, uid, values.get('mail_message_id'), context=context) if not model: model = message.model if not res_id: res_id = message.res_id + if not email_from: + email_from = message.email_from # if model and res_id: try to use ``message_get_reply_to`` that returns the document alias if model and res_id and hasattr(self.pool[model], 'message_get_reply_to'): email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0] # no alias reply_to -> reply_to will be the email_from, only the email part - if not email_reply_to and values.get('email_from'): - emails = tools.email_split(values.get('email_from')) + if not email_reply_to and email_from: + emails = tools.email_split(email_from) if emails: email_reply_to = emails[0] diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index b8a00a790a8..884d59ba9be 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -182,6 +182,14 @@ class mail_message(osv.Model): def _needaction_domain_get(self, cr, uid, context=None): return [('to_read', '=', True)] + def _get_default_from(self, cr, uid, context=None): + this = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context) + if this.alias_domain: + return '%s <%s@%s>' % (this.name, this.alias_name, this.alias_domain) + elif this.email: + return '%s <%s>' % (this.name, this.email) + raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias.")) + def _get_default_author(self, cr, uid, context=None): return self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] @@ -190,7 +198,7 @@ class mail_message(osv.Model): 'date': lambda *a: fields.datetime.now(), 'author_id': lambda self, cr, uid, ctx=None: self._get_default_author(cr, uid, ctx), 'body': '', - 'email_from': lambda self, cr, uid, ctx=None: self.pool.get('mail.mail')._get_default_from(cr, uid, ctx), + 'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx), } #------------------------------------------------------ @@ -315,8 +323,10 @@ class mail_message(osv.Model): for key, message in message_tree.iteritems(): if message.author_id: partner_ids |= set([message.author_id.id]) - if message.notified_partner_ids: + if message.subtype_id and message.notified_partner_ids: # take notified people of message with a subtype partner_ids |= set([partner.id for partner in message.notified_partner_ids]) + elif not message.subtype_id and message.partner_ids: # take specified people of message without a subtype (log) + partner_ids |= set([partner.id for partner in message.partner_ids]) if message.attachment_ids: attachment_ids |= set([attachment.id for attachment in message.attachment_ids]) # Read partners as SUPERUSER -> display the names like classic m2o even if no access @@ -336,9 +346,15 @@ class mail_message(osv.Model): else: author = (0, message.email_from) partner_ids = [] - for partner in message.notified_partner_ids: - if partner.id in partner_tree: - partner_ids.append(partner_tree[partner.id]) + if message.subtype_id: + partner_ids = [partner_tree[partner.id] for partner in message.notified_partner_ids + if partner.id in partner_tree] + else: + partner_ids = [partner_tree[partner.id] for partner in message.partner_ids + if partner.id in partner_tree] + # for partner in message.notified_partner_ids: + # if partner.id in partner_tree: + # partner_ids.append(partner_tree[partner.id]) attachment_ids = [] for attachment in message.attachment_ids: if attachment.id in attachments_tree: @@ -752,7 +768,7 @@ class mail_message(osv.Model): elif not values.get('message_id'): values['message_id'] = tools.generate_tracking_message_id('private') newid = super(mail_message, self).create(cr, uid, values, context) - self._notify(cr, uid, newid, context=context) + self._notify(cr, uid, newid, context=context, force_send=context.get('mail_notify_force_send', True)) # TDE FIXME: handle default_starred. Why not setting an inv on starred ? # Because starred will call set_message_starred, that looks for notifications. # When creating a new mail_message, it will create a notification to a message @@ -866,20 +882,16 @@ class mail_message(osv.Model): return '' return result - def _notify(self, cr, uid, newid, context=None): + def _notify(self, cr, uid, newid, context=None, force_send=False): """ Add the related record followers to the destination partner_ids if is not a private message. Call mail_notification.notify to manage the email sending """ notification_obj = self.pool.get('mail.notification') message = self.browse(cr, uid, newid, context=context) - partners_to_notify = set([]) - # message has no subtype_id: pure log message -> no partners, no one notified - if not message.subtype_id: - return True - # all followers of the mail.message document have to be added as partners and notified - if message.model and message.res_id: + # all followers of the mail.message document have to be added as partners and notified if a subtype is defined (otherwise: log message) + if message.subtype_id and message.model and message.res_id: fol_obj = self.pool.get("mail.followers") # browse as SUPERUSER because rules could restrict the search results fol_ids = fol_obj.search(cr, SUPERUSER_ID, [ @@ -889,7 +901,7 @@ class mail_message(osv.Model): ], context=context) partners_to_notify |= set(fo.partner_id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)) # remove me from notified partners, unless the message is written on my own wall - if message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id: + if message.subtype_id and message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id: partners_to_notify |= set([message.author_id]) elif message.author_id: partners_to_notify -= set([message.author_id]) @@ -900,7 +912,7 @@ class mail_message(osv.Model): # notify if partners_to_notify: - notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context) + notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context, force_send=force_send) message.refresh() # An error appear when a user receive a notification without notifying diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index b3b79bcfc43..8c507925359 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -271,16 +271,16 @@ • - + logged a note - + to nobody - diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 7bf4d47d1ff..383929a6e07 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -126,7 +126,6 @@ class mail_compose_message(osv.TransientModel): _defaults = { 'composition_mode': 'comment', - 'email_from': lambda self, cr, uid, ctx={}: self.pool.get('mail.mail')._get_default_from(cr, uid, context=ctx), 'body': lambda self, cr, uid, ctx={}: '', 'subject': lambda self, cr, uid, ctx={}: False, 'partner_ids': lambda self, cr, uid, ctx={}: [], @@ -158,7 +157,7 @@ class mail_compose_message(osv.TransientModel): return super(mail_compose_message, self).check_access_rule(cr, uid, ids, operation, context=context) - def _notify(self, cr, uid, newid, context=None): + def _notify(self, cr, uid, newid, context=None, force_send=False): """ Override specific notify method of mail.message, because we do not want that feature in the wizard. """ return @@ -280,11 +279,10 @@ class mail_compose_message(osv.TransientModel): elif mass_mail_mode: # mass mail: is a log pushed to recipients unless specified, author not added if not wizard.notify: subtype = False - context = dict(context, mail_create_nosubscribe=True) # add context key to avoid subscribing the author - msg_id = active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values) - # mass_mailing, post without notify: notify specific partners - if mass_mail_mode and not wizard.notify and post_values['partner_ids']: - self.pool.get('mail.notification')._notify(cr, uid, msg_id, post_values['partner_ids'], context=context) + context = dict(context, + mail_notify_force_send=False, # do not send emails directly but use the queue instead + mail_create_nosubscribe=True) # add context key to avoid subscribing the author + active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values) return {'type': 'ir.actions.act_window_close'} From a216e70e8513d1b9cad807b738023d8a92d8c8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 14:32:30 +0200 Subject: [PATCH 05/23] [FIX] [IMP] mail: allow to use message_post on models not having message_post method defined. mail.compose.message wizard and messaage_process now check that the active model has message_post defined. Otherwise using thread_model context key that is better managed in message_post, it is possible to use mail_thread.message_post() with a thread_model and thread_id that should not allow message_post otherwise. Hint: look for backport into 7.0 bzr revid: tde@openerp.com-20130606123230-d7ql14yydzfrlc7h --- addons/mail/mail_thread.py | 5 ++++- addons/mail/wizard/mail_compose_message.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 6a32ed3bb52..e676e69552d 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -730,6 +730,9 @@ class mail_thread(osv.AbstractModel): else: assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message." model_pool = self.pool.get('mail.thread') + if not hasattr(hasattr, 'message_post'): + context['thread_model'] = model + model_pool = self.pool['mail.thread'] new_msg_id = model_pool.message_post(cr, uid, [thread_id], context=context, subtype='mail.mt_comment', **msg) if partner_ids: @@ -1064,7 +1067,7 @@ class mail_thread(osv.AbstractModel): model = False if thread_id: model = context.get('thread_model', self._name) if self._name == 'mail.thread' else self._name - if model != self._name: + if model != self._name and hasattr(self.pool[model], 'message_post'): del context['thread_model'] return self.pool[model].message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 383929a6e07..e3383270548 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -234,6 +234,9 @@ class mail_compose_message(osv.TransientModel): for wizard in self.browse(cr, uid, ids, context=context): mass_mail_mode = wizard.composition_mode == 'mass_mail' active_model_pool = self.pool[wizard.model if wizard.model else 'mail.thread'] + if not hasattr(active_model_pool, 'message_post'): + context['thread_model'] = wizard.model + active_model_pool = self.pool['mail.thread'] # wizard works in batch mode: [res_id] or active_ids res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id] From b3cde8da4a0345ba9f1314681339bcba76bc33d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 14:34:15 +0200 Subject: [PATCH 06/23] [FIX] email_template: allow to define context action even without having create access on ir.values and ir.action Hint: look for backport into 7.0 bzr revid: tde@openerp.com-20130606123415-vuelai2eh1yy3c87 --- addons/email_template/email_template.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 4a767457321..c7efb9582fe 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -24,6 +24,7 @@ import base64 import logging import openerp +from openerp import SUPERUSER_ID from openerp.osv import osv, fields from openerp.osv import fields from openerp import tools @@ -199,7 +200,7 @@ class email_template(osv.osv): model_data_id = data_obj._get_id(cr, uid, 'mail', 'email_compose_message_wizard_form') res_id = data_obj.browse(cr, uid, model_data_id, context=context).res_id button_name = _('Send Mail (%s)') % template.name - vals['ref_ir_act_window'] = action_obj.create(cr, uid, { + vals['ref_ir_act_window'] = action_obj.create(cr, SUPERUSER_ID, { 'name': button_name, 'type': 'ir.actions.act_window', 'res_model': 'mail.compose.message', @@ -211,7 +212,7 @@ class email_template(osv.osv): 'target': 'new', 'auto_refresh':1 }, context) - vals['ref_ir_value'] = self.pool.get('ir.values').create(cr, uid, { + vals['ref_ir_value'] = self.pool.get('ir.values').create(cr, SUPERUSER_ID, { 'name': button_name, 'model': src_obj, 'key2': 'client_action_multi', From 72f6a8c646179d433de11c2f26e5f589b5ad8cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 14:37:24 +0200 Subject: [PATCH 07/23] [FIX] mail.compose.message: supports user_signature of template. Note that in case of multiple wizards (send on ids that is not a singleton list), user_signature on one template is valid for all wizard. bzr revid: tde@openerp.com-20130606123724-f7q3mwm81ek7mr5c --- addons/email_template/wizard/mail_compose_message.py | 10 +++++++--- addons/mail/mail_followers.py | 10 +++++++--- addons/mail/mail_message.py | 9 ++++++--- addons/mail/wizard/mail_compose_message.py | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 7f44828ce15..6fbc541341b 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -54,18 +54,22 @@ class mail_compose_message(osv.TransientModel): Indeed, basic mail.compose.message wizard duplicates attachments in mass mailing mode. But in 'single post' mode, attachments of an email template also have to be duplicated to avoid changing their ownership. """ + if context is None: + context = {} + wizard_context = dict(context) for wizard in self.browse(cr, uid, ids, context=context): + if wizard.template_id and not wizard.template_id.user_signature: + wizard_context['mail_notify_user_signature'] = False # template user_signature is added when generating body_html if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id: continue - template = self.pool.get('email.template').browse(cr, uid, wizard.template_id.id, context=context) new_attachment_ids = [] for attachment in wizard.attachment_ids: - if attachment in template.attachment_ids: + if attachment in wizard.template.attachment_ids: new_attachment_ids.append(self.pool.get('ir.attachment').copy(cr, uid, attachment.id, {'res_model': 'mail.compose.message', 'res_id': wizard.id}, context=context)) else: new_attachment_ids.append(attachment.id) self.write(cr, uid, wizard.id, {'attachment_ids': [(6, 0, new_attachment_ids)]}, context=context) - return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context) + return super(mail_compose_message, self).send_mail(cr, uid, ids, context=wizard_context) def onchange_template_id(self, cr, uid, ids, template_id, composition_mode, model, res_id, context=None): """ - mass_mailing: we cannot render, so return the template values diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index 93196976e90..bc78c8c61d7 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -150,7 +150,8 @@ class mail_notification(osv.Model): return footer - def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None, force_send=False): + def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None, + force_send=False, user_signature=True): """ Send by email the notification depending on the user preferences :param list partners_to_notify: optional list of partner ids restricting @@ -158,6 +159,8 @@ class mail_notification(osv.Model): :param bool force_send: if True, the generated mail.mail is immediately sent after being created, as if the scheduler was executed for this message only. + :param bool user_signature: if True, the generated mail.mail body is + the body of the related mail.message with the author's signature """ if context is None: context = {} @@ -192,8 +195,9 @@ class mail_notification(osv.Model): # add signature body_html = msg.body user_id = msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0] and msg.author_id.user_ids[0].id or None - signature_company = self.get_signature_footer(cr, uid, user_id, res_model=msg.model, res_id=msg.res_id, context=context) - body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div') + if user_signature: + signature_company = self.get_signature_footer(cr, uid, user_id, res_model=msg.model, res_id=msg.res_id, context=context) + body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div') references = False if msg.parent_id: diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 884d59ba9be..4a22684c36e 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -768,7 +768,9 @@ class mail_message(osv.Model): elif not values.get('message_id'): values['message_id'] = tools.generate_tracking_message_id('private') newid = super(mail_message, self).create(cr, uid, values, context) - self._notify(cr, uid, newid, context=context, force_send=context.get('mail_notify_force_send', True)) + self._notify(cr, uid, newid, context=context, + force_send=context.get('mail_notify_force_send', True), + user_signature=context.get('mail_notify_user_signature', True)) # TDE FIXME: handle default_starred. Why not setting an inv on starred ? # Because starred will call set_message_starred, that looks for notifications. # When creating a new mail_message, it will create a notification to a message @@ -882,7 +884,7 @@ class mail_message(osv.Model): return '' return result - def _notify(self, cr, uid, newid, context=None, force_send=False): + def _notify(self, cr, uid, newid, context=None, force_send=False, user_signature=True): """ Add the related record followers to the destination partner_ids if is not a private message. Call mail_notification.notify to manage the email sending """ @@ -912,7 +914,8 @@ class mail_message(osv.Model): # notify if partners_to_notify: - notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context, force_send=force_send) + notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context, + force_send=force_send, user_signature=user_signature) message.refresh() # An error appear when a user receive a notification without notifying diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index e3383270548..64886628478 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -157,7 +157,7 @@ class mail_compose_message(osv.TransientModel): return super(mail_compose_message, self).check_access_rule(cr, uid, ids, operation, context=context) - def _notify(self, cr, uid, newid, context=None, force_send=False): + def _notify(self, cr, uid, newid, context=None, force_send=False, user_signature=True): """ Override specific notify method of mail.message, because we do not want that feature in the wizard. """ return From 62a03693a39a59bf1833dbed0e4aff32ee5ca8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 15:08:51 +0200 Subject: [PATCH 08/23] [FIX] mail.compose.message in email_template: var typo bzr revid: tde@openerp.com-20130606130851-h5jswklvvz98enue --- addons/email_template/wizard/mail_compose_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 6fbc541341b..746c2cc55f6 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -64,7 +64,7 @@ class mail_compose_message(osv.TransientModel): continue new_attachment_ids = [] for attachment in wizard.attachment_ids: - if attachment in wizard.template.attachment_ids: + if attachment in wizard.template_id.attachment_ids: new_attachment_ids.append(self.pool.get('ir.attachment').copy(cr, uid, attachment.id, {'res_model': 'mail.compose.message', 'res_id': wizard.id}, context=context)) else: new_attachment_ids.append(attachment.id) From 05d463a0e2172095589b6601548fcaaf4e5999be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 16:27:20 +0200 Subject: [PATCH 09/23] [FIX] mail_mail: sdearch view: no need to have default_type_email bzr revid: tde@openerp.com-20130606142720-ghs664yqhgemd132 --- addons/mail/mail_mail_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_mail_view.xml b/addons/mail/mail_mail_view.xml index 1ff9a2a292e..e8e544791c8 100644 --- a/addons/mail/mail_mail_view.xml +++ b/addons/mail/mail_mail_view.xml @@ -110,7 +110,7 @@ mail.mail form tree,form - {'search_default_outgoing': 1, 'search_default_type_email': 1} + {'search_default_outgoing': 1} From a1e942c0dec4d016655f12031c9a78336b2d83d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 16:28:14 +0200 Subject: [PATCH 10/23] [FIX] mail.compose.message: fixed reply_to from template in mass mailing. bzr revid: tde@openerp.com-20130606142814-jyw76nhaeivotu6k --- addons/mail/mail_mail.py | 10 +++++++--- addons/mail/mail_message.py | 3 --- addons/mail/wizard/mail_compose_message.py | 4 +++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 4da3b27bd7d..4cf319a0cc4 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -66,6 +66,9 @@ class mail_mail(osv.Model): } def _get_default_from(self, cr, uid, context=None): + """ Kept for compatibility + TDE TODO: remove me in 8.0 + """ return self.pool['mail.message']._get_default_from(cr, uid, context=context) _defaults = { @@ -85,14 +88,15 @@ class mail_mail(osv.Model): """ if values.get('reply_to'): return values.get('reply_to') - email_reply_to = False - # model, res_id: comes from values OR related message + # email_reply_to, model, res_id: comes from values OR related message + email_reply_to = False model = values.get('model') res_id = values.get('res_id') email_from = values.get('email_from') if values.get('mail_message_id') and (not model or not res_id): message = self.pool.get('mail.message').browse(cr, uid, values.get('mail_message_id'), context=context) + email_reply_to = message.reply_to if not model: model = message.model if not res_id: @@ -101,7 +105,7 @@ class mail_mail(osv.Model): email_from = message.email_from # if model and res_id: try to use ``message_get_reply_to`` that returns the document alias - if model and res_id and hasattr(self.pool[model], 'message_get_reply_to'): + if not email_reply_to and model and res_id and hasattr(self.pool[model], 'message_get_reply_to'): email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0] # no alias reply_to -> reply_to will be the email_from, only the email part if not email_reply_to and email_from: diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 4a22684c36e..a76807bb5e8 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -352,9 +352,6 @@ class mail_message(osv.Model): else: partner_ids = [partner_tree[partner.id] for partner in message.partner_ids if partner.id in partner_tree] - # for partner in message.notified_partner_ids: - # if partner.id in partner_tree: - # partner_ids.append(partner_tree[partner.id]) attachment_ids = [] for attachment in message.attachment_ids: if attachment.id in attachments_tree: diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 64886628478..7685a27f1ed 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -259,13 +259,15 @@ class mail_compose_message(osv.TransientModel): new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context) attachment_ids.append(new_attach_id) post_values['attachment_ids'] = attachment_ids - post_values.update(email_dict) # email_from: mass mailing only can specify another email_from if email_dict.get('email_from'): post_values['email_from'] = email_dict.pop('email_from') # replies redirection: mass mailing only if not wizard.same_thread: post_values['reply_to'] = email_dict.pop('reply_to') + else: + email_dict.pop('reply_to') + post_values.update(email_dict) # clean the context (hint: mass mailing sets some default values that # could be wrongly interpreted by mail_mail) context.pop('default_email_to', None) From 1ef65f15772bda4a82bd069b87bd1c782f09278e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 17:14:40 +0200 Subject: [PATCH 11/23] [FIX] mail_mail: reply_to: use tools.email_split to avoid issues when handling email addresses. bzr revid: tde@openerp.com-20130606151440-atdhk834ny248xqq --- addons/mail/mail_mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 4cf319a0cc4..34278079fb5 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -96,7 +96,7 @@ class mail_mail(osv.Model): email_from = values.get('email_from') if values.get('mail_message_id') and (not model or not res_id): message = self.pool.get('mail.message').browse(cr, uid, values.get('mail_message_id'), context=context) - email_reply_to = message.reply_to + email_reply_to = tools.email_split(message.reply_to) if not model: model = message.model if not res_id: From 30a77204524c964f28626207772552115861bbb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 17:25:38 +0200 Subject: [PATCH 12/23] [IMP] mail_message: added reply_to to the form view of mail.message bzr revid: tde@openerp.com-20130606152538-4u3s1k4vvkotxz6c --- addons/mail/mail_mail.py | 3 ++- addons/mail/mail_message_view.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 34278079fb5..2b0bb6a6959 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -96,7 +96,8 @@ class mail_mail(osv.Model): email_from = values.get('email_from') if values.get('mail_message_id') and (not model or not res_id): message = self.pool.get('mail.message').browse(cr, uid, values.get('mail_message_id'), context=context) - email_reply_to = tools.email_split(message.reply_to) + if message.reply_to: + email_reply_to = tools.email_split(message.reply_to)[0] if not model: model = message.model if not res_id: diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index 8f2fb3f1020..abe381c9ceb 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -30,9 +30,9 @@ + - @@ -40,6 +40,7 @@ + From 216cb2122e8e3b5784e666ad2b06d60c3fc72751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 6 Jun 2013 17:28:14 +0200 Subject: [PATCH 13/23] [IMP] mail: mass_mailing: avoid having 'Followers of' when having a specified reply_to coming from the mail_message bzr revid: tde@openerp.com-20130606152814-iqftppk06s7g8weg --- addons/mail/mail_mail.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 2b0bb6a6959..62c1a9fff19 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -88,6 +88,7 @@ class mail_mail(osv.Model): """ if values.get('reply_to'): return values.get('reply_to') + redirect_gateway = True # whether replies will be redirected inside the document # email_reply_to, model, res_id: comes from values OR related message email_reply_to = False @@ -97,7 +98,8 @@ class mail_mail(osv.Model): if values.get('mail_message_id') and (not model or not res_id): message = self.pool.get('mail.message').browse(cr, uid, values.get('mail_message_id'), context=context) if message.reply_to: - email_reply_to = tools.email_split(message.reply_to)[0] + email_reply_to = message.reply_to + redirect_gateway = False if not model: model = message.model if not res_id: @@ -115,7 +117,7 @@ class mail_mail(osv.Model): email_reply_to = emails[0] # format 'Document name ' - if email_reply_to and model and res_id: + if email_reply_to and model and res_id and redirect_gateway: document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0] if document_name: # sanitize document name From a3e430ec639d8c65a91013879f95fa22fafee5df Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 6 Jun 2013 18:20:20 +0200 Subject: [PATCH 14/23] Changed to use werkzeug's LocalStack bzr revid: nicolas.vanhoren@openerp.com-20130606162020-oemq8eyuxr47a2e1 --- addons/web/http.py | 42 +++++++++++++------------------- addons/web/tests/test_dataset.py | 2 +- addons/web/tests/test_menu.py | 4 +-- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 9377ab02df8..e34ff3d382e 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -355,27 +355,19 @@ def httprequest(f): #---------------------------------------------------------- # Local storage of requests #---------------------------------------------------------- -_thlocal = threading.local() +from werkzeug.local import LocalStack -class RequestProxy(object): - def __getattr__(self, name): - return getattr(_thlocal.stack[-1], name) - def __setattr__(self, name, val): - return setattr(_thlocal.stack[-1], name, val) - def __delattr__(self, name): - return delattr(_thlocal.stack[-1], name) - @classmethod - def set_request(cls, request): - class with_obj(object): - def __enter__(self): - if getattr(_thlocal, "stack", None) is None: - _thlocal.stack = [] - _thlocal.stack.append(request) - def __exit__(self, *args): - _thlocal.stack.pop() - return with_obj() +_request_stack = LocalStack() -request = RequestProxy() +def set_request(request): + class with_obj(object): + def __enter__(self): + _request_stack.push(request) + def __exit__(self, *args): + _request_stack.pop() + return with_obj() + +request = _request_stack() #---------------------------------------------------------- # Controller registration with a metaclass @@ -659,16 +651,16 @@ class Root(object): if exposed == 'json': _logger.debug("Dispatch json to %s %s %s", ps, c, method_name) def fct(request): - req = JsonRequest(request) - with RequestProxy.set_request(req): - return req.dispatch(method) + _req = JsonRequest(request) + with set_request(_req): + return request.dispatch(method) return fct elif exposed == 'http': _logger.debug("Dispatch http to %s %s %s", ps, c, method_name) def fct(request): - req = HttpRequest(request) - with RequestProxy.set_request(req): - return req.dispatch(method) + _req = HttpRequest(request) + with set_request(_req): + return request.dispatch(method) return fct if method_name != "index": method_name = "index" diff --git a/addons/web/tests/test_dataset.py b/addons/web/tests/test_dataset.py index 72cd99a259b..8d6e7e78c03 100644 --- a/addons/web/tests/test_dataset.py +++ b/addons/web/tests/test_dataset.py @@ -3,7 +3,7 @@ import mock import unittest2 import openerp.addons.web.controllers.main from openerp.addons.web.http import request as req -from openerp.addons.web.http import RequestProxy +from openerp.addons.web.http import _request_stack class TestDataSetController(unittest2.TestCase): def setUp(self): diff --git a/addons/web/tests/test_menu.py b/addons/web/tests/test_menu.py index 7360354be43..6c86efe2fc6 100644 --- a/addons/web/tests/test_menu.py +++ b/addons/web/tests/test_menu.py @@ -3,7 +3,7 @@ import collections import mock import unittest2 from openerp.addons.web.http import request as req -from openerp.addons.web.http import RequestProxy +from openerp.addons.web.http import set_request from ..controllers import main @@ -15,7 +15,7 @@ class Placeholder(object): class LoadTest(unittest2.TestCase): def setUp(self): self.menu = main.Menu() - self.tmp_req = RequestProxy.set_request(mock.Mock()) + self.tmp_req = set_request(mock.Mock()) self.tmp_req.__enter__() # Have self.request.session.model() return a different mock object for From 3d782a25d91ff1472e2a2339d0bd112ee6bcd77a Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 6 Jun 2013 18:47:26 +0200 Subject: [PATCH 15/23] fix stupid bug bzr revid: nicolas.vanhoren@openerp.com-20130606164726-zdwd4rf93m7unt35 --- addons/web/http.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index e34ff3d382e..67d71ac7d37 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -650,15 +650,15 @@ class Root(object): method = c.get_wrapped_method(method_name) if exposed == 'json': _logger.debug("Dispatch json to %s %s %s", ps, c, method_name) - def fct(request): - _req = JsonRequest(request) + def fct(_request): + _req = JsonRequest(_request) with set_request(_req): return request.dispatch(method) return fct elif exposed == 'http': _logger.debug("Dispatch http to %s %s %s", ps, c, method_name) - def fct(request): - _req = HttpRequest(request) + def fct(_request): + _req = HttpRequest(_request) with set_request(_req): return request.dispatch(method) return fct From 9d412b63cb1d2ca391e58b37a89fc96e40e7e6cc Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 7 Jun 2013 10:01:08 +0200 Subject: [PATCH 16/23] fix another stupid bug bzr revid: nicolas.vanhoren@openerp.com-20130607080108-ajjly9a05ny96hmd --- addons/web/tests/test_dataset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/tests/test_dataset.py b/addons/web/tests/test_dataset.py index 8d6e7e78c03..98c2f95447c 100644 --- a/addons/web/tests/test_dataset.py +++ b/addons/web/tests/test_dataset.py @@ -3,12 +3,12 @@ import mock import unittest2 import openerp.addons.web.controllers.main from openerp.addons.web.http import request as req -from openerp.addons.web.http import _request_stack +from openerp.addons.web.http import set_request class TestDataSetController(unittest2.TestCase): def setUp(self): self.dataset = openerp.addons.web.controllers.main.DataSet() - self.tmp_req = RequestProxy.set_request(mock.Mock()) + self.tmp_req = set_request(mock.Mock()) self.tmp_req.__enter__() self.read = req.session.model().read self.search = req.session.model().search From a1a66985584a5d8db49d5bdd2c5b7199ee0a0b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 7 Jun 2013 11:16:11 +0200 Subject: [PATCH 17/23] [CLEAN] mail: cleaned reply_to code, finally. bzr revid: tde@openerp.com-20130607091611-7cpzlxzngkrs86a0 --- addons/mail/mail_mail.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 62c1a9fff19..1398e00255b 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -88,18 +88,16 @@ class mail_mail(osv.Model): """ if values.get('reply_to'): return values.get('reply_to') - redirect_gateway = True # whether replies will be redirected inside the document - - # email_reply_to, model, res_id: comes from values OR related message + format_name = True # whether to use a 'Followers of Pigs reply_to will be the email_from, only the email part + # no alias reply_to -> reply_to will be the email_from if not email_reply_to and email_from: - emails = tools.email_split(email_from) - if emails: - email_reply_to = emails[0] + email_reply_to = email_from # format 'Document name ' - if email_reply_to and model and res_id and redirect_gateway: + if email_reply_to and model and res_id and format_name: + emails = tools.email_split(email_reply_to) + if emails: + email_reply_to = emails[0] document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0] if document_name: # sanitize document name From 636c793c70c6504fd6305a7d054ce6554bd613dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 7 Jun 2013 11:16:19 +0200 Subject: [PATCH 18/23] [TESTS] mail: added tests bzr revid: tde@openerp.com-20130607091619-tfh78a1agqdiqc23 --- addons/mail/tests/test_mail_base.py | 1 + addons/mail/tests/test_mail_gateway.py | 97 ++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/addons/mail/tests/test_mail_base.py b/addons/mail/tests/test_mail_base.py index a3b90288f0b..7ca8b93135c 100644 --- a/addons/mail/tests/test_mail_base.py +++ b/addons/mail/tests/test_mail_base.py @@ -69,6 +69,7 @@ class TestMailBase(common.TransactionCase): self.group_employee_id = group_employee_ref and group_employee_ref[1] or False # Test users to use through the various tests + self.res_users.write(cr, uid, uid, {'name': 'Administrator'}) self.user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'signature': 'SignRaoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]}) self.user_bert_id = self.res_users.create(cr, uid, diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py index e4269d59d8b..51d6d8add30 100644 --- a/addons/mail/tests/test_mail_gateway.py +++ b/addons/mail/tests/test_mail_gateway.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.addons.mail.tests.test_mail_base import TestMailBase +from openerp.tools import mute_logger MAIL_TEMPLATE = """Return-Path: To: {to} @@ -123,6 +124,102 @@ class TestMailgateway(TestMailBase): self.assertEqual(partner_info['partner_id'], p_b_id, 'mail_thread: message_find_partner_from_emails wrong partner found') + def test_05_mail_message_mail_mail(self): + """ Tests designed for testing email values based on mail.message, aliases, ... """ + cr, uid, user_raoul_id = self.cr, self.uid, self.user_raoul_id + + # Data: update + generic variables + reply_to1 = '_reply_to1@example.com' + reply_to2 = '_reply_to2@example.com' + email_from1 = 'from@example.com' + alias_domain = 'schlouby.fr' + raoul_from = 'Raoul Grosbedon ' + raoul_from_alias = 'Raoul Grosbedon ' + raoul_reply = '"Followers of Pigs" ' + raoul_reply_alias = '"Followers of Pigs" ' + # Data: remove alias_domain to see emails with alias + param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')]) + self.registry('ir.config_parameter').unlink(cr, uid, param_ids) + + # Do: free message; specified values > default values + msg_id = self.mail_message.create(cr, user_raoul_id, {'reply_to': reply_to1, 'email_from': email_from1}) + msg = self.mail_message.browse(cr, user_raoul_id, msg_id) + # Test: message content + self.assertIn('reply_to', msg.message_id, + 'mail_message: message_id should be specific to a mail_message with a given reply_to') + self.assertEqual(msg.reply_to, reply_to1, + 'mail_message: incorrect reply_to: should come from values') + self.assertEqual(msg.email_from, email_from1, + 'mail_message: incorrect email_from: should come from values') + # Do: create a mail_mail with the previous mail_message + mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'}) + mail = self.mail_mail.browse(cr, user_raoul_id, mail_id) + # Test: mail_mail content + self.assertEqual(mail.reply_to, reply_to1, + 'mail_mail: incorrect reply_to: should come from mail.message') + self.assertEqual(mail.email_from, email_from1, + 'mail_mail: incorrect email_from: should come from mail.message') + # Do: create a mail_mail with the previous mail_message + specified reply_to + mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel', 'reply_to': reply_to2}) + mail = self.mail_mail.browse(cr, user_raoul_id, mail_id) + # Test: mail_mail content + self.assertEqual(mail.reply_to, reply_to2, + 'mail_mail: incorrect reply_to: should come from values') + self.assertEqual(mail.email_from, email_from1, + 'mail_mail: incorrect email_from: should come from mail.message') + + # Do: mail_message attached to a document + msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id}) + msg = self.mail_message.browse(cr, user_raoul_id, msg_id) + # Test: message content + self.assertIn('mail.group', msg.message_id, + 'mail_message: message_id should contain model') + self.assertIn('%s' % self.group_pigs_id, msg.message_id, + 'mail_message: message_id should contain res_id') + self.assertFalse(msg.reply_to, + 'mail_message: incorrect reply_to: should not be generated if not specified') + self.assertEqual(msg.email_from, raoul_from, + 'mail_message: incorrect email_from: should be Raoul') + # Do: create a mail_mail based on the previous mail_message + mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'}) + mail = self.mail_mail.browse(cr, user_raoul_id, mail_id) + # Test: mail_mail content + self.assertEqual(mail.reply_to, raoul_reply, + 'mail_mail: incorrect reply_to: should be Raoul') + + # Data: set catchall domain + self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain) + + # Update message + self.mail_message.write(cr, user_raoul_id, [msg_id], {'email_from': False, 'reply_to': False}) + msg.refresh() + # Do: create a mail_mail based on the previous mail_message + mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'}) + mail = self.mail_mail.browse(cr, user_raoul_id, mail_id) + # Test: mail_mail content + self.assertEqual(mail.reply_to, raoul_reply_alias, + 'mail_mail: incorrect reply_to: should be Pigs alias') + + # Update message: test alias on email_from + msg_id = self.mail_message.create(cr, user_raoul_id, {}) + msg = self.mail_message.browse(cr, user_raoul_id, msg_id) + # Do: create a mail_mail based on the previous mail_message + mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'}) + mail = self.mail_mail.browse(cr, user_raoul_id, mail_id) + # Test: mail_mail content + self.assertEqual(mail.reply_to, raoul_from_alias, + 'mail_mail: incorrect reply_to: should be message email_from using Raoul alias') + + # Update message + self.mail_message.write(cr, user_raoul_id, [msg_id], {'res_id': False, 'email_from': 'someone@schlouby.fr', 'reply_to': False}) + msg.refresh() + # Do: create a mail_mail based on the previous mail_message + mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'}) + mail = self.mail_mail.browse(cr, user_raoul_id, mail_id) + # Test: mail_mail content + self.assertEqual(mail.reply_to, msg.email_from, + 'mail_mail: incorrect reply_to: should be message email_from') + def test_10_message_process(self): """ Testing incoming emails processing. """ cr, uid, user_raoul = self.cr, self.uid, self.user_raoul From 956a05e0f264aaf988edec23a26aca1a4de97196 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 7 Jun 2013 11:18:56 +0200 Subject: [PATCH 19/23] Replaced main bzr revid: nicolas.vanhoren@openerp.com-20130607091856-mnjnllza9oafqxb7 --- addons/web/controllers/main.py | 277 ++++++++++++++++----------------- 1 file changed, 134 insertions(+), 143 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 78aa85669be..b7071eff9e2 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -36,6 +36,8 @@ from openerp.tools.translate import _ from .. import http openerpweb = http +from openerp.addons.web.http import request as req + #---------------------------------------------------------- # OpenERP Web helpers #---------------------------------------------------------- @@ -84,7 +86,7 @@ def rjsmin(script): ).strip() return result -def db_list(req): +def db_list(): proxy = req.session.proxy("db") dbs = proxy.list() h = req.httprequest.environ['HTTP_HOST'].split(':')[0] @@ -93,7 +95,7 @@ def db_list(req): dbs = [i for i in dbs if re.match(r, i)] return dbs -def db_monodb_redirect(req): +def db_monodb_redirect(): db = False redirect = False @@ -103,7 +105,7 @@ def db_monodb_redirect(req): return (db_url, False) try: - dbs = db_list(req) + dbs = db_list() except Exception: # ignore access denied dbs = [] @@ -124,11 +126,11 @@ def db_monodb_redirect(req): redirect = req.httprequest.path + '?' + urllib.urlencode(query) return (db, redirect) -def db_monodb(req): +def db_monodb(): # if only one db exists, return it else return False - return db_monodb_redirect(req)[0] + return db_monodb_redirect()[0] -def redirect_with_hash(req, url, code=303): +def redirect_with_hash(url, code=303): if req.httprequest.user_agent.browser == 'msie': try: version = float(req.httprequest.user_agent.version) @@ -182,7 +184,7 @@ def module_topological_sort(modules): visit(n) return L -def module_installed(req): +def module_installed(): # Candidates module the current heuristic is the /static dir loadable = openerpweb.addons_manifest.keys() modules = {} @@ -224,14 +226,14 @@ def module_installed_bypass_session(dbname): sorted_modules = module_topological_sort(modules) return sorted_modules -def module_boot(req, db=None): +def module_boot(db=None): server_wide_modules = openerp.conf.server_wide_modules or ['web'] serverside = [] dbside = [] for i in server_wide_modules: if i in openerpweb.addons_manifest: serverside.append(i) - monodb = db or db_monodb(req) + monodb = db or db_monodb() if monodb: dbside = module_installed_bypass_session(monodb) dbside = [i for i in dbside if i not in serverside] @@ -308,9 +310,9 @@ def fs2web(path): """convert FS path into web path""" return '/'.join(path.split(os.path.sep)) -def manifest_glob(req, extension, addons=None, db=None): +def manifest_glob(extension, addons=None, db=None): if addons is None: - addons = module_boot(req, db=db) + addons = module_boot(db=db) else: addons = addons.split(',') r = [] @@ -326,7 +328,7 @@ def manifest_glob(req, extension, addons=None, db=None): r.append((path, fs2web(path[len(addons_path):]))) return r -def manifest_list(req, extension, mods=None, db=None): +def manifest_list(extension, mods=None, db=None): """ list ressources to load specifying either: mods: a comma separated string listing modules db: a database name (return all installed modules in that database) @@ -338,7 +340,7 @@ def manifest_list(req, extension, mods=None, db=None): elif db: path += '?' + urllib.urlencode({'db': db}) return [path] - files = manifest_glob(req, extension, addons=mods, db=db) + files = manifest_glob(extension, addons=mods, db=db) return [wp for _fp, wp in files] def get_last_modified(files): @@ -355,15 +357,13 @@ def get_last_modified(files): for f in files) return datetime.datetime(1970, 1, 1) -def make_conditional(req, response, last_modified=None, etag=None): +def make_conditional(response, last_modified=None, etag=None): """ Makes the provided response conditional based upon the request, and mandates revalidation from clients Uses Werkzeug's own :meth:`ETagResponseMixin.make_conditional`, after setting ``last_modified`` and ``etag`` correctly on the response object - :param req: OpenERP request - :type req: web.common.http.WebRequest :param response: Werkzeug response :type response: werkzeug.wrappers.Response :param datetime.datetime last_modified: last modification date of the response content @@ -379,7 +379,7 @@ def make_conditional(req, response, last_modified=None, etag=None): response.set_etag(etag) return response.make_conditional(req.httprequest) -def login_and_redirect(req, db, login, key, redirect_url='/'): +def login_and_redirect(db, login, key, redirect_url='/'): wsgienv = req.httprequest.environ env = dict( base_location=req.httprequest.url_root.rstrip('/'), @@ -387,23 +387,23 @@ def login_and_redirect(req, db, login, key, redirect_url='/'): REMOTE_ADDR=wsgienv['REMOTE_ADDR'], ) req.session.authenticate(db, login, key, env) - return set_cookie_and_redirect(req, redirect_url) + return set_cookie_and_redirect(redirect_url) -def set_cookie_and_redirect(req, redirect_url): +def set_cookie_and_redirect(redirect_url): redirect = werkzeug.utils.redirect(redirect_url, 303) redirect.autocorrect_location_header = False cookie_val = urllib2.quote(simplejson.dumps(req.session_id)) redirect.set_cookie('instance0|session_id', cookie_val) return redirect -def load_actions_from_ir_values(req, key, key2, models, meta): +def load_actions_from_ir_values(key, key2, models, meta): Values = req.session.model('ir.values') actions = Values.get(key, key2, models, meta, req.context) - return [(id, name, clean_action(req, action)) + return [(id, name, clean_action(action)) for id, name, action in actions] -def clean_action(req, action): +def clean_action(action): action.setdefault('flags', {}) action_type = action.setdefault('type', 'ir.actions.act_window_close') if action_type == 'ir.actions.act_window': @@ -521,7 +521,7 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False): res["children"] = kids return res -def content_disposition(filename, req): +def content_disposition(filename): filename = filename.encode('utf8') escaped = urllib2.quote(filename) browser = req.httprequest.user_agent.browser @@ -568,28 +568,28 @@ class Home(openerpweb.Controller): _cp_path = '/' @openerpweb.httprequest - def index(self, req, s_action=None, db=None, **kw): - db, redir = db_monodb_redirect(req) + def index(self, s_action=None, db=None, **kw): + db, redir = db_monodb_redirect() if redir: - return redirect_with_hash(req, redir) + return redirect_with_hash(redir) - js = "\n ".join('' % i for i in manifest_list(req, 'js', db=db)) - css = "\n ".join('' % i for i in manifest_list(req, 'css', db=db)) + js = "\n ".join('' % i for i in manifest_list('js', db=db)) + css = "\n ".join('' % i for i in manifest_list('css', db=db)) r = html_template % { 'js': js, 'css': css, - 'modules': simplejson.dumps(module_boot(req, db=db)), + 'modules': simplejson.dumps(module_boot(db=db)), 'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));' } return r @openerpweb.httprequest - def login(self, req, db, login, key): - return login_and_redirect(req, db, login, key) + def login(self, db, login, key): + return login_and_redirect(db, login, key) @openerpweb.jsonrequest - def jsonrpc(self, req, service, method, args): + def jsonrpc(self, service, method, args): """ Method used by client APIs to contact OpenERP. """ return getattr(req.session.proxy(service), method)(*args) @@ -597,20 +597,20 @@ class WebClient(openerpweb.Controller): _cp_path = "/web/webclient" @openerpweb.jsonrequest - def csslist(self, req, mods=None): - return manifest_list(req, 'css', mods=mods) + def csslist(self, mods=None): + return manifest_list('css', mods=mods) @openerpweb.jsonrequest - def jslist(self, req, mods=None): - return manifest_list(req, 'js', mods=mods) + def jslist(self, mods=None): + return manifest_list('js', mods=mods) @openerpweb.jsonrequest - def qweblist(self, req, mods=None): - return manifest_list(req, 'qweb', mods=mods) + def qweblist(self, mods=None): + return manifest_list('qweb', mods=mods) @openerpweb.httprequest - def css(self, req, mods=None, db=None): - files = list(manifest_glob(req, 'css', addons=mods, db=db)) + def css(self, mods=None, db=None): + files = list(manifest_glob('css', addons=mods, db=db)) last_modified = get_last_modified(f[0] for f in files) if req.httprequest.if_modified_since and req.httprequest.if_modified_since >= last_modified: return werkzeug.wrappers.Response(status=304) @@ -656,12 +656,12 @@ class WebClient(openerpweb.Controller): content = '\n'.join(matches) return make_conditional( - req, req.make_response(content, [('Content-Type', 'text/css')]), + req.make_response(content, [('Content-Type', 'text/css')]), last_modified, checksum) @openerpweb.httprequest - def js(self, req, mods=None, db=None): - files = [f[0] for f in manifest_glob(req, 'js', addons=mods, db=db)] + def js(self, mods=None, db=None): + files = [f[0] for f in manifest_glob('js', addons=mods, db=db)] last_modified = get_last_modified(files) if req.httprequest.if_modified_since and req.httprequest.if_modified_since >= last_modified: return werkzeug.wrappers.Response(status=304) @@ -669,12 +669,12 @@ class WebClient(openerpweb.Controller): content, checksum = concat_js(files) return make_conditional( - req, req.make_response(content, [('Content-Type', 'application/javascript')]), + req.make_response(content, [('Content-Type', 'application/javascript')]), last_modified, checksum) @openerpweb.httprequest - def qweb(self, req, mods=None, db=None): - files = [f[0] for f in manifest_glob(req, 'qweb', addons=mods, db=db)] + def qweb(self, mods=None, db=None): + files = [f[0] for f in manifest_glob('qweb', addons=mods, db=db)] last_modified = get_last_modified(files) if req.httprequest.if_modified_since and req.httprequest.if_modified_since >= last_modified: return werkzeug.wrappers.Response(status=304) @@ -682,11 +682,11 @@ class WebClient(openerpweb.Controller): content, checksum = concat_xml(files) return make_conditional( - req, req.make_response(content, [('Content-Type', 'text/xml')]), + req.make_response(content, [('Content-Type', 'text/xml')]), last_modified, checksum) @openerpweb.jsonrequest - def bootstrap_translations(self, req, mods): + def bootstrap_translations(self, mods): """ Load local translations from *.po files, as a temporary solution until we have established a valid session. This is meant only for translating the login page and db management chrome, using @@ -709,7 +709,7 @@ class WebClient(openerpweb.Controller): "lang_parameters": None} @openerpweb.jsonrequest - def translations(self, req, mods, lang): + def translations(self, mods, lang): res_lang = req.session.model('res.lang') ids = res_lang.search([("code", "=", lang)]) lang_params = None @@ -734,20 +734,19 @@ class WebClient(openerpweb.Controller): "lang_parameters": lang_params} @openerpweb.jsonrequest - def version_info(self, req): + def version_info(self): return openerp.service.common.exp_version() class Proxy(openerpweb.Controller): _cp_path = '/web/proxy' @openerpweb.jsonrequest - def load(self, req, path): + def load(self, path): """ Proxies an HTTP request through a JSON request. It is strongly recommended to not request binary files through this, as the result will be a binary data blob as well. - :param req: OpenERP request :param path: actual request path :return: file content """ @@ -760,11 +759,11 @@ class Database(openerpweb.Controller): _cp_path = "/web/database" @openerpweb.jsonrequest - def get_list(self, req): - return db_list(req) + def get_list(self): + return db_list() @openerpweb.jsonrequest - def create(self, req, fields): + def create(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) return req.session.proxy("db").create_database( params['super_admin_pwd'], @@ -774,7 +773,7 @@ class Database(openerpweb.Controller): params['create_admin_pwd']) @openerpweb.jsonrequest - def duplicate(self, req, fields): + def duplicate(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) return req.session.proxy("db").duplicate_database( params['super_admin_pwd'], @@ -782,7 +781,7 @@ class Database(openerpweb.Controller): params['db_name']) @openerpweb.jsonrequest - def duplicate(self, req, fields): + def duplicate(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) duplicate_attrs = ( params['super_admin_pwd'], @@ -793,7 +792,7 @@ class Database(openerpweb.Controller): return req.session.proxy("db").duplicate_database(*duplicate_attrs) @openerpweb.jsonrequest - def drop(self, req, fields): + def drop(self, fields): password, db = operator.itemgetter( 'drop_pwd', 'drop_db')( dict(map(operator.itemgetter('name', 'value'), fields))) @@ -806,7 +805,7 @@ class Database(openerpweb.Controller): return {'error': _('Could not drop database !'), 'title': _('Drop Database')} @openerpweb.httprequest - def backup(self, req, backup_db, backup_pwd, token): + def backup(self, backup_db, backup_pwd, token): try: db_dump = base64.b64decode( req.session.proxy("db").dump(backup_pwd, backup_db)) @@ -817,14 +816,14 @@ class Database(openerpweb.Controller): } return req.make_response(db_dump, [('Content-Type', 'application/octet-stream; charset=binary'), - ('Content-Disposition', content_disposition(filename, req))], + ('Content-Disposition', content_disposition(filename))], {'fileToken': int(token)} ) except Exception, e: return simplejson.dumps([[],[{'error': openerp.tools.ustr(e), 'title': _('Backup Database')}]]) @openerpweb.httprequest - def restore(self, req, db_file, restore_pwd, new_db): + def restore(self, db_file, restore_pwd, new_db): try: data = base64.b64encode(db_file.read()) req.session.proxy("db").restore(restore_pwd, new_db, data) @@ -833,7 +832,7 @@ class Database(openerpweb.Controller): raise Exception("AccessDenied") @openerpweb.jsonrequest - def change_password(self, req, fields): + def change_password(self, fields): old_password, new_password = operator.itemgetter( 'old_pwd', 'new_pwd')( dict(map(operator.itemgetter('name', 'value'), fields))) @@ -847,7 +846,7 @@ class Database(openerpweb.Controller): class Session(openerpweb.Controller): _cp_path = "/web/session" - def session_info(self, req): + def session_info(self): req.session.ensure_valid() return { "session_id": req.session_id, @@ -858,11 +857,11 @@ class Session(openerpweb.Controller): } @openerpweb.jsonrequest - def get_session_info(self, req): - return self.session_info(req) + def get_session_info(self): + return self.session_info() @openerpweb.jsonrequest - def authenticate(self, req, db, login, password, base_location=None): + def authenticate(self, db, login, password, base_location=None): wsgienv = req.httprequest.environ env = dict( base_location=base_location, @@ -871,10 +870,10 @@ class Session(openerpweb.Controller): ) req.session.authenticate(db, login, password, env) - return self.session_info(req) + return self.session_info() @openerpweb.jsonrequest - def change_password (self,req,fields): + def change_password (self, fields): old_password, new_password,confirm_password = operator.itemgetter('old_pwd', 'new_password','confirm_pwd')( dict(map(operator.itemgetter('name', 'value'), fields))) if not (old_password.strip() and new_password.strip() and confirm_password.strip()): @@ -890,24 +889,24 @@ class Session(openerpweb.Controller): return {'error': _('Error, password not changed !'), 'title': _('Change Password')} @openerpweb.jsonrequest - def sc_list(self, req): + def sc_list(self): return req.session.model('ir.ui.view_sc').get_sc( req.session._uid, "ir.ui.menu", req.context) @openerpweb.jsonrequest - def get_lang_list(self, req): + def get_lang_list(self): try: return req.session.proxy("db").list_lang() or [] except Exception, e: return {"error": e, "title": _("Languages")} @openerpweb.jsonrequest - def modules(self, req): + def modules(self): # return all installed modules. Web client is smart enough to not load a module twice - return module_installed(req) + return module_installed() @openerpweb.jsonrequest - def save_session_action(self, req, the_action): + def save_session_action(self, the_action): """ This method store an action object in the session object and returns an integer identifying that action. The method get_session_action() can be used to get @@ -931,7 +930,7 @@ class Session(openerpweb.Controller): return key @openerpweb.jsonrequest - def get_session_action(self, req, key): + def get_session_action(self, key): """ Gets back a previously saved action. This method can return None if the action was saved since too much time (this case should be handled in a smart way). @@ -947,23 +946,21 @@ class Session(openerpweb.Controller): return saved_actions["actions"].get(key) @openerpweb.jsonrequest - def check(self, req): + def check(self): req.session.assert_valid() return None @openerpweb.jsonrequest - def destroy(self, req): + def destroy(self): req.session._suicide = True class Menu(openerpweb.Controller): _cp_path = "/web/menu" @openerpweb.jsonrequest - def get_user_roots(self, req): + def get_user_roots(self): """ Return all root menu ids visible for the session user. - :param req: A request object, with an OpenERP session attribute - :type req: < session -> OpenERPSession > :return: the root menu ids :rtype: list(int) """ @@ -983,18 +980,16 @@ class Menu(openerpweb.Controller): return Menus.search(menu_domain, 0, False, False, req.context) @openerpweb.jsonrequest - def load(self, req): + def load(self): """ Loads all menu items (all applications and their sub-menus). - :param req: A request object, with an OpenERP session attribute - :type req: < session -> OpenERPSession > :return: the menu root :rtype: dict('children': menu_nodes) """ Menus = req.session.model('ir.ui.menu') fields = ['name', 'sequence', 'parent_id', 'action'] - menu_root_ids = self.get_user_roots(req) + menu_root_ids = self.get_user_roots() menu_roots = Menus.read(menu_root_ids, fields, req.context) if menu_root_ids else [] menu_root = { 'id': False, @@ -1036,7 +1031,7 @@ class Menu(openerpweb.Controller): return menu_root @openerpweb.jsonrequest - def load_needaction(self, req, menu_ids): + def load_needaction(self, menu_ids): """ Loads needaction counters for specific menu ids. :return: needaction data @@ -1045,9 +1040,9 @@ class Menu(openerpweb.Controller): return req.session.model('ir.ui.menu').get_needaction_data(menu_ids, req.context) @openerpweb.jsonrequest - def action(self, req, menu_id): + def action(self, menu_id): # still used by web_shortcut - actions = load_actions_from_ir_values(req,'action', 'tree_but_open', + actions = load_actions_from_ir_values('action', 'tree_but_open', [('ir.ui.menu', menu_id)], False) return {"action": actions} @@ -1055,15 +1050,13 @@ class DataSet(openerpweb.Controller): _cp_path = "/web/dataset" @openerpweb.jsonrequest - def search_read(self, req, model, fields=False, offset=0, limit=False, domain=None, sort=None): - return self.do_search_read(req, model, fields, offset, limit, domain, sort) - def do_search_read(self, req, model, fields=False, offset=0, limit=False, domain=None + def search_read(self, model, fields=False, offset=0, limit=False, domain=None, sort=None): + return self.do_search_read(model, fields, offset, limit, domain, sort) + def do_search_read(self, model, fields=False, offset=0, limit=False, domain=None , sort=None): """ Performs a search() followed by a read() (if needed) using the provided search criteria - :param req: a JSON-RPC request object - :type req: openerpweb.JsonRequest :param str model: the name of the model to search on :param fields: a list of the fields to return in the result records :type fields: [str] @@ -1099,7 +1092,7 @@ class DataSet(openerpweb.Controller): } @openerpweb.jsonrequest - def load(self, req, model, id, fields): + def load(self, model, id, fields): m = req.session.model(model) value = {} r = m.read([id], False, req.context) @@ -1107,10 +1100,10 @@ class DataSet(openerpweb.Controller): value = r[0] return {'value': value} - def call_common(self, req, model, method, args, domain_id=None, context_id=None): - return self._call_kw(req, model, method, args, {}) + def call_common(self, model, method, args, domain_id=None, context_id=None): + return self._call_kw(model, method, args, {}) - def _call_kw(self, req, model, method, args, kwargs): + def _call_kw(self, model, method, args, kwargs): # Temporary implements future display_name special field for model#read() if method == 'read' and kwargs.get('context', {}).get('future_display_name'): if 'display_name' in args[1]: @@ -1125,26 +1118,26 @@ class DataSet(openerpweb.Controller): return getattr(req.session.model(model), method)(*args, **kwargs) @openerpweb.jsonrequest - def call(self, req, model, method, args, domain_id=None, context_id=None): - return self._call_kw(req, model, method, args, {}) + def call(self, model, method, args, domain_id=None, context_id=None): + return self._call_kw(model, method, args, {}) @openerpweb.jsonrequest - def call_kw(self, req, model, method, args, kwargs): - return self._call_kw(req, model, method, args, kwargs) + def call_kw(self, model, method, args, kwargs): + return self._call_kw(model, method, args, kwargs) @openerpweb.jsonrequest - def call_button(self, req, model, method, args, domain_id=None, context_id=None): - action = self._call_kw(req, model, method, args, {}) + def call_button(self, model, method, args, domain_id=None, context_id=None): + action = self._call_kw(model, method, args, {}) if isinstance(action, dict) and action.get('type') != '': - return clean_action(req, action) + return clean_action(action) return False @openerpweb.jsonrequest - def exec_workflow(self, req, model, id, signal): + def exec_workflow(self, model, id, signal): return req.session.exec_workflow(model, id, signal) @openerpweb.jsonrequest - def resequence(self, req, model, ids, field='sequence', offset=0): + def resequence(self, model, ids, field='sequence', offset=0): """ Re-sequences a number of records in the model, by their ids The re-sequencing starts at the first model of ``ids``, the sequence @@ -1170,7 +1163,7 @@ class View(openerpweb.Controller): _cp_path = "/web/view" @openerpweb.jsonrequest - def add_custom(self, req, view_id, arch): + def add_custom(self, view_id, arch): CustomView = req.session.model('ir.ui.view.custom') CustomView.create({ 'user_id': req.session._uid, @@ -1180,7 +1173,7 @@ class View(openerpweb.Controller): return {'result': True} @openerpweb.jsonrequest - def undo_custom(self, req, view_id, reset=False): + def undo_custom(self, view_id, reset=False): CustomView = req.session.model('ir.ui.view.custom') vcustom = CustomView.search([('user_id', '=', req.session._uid), ('ref_id' ,'=', view_id)], 0, False, False, req.context) @@ -1196,16 +1189,16 @@ class TreeView(View): _cp_path = "/web/treeview" @openerpweb.jsonrequest - def action(self, req, model, id): + def action(self, model, id): return load_actions_from_ir_values( - req,'action', 'tree_but_open',[(model, id)], + 'action', 'tree_but_open',[(model, id)], False) class Binary(openerpweb.Controller): _cp_path = "/web/binary" @openerpweb.httprequest - def image(self, req, model, id, field, **kw): + def image(self, model, id, field, **kw): last_update = '__last_update' Model = req.session.model(model) headers = [('Content-Type', 'image/png')] @@ -1245,7 +1238,7 @@ class Binary(openerpweb.Controller): image_data = base64.b64decode(image_base64) except Exception: - image_data = self.placeholder(req) + image_data = self.placeholder() headers.append(('ETag', retag)) headers.append(('Content-Length', len(image_data))) try: @@ -1255,20 +1248,18 @@ class Binary(openerpweb.Controller): pass return req.make_response(image_data, headers) - def placeholder(self, req, image='placeholder.png'): + def placeholder(self, image='placeholder.png'): addons_path = openerpweb.addons_manifest['web']['addons_path'] return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read() @openerpweb.httprequest - def saveas(self, req, model, field, id=None, filename_field=None, **kw): + def saveas(self, model, field, id=None, filename_field=None, **kw): """ Download link for files stored as binary fields. If the ``id`` parameter is omitted, fetches the default value for the binary field (via ``default_get``), otherwise fetches the field for that precise record. - :param req: OpenERP request - :type req: :class:`web.common.http.HttpRequest` :param str model: name of the model to fetch the binary from :param str field: binary field :param str id: id of the record from which to fetch the binary @@ -1292,10 +1283,10 @@ class Binary(openerpweb.Controller): filename = res.get(filename_field, '') or filename return req.make_response(filecontent, [('Content-Type', 'application/octet-stream'), - ('Content-Disposition', content_disposition(filename, req))]) + ('Content-Disposition', content_disposition(filename))]) @openerpweb.httprequest - def saveas_ajax(self, req, data, token): + def saveas_ajax(self, data, token): jdata = simplejson.loads(data) model = jdata['model'] field = jdata['field'] @@ -1324,11 +1315,11 @@ class Binary(openerpweb.Controller): filename = res.get(filename_field, '') or filename return req.make_response(filecontent, headers=[('Content-Type', 'application/octet-stream'), - ('Content-Disposition', content_disposition(filename, req))], + ('Content-Disposition', content_disposition(filename))], cookies={'fileToken': int(token)}) @openerpweb.httprequest - def upload(self, req, callback, ufile): + def upload(self, callback, ufile): # TODO: might be useful to have a configuration flag for max-length file uploads out = """