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

:organization: Logilab
Nicolas Chauvat's avatar
Nicolas Chauvat committed
4
:copyright: 2003-2013 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
__docformat__ = "restructuredtext en"
8
9
10
11
12

try:
    from cubicweb import _
except ImportError:
    _ = unicode
Nicolas Chauvat's avatar
Nicolas Chauvat committed
13

14
from logilab.mtconverter import xml_escape
Nicolas Chauvat's avatar
Nicolas Chauvat committed
15

16
from cubicweb.predicates import is_instance
Sylvain Thénault's avatar
Sylvain Thénault committed
17
from cubicweb.uilib import soup2xhtml
18
from cubicweb.view import EntityView
Julien Cristau's avatar
Julien Cristau committed
19
20
from cubicweb.web import formwidgets
from cubicweb.web.views import baseviews, primary, uicfg
Nicolas Chauvat's avatar
Nicolas Chauvat committed
21

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

25
26
uicfg.autoform_field_kwargs.tag_attribute(('Email', 'subject'),
                                          {'widget': formwidgets.TextInput})
27

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
28
29
30
31
32
33
uicfg.actionbox_appearsin_addmenu.tag_subject_of(('Email', 'attachment', '*'),
                                                 True)

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

34
35
36
37
38
39
40

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
41
        return xml_escape(message.get('From', ''))
42
    return email._cw._('unknown sender')
43

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

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
47
    def render_entity_attributes(self, entity):
48
49
        self.w(u'<div class="emailheader"><table>')
        self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
50
               (self._cw._('From'), formated_sender(entity)))
51
        self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
52
               (self._cw._('To'), ', '.join(ea.view('oneline') for ea in entity.recipients)))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
53
        if entity.cc:
54
            self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
55
                   (self._cw._('CC'), ', '.join(ea.view('oneline') for ea in entity.cc)))
56
        self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
57
               (self._cw._('Date'), self._cw.format_date(entity.date, time=True)))
58
        self.w(u'<tr><td>%s</td><td>%s</td></tr>' %
59
               (self._cw._('Subject'), xml_escape(entity.subject)))
60
        self.w(u'</table></div><div class="emailcontent">')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
61
62
63
        for part in entity.parts_in_order():
            content, mime = part.content, part.content_format
            if mime == 'text/html':
64
                content = soup2xhtml(content, self._cw.encoding)
Aurelien Campeas's avatar
Aurelien Campeas committed
65
66
            elif 'pgp-signature' in mime:
                content = entity._cw_mtc_transform(content, mime, 'text/html', self._cw.encoding)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
67
            elif mime != 'text/xhtml':
68
                content = xml_escape(content)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
69
70
71
72
73
                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"/>')
74
        self.w(u'</div>')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
75

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


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

Nicolas Chauvat's avatar
Nicolas Chauvat committed
89
    def cell_call(self, row, col):
90
        entity = self.cw_rset.get_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
91
92
        self.w(entity.headers)

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
93
94

class EmailOneLineView(baseviews.OneLineView):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
95
96
97
    """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
98
    __select__ = is_instance('Email')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
99
    title = _('oneline')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
100

Nicolas Chauvat's avatar
Nicolas Chauvat committed
101
    def cell_call(self, row, col, contexteid=None):
102
        entity = self.cw_rset.get_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
103
        self.w(u'<div class="email">')
104
        self.w(u'<i>%s&nbsp;%s</i> ' % (
105
            self._cw._('email_date'), self._cw.format_date(entity.date, time=True)))
106
107
        sender = entity.senderaddr
        if sender is None or contexteid != sender.eid:
Nicolas Chauvat's avatar
Nicolas Chauvat committed
108
            self.w(u'<b>%s</b>&nbsp;%s '
109
                   % (self._cw._('email_from'), formated_sender(entity)))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
110
111
112
        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'
113
                   % (self._cw._('email_to'), recipients))
114
        self.w(u'<br/>\n<a href="%s">%s</a>' % (
115
            xml_escape(entity.absolute_url()), xml_escape(entity.subject)))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
116
117
        self.w(u'</div>')

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

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

124

Nicolas Chauvat's avatar
Nicolas Chauvat committed
125
126
class EmailInContextView(EmailOneLineView):
    """short view inside the context of the email"""
Sylvain Thénault's avatar
Sylvain Thénault committed
127
    __regid__ = 'incontext'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
128

Sylvain Thénault's avatar
Sylvain Thénault committed
129
130
131
class EmailTreeItemView(EmailOutOfContextView):
    __regid__ = 'treeitem'
    title = None
Nicolas Chauvat's avatar
Nicolas Chauvat committed
132
133
134

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
135
    __select__ = is_instance('EmailPart')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
136
    def cell_call(self, row, col):
137
        entity = self.cw_rset.get_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
138
        entity.reverse_parts[0].view('outofcontext', w=self.w)
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
139
140


141
142
class EmailThreadView(EntityView):
    __regid__ = 'emailthread'
Sylvain Thénault's avatar
Sylvain Thénault committed
143
    __select__ = is_instance('EmailThread')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
144

145
    def entity_call(self, entity):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
146
147
148
149
150
151
152
153
154
155
156
157
        # 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
158
        rset =  self._cw.execute('DISTINCT Any X,D ORDERBY D '
Nicolas Chauvat's avatar
Nicolas Chauvat committed
159
160
                                 '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
161
                                 {'x': entity.eid})
Nicolas Chauvat's avatar
Nicolas Chauvat committed
162
163
164
165
        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
166
167


168
169
170
171
172
173
174
175
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)