entities.py 4.11 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
    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:
64
                result.append(part)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
65
66
67
68
69
70
71
72
        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()]
73
74
                result.append(selected)
        return sorted(result, key=lambda x: x.ordernum)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

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

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


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

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

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

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


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

    def dc_title(self):
        return self.title