##// END OF EJS Templates
notifications: added update PR case.
marcink -
r4137:754144f0 default
parent child Browse files
Show More
@@ -1,390 +1,394 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Model for notifications
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from pyramid.threadlocal import get_current_request
30 30 from sqlalchemy.sql.expression import false, true
31 31
32 32 import rhodecode
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import Notification, User, UserNotification
36 36 from rhodecode.model.meta import Session
37 37 from rhodecode.translation import TranslationString
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class NotificationModel(BaseModel):
43 43
44 44 cls = Notification
45 45
46 46 def __get_notification(self, notification):
47 47 if isinstance(notification, Notification):
48 48 return notification
49 49 elif isinstance(notification, (int, long)):
50 50 return Notification.get(notification)
51 51 else:
52 52 if notification:
53 53 raise Exception('notification must be int, long or Instance'
54 54 ' of Notification got %s' % type(notification))
55 55
56 56 def create(
57 57 self, created_by, notification_subject, notification_body,
58 58 notification_type=Notification.TYPE_MESSAGE, recipients=None,
59 59 mention_recipients=None, with_email=True, email_kwargs=None):
60 60 """
61 61
62 62 Creates notification of given type
63 63
64 64 :param created_by: int, str or User instance. User who created this
65 65 notification
66 66 :param notification_subject: subject of notification itself
67 67 :param notification_body: body of notification text
68 68 :param notification_type: type of notification, based on that we
69 69 pick templates
70 70
71 71 :param recipients: list of int, str or User objects, when None
72 72 is given send to all admins
73 73 :param mention_recipients: list of int, str or User objects,
74 74 that were mentioned
75 75 :param with_email: send email with this notification
76 76 :param email_kwargs: dict with arguments to generate email
77 77 """
78 78
79 79 from rhodecode.lib.celerylib import tasks, run_task
80 80
81 81 if recipients and not getattr(recipients, '__iter__', False):
82 82 raise Exception('recipients must be an iterable object')
83 83
84 84 created_by_obj = self._get_user(created_by)
85 85 # default MAIN body if not given
86 86 email_kwargs = email_kwargs or {'body': notification_body}
87 87 mention_recipients = mention_recipients or set()
88 88
89 89 if not created_by_obj:
90 90 raise Exception('unknown user %s' % created_by)
91 91
92 92 if recipients is None:
93 93 # recipients is None 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 notification_type, recipients_objs)
97 97 else:
98 98 recipients_objs = set()
99 99 for u in recipients:
100 100 obj = self._get_user(u)
101 101 if obj:
102 102 recipients_objs.add(obj)
103 103 else: # we didn't find this user, log the error and carry on
104 104 log.error('cannot notify unknown user %r', u)
105 105
106 106 if not recipients_objs:
107 107 raise Exception('no valid recipients specified')
108 108
109 109 log.debug('sending notifications %s to %s',
110 110 notification_type, recipients_objs)
111 111
112 112 # add mentioned users into recipients
113 113 final_recipients = set(recipients_objs).union(mention_recipients)
114 114
115 115 notification = Notification.create(
116 116 created_by=created_by_obj, subject=notification_subject,
117 117 body=notification_body, recipients=final_recipients,
118 118 type_=notification_type
119 119 )
120 120
121 121 if not with_email: # skip sending email, and just create notification
122 122 return notification
123 123
124 124 # don't send email to person who created this comment
125 125 rec_objs = set(recipients_objs).difference({created_by_obj})
126 126
127 127 # now notify all recipients in question
128 128
129 129 for recipient in rec_objs.union(mention_recipients):
130 130 # inject current recipient
131 131 email_kwargs['recipient'] = recipient
132 132 email_kwargs['mention'] = recipient in mention_recipients
133 133 (subject, headers, email_body,
134 134 email_body_plaintext) = EmailNotificationModel().render_email(
135 135 notification_type, **email_kwargs)
136 136
137 137 log.debug(
138 138 'Creating notification email task for user:`%s`', recipient)
139 139 task = run_task(
140 140 tasks.send_email, recipient.email, subject,
141 141 email_body_plaintext, email_body)
142 142 log.debug('Created email task: %s', task)
143 143
144 144 return notification
145 145
146 146 def delete(self, user, notification):
147 147 # we don't want to remove actual notification just the assignment
148 148 try:
149 149 notification = self.__get_notification(notification)
150 150 user = self._get_user(user)
151 151 if notification and user:
152 152 obj = UserNotification.query()\
153 153 .filter(UserNotification.user == user)\
154 154 .filter(UserNotification.notification == notification)\
155 155 .one()
156 156 Session().delete(obj)
157 157 return True
158 158 except Exception:
159 159 log.error(traceback.format_exc())
160 160 raise
161 161
162 162 def get_for_user(self, user, filter_=None):
163 163 """
164 164 Get mentions for given user, filter them if filter dict is given
165 165 """
166 166 user = self._get_user(user)
167 167
168 168 q = UserNotification.query()\
169 169 .filter(UserNotification.user == user)\
170 170 .join((
171 171 Notification, UserNotification.notification_id ==
172 172 Notification.notification_id))
173 173 if filter_ == ['all']:
174 174 q = q # no filter
175 175 elif filter_ == ['unread']:
176 176 q = q.filter(UserNotification.read == false())
177 177 elif filter_:
178 178 q = q.filter(Notification.type_.in_(filter_))
179 179
180 180 return q
181 181
182 182 def mark_read(self, user, notification):
183 183 try:
184 184 notification = self.__get_notification(notification)
185 185 user = self._get_user(user)
186 186 if notification and user:
187 187 obj = UserNotification.query()\
188 188 .filter(UserNotification.user == user)\
189 189 .filter(UserNotification.notification == notification)\
190 190 .one()
191 191 obj.read = True
192 192 Session().add(obj)
193 193 return True
194 194 except Exception:
195 195 log.error(traceback.format_exc())
196 196 raise
197 197
198 198 def mark_all_read_for_user(self, user, filter_=None):
199 199 user = self._get_user(user)
200 200 q = UserNotification.query()\
201 201 .filter(UserNotification.user == user)\
202 202 .filter(UserNotification.read == false())\
203 203 .join((
204 204 Notification, UserNotification.notification_id ==
205 205 Notification.notification_id))
206 206 if filter_ == ['unread']:
207 207 q = q.filter(UserNotification.read == false())
208 208 elif filter_:
209 209 q = q.filter(Notification.type_.in_(filter_))
210 210
211 211 # this is a little inefficient but sqlalchemy doesn't support
212 212 # update on joined tables :(
213 213 for obj in q.all():
214 214 obj.read = True
215 215 Session().add(obj)
216 216
217 217 def get_unread_cnt_for_user(self, user):
218 218 user = self._get_user(user)
219 219 return UserNotification.query()\
220 220 .filter(UserNotification.read == false())\
221 221 .filter(UserNotification.user == user).count()
222 222
223 223 def get_unread_for_user(self, user):
224 224 user = self._get_user(user)
225 225 return [x.notification for x in UserNotification.query()
226 226 .filter(UserNotification.read == false())
227 227 .filter(UserNotification.user == user).all()]
228 228
229 229 def get_user_notification(self, user, notification):
230 230 user = self._get_user(user)
231 231 notification = self.__get_notification(notification)
232 232
233 233 return UserNotification.query()\
234 234 .filter(UserNotification.notification == notification)\
235 235 .filter(UserNotification.user == user).scalar()
236 236
237 237 def make_description(self, notification, translate, show_age=True):
238 238 """
239 239 Creates a human readable description based on properties
240 240 of notification object
241 241 """
242 242 _ = translate
243 243 _map = {
244 244 notification.TYPE_CHANGESET_COMMENT: [
245 245 _('%(user)s commented on commit %(date_or_age)s'),
246 246 _('%(user)s commented on commit at %(date_or_age)s'),
247 247 ],
248 248 notification.TYPE_MESSAGE: [
249 249 _('%(user)s sent message %(date_or_age)s'),
250 250 _('%(user)s sent message at %(date_or_age)s'),
251 251 ],
252 252 notification.TYPE_MENTION: [
253 253 _('%(user)s mentioned you %(date_or_age)s'),
254 254 _('%(user)s mentioned you at %(date_or_age)s'),
255 255 ],
256 256 notification.TYPE_REGISTRATION: [
257 257 _('%(user)s registered in RhodeCode %(date_or_age)s'),
258 258 _('%(user)s registered in RhodeCode at %(date_or_age)s'),
259 259 ],
260 260 notification.TYPE_PULL_REQUEST: [
261 261 _('%(user)s opened new pull request %(date_or_age)s'),
262 262 _('%(user)s opened new pull request at %(date_or_age)s'),
263 263 ],
264 notification.TYPE_PULL_REQUEST_UPDATE: [
265 _('%(user)s updated pull request %(date_or_age)s'),
266 _('%(user)s updated pull request at %(date_or_age)s'),
267 ],
264 268 notification.TYPE_PULL_REQUEST_COMMENT: [
265 269 _('%(user)s commented on pull request %(date_or_age)s'),
266 270 _('%(user)s commented on pull request at %(date_or_age)s'),
267 271 ],
268 272 }
269 273
270 274 templates = _map[notification.type_]
271 275
272 276 if show_age:
273 277 template = templates[0]
274 278 date_or_age = h.age(notification.created_on)
275 279 if translate:
276 280 date_or_age = translate(date_or_age)
277 281
278 282 if isinstance(date_or_age, TranslationString):
279 283 date_or_age = date_or_age.interpolate()
280 284
281 285 else:
282 286 template = templates[1]
283 287 date_or_age = h.format_date(notification.created_on)
284 288
285 289 return template % {
286 290 'user': notification.created_by_user.username,
287 291 'date_or_age': date_or_age,
288 292 }
289 293
290 294
291 295 class EmailNotificationModel(BaseModel):
292 296 TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT
293 297 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
294 298 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
295 299 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
296 300 TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE
297 301 TYPE_MAIN = Notification.TYPE_MESSAGE
298 302
299 303 TYPE_PASSWORD_RESET = 'password_reset'
300 304 TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation'
301 305 TYPE_EMAIL_TEST = 'email_test'
302 306 TYPE_TEST = 'test'
303 307
304 308 email_types = {
305 309 TYPE_MAIN:
306 310 'rhodecode:templates/email_templates/main.mako',
307 311 TYPE_TEST:
308 312 'rhodecode:templates/email_templates/test.mako',
309 313 TYPE_EMAIL_TEST:
310 314 'rhodecode:templates/email_templates/email_test.mako',
311 315 TYPE_REGISTRATION:
312 316 'rhodecode:templates/email_templates/user_registration.mako',
313 317 TYPE_PASSWORD_RESET:
314 318 'rhodecode:templates/email_templates/password_reset.mako',
315 319 TYPE_PASSWORD_RESET_CONFIRMATION:
316 320 'rhodecode:templates/email_templates/password_reset_confirmation.mako',
317 321 TYPE_COMMIT_COMMENT:
318 322 'rhodecode:templates/email_templates/commit_comment.mako',
319 323 TYPE_PULL_REQUEST:
320 324 'rhodecode:templates/email_templates/pull_request_review.mako',
321 325 TYPE_PULL_REQUEST_COMMENT:
322 326 'rhodecode:templates/email_templates/pull_request_comment.mako',
323 327 TYPE_PULL_REQUEST_UPDATE:
324 328 'rhodecode:templates/email_templates/pull_request_update.mako',
325 329 }
326 330
327 331 def __init__(self):
328 332 """
329 333 Example usage::
330 334
331 335 (subject, headers, email_body,
332 336 email_body_plaintext) = EmailNotificationModel().render_email(
333 337 EmailNotificationModel.TYPE_TEST, **email_kwargs)
334 338
335 339 """
336 340 super(EmailNotificationModel, self).__init__()
337 341 self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title')
338 342
339 343 def _update_kwargs_for_render(self, kwargs):
340 344 """
341 345 Inject params required for Mako rendering
342 346
343 347 :param kwargs:
344 348 """
345 349
346 350 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
347 351 kwargs['rhodecode_version'] = rhodecode.__version__
348 352 instance_url = h.route_url('home')
349 353 _kwargs = {
350 354 'instance_url': instance_url,
351 355 'whitespace_filter': self.whitespace_filter
352 356 }
353 357 _kwargs.update(kwargs)
354 358 return _kwargs
355 359
356 360 def whitespace_filter(self, text):
357 361 return text.replace('\n', '').replace('\t', '')
358 362
359 363 def get_renderer(self, type_, request):
360 364 template_name = self.email_types[type_]
361 365 return request.get_partial_renderer(template_name)
362 366
363 367 def render_email(self, type_, **kwargs):
364 368 """
365 369 renders template for email, and returns a tuple of
366 370 (subject, email_headers, email_html_body, email_plaintext_body)
367 371 """
368 372 # translator and helpers inject
369 373 _kwargs = self._update_kwargs_for_render(kwargs)
370 374 request = get_current_request()
371 375 email_template = self.get_renderer(type_, request=request)
372 376
373 377 subject = email_template.render('subject', **_kwargs)
374 378
375 379 try:
376 380 headers = email_template.render('headers', **_kwargs)
377 381 except AttributeError:
378 382 # it's not defined in template, ok we can skip it
379 383 headers = ''
380 384
381 385 try:
382 386 body_plaintext = email_template.render('body_plaintext', **_kwargs)
383 387 except AttributeError:
384 388 # it's not defined in template, ok we can skip it
385 389 body_plaintext = ''
386 390
387 391 # render WHOLE template
388 392 body = email_template.render(None, **_kwargs)
389 393
390 394 return subject, headers, body, body_plaintext
General Comments 0
You need to be logged in to leave comments. Login now