entities.py 4.38 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
"""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'
34
35
36
37

    def parent(self):
        """for breadcrumbs"""
        return self.thread
Nicolas Chauvat's avatar
Nicolas Chauvat committed
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
    
    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]
    
122
123
124
125
    def parent(self):
        """for breadcrumbs"""
        return self.email
    
Nicolas Chauvat's avatar
Nicolas Chauvat committed
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
    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