Changeset - 6bd262eaa058
[Not reviewed]
default
0 1 0
Thomas De Schampheleire - 7 years ago 2018-09-18 20:57:32
thomas.de_schampheleire@nokia.com
notification: don't repeat common actions for each email recipient

The emails sent upon PR creation or comments are identical for all
recipients, except for the 'To:' header added by the send_email method.
Calculation of subject, body, etc. can thus be moved outside of the
loop.

Move the manipulation of the recipient list down to where it is used, for
clarity.
1 file changed with 27 insertions and 27 deletions:
0 comments (0 inline, 0 general)
kallithea/model/notification.py
Show inline comments
 
@@ -5,226 +5,226 @@
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.model.notification
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Model for notifications
 

	
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Nov 20, 2011
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 

	
 
from tg import tmpl_context as c, app_globals
 
from tg.i18n import ugettext as _
 
from sqlalchemy.orm import joinedload, subqueryload
 

	
 
import kallithea
 
from kallithea.lib import helpers as h
 
from kallithea.lib.utils2 import safe_unicode
 
from kallithea.model.db import Notification, User, UserNotification
 
from kallithea.model.meta import Session
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class NotificationModel(object):
 

	
 
    def create(self, created_by, subject, body, recipients=None,
 
               type_=Notification.TYPE_MESSAGE, with_email=True,
 
               email_kwargs=None, repo_name=None):
 
        """
 

	
 
        Creates notification of given type
 

	
 
        :param created_by: int, str or User instance. User who created this
 
            notification
 
        :param subject:
 
        :param body:
 
        :param recipients: list of int, str or User objects, when None
 
            is given send to all admins
 
        :param type_: type of notification
 
        :param with_email: send email with this notification
 
        :param email_kwargs: additional dict to pass as args to email template
 
        """
 
        from kallithea.lib.celerylib import tasks
 
        email_kwargs = email_kwargs or {}
 
        if recipients and not getattr(recipients, '__iter__', False):
 
            raise Exception('recipients must be a list or iterable')
 

	
 
        created_by_obj = User.guess_instance(created_by)
 

	
 
        recipients_objs = []
 
        if recipients:
 
            for u in recipients:
 
                obj = User.guess_instance(u)
 
                if obj is not None:
 
                    recipients_objs.append(obj)
 
                else:
 
                    # TODO: inform user that requested operation couldn't be completed
 
                    log.error('cannot email unknown user %r', u)
 
            recipients_objs = set(recipients_objs)
 
            log.debug('sending notifications %s to %s',
 
                type_, recipients_objs
 
            )
 
        elif recipients is None:
 
            # empty recipients means to all admins
 
            recipients_objs = User.query().filter(User.admin == True).all()
 
            log.debug('sending notifications %s to admins: %s',
 
                type_, recipients_objs
 
            )
 
        #else: silently skip notification mails?
 

	
 
        # TODO: inform user who are notified
 
        notif = Notification.create(
 
            created_by=created_by_obj, subject=subject,
 
            body=body, recipients=recipients_objs, type_=type_
 
        )
 

	
 
        if not with_email:
 
            return notif
 

	
 
        # don't send email to person who created this comment
 
        rec_objs = set(recipients_objs).difference(set([created_by_obj]))
 

	
 
        headers = {}
 
        headers['X-Kallithea-Notification-Type'] = type_
 
        if 'threading' in email_kwargs:
 
            headers['References'] = ' '.join('<%s>' % x for x in email_kwargs['threading'])
 

	
 
        # this is passed into template
 
        html_kwargs = {
 
                  'subject': subject,
 
                  'body': h.render_w_mentions(body, repo_name),
 
                  'when': h.fmt_date(notif.created_on),
 
                  'user': notif.created_by_user.username,
 
                  }
 

	
 
        txt_kwargs = {
 
                  'subject': subject,
 
                  'body': body,
 
                  'when': h.fmt_date(notif.created_on),
 
                  'user': notif.created_by_user.username,
 
                  }
 

	
 
        html_kwargs.update(email_kwargs)
 
        txt_kwargs.update(email_kwargs)
 
        email_subject = EmailNotificationModel() \
 
                            .get_email_description(type_, **txt_kwargs)
 
        email_txt_body = EmailNotificationModel() \
 
                            .get_email_tmpl(type_, 'txt', **txt_kwargs)
 
        email_html_body = EmailNotificationModel() \
 
                            .get_email_tmpl(type_, 'html', **html_kwargs)
 

	
 
        # don't send email to person who created this comment
 
        rec_objs = set(recipients_objs).difference(set([created_by_obj]))
 

	
 
        # send email with notification to all other participants
 
        for rec in rec_objs:
 
            # this is passed into template
 
            html_kwargs = {
 
                      'subject': subject,
 
                      'body': h.render_w_mentions(body, repo_name),
 
                      'when': h.fmt_date(notif.created_on),
 
                      'user': notif.created_by_user.username,
 
                      }
 

	
 
            txt_kwargs = {
 
                      'subject': subject,
 
                      'body': body,
 
                      'when': h.fmt_date(notif.created_on),
 
                      'user': notif.created_by_user.username,
 
                      }
 

	
 
            html_kwargs.update(email_kwargs)
 
            txt_kwargs.update(email_kwargs)
 
            email_subject = EmailNotificationModel() \
 
                                .get_email_description(type_, **txt_kwargs)
 
            email_txt_body = EmailNotificationModel() \
 
                                .get_email_tmpl(type_, 'txt', **txt_kwargs)
 
            email_html_body = EmailNotificationModel() \
 
                                .get_email_tmpl(type_, 'html', **html_kwargs)
 

	
 
            tasks.send_email([rec.email], email_subject, email_txt_body,
 
                     email_html_body, headers, author=created_by_obj)
 

	
 
        return notif
 

	
 
    def delete(self, user, notification):
 
        # we don't want to remove actual notification just the assignment
 
        try:
 
            notification = Notification.guess_instance(notification)
 
            user = User.guess_instance(user)
 
            if notification and user:
 
                obj = UserNotification.query() \
 
                        .filter(UserNotification.user == user) \
 
                        .filter(UserNotification.notification
 
                                == notification) \
 
                        .one()
 
                Session().delete(obj)
 
                return True
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def query_for_user(self, user, filter_=None):
 
        """
 
        Get notifications for given user, filter them if filter dict is given
 

	
 
        :param user:
 
        :param filter:
 
        """
 
        user = User.guess_instance(user)
 

	
 
        q = UserNotification.query() \
 
            .filter(UserNotification.user == user) \
 
            .join((Notification, UserNotification.notification_id ==
 
                                 Notification.notification_id)) \
 
            .options(joinedload('notification')) \
 
            .options(subqueryload('notification.created_by_user')) \
 
            .order_by(Notification.created_on.desc())
 

	
 
        if filter_:
 
            q = q.filter(Notification.type_.in_(filter_))
 

	
 
        return q
 

	
 
    def mark_read(self, user, notification):
 
        try:
 
            notification = Notification.guess_instance(notification)
 
            user = User.guess_instance(user)
 
            if notification and user:
 
                obj = UserNotification.query() \
 
                        .filter(UserNotification.user == user) \
 
                        .filter(UserNotification.notification
 
                                == notification) \
 
                        .one()
 
                obj.read = True
 
                return True
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def mark_all_read_for_user(self, user, filter_=None):
 
        user = User.guess_instance(user)
 
        q = UserNotification.query() \
 
            .filter(UserNotification.user == user) \
 
            .filter(UserNotification.read == False) \
 
            .join((Notification, UserNotification.notification_id ==
 
                                 Notification.notification_id))
 
        if filter_:
 
            q = q.filter(Notification.type_.in_(filter_))
 

	
 
        # this is a little inefficient but sqlalchemy doesn't support
 
        # update on joined tables :(
 
        for obj in q:
 
            obj.read = True
 

	
 
    def get_unread_cnt_for_user(self, user):
 
        user = User.guess_instance(user)
 
        return UserNotification.query() \
 
                .filter(UserNotification.read == False) \
 
                .filter(UserNotification.user == user).count()
 

	
 
    def get_unread_for_user(self, user):
 
        user = User.guess_instance(user)
 
        return [x.notification for x in UserNotification.query() \
 
                .filter(UserNotification.read == False) \
 
                .filter(UserNotification.user == user).all()]
 

	
 
    def get_user_notification(self, user, notification):
 
        user = User.guess_instance(user)
 
        notification = Notification.guess_instance(notification)
 

	
 
        return UserNotification.query() \
 
            .filter(UserNotification.notification == notification) \
 
            .filter(UserNotification.user == user).scalar()
 

	
 
    def make_description(self, notification, show_age=True):
0 comments (0 inline, 0 general)