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