Merge lp:~therp-nl/server-env-tools/6.1-fetchmail_attach_from_folder into lp:~server-env-tools-core-editors/server-env-tools/6.1

Proposed by Holger Brunn (Therp)
Status: Merged
Merged at revision: 35
Proposed branch: lp:~therp-nl/server-env-tools/6.1-fetchmail_attach_from_folder
Merge into: lp:~server-env-tools-core-editors/server-env-tools/6.1
Diff against target: 1012 lines (+938/-0)
14 files modified
fetchmail_attach_from_folder/__init__.py (+25/-0)
fetchmail_attach_from_folder/__openerp__.py (+45/-0)
fetchmail_attach_from_folder/match_algorithm/__init__.py (+26/-0)
fetchmail_attach_from_folder/match_algorithm/base.py (+43/-0)
fetchmail_attach_from_folder/match_algorithm/email_domain.py (+44/-0)
fetchmail_attach_from_folder/match_algorithm/email_exact.py (+56/-0)
fetchmail_attach_from_folder/match_algorithm/openerp_standard.py (+51/-0)
fetchmail_attach_from_folder/model/__init__.py (+24/-0)
fetchmail_attach_from_folder/model/fetchmail_server.py (+287/-0)
fetchmail_attach_from_folder/model/fetchmail_server_folder.py (+120/-0)
fetchmail_attach_from_folder/view/fetchmail_server.xml (+58/-0)
fetchmail_attach_from_folder/wizard/__init__.py (+23/-0)
fetchmail_attach_from_folder/wizard/attach_mail_manually.py (+110/-0)
fetchmail_attach_from_folder/wizard/attach_mail_manually.xml (+26/-0)
To merge this branch: bzr merge lp:~therp-nl/server-env-tools/6.1-fetchmail_attach_from_folder
Reviewer Review Type Date Requested Status
Alexandre Fayolle - camptocamp code review, no test Approve
Ronald Portier (Therp) (community) Approve
Stefan Rijnhart (Opener) Pending
Review via email: mp+159651@code.launchpad.net

This proposal supersedes a proposal from 2013-04-05.

Description of the change

Found a concurrency problem, causing long running fetching threads to be called multiple times, resulting in duplicated mails

To post a comment you must log in.
Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote : Posted in a previous version of this proposal
review: Approve
Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : Posted in a previous version of this proposal

Suggested enhancements:

formatting : would have been nice to get the proper spaces added (after commas, before and after assignment equal signs...)

the fetch_mail method (line 416 of the MP) could be split. I would personnally add one method for the body of the "for folder" loop and another for the body of the for message loop. This gains 8 columns, with allows for less line wrapping and clearer code, within the 80 col convention.

line 497: prefer "key in dictionary" to dictionary.has_key(key).

line 632: you are passing a cmp func to sorted to compare by the first element of a tuple. There are several small issues in there:

1. it is more efficient to use the key named argument (passing itemgetter(0) as key function in this case)
2. but it looks to me that the default sorting of Python does exactly what you want
3. you have a local list : using tuple(sorted(algorithms)) eats up unnecessary memory. Just use algorithms.sort() then return algorithms

in the column definitions (line 634 of the MP and following), using double quotes as delimiter removes the need to escape the single quotes inside the various strings, which makes the code easier to read.

review: Needs Fixing (code review, no test)
Revision history for this message
Ronald Portier (Therp) (rportier1962) wrote : Posted in a previous version of this proposal

Hello Holger,

I made a slightly modified branch:
https://code.launchpad.net/~therp-nl/server-env-tools/6.1-fetchmail_attach_mail_from_folder_rpo

- solved a problem when trying to match on either, from, or to, or cc, or bcc: code was chocking on fields not present in mail.
- enhanced change of matching by converting address to lowercase. To complement this, really should be a good idea to have only lower case e-mail adresses in for instance res.partner.address as well. Or have a view that always converts to lowercase...
- some minor code reorganisations to make it easer to step debug and see what is going on.

Ronald

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote : Posted in a previous version of this proposal

Thanks Ronald, would you issue a merge request on this branch as a reminder? I see that assumingly your IDE already took care of a few formatting issues that were pointed out by Alexandre, but there's more to do for me

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote : Posted in a previous version of this proposal

Thanks everyone, great to see such collaboration!

review: Approve
Revision history for this message
Ronald Portier (Therp) (rportier1962) wrote : Posted in a previous version of this proposal

Holger,

I just noticed that any mail retrieved gets a status 'received'.

However, it is possible to retrieve both received and send-mail.

So if the customer-email address is found in a sender field (most likely from), the mail is really received.

If it found in a receiver field (to, cc or bcc), the mail should get a status sent.

Kind regard, Ronald

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote : Posted in a previous version of this proposal

Ronald: That's a very good point, thanks. But I think it depends too much on the configured object/field what the state of a message should be to put it into the code. That's why I made it a configuration option for a folder in rev30, with 'received' as default.

Alexandre: Today I did the refactoring you proposed, now I think all your points are covered. Could you have another look?

Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : Posted in a previous version of this proposal

LGTM.

Thanks for taking the time to implement my suggestions.

review: Approve (code review, no test)
Revision history for this message
Ronald Portier (Therp) (rportier1962) wrote : Posted in a previous version of this proposal

Holger,

Great improvements! Thanx.

Sorry to bother you, but I have one more concern: when we are searching for matches, I think there will be a danger that we match on our own e-mail, instead of the partner e-mail.

Example, suppose I have a folder for all my customer mail. Matching will be done on email in res.partner.address.

As I have ingoing and outgoing mail in the same folder, I create two configurations/folders. One having to, cc and bcc as Field (email), and linking it to status send. The other to from and linking it to status received.

Now when I send a mail to a customer, it will find my own e-mail in from, an ingoing mail will be registered, with myself as the customer.

So I think there should be a way to exclude our own e-mail addresses from the search. Maybe by having all mail addresses with our own domain (might be multiple!) excluded from the search results.

What do you think?

Regards, Ronald

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote : Posted in a previous version of this proposal

I think you can exclude such cases by setting a domain in your configuration. So for matching customers only, you could use [('is_customer','=',True)], causing only customers to be possible candidates for matching in the first place. Or filter out employees: [('is_employee','=',False)]. The domain expression is passed directly to the model's search function, see currently lines 261ff in the MP.

In the end you get an expression like
((model_field=='address1') || (model_field=='address2') || ...) && your_domain_expression

Does this help?

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote : Posted in a previous version of this proposal

now I realize btw that today's refactoring would be a lot more useful if the functions passed down the ids of the mail.message(s) created. I'll fix that tomorrow

Revision history for this message
Ronald Portier (Therp) (rportier1962) wrote : Posted in a previous version of this proposal

Approve.

I tested the following use-case: single imap folder contains mail sent to and received from customers.

Did the following configuration;
- Created an incoming server document, with all the usual options
- Linked two folder configurations to this document:

1. - Folder 'Klanten' (customers)
   - match algorithm: Exact mailaddress
   - Model 'relatieadressen' (res.partner.address)
   - Field (emal): from
   - Field (model): email
   - Message state: Received
   - Domain: [('partner_id.customer', '=', True)]
   - Delete matches: False
   - Flag nonmatching: True
   - Use first match: True
2. The same as 1., except for the following:
   - Field (email): to, cc, bcc
   - Message state: Sent

I then used my mail client to send a mail wit an attachment (here our offer....) to a test customer-email. Then I used the test customer email account to sent back a reply, (I agree to the offer, find enclosed...), with another attachment.

Then I placed both the e-mail sent, and the reply received in the folder Klanten (customers) and ran the receive mail action in the incoming mail server document.

Outcome was as expected:
- I could find both mails, with the right status, on the tab 'Email' within the partner record
- I could see and open both the outgoing and the incoming attachment from within OpenERP.

Kind regards,

Ronald

review: Approve
Revision history for this message
Ronald Portier (Therp) (rportier1962) wrote :

Saw the problem. Solution looks good to me.

review: Approve
34. By Ronald Portier (Therp)

[MRG] [ADD] mail_client_view: gives normal users access to the mail views.

The only other possibilities users have of looking at e-mails, is through the
partner object, or other objects mails are directly linked to. This makes it
impossible to get an overall view, or to search through mails.

Furthermore the module makes newly received mails stand out with a red circle
in the mail view. These mails are in need of action. Via a simple click a user
can either mark a message as also in need of action, or the other way around,
as having been handled.

Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) :
review: Approve (code review, no test)
35. By Holger Brunn (Therp)

[MRG][ADD] fetchmail_attach_from_folder

Adds the possibility to attach emails from a certain IMAP folder to objects,
ie partners. Matching is done via several algorithms, ie email address.

This gives a simple possibility to archive emails in OpenERP without a mail
client integration.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'fetchmail_attach_from_folder'
=== added file 'fetchmail_attach_from_folder/__init__.py'
--- fetchmail_attach_from_folder/__init__.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/__init__.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,25 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23import match_algorithm
24import model
25import wizard
026
=== added file 'fetchmail_attach_from_folder/__openerp__.py'
--- fetchmail_attach_from_folder/__openerp__.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/__openerp__.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,45 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23{
24 'name': 'Attach mails in an IMAP folder to existing objects',
25 'version': '1.0',
26 'description': """
27 Adds the possibility to attach emails from a certain IMAP folder to objects,
28 ie partners. Matching is done via several algorithms, ie email address.
29
30 This gives a simple possibility to archive emails in OpenERP without a mail
31 client integration.
32 """,
33 'author': 'Therp BV',
34 'website': 'http://www.therp.nl',
35 "category": "Tools",
36 "depends": ['fetchmail'],
37 'data': [
38 'view/fetchmail_server.xml',
39 'wizard/attach_mail_manually.xml',
40 ],
41 'js': [],
42 'installable': True,
43 'active': False,
44 'certificate': '',
45}
046
=== added directory 'fetchmail_attach_from_folder/match_algorithm'
=== added file 'fetchmail_attach_from_folder/match_algorithm/__init__.py'
--- fetchmail_attach_from_folder/match_algorithm/__init__.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/match_algorithm/__init__.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,26 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23import base
24import email_exact
25import email_domain
26import openerp_standard
027
=== added file 'fetchmail_attach_from_folder/match_algorithm/base.py'
--- fetchmail_attach_from_folder/match_algorithm/base.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/match_algorithm/base.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,43 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23class base(object):
24 name = None
25 '''Name shown to the user'''
26
27 required_fields = []
28 '''Fields on fetchmail_server folder that are required for this algorithm'''
29
30 readonly_fields = []
31 '''Fields on fetchmail_server folder that are readonly for this algorithm'''
32
33
34 def search_matches(self, cr, uid, conf, mail_message, mail_message_org):
35 '''Returns ids found for model with mail_message'''
36 return []
37
38 def handle_match(
39 self, cr, uid, connection, object_id, folder,
40 mail_message, mail_message_org, msgid, context=None):
41 '''Do whatever it takes to handle a match'''
42 return folder.server_id.attach_mail(connection, object_id, folder,
43 mail_message, msgid)
044
=== added file 'fetchmail_attach_from_folder/match_algorithm/email_domain.py'
--- fetchmail_attach_from_folder/match_algorithm/email_domain.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/match_algorithm/email_domain.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,44 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23from email_exact import email_exact
24
25class email_domain(email_exact):
26 '''Search objects by domain name of email address.
27 Beware of match_first here, this is most likely to get it wrong (gmail)'''
28 name = 'Domain of email address'
29
30 def search_matches(self, cr, uid, conf, mail_message, mail_message_org):
31 ids = super(email_domain, self).search_matches(
32 cr, uid, conf, mail_message, mail_message_org)
33 if not ids:
34 domains = []
35 for addr in self._get_mailaddresses(conf, mail_message):
36 domains.append(addr.split('@')[-1])
37 ids = conf.pool.get(conf.model_id.model).search(
38 cr, uid,
39 self._get_mailaddress_search_domain(
40 conf, mail_message,
41 operator='like',
42 values=['%@'+domain for domain in set(domains)]),
43 order=conf.model_order)
44 return ids
045
=== added file 'fetchmail_attach_from_folder/match_algorithm/email_exact.py'
--- fetchmail_attach_from_folder/match_algorithm/email_exact.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/match_algorithm/email_exact.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,56 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23from base import base
24from openerp.tools.safe_eval import safe_eval
25from openerp.addons.mail.mail_message import to_email
26
27class email_exact(base):
28 '''Search for exactly the mailadress as noted in the email'''
29
30 name = 'Exact mailadress'
31 required_fields = ['model_field', 'mail_field']
32
33 def _get_mailaddresses(self, conf, mail_message):
34 mailaddresses = []
35 fields = conf.mail_field.split(',')
36 for field in fields:
37 if field in mail_message:
38 mailaddresses += to_email(mail_message[field])
39 return [ addr.lower() for addr in mailaddresses ]
40
41 def _get_mailaddress_search_domain(
42 self, conf, mail_message, operator='=', values=None):
43 mailaddresses = values or self._get_mailaddresses(
44 conf, mail_message)
45 if not mailaddresses:
46 return [(0, '=', 1)]
47 search_domain = ((['|'] * (len(mailaddresses) - 1)) + [
48 (conf.model_field, operator, addr) for addr in mailaddresses] +
49 safe_eval(conf.domain or '[]'))
50 return search_domain
51
52 def search_matches(self, cr, uid, conf, mail_message, mail_message_org):
53 conf_model = conf.pool.get(conf.model_id.model)
54 search_domain = self._get_mailaddress_search_domain(conf, mail_message)
55 return conf_model.search(
56 cr, uid, search_domain, order=conf.model_order)
057
=== added file 'fetchmail_attach_from_folder/match_algorithm/openerp_standard.py'
--- fetchmail_attach_from_folder/match_algorithm/openerp_standard.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/match_algorithm/openerp_standard.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,51 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23from base import base
24from openerp.tools.safe_eval import safe_eval
25
26class openerp_standard(base):
27 '''No search at all. Use OpenERP's standard mechanism to attach mails to
28 mail.thread objects. Note that this algorithm always matches.'''
29
30 name = 'OpenERP standard'
31 readonly_fields = ['model_field', 'mail_field', 'match_first', 'domain',
32 'model_order', 'flag_nonmatching']
33
34 def search_matches(self, cr, uid, conf, mail_message, mail_message_org):
35 '''Always match. Duplicates will be fished out by message_id'''
36 return [True]
37
38 def handle_match(
39 self, cr, uid, connection, object_id, folder,
40 mail_message, mail_message_org, msgid, context):
41 result = folder.pool.get('mail.thread').message_process(
42 cr, uid,
43 folder.model_id.model, mail_message_org,
44 save_original=folder.server_id.original,
45 strip_attachments=(not folder.server_id.attach),
46 context=context)
47
48 if folder.delete_matching:
49 connection.store(msgid, '+FLAGS', '\\DELETED')
50
51 return [result]
052
=== added directory 'fetchmail_attach_from_folder/model'
=== added file 'fetchmail_attach_from_folder/model/__init__.py'
--- fetchmail_attach_from_folder/model/__init__.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/model/__init__.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,24 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23import fetchmail_server
24import fetchmail_server_folder
025
=== added file 'fetchmail_attach_from_folder/model/fetchmail_server.py'
--- fetchmail_attach_from_folder/model/fetchmail_server.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/model/fetchmail_server.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,287 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23import base64
24import simplejson
25from lxml import etree
26from openerp.osv.orm import Model, except_orm, browse_null
27from openerp.tools.translate import _
28from openerp.osv import fields
29from openerp.addons.fetchmail.fetchmail import logger
30from openerp.tools.misc import UnquoteEvalContext
31from openerp.tools.safe_eval import safe_eval
32
33
34class fetchmail_server(Model):
35 _inherit = 'fetchmail.server'
36
37 _columns = {
38 'folder_ids': fields.one2many(
39 'fetchmail.server.folder', 'server_id', 'Folders'),
40 }
41
42 _defaults = {
43 'type': 'imap',
44 }
45
46 def __init__(self, pool, cr):
47 self._columns['object_id'].required = False
48 return super(fetchmail_server, self).__init__(pool, cr)
49
50 def onchange_server_type(
51 self, cr, uid, ids, server_type=False, ssl=False,
52 object_id=False):
53 retval = super(
54 fetchmail_server, self).onchange_server_type(cr, uid,
55 ids, server_type, ssl,
56 object_id)
57 retval['value']['state'] = 'draft'
58 return retval
59
60 def fetch_mail(self, cr, uid, ids, context=None):
61 if context is None:
62 context = {}
63
64 check_original = []
65
66 for this in self.browse(cr, uid, ids, context):
67 if this.object_id:
68 check_original.append(this.id)
69
70 context.update(
71 {
72 'fetchmail_server_id': this.id,
73 'server_type': this.type
74 })
75
76 connection = this.connect()
77 for folder in this.folder_ids:
78 this.handle_folder(connection, folder)
79
80 connection.close()
81
82 return super(fetchmail_server, self).fetch_mail(
83 cr, uid, check_original, context)
84
85 def handle_folder(self, cr, uid, ids, connection, folder, context=None):
86 '''Return ids of objects matched'''
87
88 matched_object_ids = []
89
90 for this in self.browse(cr, uid, ids, context=context):
91 logger.info('start checking for emails in %s server %s',
92 folder.path, this.name)
93
94 match_algorithm = folder.get_algorithm()
95
96 if connection.select(folder.path)[0] != 'OK':
97 logger.error(
98 'Could not open mailbox %s on %s' % (
99 folder.path, this.server))
100 connection.select()
101 continue
102 result, msgids = this.get_msgids(connection)
103 if result != 'OK':
104 logger.error(
105 'Could not search mailbox %s on %s' % (
106 folder.path, this.server))
107 continue
108
109 for msgid in msgids[0].split():
110 matched_object_ids += this.apply_matching(
111 connection, folder, msgid, match_algorithm)
112
113 logger.info('finished checking for emails in %s server %s',
114 folder.path, this.name)
115
116 return matched_object_ids
117
118 def get_msgids(self, cr, uid, ids, connection, context=None):
119 '''Return imap ids of messages to process'''
120 return connection.search(None, 'UNDELETED')
121
122 def apply_matching(self, cr, uid, ids, connection, folder, msgid,
123 match_algorithm, context=None):
124 '''Return ids of objects matched'''
125
126 matched_object_ids = []
127
128 for this in self.browse(cr, uid, ids, context=context):
129 result, msgdata = connection.fetch(msgid, '(RFC822)')
130
131 if result != 'OK':
132 logger.error(
133 'Could not fetch %s in %s on %s' % (
134 msgid, folder.path, this.server))
135 continue
136
137 mail_message = self.pool.get('mail.message').parse_message(
138 msgdata[0][1], this.original)
139
140 if self.pool.get('mail.message').search(cr, uid, [
141 ('message_id', '=', mail_message['message-id'])]):
142 continue
143
144 found_ids = match_algorithm.search_matches(
145 cr, uid, folder,
146 mail_message, msgdata[0][1])
147
148 if found_ids and (len(found_ids) == 1 or
149 folder.match_first):
150 try:
151 cr.execute('savepoint apply_matching')
152 match_algorithm.handle_match(
153 cr, uid, connection,
154 found_ids[0], folder, mail_message,
155 msgdata[0][1], msgid, context)
156 cr.execute('release savepoint apply_matching')
157 matched_object_ids += found_ids[:1]
158 except Exception, e:
159 cr.execute('rollback to savepoint apply_matching')
160 logger.exception(
161 "Failed to fetch mail %s from %s",
162 msgid, this.name)
163 elif folder.flag_nonmatching:
164 connection.store(msgid, '+FLAGS', '\\FLAGGED')
165
166 return matched_object_ids
167
168 def attach_mail(
169 self, cr, uid, ids, connection, object_id, folder,
170 mail_message, msgid, context=None):
171 '''Return ids of messages created'''
172
173 mail_message_ids = []
174
175 for this in self.browse(cr, uid, ids, context):
176 partner_id = None
177 if folder.model_id.model == 'res.partner':
178 partner_id = object_id
179 if 'partner_id' in self.pool.get(folder.model_id.model)._columns:
180 partner_id = self.pool.get(
181 folder.model_id.model).browse(
182 cr, uid, object_id, context
183 ).partner_id.id
184
185 attachments=[]
186 if this.attach and mail_message.get('attachments'):
187 for attachment in mail_message['attachments']:
188 fname, fcontent = attachment
189 if isinstance(fcontent, unicode):
190 fcontent = fcontent.encode('utf-8')
191 data_attach = {
192 'name': fname,
193 'datas': base64.b64encode(str(fcontent)),
194 'datas_fname': fname,
195 'description': _('Mail attachment'),
196 'res_model': folder.model_id.model,
197 'res_id': object_id,
198 }
199 attachments.append(
200 self.pool.get('ir.attachment').create(
201 cr, uid, data_attach, context=context))
202
203 mail_message_ids.append(
204 self.pool.get('mail.message').create(
205 cr, uid,
206 {
207 'partner_id': partner_id,
208 'model': folder.model_id.model,
209 'res_id': object_id,
210 'body_text': mail_message.get('body'),
211 'body_html': mail_message.get('body_html'),
212 'subject': mail_message.get('subject'),
213 'email_to': mail_message.get('to'),
214 'email_from': mail_message.get('from'),
215 'email_cc': mail_message.get('cc'),
216 'reply_to': mail_message.get('reply'),
217 'date': mail_message.get('date'),
218 'message_id': mail_message.get('message-id'),
219 'subtype': mail_message.get('subtype'),
220 'headers': mail_message.get('headers'),
221 'state': folder.msg_state,
222 'attachment_ids': [(6, 0, attachments)],
223 },
224 context))
225
226 if folder.delete_matching:
227 connection.store(msgid, '+FLAGS', '\\DELETED')
228 return mail_message_ids
229
230 def button_confirm_login(self, cr, uid, ids, context=None):
231 retval = super(fetchmail_server, self).button_confirm_login(cr, uid,
232 ids,
233 context)
234
235 for this in self.browse(cr, uid, ids, context):
236 this.write({'state': 'draft'})
237 connection = this.connect()
238 connection.select()
239 for folder in this.folder_ids:
240 if connection.select(folder.path)[0] != 'OK':
241 raise except_orm(
242 _('Error'), _('Mailbox %s not found!') %
243 folder.path)
244 folder.get_algorithm().search_matches(
245 cr, uid, folder, browse_null(), '')
246 connection.close()
247 this.write({'state': 'done'})
248
249 return retval
250
251 def fields_view_get(self, cr, user, view_id=None, view_type='form',
252 context=None, toolbar=False, submenu=False):
253 result = super(fetchmail_server, self).fields_view_get(
254 cr, user, view_id, view_type, context, toolbar, submenu)
255
256 if view_type == 'form':
257 view = etree.fromstring(
258 result['fields']['folder_ids']['views']['form']['arch'])
259 modifiers = {}
260 docstr = ''
261 for algorithm in self.pool.get('fetchmail.server.folder')\
262 ._get_match_algorithms().itervalues():
263 for modifier in ['required', 'readonly']:
264 for field in getattr(algorithm, modifier + '_fields'):
265 modifiers.setdefault(field, {})
266 modifiers[field].setdefault(modifier, [])
267 if modifiers[field][modifier]:
268 modifiers[field][modifier].insert(0, '|')
269 modifiers[field][modifier].append(
270 ("match_algorithm", "==", algorithm.__name__))
271 docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \
272 '\n\n'
273
274 for field in view:
275 if field.tag == 'field' and field.get('name') in modifiers:
276 field.set('modifiers', simplejson.dumps(
277 dict(
278 eval(field.attrib['modifiers'],
279 UnquoteEvalContext({})),
280 **modifiers[field.attrib['name']])))
281 if (field.tag == 'field' and
282 field.get('name') == 'match_algorithm'):
283 field.set('help', docstr)
284 result['fields']['folder_ids']['views']['form']['arch'] = \
285 etree.tostring(view)
286
287 return result
0288
=== added file 'fetchmail_attach_from_folder/model/fetchmail_server_folder.py'
--- fetchmail_attach_from_folder/model/fetchmail_server_folder.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/model/fetchmail_server_folder.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,120 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21########################################################################
22
23from openerp.osv import fields
24from openerp.osv.orm import Model
25from .. import match_algorithm
26
27
28class fetchmail_server_folder(Model):
29 _name = 'fetchmail.server.folder'
30 _rec_name = 'path'
31
32 def _get_match_algorithms(self):
33 def get_all_subclasses(cls):
34 return cls.__subclasses__() + [subsub
35 for sub in cls.__subclasses__()
36 for subsub in get_all_subclasses(sub)]
37 return dict([(cls.__name__, cls) for cls in get_all_subclasses(
38 match_algorithm.base.base)])
39
40 def _get_match_algorithms_sel(self, cr, uid, context=None):
41 algorithms = []
42 for cls in self._get_match_algorithms().itervalues():
43 algorithms.append((cls.__name__, cls.name))
44 algorithms.sort()
45 return algorithms
46
47 _columns = {
48 'sequence': fields.integer('Sequence'),
49 'path': fields.char(
50 'Path', size=256, help='The path to your mail '
51 "folder. Typically would be something like 'INBOX.myfolder'",
52 required=True),
53 'model_id': fields.many2one(
54 'ir.model', 'Model', required=True,
55 help='The model to attach emails to'),
56 'model_field': fields.char(
57 'Field (model)', size=128,
58 help='The field in your model that contains the field to match '
59 'against.\n'
60 'Examples:\n'
61 "'email' if your model is res.partner, or "
62 "'partner_id.email' if you're matching sale orders"),
63 'model_order': fields.char(
64 'Order (model)', size=128,
65 help='Fields to order by, this mostly useful in conjunction '
66 "with 'Use 1st match'"),
67 'match_algorithm': fields.selection(
68 _get_match_algorithms_sel,
69 'Match algorithm', required=True, translate=True,
70 help='The algorithm used to determine which object an email '
71 'matches.'),
72 'mail_field': fields.char(
73 'Field (email)', size=128,
74 help='The field in the email used for matching. Typically '
75 "this is 'to' or 'from'"),
76 'server_id': fields.many2one('fetchmail.server', 'Server'),
77 'delete_matching': fields.boolean(
78 'Delete matches',
79 help='Delete matched emails from server'),
80 'flag_nonmatching': fields.boolean(
81 'Flag nonmatching',
82 help="Flag emails in the server that don't match any object "
83 'in OpenERP'),
84 'match_first': fields.boolean(
85 'Use 1st match',
86 help='If there are multiple matches, use the first one. If '
87 'not checked, multiple matches count as no match at all'),
88 'domain': fields.char(
89 'Domain', size=128, help='Fill in a search '
90 'filter to narrow down objects to match'),
91 'msg_state': fields.selection(
92 [
93 ('sent', 'Sent'),
94 ('received', 'Received'),
95 ],
96 'Message state',
97 help='The state messages fetched from this folder should be '
98 'assigned in OpenERP'),
99 }
100
101 _defaults = {
102 'flag_nonmatching': True,
103 'msg_state': 'received',
104 }
105
106 def get_algorithm(self, cr, uid, ids, context=None):
107 for this in self.browse(cr, uid, ids, context):
108 return self._get_match_algorithms()[this.match_algorithm]()
109
110 def button_attach_mail_manually(self, cr, uid, ids, context=None):
111 for this in self.browse(cr, uid, ids, context):
112 context.update({'default_folder_id': this.id})
113 return {
114 'type': 'ir.actions.act_window',
115 'res_model': 'fetchmail.attach.mail.manually',
116 'target': 'new',
117 'context': context,
118 'view_type': 'form',
119 'view_mode': 'form',
120 }
0121
=== added directory 'fetchmail_attach_from_folder/view'
=== added file 'fetchmail_attach_from_folder/view/fetchmail_server.xml'
--- fetchmail_attach_from_folder/view/fetchmail_server.xml 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/view/fetchmail_server.xml 2013-04-18 14:54:31 +0000
@@ -0,0 +1,58 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record model="ir.ui.view" id="view_email_server_form">
5 <field name="name">fetchmail.server.form</field>
6 <field name="model">fetchmail.server</field>
7 <field name="type">form</field>
8 <field name="inherit_id" ref="fetchmail.view_email_server_form" />
9 <field name="arch" type="xml">
10 <data>
11 <field name="object_id" position="attributes">
12 <attribute name="attrs">{'required': [('type', '!=', 'imap')]}</attribute>
13 </field>
14 <xpath expr="//page[@string='Server &amp; Login']/group[3]" position="after">
15 <group col="2" colspan="2" attrs="{'invisible': [('type','!=','imap')]}">
16 <separator string="Folders to monitor" colspan="2"/>
17 <field
18 name="folder_ids"
19 nolabel="1"
20 colspan="2"
21 on_change="onchange_server_type(type, is_ssl, object_id)">
22 <tree>
23 <field name="sequence" invisible="1" />
24 <field name="path" />
25 <field name="model_id" />
26 <field name="model_field" />
27 <field name="match_algorithm" />
28 <field name="mail_field" />
29 </tree>
30 <form col="6">
31 <field name="path" />
32 <field name="model_id" />
33 <field name="model_field" />
34 <field name="match_algorithm" />
35 <field name="mail_field" />
36 <newline />
37 <field name="delete_matching" />
38 <field name="flag_nonmatching" />
39 <field name="match_first" />
40 <field name="msg_state" />
41 <field name="model_order" attrs="{'readonly': [('match_first','==',False)], 'required': [('match_first','==',True)]}" />
42 <field name="domain" />
43 <newline />
44 <label />
45 <label />
46 <label />
47 <label />
48 <label />
49 <button type="object" name="button_attach_mail_manually" string="Attach mail manually" icon="gtk-redo" />
50 </form>
51 </field>
52 </group>
53 </xpath>
54 </data>
55 </field>
56 </record>
57 </data>
58</openerp>
059
=== added directory 'fetchmail_attach_from_folder/wizard'
=== added file 'fetchmail_attach_from_folder/wizard/__init__.py'
--- fetchmail_attach_from_folder/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/wizard/__init__.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,23 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23import attach_mail_manually
024
=== added file 'fetchmail_attach_from_folder/wizard/attach_mail_manually.py'
--- fetchmail_attach_from_folder/wizard/attach_mail_manually.py 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/wizard/attach_mail_manually.py 2013-04-18 14:54:31 +0000
@@ -0,0 +1,110 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2013 Therp BV (<http://therp.nl>)
6# All Rights Reserved
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23from openerp.osv import fields
24from openerp.osv.orm import TransientModel
25
26
27class attach_mail_manually(TransientModel):
28 _name = 'fetchmail.attach.mail.manually'
29
30 _columns = {
31 'folder_id': fields.many2one('fetchmail.server.folder', 'Folder',
32 readonly=True),
33 'mail_ids': fields.one2many(
34 'fetchmail.attach.mail.manually.mail', 'wizard_id', 'Emails'),
35 }
36
37 def default_get(self, cr, uid, fields_list, context=None):
38 if context is None:
39 context = {}
40
41 defaults = super(attach_mail_manually, self).default_get(cr, uid,
42 fields_list, context)
43
44 for folder in self.pool.get('fetchmail.server.folder').browse(cr, uid,
45 [context.get('default_folder_id')], context):
46 defaults['mail_ids']=[]
47 connection = folder.server_id.connect()
48 connection.select(folder.path)
49 result, msgids = connection.search(None,
50 'FLAGGED' if folder.flag_nonmatching else 'UNDELETED')
51 if result != 'OK':
52 logger.error('Could not search mailbox %s on %s' % (
53 folder.path, this.server))
54 continue
55 attach_mail_manually_mail._columns['object_id'].selection=[
56 (folder.model_id.model, folder.model_id.name)]
57 for msgid in msgids[0].split():
58 result, msgdata = connection.fetch(msgid, '(RFC822)')
59 if result != 'OK':
60 logger.error('Could not fetch %s in %s on %s' % (
61 msgid, folder.path, this.server))
62 continue
63 mail_message = self.pool.get('mail.message').parse_message(
64 msgdata[0][1])
65 defaults['mail_ids'].append((0, 0, {
66 'msgid': msgid,
67 'subject': mail_message.get('subject', ''),
68 'date': mail_message.get('date', ''),
69 'object_id': folder.model_id.model+',False'
70 }))
71 connection.close()
72
73 return defaults
74
75 def attach_mails(self, cr, uid, ids, context=None):
76 for this in self.browse(cr, uid, ids, context):
77 for mail in this.mail_ids:
78 connection = this.folder_id.server_id.connect()
79 connection.select(this.folder_id.path)
80 result, msgdata = connection.fetch(mail.msgid, '(RFC822)')
81 if result != 'OK':
82 logger.error('Could not fetch %s in %s on %s' % (
83 msgid, folder.path, this.server))
84 continue
85
86 mail_message = self.pool.get('mail.message').parse_message(
87 msgdata[0][1], this.folder_id.server_id.original)
88
89 this.folder_id.server_id.attach_mail(connection,
90 mail.object_id.id, this.folder_id, mail_message,
91 mail.msgid)
92 connection.close()
93 return {'type': 'ir.actions.act_window_close'}
94
95class attach_mail_manually_mail(TransientModel):
96 _name = 'fetchmail.attach.mail.manually.mail'
97
98 _columns = {
99 'wizard_id': fields.many2one('fetchmail.attach.mail.manually',
100 readonly=True),
101 'msgid': fields.char('Message id', size=16, readonly=True),
102 'subject': fields.char('Subject', size=128, readonly=True),
103 'date': fields.datetime('Date', readonly=True),
104 'object_id': fields.reference('Object',
105 selection=lambda self, cr, uid, context:
106 [(m.model, m.name) for m in
107 self.pool.get('ir.model').browse(cr, uid,
108 self.pool.get('ir.model').search(cr, uid, []),
109 context)], size=128),
110 }
0111
=== added file 'fetchmail_attach_from_folder/wizard/attach_mail_manually.xml'
--- fetchmail_attach_from_folder/wizard/attach_mail_manually.xml 1970-01-01 00:00:00 +0000
+++ fetchmail_attach_from_folder/wizard/attach_mail_manually.xml 2013-04-18 14:54:31 +0000
@@ -0,0 +1,26 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record model="ir.ui.view" id="view_attach_mail_manually">
5 <field name="name">fetchmail.attach.mail.manually</field>
6 <field name="model">fetchmail.attach.mail.manually</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form col="4">
10 <field name="folder_id" colspan="4" />
11 <field name="mail_ids" nolabel="1" colspan="4">
12 <tree editable="top">
13 <field name="subject" />
14 <field name="date" />
15 <field name="object_id" />
16 </tree>
17 </field>
18 <label string="" />
19 <label string="" />
20 <button special="cancel" string="Cancel" icon="gtk-cancel" />
21 <button string="Save" type="object" name="attach_mails" icon="gtk-ok" />
22 </form>
23 </field>
24 </record>
25 </data>
26</openerp>

Subscribers

People subscribed via source and target branches