email.py 6.74 KB
Newer Older
Nicolas Chauvat's avatar
Nicolas Chauvat committed
1
2
3
"""Specific views for email related entities

:organization: Logilab
Sylvain Thénault's avatar
Sylvain Thénault committed
4
:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
Nicolas Chauvat's avatar
Nicolas Chauvat committed
5
6
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
7
8
__docformat__ = "restructuredtext en"
_ = unicode
Nicolas Chauvat's avatar
Nicolas Chauvat committed
9

10
from logilab.mtconverter import xml_escape
Nicolas Chauvat's avatar
Nicolas Chauvat committed
11

12
from cubicweb.predicates import is_instance
Sylvain Thénault's avatar
Sylvain Thénault committed
13
from cubicweb.uilib import soup2xhtml
14
from cubicweb.view import EntityView
15
from cubicweb.web import uicfg, formwidgets
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
16
from cubicweb.web.views import baseviews, primary
Nicolas Chauvat's avatar
Nicolas Chauvat committed
17

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
18
for rtype in ('sender', 'recipients', 'cc', 'parts'):
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
19
    uicfg.primaryview_section.tag_subject_of(('Email', rtype, '*'), 'hidden')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
20

21
22
uicfg.autoform_field_kwargs.tag_attribute(('Email', 'subject'),
                                          {'widget': formwidgets.TextInput})
23

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
24
25
26
27
28
29
uicfg.actionbox_appearsin_addmenu.tag_subject_of(('Email', 'attachment', '*'),
                                                 True)

uicfg.actionbox_appearsin_addmenu.tag_object_of(('EmailThread', 'forked_from', 'EmailThread'),
                                                True)

30
31
32
33
34
35
36

def formated_sender(email):
    if email.sender:
        return email.sender[0].view('oneline')
    # sender address has been removed, look in email's headers
    message = email.umessage_headers()
    if message:
Carlos's avatar
Carlos committed
37
        return xml_escape(message.get('From', ''))
38
    return email._cw._('unknown sender')
39

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
40
class EmailPrimaryView(primary.PrimaryView):
Sylvain Thénault's avatar
Sylvain Thénault committed
41
    __select__ = is_instance('Email')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
42

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
43
    def render_entity_attributes(self, entity):
44
45
        self.w(u'<div class="emailheader"><table>')
        self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
46
               (self._cw._('From'), formated_sender(entity)))
47
        self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
48
               (self._cw._('To'), ', '.join(ea.view('oneline') for ea in entity.recipients)))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
49
        if entity.cc:
50
            self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
51
                   (self._cw._('CC'), ', '.join(ea.view('oneline') for ea in entity.cc)))
52
        self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
53
               (self._cw._('Date'), self._cw.format_date(entity.date, time=True)))
54
        self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
55
               (self._cw._('Subject'), xml_escape(entity.subject)))
56
        self.w(u'</table></div><div class="emailcontent">')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
57
58
59
        for part in entity.parts_in_order():
            content, mime = part.content, part.content_format
            if mime == 'text/html':
60
                content = soup2xhtml(content, self._cw.encoding)
Aurelien Campeas's avatar
Aurelien Campeas committed
61
62
            elif 'pgp-signature' in mime:
                content = entity._cw_mtc_transform(content, mime, 'text/html', self._cw.encoding)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
63
            elif mime != 'text/xhtml':
64
                content = xml_escape(content)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
65
66
67
68
69
                if mime == 'text/plain':
                    content = content.replace('\n', '<br/>').replace(' ', '&nbsp;')
            # XXX some headers to skip if html ?
            self.w(content)
            self.w(u'<br class="partseparator"/>')
70
        self.w(u'</div>')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
71

72
73
    def render_entity_title(self, entity):
        self.w(u'<h1><span class="etype">%s</span> %s</h1>'
74
               % (entity.dc_type().capitalize(), xml_escape(entity.dc_title())))
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
75
76


Nicolas Chauvat's avatar
Nicolas Chauvat committed
77
78
class EmailHeadersView(baseviews.EntityView):
    """display email's headers"""
Sylvain Thénault's avatar
Sylvain Thénault committed
79
    __regid__ = 'headers'
Sylvain Thénault's avatar
Sylvain Thénault committed
80
    __select__ = is_instance('Email')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
81
82
83
    title = _('headers')
    templatable = False
    content_type = 'text/plain'
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
84

Nicolas Chauvat's avatar
Nicolas Chauvat committed
85
    def cell_call(self, row, col):
86
        entity = self.cw_rset.get_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
87
88
        self.w(entity.headers)

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
89
90

class EmailOneLineView(baseviews.OneLineView):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
91
92
93
    """short view usable in the context of the email sender/recipient (in which
    case the caller should specify its context eid) or outside any context
    """
Sylvain Thénault's avatar
Sylvain Thénault committed
94
    __select__ = is_instance('Email')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
95
    title = _('oneline')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
96

Nicolas Chauvat's avatar
Nicolas Chauvat committed
97
    def cell_call(self, row, col, contexteid=None):
98
        entity = self.cw_rset.get_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
99
        self.w(u'<div class="email">')
100
        self.w(u'<i>%s&nbsp;%s</i> ' % (
101
            self._cw._('email_date'), self._cw.format_date(entity.date, time=True)))
102
103
        sender = entity.senderaddr
        if sender is None or contexteid != sender.eid:
Nicolas Chauvat's avatar
Nicolas Chauvat committed
104
            self.w(u'<b>%s</b>&nbsp;%s '
105
                   % (self._cw._('email_from'), formated_sender(entity)))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
106
107
108
        if contexteid not in (r.eid for r in entity.recipients):
            recipients = ', '.join(r.view('oneline') for r in entity.recipients)
            self.w(u'<b>%s</b>&nbsp;%s'
109
                   % (self._cw._('email_to'), recipients))
110
        self.w(u'<br/>\n<a href="%s">%s</a>' % (
111
            xml_escape(entity.absolute_url()), xml_escape(entity.subject)))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
112
113
        self.w(u'</div>')

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
114

Nicolas Chauvat's avatar
Nicolas Chauvat committed
115
116
class EmailOutOfContextView(EmailOneLineView):
    """short view outside the context of the email"""
Sylvain Thénault's avatar
Sylvain Thénault committed
117
    __regid__ = 'outofcontext'
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
118
    title = _('out of context')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
119

120

Nicolas Chauvat's avatar
Nicolas Chauvat committed
121
122
class EmailInContextView(EmailOneLineView):
    """short view inside the context of the email"""
Sylvain Thénault's avatar
Sylvain Thénault committed
123
    __regid__ = 'incontext'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
124

Sylvain Thénault's avatar
Sylvain Thénault committed
125
126
127
class EmailTreeItemView(EmailOutOfContextView):
    __regid__ = 'treeitem'
    title = None
Nicolas Chauvat's avatar
Nicolas Chauvat committed
128
129
130

class EmailPartOutOfContextView(baseviews.OutOfContextView):
    """out of context an email part is redirecting to related email view"""
Sylvain Thénault's avatar
Sylvain Thénault committed
131
    __select__ = is_instance('EmailPart')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
132
    def cell_call(self, row, col):
133
        entity = self.cw_rset.get_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
134
        entity.reverse_parts[0].view('outofcontext', w=self.w)
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
135
136


137
138
class EmailThreadView(EntityView):
    __regid__ = 'emailthread'
Sylvain Thénault's avatar
Sylvain Thénault committed
139
    __select__ = is_instance('EmailThread')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
140

141
    def entity_call(self, entity):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
142
143
144
145
146
147
148
149
150
151
152
153
        # get top level emails in this thread (ie message which are not a reply
        # of a message in this thread)
        #
        # Warn: adding Y in_thread E changes the meaning of the query since it joins
        # with messages which are not a direct reply (eg if A reply_to B, B reply_to C
        # B is also retreived since it's not a reply of C
        #
        # XXX  union with
        #   DISTINCT Any X,D ORDERBY D WHERE X date D, X in_thread E, X reply_to Y,
        #   NOT Y in_thread E, E eid %(x)s'
        # to get message which are a reply of a message in another thread ?
        # we may get duplicates in this case
154
        rset =  self._cw.execute('DISTINCT Any X,D ORDERBY D '
Nicolas Chauvat's avatar
Nicolas Chauvat committed
155
156
                                 'WHERE X date D, X in_thread E, '
                                 'NOT X reply_to Y, E eid %(x)s',
Sylvain Thénault's avatar
Sylvain Thénault committed
157
                                 {'x': entity.eid})
Nicolas Chauvat's avatar
Nicolas Chauvat committed
158
159
160
161
        if rset:
            self.w(u'<ul>')
            self.w(u'\n'.join(email.view('tree') for email in rset.entities()))
            self.w(u'</ul>')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
162
163


164
165
166
167
168
169
170
171
class EmailThreadPrimaryView(primary.PrimaryView):
    __select__ = is_instance('EmailThread')

    def cell_call(self, row, col):
        entity = self.cw_rset.complete_entity(row, col)
        self.w(u'<h1>%s</h1>' % xml_escape(entity.title))
        entity.view('emailthread', w=self.w)