entities.py 4.04 KB
Newer Older
Nicolas Chauvat's avatar
Nicolas Chauvat committed
1
2
3
"""entity classes for entity types provided by the cubicweb email package

:organization: Logilab
4
:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
Nicolas Chauvat's avatar
Nicolas Chauvat committed
5
6
7
8
9
10
11
12
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"

import re

from logilab.common import umessage

Sylvain Thénault's avatar
Sylvain Thénault committed
13
14
from cubicweb.entities import AnyEntity, fetch_config, adapters
from cubicweb.selectors import is_instance
Nicolas Chauvat's avatar
Nicolas Chauvat committed
15
16
17
from cubes.email.emailcites import parse_body


Sylvain Thénault's avatar
Sylvain Thénault committed
18
class Email(AnyEntity):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
19
    """customized class for Email entities"""
Sylvain Thénault's avatar
Sylvain Thénault committed
20
    __regid__ = 'Email'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
21
    fetch_attrs, fetch_order = fetch_config(['subject'])
22

Nicolas Chauvat's avatar
Nicolas Chauvat committed
23
24
25
26
27
    def dc_title(self):
        return self.subject

    @property
    def senderaddr(self):
28
        return self.sender and self.sender[0] or None
29

Nicolas Chauvat's avatar
Nicolas Chauvat committed
30
31
    @property
    def in_reply_to(self):
32
        return self.reply_to and self.reply_to[0] or None
33

Nicolas Chauvat's avatar
Nicolas Chauvat committed
34
35
    @property
    def thread(self):
36
        return self.in_thread and self.in_thread[0] or None
37

Nicolas Chauvat's avatar
Nicolas Chauvat committed
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    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:
55
                result.append(part)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
56
57
58
59
60
61
62
63
        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()]
64
65
                result.append(selected)
        return sorted(result, key=lambda x: x.ordernum)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

    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)
81

Nicolas Chauvat's avatar
Nicolas Chauvat committed
82
83
84
85
86
87
88
89
    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')
90
91


Sylvain Thénault's avatar
Sylvain Thénault committed
92

Nicolas Chauvat's avatar
Nicolas Chauvat committed
93
94
class EmailPart(AnyEntity):
    """customized class for EmailPart entities"""
Sylvain Thénault's avatar
Sylvain Thénault committed
95
    __regid__ = 'EmailPart'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
96
97
98

    def dc_title(self):
        return '%s (%s %s)' % (self.email.subject,
99
                               self._cw._('part'), self.ordernum)
100

Nicolas Chauvat's avatar
Nicolas Chauvat committed
101
102
103
    @property
    def email(self):
        return self.reverse_parts[0]
104

105
106
107
    def parent(self):
        """for breadcrumbs"""
        return self.email
108

Nicolas Chauvat's avatar
Nicolas Chauvat committed
109
110
111
112
113
114
    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.
        """
115
        content = self.printable_value('content', format='text/plain')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
116
        return parse_body(content).actual_content
117
118


Nicolas Chauvat's avatar
Nicolas Chauvat committed
119
120
class EmailThread(AnyEntity):
    """customized class for EmailThread entities"""
Sylvain Thénault's avatar
Sylvain Thénault committed
121
    __regid__ = 'EmailThread'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
122
123
124
125
    fetch_attrs, fetch_order = fetch_config(['title'])

    def dc_title(self):
        return self.title
Sylvain Thénault's avatar
Sylvain Thénault committed
126
127
128
129
130
131


class EmailITreeAdapter(adapters.ITreeAdapter):
    __select__ = is_instance('Email')
    tree_relation = 'reply_to'