[IMP] mail: initial model for mail.alias - work-in-progress
authorOlivier Dony <odo@openerp.com>
Thu, 14 Jun 2012 14:17:32 +0000 (16:17 +0200)
committerOlivier Dony <odo@openerp.com>
Thu, 14 Jun 2012 14:17:32 +0000 (16:17 +0200)
bzr revid: odo@openerp.com-20120614141732-oa6fhfqgl887c04i

addons/mail/__init__.py
addons/mail/mail_alias.py [new file with mode: 0644]
addons/mail/mail_thread.py

index d9835f8..4c31333 100644 (file)
@@ -23,6 +23,7 @@ import mail_message
 import mail_thread
 import mail_group
 import mail_subscription
+import mail_alias
 import res_users
 import res_partner
 import report
diff --git a/addons/mail/mail_alias.py b/addons/mail/mail_alias.py
new file mode 100644 (file)
index 0000000..b5aa6f6
--- /dev/null
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Business Applications
+#    Copyright (C) 2012 OpenERP S.A. (<http://openerp.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+
+class mail_alias(osv.Model):
+    """A Mail Alias is a mapping of an email address with a given OpenERP Document
+       model. It is used by OpenERP's mail gateway when processing incoming emails
+       sent to the system. If the recipient address (To) of the message matches
+       a Mail Alias, the message will be either processed following the rules
+       of that alias. If the message is a reply it will be attached to the
+       existing discussion on the corresponding record, otherwise a new
+       record of the corresponding model will be created.
+       
+       This is meant to be used in combination with a catch-all email configuration
+       on the company's mail server, so that as soon as a new mail.alias is
+       created, it becomes immediately usable and OpenERP will accept email for it.
+     """
+    _name = 'mail.alias'
+    _description = "Mail Alias"
+    _rec_name = 'alias_name'
+
+    _columns = {
+        'alias_name': fields.char('Mailbox Alias', required=True,
+                            help="The name of the mailbox alias, e.g. `jobs' "
+                                 "if you want to catch emails for <jobs@example.my.openerp.com>",),
+        'alias_model_id': fields.many2one('ir.model', 'Aliased Model', required=True,
+                                          help="The model (OpenERP Document Kind) to which this alias "
+                                               "corresponds. Any incoming email that does not reply to an "
+                                               "existing record will cause the creation of a new record "
+                                               "of this model (e.g. a Project Task)",
+                                          # only allow selecting mail_thread models!
+                                          domain="[('field_ids', 'in', 'message_ids')]"),
+        'alias_user_id': fields.many2one('res.users', 'Owner',
+                                           help="The owner of records created upon receiving emails on this alias. "
+                                                "If this field is kept empty the system will attempt to find the right owner "
+                                                "based on the sender (From) address, or will use the Administrator account "
+                                                "if no system user is found for that address."),
+        'alias_defaults': fields.text('Default Values', required=True,
+                                      help="The representation of a Python dictionary that will be evaluated to provide "
+                                           "default values when creating new records."),
+        'alias_force_thread_id': fields.integer('Record Thread ID',
+                                      help="Optional ID of the thread (record) to which all "
+                                           "messages will be attached, even if they did not reply to it. "
+                                           "If set, this will disable the creation of new records completely.")
+    }
+    _defaults = {
+        'alias_defaults': '{}',
+        'alias_user_id': lambda s,c,u,ctx: u
+    }
+    _sql_constraint = [
+        ('mailbox_uniq', 'unique (alias_name)', 'Unfortunately this mail alias is already used, please choose a unique one')
+    ]
+    
+    def create_unique_alias(self, cr, uid, values, context=None):
+        # TODO: call create() with `values` after checking that `alias_name`
+        # is unique. If not unique, append a sequential number after it until
+        # a unique one if found. 
+        # E.g if create_unique_alias is called with {'alias_name': 'abc'}
+        # and 'abc', 'abc1', 'abc2' alias exist, replace alias_name with 'abc3'. 
+        return
+        
+        
+        
\ No newline at end of file
index b1b6c68..40824ba 100644 (file)
@@ -72,6 +72,7 @@ class mail_thread(osv.osv):
     _columns = {
         'message_ids': fields.function(_get_message_ids, method=True,
                         type='one2many', obj='mail.message', string='Temp messages', _fields_id = 'res_id'),
+        'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
     }
 
     #------------------------------------------------------
@@ -445,6 +446,13 @@ class mail_thread(osv.osv):
         msgs = msg_obj.read(cr, uid, msg_ids, context=context)
         return msgs
 
+
+    def message_catchall(self, cr, uid, message, context=None):
+        """TODO proper docstring, inspired by messsage_process()"""
+        # TODO
+        pass
+
+
     #------------------------------------------------------
     # Email specific
     #------------------------------------------------------
@@ -452,7 +460,7 @@ class mail_thread(osv.osv):
 
     def message_process(self, cr, uid, model, message, custom_values=None,
                         save_original=False, strip_attachments=False,
-                        context=None):
+                        thread_id=None, context=None):
         """Process an incoming RFC2822 email message related to the
            given thread model, relying on ``mail.message.parse()``
            for the parsing operation, and then calling ``message_new``
@@ -472,6 +480,10 @@ class mail_thread(osv.osv):
                email source attached to the message after it is imported.
            :param bool strip_attachments: whether to strip all attachments
                before processing the message, in order to save some space.
+           :param int thread_id: optional ID of the record/thread from ``model``
+               to which this mail should be attached. When provided, this
+               overrides the automatic detection based on the message
+               headers.
         """
         # extract message bytes - we are forced to pass the message as binary because
         # we don't know its encoding until we parse its headers and hence can't
@@ -479,14 +491,13 @@ class mail_thread(osv.osv):
         if isinstance(message, xmlrpclib.Binary):
             message = str(message.data)
 
+        if context is None: context = {}
+
+        mail_message = self.pool.get('mail.message')
         model_pool = self.pool.get(model)
         if self._name != model:
-            if context is None: context = {}
             context.update({'thread_model': model})
 
-        mail_message = self.pool.get('mail.message')
-        res_id = False
-
         # Parse Message
         # Warning: message_from_string doesn't always work correctly on unicode,
         # we must use utf-8 strings here :-(
@@ -504,8 +515,7 @@ class mail_thread(osv.osv):
                 return model_pool.message_new(cr, uid, msg,
                                               custom_values,
                                               context=context)
-        res_id = False
-        if msg.get('references') or msg.get('in-reply-to'):
+        if not thread_id and (msg.get('references') or msg.get('in-reply-to')):
             references = msg.get('references') or msg.get('in-reply-to')
             if '\r\n' in references:
                 references = references.split('\r\n')
@@ -513,26 +523,23 @@ class mail_thread(osv.osv):
                 references = references.split(' ')
             for ref in references:
                 ref = ref.strip()
-                res_id = tools.reference_re.search(ref)
-                if res_id:
-                    res_id = res_id.group(1)
-                else:
-                    res_id = tools.res_re.search(msg['subject'])
-                    if res_id:
-                        res_id = res_id.group(1)
-                if res_id:
-                    res_id = int(res_id)
-                    if model_pool.exists(cr, uid, res_id):
-                        if hasattr(model_pool, 'message_update'):
-                            model_pool.message_update(cr, uid, [res_id], msg, {}, context=context)
-                    else:
-                        # referenced thread was not found, we'll have to create a new one
-                        res_id = False
-        if not res_id:
-            res_id = create_record(msg)
+                thread_id = tools.reference_re.search(ref)
+                if not thread_id:
+                    thread_id = tools.res_re.search(msg['subject'])
+                if thread_id:
+                    thread_id = int(thread_id.group(1))
+                    if not model_pool.exists(cr, uid, thread_id) or \
+                        not hasattr(model_pool, 'message_update'):
+                            # referenced thread not found or not updatable,
+                            # -> create a new one
+                            thread_id = False
+        if not thread_id:
+            thread_id = create_record(msg)
+        else:
+            model_pool.message_update(cr, uid, [thread_id], msg, {}, context=context)
         #To forward the email to other followers
-        self.message_forward(cr, uid, model, [res_id], msg_txt, context=context)
-        return res_id
+        self.message_forward(cr, uid, model, [thread_id], msg_txt, context=context)
+        return thread_id
 
     def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
         """Called by ``message_process`` when a new message is received