##// END OF EJS Templates
notifications: use a set initially instead of list later converted to set.
marcink -
r3273:97a79574 default
parent child Browse files
Show More
@@ -1,386 +1,385 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 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 recipients_objs = []
98 recipients_objs = set()
99 99 for u in recipients:
100 100 obj = self._get_user(u)
101 101 if obj:
102 recipients_objs.append(obj)
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 recipients_objs = set(recipients_objs)
107 106 if not recipients_objs:
108 107 raise Exception('no valid recipients specified')
109 108
110 109 log.debug('sending notifications %s to %s',
111 110 notification_type, recipients_objs)
112 111
113 112 # add mentioned users into recipients
114 113 final_recipients = set(recipients_objs).union(mention_recipients)
115 114 notification = Notification.create(
116 115 created_by=created_by_obj, subject=notification_subject,
117 116 body=notification_body, recipients=final_recipients,
118 117 type_=notification_type
119 118 )
120 119
121 120 if not with_email: # skip sending email, and just create notification
122 121 return notification
123 122
124 123 # don't send email to person who created this comment
125 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
124 rec_objs = set(recipients_objs).difference({created_by_obj})
126 125
127 126 # now notify all recipients in question
128 127
129 128 for recipient in rec_objs.union(mention_recipients):
130 129 # inject current recipient
131 130 email_kwargs['recipient'] = recipient
132 131 email_kwargs['mention'] = recipient in mention_recipients
133 132 (subject, headers, email_body,
134 133 email_body_plaintext) = EmailNotificationModel().render_email(
135 134 notification_type, **email_kwargs)
136 135
137 136 log.debug(
138 137 'Creating notification email task for user:`%s`', recipient)
139 138 task = run_task(
140 139 tasks.send_email, recipient.email, subject,
141 140 email_body_plaintext, email_body)
142 141 log.debug('Created email task: %s', task)
143 142
144 143 return notification
145 144
146 145 def delete(self, user, notification):
147 146 # we don't want to remove actual notification just the assignment
148 147 try:
149 148 notification = self.__get_notification(notification)
150 149 user = self._get_user(user)
151 150 if notification and user:
152 151 obj = UserNotification.query()\
153 152 .filter(UserNotification.user == user)\
154 153 .filter(UserNotification.notification == notification)\
155 154 .one()
156 155 Session().delete(obj)
157 156 return True
158 157 except Exception:
159 158 log.error(traceback.format_exc())
160 159 raise
161 160
162 161 def get_for_user(self, user, filter_=None):
163 162 """
164 163 Get mentions for given user, filter them if filter dict is given
165 164 """
166 165 user = self._get_user(user)
167 166
168 167 q = UserNotification.query()\
169 168 .filter(UserNotification.user == user)\
170 169 .join((
171 170 Notification, UserNotification.notification_id ==
172 171 Notification.notification_id))
173 172 if filter_ == ['all']:
174 173 q = q # no filter
175 174 elif filter_ == ['unread']:
176 175 q = q.filter(UserNotification.read == false())
177 176 elif filter_:
178 177 q = q.filter(Notification.type_.in_(filter_))
179 178
180 179 return q
181 180
182 181 def mark_read(self, user, notification):
183 182 try:
184 183 notification = self.__get_notification(notification)
185 184 user = self._get_user(user)
186 185 if notification and user:
187 186 obj = UserNotification.query()\
188 187 .filter(UserNotification.user == user)\
189 188 .filter(UserNotification.notification == notification)\
190 189 .one()
191 190 obj.read = True
192 191 Session().add(obj)
193 192 return True
194 193 except Exception:
195 194 log.error(traceback.format_exc())
196 195 raise
197 196
198 197 def mark_all_read_for_user(self, user, filter_=None):
199 198 user = self._get_user(user)
200 199 q = UserNotification.query()\
201 200 .filter(UserNotification.user == user)\
202 201 .filter(UserNotification.read == false())\
203 202 .join((
204 203 Notification, UserNotification.notification_id ==
205 204 Notification.notification_id))
206 205 if filter_ == ['unread']:
207 206 q = q.filter(UserNotification.read == false())
208 207 elif filter_:
209 208 q = q.filter(Notification.type_.in_(filter_))
210 209
211 210 # this is a little inefficient but sqlalchemy doesn't support
212 211 # update on joined tables :(
213 212 for obj in q.all():
214 213 obj.read = True
215 214 Session().add(obj)
216 215
217 216 def get_unread_cnt_for_user(self, user):
218 217 user = self._get_user(user)
219 218 return UserNotification.query()\
220 219 .filter(UserNotification.read == false())\
221 220 .filter(UserNotification.user == user).count()
222 221
223 222 def get_unread_for_user(self, user):
224 223 user = self._get_user(user)
225 224 return [x.notification for x in UserNotification.query()
226 225 .filter(UserNotification.read == false())
227 226 .filter(UserNotification.user == user).all()]
228 227
229 228 def get_user_notification(self, user, notification):
230 229 user = self._get_user(user)
231 230 notification = self.__get_notification(notification)
232 231
233 232 return UserNotification.query()\
234 233 .filter(UserNotification.notification == notification)\
235 234 .filter(UserNotification.user == user).scalar()
236 235
237 236 def make_description(self, notification, translate, show_age=True):
238 237 """
239 238 Creates a human readable description based on properties
240 239 of notification object
241 240 """
242 241 _ = translate
243 242 _map = {
244 243 notification.TYPE_CHANGESET_COMMENT: [
245 244 _('%(user)s commented on commit %(date_or_age)s'),
246 245 _('%(user)s commented on commit at %(date_or_age)s'),
247 246 ],
248 247 notification.TYPE_MESSAGE: [
249 248 _('%(user)s sent message %(date_or_age)s'),
250 249 _('%(user)s sent message at %(date_or_age)s'),
251 250 ],
252 251 notification.TYPE_MENTION: [
253 252 _('%(user)s mentioned you %(date_or_age)s'),
254 253 _('%(user)s mentioned you at %(date_or_age)s'),
255 254 ],
256 255 notification.TYPE_REGISTRATION: [
257 256 _('%(user)s registered in RhodeCode %(date_or_age)s'),
258 257 _('%(user)s registered in RhodeCode at %(date_or_age)s'),
259 258 ],
260 259 notification.TYPE_PULL_REQUEST: [
261 260 _('%(user)s opened new pull request %(date_or_age)s'),
262 261 _('%(user)s opened new pull request at %(date_or_age)s'),
263 262 ],
264 263 notification.TYPE_PULL_REQUEST_COMMENT: [
265 264 _('%(user)s commented on pull request %(date_or_age)s'),
266 265 _('%(user)s commented on pull request at %(date_or_age)s'),
267 266 ],
268 267 }
269 268
270 269 templates = _map[notification.type_]
271 270
272 271 if show_age:
273 272 template = templates[0]
274 273 date_or_age = h.age(notification.created_on)
275 274 if translate:
276 275 date_or_age = translate(date_or_age)
277 276
278 277 if isinstance(date_or_age, TranslationString):
279 278 date_or_age = date_or_age.interpolate()
280 279
281 280 else:
282 281 template = templates[1]
283 282 date_or_age = h.format_date(notification.created_on)
284 283
285 284 return template % {
286 285 'user': notification.created_by_user.username,
287 286 'date_or_age': date_or_age,
288 287 }
289 288
290 289
291 290 class EmailNotificationModel(BaseModel):
292 291 TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT
293 292 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
294 293 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
295 294 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
296 295 TYPE_MAIN = Notification.TYPE_MESSAGE
297 296
298 297 TYPE_PASSWORD_RESET = 'password_reset'
299 298 TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation'
300 299 TYPE_EMAIL_TEST = 'email_test'
301 300 TYPE_TEST = 'test'
302 301
303 302 email_types = {
304 303 TYPE_MAIN:
305 304 'rhodecode:templates/email_templates/main.mako',
306 305 TYPE_TEST:
307 306 'rhodecode:templates/email_templates/test.mako',
308 307 TYPE_EMAIL_TEST:
309 308 'rhodecode:templates/email_templates/email_test.mako',
310 309 TYPE_REGISTRATION:
311 310 'rhodecode:templates/email_templates/user_registration.mako',
312 311 TYPE_PASSWORD_RESET:
313 312 'rhodecode:templates/email_templates/password_reset.mako',
314 313 TYPE_PASSWORD_RESET_CONFIRMATION:
315 314 'rhodecode:templates/email_templates/password_reset_confirmation.mako',
316 315 TYPE_COMMIT_COMMENT:
317 316 'rhodecode:templates/email_templates/commit_comment.mako',
318 317 TYPE_PULL_REQUEST:
319 318 'rhodecode:templates/email_templates/pull_request_review.mako',
320 319 TYPE_PULL_REQUEST_COMMENT:
321 320 'rhodecode:templates/email_templates/pull_request_comment.mako',
322 321 }
323 322
324 323 def __init__(self):
325 324 """
326 325 Example usage::
327 326
328 327 (subject, headers, email_body,
329 328 email_body_plaintext) = EmailNotificationModel().render_email(
330 329 EmailNotificationModel.TYPE_TEST, **email_kwargs)
331 330
332 331 """
333 332 super(EmailNotificationModel, self).__init__()
334 333 self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title')
335 334
336 335 def _update_kwargs_for_render(self, kwargs):
337 336 """
338 337 Inject params required for Mako rendering
339 338
340 339 :param kwargs:
341 340 """
342 341
343 342 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
344 343 instance_url = h.route_url('home')
345 344 _kwargs = {
346 345 'instance_url': instance_url,
347 346 'whitespace_filter': self.whitespace_filter
348 347 }
349 348 _kwargs.update(kwargs)
350 349 return _kwargs
351 350
352 351 def whitespace_filter(self, text):
353 352 return text.replace('\n', '').replace('\t', '')
354 353
355 354 def get_renderer(self, type_, request):
356 355 template_name = self.email_types[type_]
357 356 return request.get_partial_renderer(template_name)
358 357
359 358 def render_email(self, type_, **kwargs):
360 359 """
361 360 renders template for email, and returns a tuple of
362 361 (subject, email_headers, email_html_body, email_plaintext_body)
363 362 """
364 363 # translator and helpers inject
365 364 _kwargs = self._update_kwargs_for_render(kwargs)
366 365 request = get_current_request()
367 366 email_template = self.get_renderer(type_, request=request)
368 367
369 368 subject = email_template.render('subject', **_kwargs)
370 369
371 370 try:
372 371 headers = email_template.render('headers', **_kwargs)
373 372 except AttributeError:
374 373 # it's not defined in template, ok we can skip it
375 374 headers = ''
376 375
377 376 try:
378 377 body_plaintext = email_template.render('body_plaintext', **_kwargs)
379 378 except AttributeError:
380 379 # it's not defined in template, ok we can skip it
381 380 body_plaintext = ''
382 381
383 382 # render WHOLE template
384 383 body = email_template.render(None, **_kwargs)
385 384
386 385 return subject, headers, body, body_plaintext
General Comments 0
You need to be logged in to leave comments. Login now