##// END OF EJS Templates
Don't send emails to person who comment on changeset
marcink -
r2387:7d517a35 beta
parent child Browse files
Show More
@@ -1,153 +1,154 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.comment
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 comments model for RhodeCode
7 7
8 8 :created_on: Nov 11, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from pylons.i18n.translation import _
30 30 from sqlalchemy.util.compat import defaultdict
31 31
32 32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
36 36 from rhodecode.model.notification import NotificationModel
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class ChangesetCommentsModel(BaseModel):
42 42
43 43 def __get_changeset_comment(self, changeset_comment):
44 44 return self._get_instance(ChangesetComment, changeset_comment)
45 45
46 46 def _extract_mentions(self, s):
47 47 user_objects = []
48 48 for username in extract_mentioned_users(s):
49 49 user_obj = User.get_by_username(username, case_insensitive=True)
50 50 if user_obj:
51 51 user_objects.append(user_obj)
52 52 return user_objects
53 53
54 54 def create(self, text, repo_id, user_id, revision, f_path=None,
55 55 line_no=None):
56 56 """
57 57 Creates new comment for changeset
58 58
59 59 :param text:
60 60 :param repo_id:
61 61 :param user_id:
62 62 :param revision:
63 63 :param f_path:
64 64 :param line_no:
65 65 """
66 66
67 67 if text:
68 68 repo = Repository.get(repo_id)
69 69 cs = repo.scm_instance.get_changeset(revision)
70 70 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
71 71 author_email = cs.author_email
72 72 comment = ChangesetComment()
73 73 comment.repo = repo
74 74 comment.user_id = user_id
75 75 comment.revision = revision
76 76 comment.text = text
77 77 comment.f_path = f_path
78 78 comment.line_no = line_no
79 79
80 80 self.sa.add(comment)
81 81 self.sa.flush()
82 82 # make notification
83 83 line = ''
84 84 if line_no:
85 85 line = _('on line %s') % line_no
86 86 subj = safe_unicode(
87 87 h.link_to('Re commit: %(commit_desc)s %(line)s' % \
88 88 {'commit_desc': desc, 'line': line},
89 89 h.url('changeset_home', repo_name=repo.repo_name,
90 90 revision=revision,
91 91 anchor='comment-%s' % comment.comment_id,
92 92 qualified=True,
93 93 )
94 94 )
95 95 )
96 96
97 97 body = text
98 98
99 99 # get the current participants of this changeset
100 100 recipients = ChangesetComment.get_users(revision=revision)
101 101
102 102 # add changeset author if it's in rhodecode system
103 103 recipients += [User.get_by_email(author_email)]
104 104
105 # create notification objects, and emails
105 106 NotificationModel().create(
106 107 created_by=user_id, subject=subj, body=body,
107 108 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
108 109 )
109 110
110 111 mention_recipients = set(self._extract_mentions(body))\
111 112 .difference(recipients)
112 113 if mention_recipients:
113 114 subj = _('[Mention]') + ' ' + subj
114 115 NotificationModel().create(
115 116 created_by=user_id, subject=subj, body=body,
116 117 recipients=mention_recipients,
117 118 type_=Notification.TYPE_CHANGESET_COMMENT
118 119 )
119 120
120 121 return comment
121 122
122 123 def delete(self, comment):
123 124 """
124 125 Deletes given comment
125 126
126 127 :param comment_id:
127 128 """
128 129 comment = self.__get_changeset_comment(comment)
129 130 self.sa.delete(comment)
130 131
131 132 return comment
132 133
133 134 def get_comments(self, repo_id, revision):
134 135 return ChangesetComment.query()\
135 136 .filter(ChangesetComment.repo_id == repo_id)\
136 137 .filter(ChangesetComment.revision == revision)\
137 138 .filter(ChangesetComment.line_no == None)\
138 139 .filter(ChangesetComment.f_path == None).all()
139 140
140 141 def get_inline_comments(self, repo_id, revision):
141 142 comments = self.sa.query(ChangesetComment)\
142 143 .filter(ChangesetComment.repo_id == repo_id)\
143 144 .filter(ChangesetComment.revision == revision)\
144 145 .filter(ChangesetComment.line_no != None)\
145 146 .filter(ChangesetComment.f_path != None)\
146 147 .order_by(ChangesetComment.comment_id.asc())\
147 148 .all()
148 149
149 150 paths = defaultdict(lambda: defaultdict(list))
150 151
151 152 for co in comments:
152 153 paths[co.f_path][co.line_no].append(co)
153 154 return paths.items()
@@ -1,226 +1,229 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.notification
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Model for notifications
7 7
8 8
9 9 :created_on: Nov 20, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30 import datetime
31 31
32 32 from pylons.i18n.translation import _
33 33
34 34 import rhodecode
35 35 from rhodecode.config.conf import DATETIME_FORMAT
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.model import BaseModel
38 38 from rhodecode.model.db import Notification, User, UserNotification
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 class NotificationModel(BaseModel):
44 44
45 45 def __get_user(self, user):
46 46 return self._get_instance(User, user, callback=User.get_by_username)
47 47
48 48 def __get_notification(self, notification):
49 49 if isinstance(notification, Notification):
50 50 return notification
51 51 elif isinstance(notification, (int, long)):
52 52 return Notification.get(notification)
53 53 else:
54 54 if notification:
55 55 raise Exception('notification must be int, long or Instance'
56 56 ' of Notification got %s' % type(notification))
57 57
58 58 def create(self, created_by, subject, body, recipients=None,
59 59 type_=Notification.TYPE_MESSAGE, with_email=True,
60 60 email_kwargs={}):
61 61 """
62 62
63 63 Creates notification of given type
64 64
65 65 :param created_by: int, str or User instance. User who created this
66 66 notification
67 67 :param subject:
68 68 :param body:
69 69 :param recipients: list of int, str or User objects, when None
70 70 is given send to all admins
71 71 :param type_: type of notification
72 72 :param with_email: send email with this notification
73 73 :param email_kwargs: additional dict to pass as args to email template
74 74 """
75 75 from rhodecode.lib.celerylib import tasks, run_task
76 76
77 77 if recipients and not getattr(recipients, '__iter__', False):
78 78 raise Exception('recipients must be a list of iterable')
79 79
80 80 created_by_obj = self.__get_user(created_by)
81 81
82 82 if recipients:
83 83 recipients_objs = []
84 84 for u in recipients:
85 85 obj = self.__get_user(u)
86 86 if obj:
87 87 recipients_objs.append(obj)
88 88 recipients_objs = set(recipients_objs)
89 89 log.debug('sending notifications %s to %s' % (
90 90 type_, recipients_objs)
91 91 )
92 92 else:
93 93 # empty recipients means to all admins
94 94 recipients_objs = User.query().filter(User.admin == True).all()
95 95 log.debug('sending notifications %s to admins: %s' % (
96 96 type_, recipients_objs)
97 97 )
98 98 notif = Notification.create(
99 99 created_by=created_by_obj, subject=subject,
100 100 body=body, recipients=recipients_objs, type_=type_
101 101 )
102 102
103 103 if with_email is False:
104 104 return notif
105 105
106 # send email with notification
107 for rec in recipients_objs:
106 #don't send email to person who created this comment
107 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
108
109 # send email with notification to all other participants
110 for rec in rec_objs:
108 111 email_subject = NotificationModel().make_description(notif, False)
109 112 type_ = type_
110 113 email_body = body
111 114 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
112 115 kwargs.update(email_kwargs)
113 116 email_body_html = EmailNotificationModel()\
114 117 .get_email_tmpl(type_, **kwargs)
115 118
116 119 run_task(tasks.send_email, rec.email, email_subject, email_body,
117 120 email_body_html)
118 121
119 122 return notif
120 123
121 124 def delete(self, user, notification):
122 125 # we don't want to remove actual notification just the assignment
123 126 try:
124 127 notification = self.__get_notification(notification)
125 128 user = self.__get_user(user)
126 129 if notification and user:
127 130 obj = UserNotification.query()\
128 131 .filter(UserNotification.user == user)\
129 132 .filter(UserNotification.notification
130 133 == notification)\
131 134 .one()
132 135 self.sa.delete(obj)
133 136 return True
134 137 except Exception:
135 138 log.error(traceback.format_exc())
136 139 raise
137 140
138 141 def get_for_user(self, user):
139 142 user = self.__get_user(user)
140 143 return user.notifications
141 144
142 145 def mark_all_read_for_user(self, user):
143 146 user = self.__get_user(user)
144 147 UserNotification.query()\
145 148 .filter(UserNotification.read==False)\
146 149 .update({'read': True})
147 150
148 151 def get_unread_cnt_for_user(self, user):
149 152 user = self.__get_user(user)
150 153 return UserNotification.query()\
151 154 .filter(UserNotification.read == False)\
152 155 .filter(UserNotification.user == user).count()
153 156
154 157 def get_unread_for_user(self, user):
155 158 user = self.__get_user(user)
156 159 return [x.notification for x in UserNotification.query()\
157 160 .filter(UserNotification.read == False)\
158 161 .filter(UserNotification.user == user).all()]
159 162
160 163 def get_user_notification(self, user, notification):
161 164 user = self.__get_user(user)
162 165 notification = self.__get_notification(notification)
163 166
164 167 return UserNotification.query()\
165 168 .filter(UserNotification.notification == notification)\
166 169 .filter(UserNotification.user == user).scalar()
167 170
168 171 def make_description(self, notification, show_age=True):
169 172 """
170 173 Creates a human readable description based on properties
171 174 of notification object
172 175 """
173 176
174 177 _map = {
175 178 notification.TYPE_CHANGESET_COMMENT: _('commented on commit'),
176 179 notification.TYPE_MESSAGE: _('sent message'),
177 180 notification.TYPE_MENTION: _('mentioned you'),
178 181 notification.TYPE_REGISTRATION: _('registered in RhodeCode')
179 182 }
180 183
181 184 tmpl = "%(user)s %(action)s %(when)s"
182 185 if show_age:
183 186 when = h.age(notification.created_on)
184 187 else:
185 188 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
186 189 when = DTF(notification.created_on)
187 190
188 191 data = dict(
189 192 user=notification.created_by_user.username,
190 193 action=_map[notification.type_], when=when,
191 194 )
192 195 return tmpl % data
193 196
194 197
195 198 class EmailNotificationModel(BaseModel):
196 199
197 200 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
198 201 TYPE_PASSWORD_RESET = 'passoword_link'
199 202 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
200 203 TYPE_DEFAULT = 'default'
201 204
202 205 def __init__(self):
203 206 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
204 207 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
205 208
206 209 self.email_types = {
207 210 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
208 211 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
209 212 self.TYPE_REGISTRATION: 'email_templates/registration.html',
210 213 self.TYPE_DEFAULT: 'email_templates/default.html'
211 214 }
212 215
213 216 def get_email_tmpl(self, type_, **kwargs):
214 217 """
215 218 return generated template for email based on given type
216 219
217 220 :param type_:
218 221 """
219 222
220 223 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
221 224 email_template = self._tmpl_lookup.get_template(base)
222 225 # translator inject
223 226 _kwargs = {'_': _}
224 227 _kwargs.update(kwargs)
225 228 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
226 229 return email_template.render(**_kwargs)
General Comments 0
You need to be logged in to leave comments. Login now