entities.py 4.15 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
13
14
15
16
17
18
19
20
21
22
23
24
: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'])
    __implements__ = AnyEntity.__implements__ + (ITree,)
25

Nicolas Chauvat's avatar
Nicolas Chauvat committed
26
    tree_attribute = 'reply_to'
27
28
29
30

    def parent(self):
        """for breadcrumbs"""
        return self.thread
31

Nicolas Chauvat's avatar
Nicolas Chauvat committed
32
33
34
35
36
    def dc_title(self):
        return self.subject

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

Nicolas Chauvat's avatar
Nicolas Chauvat committed
39
40
    @property
    def in_reply_to(self):
41
        return self.reply_to and self.reply_to[0] or None
42

Nicolas Chauvat's avatar
Nicolas Chauvat committed
43
44
    @property
    def thread(self):
45
        return self.in_thread and self.in_thread[0] or None
46

Nicolas Chauvat's avatar
Nicolas Chauvat committed
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
    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)
91

Nicolas Chauvat's avatar
Nicolas Chauvat committed
92
93
94
95
96
97
98
99
    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')
100
101


Nicolas Chauvat's avatar
Nicolas Chauvat committed
102
103
104
105
106
107
108
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)
109

Nicolas Chauvat's avatar
Nicolas Chauvat committed
110
111
112
    @property
    def email(self):
        return self.reverse_parts[0]
113

114
115
116
    def parent(self):
        """for breadcrumbs"""
        return self.email
117

Nicolas Chauvat's avatar
Nicolas Chauvat committed
118
119
120
121
122
123
    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.
        """
124
        content = self.printable_value('content', format='text/plain')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
125
        return parse_body(content).actual_content
126
127


Nicolas Chauvat's avatar
Nicolas Chauvat committed
128
129
130
131
132
133
134
class EmailThread(AnyEntity):
    """customized class for EmailThread entities"""
    id = 'EmailThread'
    fetch_attrs, fetch_order = fetch_config(['title'])

    def dc_title(self):
        return self.title