entities.py 4.22 KB
Newer Older
Nicolas Chauvat's avatar
Nicolas Chauvat committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""entity classes for entity types provided by the cubicweb email package

:organization: Logilab
:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""

__docformat__ = "restructuredtext en"

import re

from logilab.common import umessage

from cubicweb.interfaces import ITree
from cubicweb.common.mixins import TreeMixIn
from cubicweb.entities import AnyEntity, fetch_config

from cubes.email.emailcites import parse_body


class Email(TreeMixIn, AnyEntity):
    """customized class for Email entities"""
    id = 'Email'
    fetch_attrs, fetch_order = fetch_config(['subject'])
    __rtags__ = {'attachment' : 'create',
                 }
    __implements__ = AnyEntity.__implements__ + (ITree,)
    
    widgets = {
        'subject' : "StringWidget",
        }
    
    tree_attribute = 'reply_to'
    
    def dc_title(self):
        return self.subject

    @property
    def senderaddr(self):
        return self.sender[0]
    
    @property
    def in_reply_to(self):
        return self.reply_to and self.reply_to[0]
    
    @property
    def thread(self):
        return self.in_thread and self.in_thread[0]

    
    def parts_in_order(self, prefered_mime_type='text/html'):
        """sort an email parts in order, selecting among alternatives according to a
        prefered mime type
        """
        parts_by_eid = {}
        alternatives = []
        result = []
        for part in self.parts:
            parts_by_eid[part.eid] = part
            if part.alternative:
                for altset in alternatives:
                    if part.eid in altset:
                        break
                else:
                    alternatives.append(set(p.eid for p in part.alternative))
                    alternatives[-1].add(part.eid)
            else:
                result.append((part.ordernum, part))
        if alternatives:
            for altset in alternatives:
                selected = [parts_by_eid[peid] for peid in altset
                            if parts_by_eid[peid].content_format == prefered_mime_type]
                if selected:
                    selected = selected[0]
                else:
                    selected = parts_by_eid[altset.pop()]
                result.append((selected.ordernum, selected))
        result.sort()
        return [p[1] for p in result]

    def references(self):
        result = set()
        message = self.umessage_headers()
        if not message:
            return result
        replyto = message.get('In-reply-to')
        if replyto:
            result.add(replyto)
        result.update(message.get_all('References', ()))
        return result

    lines_rgx = re.compile('^Lines:\s*\d+\s*\n', re.I|re.U|re.M)
    clength_rgx = re.compile('^Content-Length:\s*\d+\s*\n', re.I|re.U|re.M)
    ctype_rgx = re.compile('^Content-Type:[^:]', re.I|re.U|re.M)
    
    def umessage_headers(self):
        if not self.headers:
            return None
        headers = self.lines_rgx.sub('', self.headers)
        headers = self.clength_rgx.sub('', headers)
        headers = self.ctype_rgx.sub('Content-type: text/plain; charset=utf8', headers)
        headers = headers.encode('utf8')
        return umessage.message_from_string(headers + '\n\n')
    
    
class EmailPart(AnyEntity):
    """customized class for EmailPart entities"""
    id = 'EmailPart'

    def dc_title(self):
        return '%s (%s %s)' % (self.email.subject,
                               self.req._('part'), self.ordernum)
    
    @property
    def email(self):
        return self.reverse_parts[0]
    
    def actual_content(self):
        """return content of this part with citations and signature removed

        this method may raise `TransformError` exception if the part can't
        be displayed as text/plain.
        """
        content = self.printable_value('content', format='text/plain')        
        return parse_body(content).actual_content
    
        
class EmailThread(AnyEntity):
    """customized class for EmailThread entities"""
    id = 'EmailThread'
    fetch_attrs, fetch_order = fetch_config(['title'])
    __rtags__ = {('forked_from',  '*', 'object') : 'create',
                 }

    def dc_title(self):
        return self.title