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