sobjects/notification.py
branchstable
changeset 9013 b4bcabf55e77
parent 9006 e4ea8f9ffa11
child 9305 f7a738afc295
equal deleted inserted replaced
9012:2cf127d4f5fd 9013:b4bcabf55e77
    24 
    24 
    25 from logilab.common.textutils import normalize_text
    25 from logilab.common.textutils import normalize_text
    26 from logilab.common.deprecation import class_renamed, class_moved, deprecated
    26 from logilab.common.deprecation import class_renamed, class_moved, deprecated
    27 from logilab.common.registry import yes
    27 from logilab.common.registry import yes
    28 
    28 
    29 from cubicweb.view import Component
    29 from cubicweb.entity import Entity
    30 from cubicweb.mail import NotificationView as BaseNotificationView, SkipEmail
    30 from cubicweb.view import Component, EntityView
    31 from cubicweb.server.hook import SendMailOp
    31 from cubicweb.server.hook import SendMailOp
       
    32 from cubicweb.mail import construct_message_id, format_mail
       
    33 from cubicweb.server.session import Session
    32 
    34 
    33 
    35 
    34 class RecipientsFinder(Component):
    36 class RecipientsFinder(Component):
    35     """this component is responsible to find recipients of a notification
    37     """this component is responsible to find recipients of a notification
    36 
    38 
    57         return dests
    59         return dests
    58 
    60 
    59 
    61 
    60 # abstract or deactivated notification views and mixin ########################
    62 # abstract or deactivated notification views and mixin ########################
    61 
    63 
    62 class NotificationView(BaseNotificationView):
    64 
    63     """overriden to delay actual sending of mails to a commit operation by
    65 class SkipEmail(Exception):
    64     default
    66     """raise this if you decide to skip an email during its generation"""
       
    67 
       
    68 
       
    69 class NotificationView(EntityView):
       
    70     """abstract view implementing the "email" API (eg to simplify sending
       
    71     notification)
    65     """
    72     """
       
    73     # XXX refactor this class to work with len(rset) > 1
       
    74 
       
    75     msgid_timestamp = True
       
    76 
       
    77     # to be defined on concrete sub-classes
       
    78     content = None # body of the mail
       
    79     message = None # action verb of the subject
       
    80 
       
    81     # this is usually the method to call
       
    82     def render_and_send(self, **kwargs):
       
    83         """generate and send an email message for this view"""
       
    84         delayed = kwargs.pop('delay_to_commit', None)
       
    85         for recipients, msg in self.render_emails(**kwargs):
       
    86             if delayed is None:
       
    87                 self.send(recipients, msg)
       
    88             elif delayed:
       
    89                 self.send_on_commit(recipients, msg)
       
    90             else:
       
    91                 self.send_now(recipients, msg)
       
    92 
       
    93     def cell_call(self, row, col=0, **kwargs):
       
    94         self.w(self._cw._(self.content) % self.context(**kwargs))
       
    95 
       
    96     def render_emails(self, **kwargs):
       
    97         """generate and send emails for this view (one per recipient)"""
       
    98         self._kwargs = kwargs
       
    99         recipients = self.recipients()
       
   100         if not recipients:
       
   101             self.info('skipping %s notification, no recipients', self.__regid__)
       
   102             return
       
   103         if self.cw_rset is not None:
       
   104             entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
       
   105             # if the view is using timestamp in message ids, no way to reference
       
   106             # previous email
       
   107             if not self.msgid_timestamp:
       
   108                 refs = [self.construct_message_id(eid)
       
   109                         for eid in entity.cw_adapt_to('INotifiable').notification_references(self)]
       
   110             else:
       
   111                 refs = ()
       
   112             msgid = self.construct_message_id(entity.eid)
       
   113         else:
       
   114             refs = ()
       
   115             msgid = None
       
   116         req = self._cw
       
   117         self.user_data = req.user_data()
       
   118         origlang = req.lang
       
   119         for something in recipients:
       
   120             if isinstance(something, Entity):
       
   121                 # hi-jack self._cw to get a session for the returned user
       
   122                 self._cw = Session(something, self._cw.repo)
       
   123                 self._cw.set_cnxset()
       
   124                 emailaddr = something.cw_adapt_to('IEmailable').get_email()
       
   125             else:
       
   126                 emailaddr, lang = something
       
   127                 self._cw.set_language(lang)
       
   128             # since the same view (eg self) may be called multiple time and we
       
   129             # need a fresh stream at each iteration, reset it explicitly
       
   130             self.w = None
       
   131             # XXX call render before subject to set .row/.col attributes on the
       
   132             #     view
       
   133             try:
       
   134                 content = self.render(row=0, col=0, **kwargs)
       
   135                 subject = self.subject()
       
   136             except SkipEmail:
       
   137                 continue
       
   138             except Exception as ex:
       
   139                 # shouldn't make the whole transaction fail because of rendering
       
   140                 # error (unauthorized or such) XXX check it doesn't actually
       
   141                 # occurs due to rollback on such error
       
   142                 self.exception(str(ex))
       
   143                 continue
       
   144             msg = format_mail(self.user_data, [emailaddr], content, subject,
       
   145                               config=self._cw.vreg.config, msgid=msgid, references=refs)
       
   146             yield [emailaddr], msg
       
   147             if isinstance(something, Entity):
       
   148                 self._cw.commit()
       
   149                 self._cw.close()
       
   150                 self._cw = req
       
   151         # restore language
       
   152         req.set_language(origlang)
       
   153 
       
   154     # recipients / email sending ###############################################
       
   155 
       
   156     def recipients(self):
       
   157         """return a list of either 2-uple (email, language) or user entity to
       
   158         who this email should be sent
       
   159         """
       
   160         finder = self._cw.vreg['components'].select(
       
   161             'recipients_finder', self._cw, rset=self.cw_rset,
       
   162             row=self.cw_row or 0, col=self.cw_col or 0)
       
   163         return finder.recipients()
       
   164 
       
   165     def send_now(self, recipients, msg):
       
   166         self._cw.vreg.config.sendmails([(msg, recipients)])
       
   167 
    66     def send_on_commit(self, recipients, msg):
   168     def send_on_commit(self, recipients, msg):
    67         SendMailOp(self._cw, recipients=recipients, msg=msg)
   169         SendMailOp(self._cw, recipients=recipients, msg=msg)
    68     send = send_on_commit
   170     send = send_on_commit
       
   171 
       
   172     # email generation helpers #################################################
       
   173 
       
   174     def construct_message_id(self, eid):
       
   175         return construct_message_id(self._cw.vreg.config.appid, eid,
       
   176                                     self.msgid_timestamp)
       
   177 
       
   178     def format_field(self, attr, value):
       
   179         return ':%(attr)s: %(value)s' % {'attr': attr, 'value': value}
       
   180 
       
   181     def format_section(self, attr, value):
       
   182         return '%(attr)s\n%(ul)s\n%(value)s\n' % {
       
   183             'attr': attr, 'ul': '-'*len(attr), 'value': value}
       
   184 
       
   185     def subject(self):
       
   186         entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
       
   187         subject = self._cw._(self.message)
       
   188         etype = entity.dc_type()
       
   189         eid = entity.eid
       
   190         login = self.user_data['login']
       
   191         return self._cw._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
       
   192 
       
   193     def context(self, **kwargs):
       
   194         entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
       
   195         for key, val in kwargs.iteritems():
       
   196             if val and isinstance(val, unicode) and val.strip():
       
   197                kwargs[key] = self._cw._(val)
       
   198         kwargs.update({'user': self.user_data['login'],
       
   199                        'eid': entity.eid,
       
   200                        'etype': entity.dc_type(),
       
   201                        'url': entity.absolute_url(),
       
   202                        'title': entity.dc_long_title(),})
       
   203         return kwargs
    69 
   204 
    70 
   205 
    71 class StatusChangeMixIn(object):
   206 class StatusChangeMixIn(object):
    72     __regid__ = 'notif_status_change'
   207     __regid__ = 'notif_status_change'
    73     msgid_timestamp = True
   208     msgid_timestamp = True
   155 %(changes)s
   290 %(changes)s
   156 
   291 
   157 url: %(url)s
   292 url: %(url)s
   158 """
   293 """
   159 
   294 
   160     def context(self, **kwargs):
   295     def context(self, changes=(), **kwargs):
   161         context = super(EntityUpdatedNotificationView, self).context(**kwargs)
   296         context = super(EntityUpdatedNotificationView, self).context(**kwargs)
   162         changes = self._cw.transaction_data['changes'][self.cw_rset[0][0]]
       
   163         _ = self._cw._
   297         _ = self._cw._
   164         formatted_changes = []
   298         formatted_changes = []
   165         entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
   299         entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
   166         for attr, oldvalue, newvalue in sorted(changes):
   300         for attr, oldvalue, newvalue in sorted(changes):
   167             # check current user has permission to see the attribute
   301             # check current user has permission to see the attribute
   193 
   327 
   194     def subject(self):
   328     def subject(self):
   195         entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
   329         entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
   196         return  u'%s #%s (%s)' % (self._cw.__('Updated %s' % entity.e_schema),
   330         return  u'%s #%s (%s)' % (self._cw.__('Updated %s' % entity.e_schema),
   197                                   entity.eid, self.user_data['login'])
   331                                   entity.eid, self.user_data['login'])
   198 
       
   199 
       
   200 from cubicweb.hooks.notification import RenderAndSendNotificationView
       
   201 from cubicweb.mail import parse_message_id
       
   202 
       
   203 NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
       
   204 RenderAndSendNotificationView = class_moved(RenderAndSendNotificationView)
       
   205 parse_message_id = deprecated('parse_message_id is now defined in cubicweb.mail')(parse_message_id)
       
   206