##// END OF EJS Templates
fixes #691: Notifications for pull requests: move link to pull request to the top...
marcink -
r3121:3274ba9f beta
parent child Browse files
Show More
@@ -1,277 +1,278 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.notification
3 rhodecode.model.notification
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Model for notifications
6 Model for notifications
7
7
8
8
9 :created_on: Nov 20, 2011
9 :created_on: Nov 20, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import Notification, User, UserNotification
36 from rhodecode.model.db import Notification, User, UserNotification
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class NotificationModel(BaseModel):
41 class NotificationModel(BaseModel):
42
42
43 cls = Notification
43 cls = Notification
44
44
45 def __get_notification(self, notification):
45 def __get_notification(self, notification):
46 if isinstance(notification, Notification):
46 if isinstance(notification, Notification):
47 return notification
47 return notification
48 elif isinstance(notification, (int, long)):
48 elif isinstance(notification, (int, long)):
49 return Notification.get(notification)
49 return Notification.get(notification)
50 else:
50 else:
51 if notification:
51 if notification:
52 raise Exception('notification must be int, long or Instance'
52 raise Exception('notification must be int, long or Instance'
53 ' of Notification got %s' % type(notification))
53 ' of Notification got %s' % type(notification))
54
54
55 def create(self, created_by, subject, body, recipients=None,
55 def create(self, created_by, subject, body, recipients=None,
56 type_=Notification.TYPE_MESSAGE, with_email=True,
56 type_=Notification.TYPE_MESSAGE, with_email=True,
57 email_kwargs={}):
57 email_kwargs={}):
58 """
58 """
59
59
60 Creates notification of given type
60 Creates notification of given type
61
61
62 :param created_by: int, str or User instance. User who created this
62 :param created_by: int, str or User instance. User who created this
63 notification
63 notification
64 :param subject:
64 :param subject:
65 :param body:
65 :param body:
66 :param recipients: list of int, str or User objects, when None
66 :param recipients: list of int, str or User objects, when None
67 is given send to all admins
67 is given send to all admins
68 :param type_: type of notification
68 :param type_: type of notification
69 :param with_email: send email with this notification
69 :param with_email: send email with this notification
70 :param email_kwargs: additional dict to pass as args to email template
70 :param email_kwargs: additional dict to pass as args to email template
71 """
71 """
72 from rhodecode.lib.celerylib import tasks, run_task
72 from rhodecode.lib.celerylib import tasks, run_task
73
73
74 if recipients and not getattr(recipients, '__iter__', False):
74 if recipients and not getattr(recipients, '__iter__', False):
75 raise Exception('recipients must be a list of iterable')
75 raise Exception('recipients must be a list of iterable')
76
76
77 created_by_obj = self._get_user(created_by)
77 created_by_obj = self._get_user(created_by)
78
78
79 if recipients:
79 if recipients:
80 recipients_objs = []
80 recipients_objs = []
81 for u in recipients:
81 for u in recipients:
82 obj = self._get_user(u)
82 obj = self._get_user(u)
83 if obj:
83 if obj:
84 recipients_objs.append(obj)
84 recipients_objs.append(obj)
85 recipients_objs = set(recipients_objs)
85 recipients_objs = set(recipients_objs)
86 log.debug('sending notifications %s to %s' % (
86 log.debug('sending notifications %s to %s' % (
87 type_, recipients_objs)
87 type_, recipients_objs)
88 )
88 )
89 else:
89 else:
90 # empty recipients means to all admins
90 # empty recipients means to all admins
91 recipients_objs = User.query().filter(User.admin == True).all()
91 recipients_objs = User.query().filter(User.admin == True).all()
92 log.debug('sending notifications %s to admins: %s' % (
92 log.debug('sending notifications %s to admins: %s' % (
93 type_, recipients_objs)
93 type_, recipients_objs)
94 )
94 )
95 notif = Notification.create(
95 notif = Notification.create(
96 created_by=created_by_obj, subject=subject,
96 created_by=created_by_obj, subject=subject,
97 body=body, recipients=recipients_objs, type_=type_
97 body=body, recipients=recipients_objs, type_=type_
98 )
98 )
99
99
100 if with_email is False:
100 if with_email is False:
101 return notif
101 return notif
102
102
103 #don't send email to person who created this comment
103 #don't send email to person who created this comment
104 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
104 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
105
105
106 # send email with notification to all other participants
106 # send email with notification to all other participants
107 for rec in rec_objs:
107 for rec in rec_objs:
108 email_subject = NotificationModel().make_description(notif, False)
108 email_subject = NotificationModel().make_description(notif, False)
109 type_ = type_
109 type_ = type_
110 email_body = body
110 email_body = body
111 ## this is passed into template
111 ## this is passed into template
112 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
112 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
113 kwargs.update(email_kwargs)
113 kwargs.update(email_kwargs)
114 email_body_html = EmailNotificationModel()\
114 email_body_html = EmailNotificationModel()\
115 .get_email_tmpl(type_, **kwargs)
115 .get_email_tmpl(type_, **kwargs)
116
116
117 run_task(tasks.send_email, rec.email, email_subject, email_body,
117 run_task(tasks.send_email, rec.email, email_subject, email_body,
118 email_body_html)
118 email_body_html)
119
119
120 return notif
120 return notif
121
121
122 def delete(self, user, notification):
122 def delete(self, user, notification):
123 # we don't want to remove actual notification just the assignment
123 # we don't want to remove actual notification just the assignment
124 try:
124 try:
125 notification = self.__get_notification(notification)
125 notification = self.__get_notification(notification)
126 user = self._get_user(user)
126 user = self._get_user(user)
127 if notification and user:
127 if notification and user:
128 obj = UserNotification.query()\
128 obj = UserNotification.query()\
129 .filter(UserNotification.user == user)\
129 .filter(UserNotification.user == user)\
130 .filter(UserNotification.notification
130 .filter(UserNotification.notification
131 == notification)\
131 == notification)\
132 .one()
132 .one()
133 self.sa.delete(obj)
133 self.sa.delete(obj)
134 return True
134 return True
135 except Exception:
135 except Exception:
136 log.error(traceback.format_exc())
136 log.error(traceback.format_exc())
137 raise
137 raise
138
138
139 def get_for_user(self, user, filter_=None):
139 def get_for_user(self, user, filter_=None):
140 """
140 """
141 Get mentions for given user, filter them if filter dict is given
141 Get mentions for given user, filter them if filter dict is given
142
142
143 :param user:
143 :param user:
144 :type user:
144 :type user:
145 :param filter:
145 :param filter:
146 """
146 """
147 user = self._get_user(user)
147 user = self._get_user(user)
148
148
149 q = UserNotification.query()\
149 q = UserNotification.query()\
150 .filter(UserNotification.user == user)\
150 .filter(UserNotification.user == user)\
151 .join((Notification, UserNotification.notification_id ==
151 .join((Notification, UserNotification.notification_id ==
152 Notification.notification_id))
152 Notification.notification_id))
153
153
154 if filter_:
154 if filter_:
155 q = q.filter(Notification.type_.in_(filter_))
155 q = q.filter(Notification.type_.in_(filter_))
156
156
157 return q.all()
157 return q.all()
158
158
159 def mark_read(self, user, notification):
159 def mark_read(self, user, notification):
160 try:
160 try:
161 notification = self.__get_notification(notification)
161 notification = self.__get_notification(notification)
162 user = self._get_user(user)
162 user = self._get_user(user)
163 if notification and user:
163 if notification and user:
164 obj = UserNotification.query()\
164 obj = UserNotification.query()\
165 .filter(UserNotification.user == user)\
165 .filter(UserNotification.user == user)\
166 .filter(UserNotification.notification
166 .filter(UserNotification.notification
167 == notification)\
167 == notification)\
168 .one()
168 .one()
169 obj.read = True
169 obj.read = True
170 self.sa.add(obj)
170 self.sa.add(obj)
171 return True
171 return True
172 except Exception:
172 except Exception:
173 log.error(traceback.format_exc())
173 log.error(traceback.format_exc())
174 raise
174 raise
175
175
176 def mark_all_read_for_user(self, user, filter_=None):
176 def mark_all_read_for_user(self, user, filter_=None):
177 user = self._get_user(user)
177 user = self._get_user(user)
178 q = UserNotification.query()\
178 q = UserNotification.query()\
179 .filter(UserNotification.user == user)\
179 .filter(UserNotification.user == user)\
180 .filter(UserNotification.read == False)\
180 .filter(UserNotification.read == False)\
181 .join((Notification, UserNotification.notification_id ==
181 .join((Notification, UserNotification.notification_id ==
182 Notification.notification_id))
182 Notification.notification_id))
183 if filter_:
183 if filter_:
184 q = q.filter(Notification.type_.in_(filter_))
184 q = q.filter(Notification.type_.in_(filter_))
185
185
186 # this is a little inefficient but sqlalchemy doesn't support
186 # this is a little inefficient but sqlalchemy doesn't support
187 # update on joined tables :(
187 # update on joined tables :(
188 for obj in q.all():
188 for obj in q.all():
189 obj.read = True
189 obj.read = True
190 self.sa.add(obj)
190 self.sa.add(obj)
191
191
192 def get_unread_cnt_for_user(self, user):
192 def get_unread_cnt_for_user(self, user):
193 user = self._get_user(user)
193 user = self._get_user(user)
194 return UserNotification.query()\
194 return UserNotification.query()\
195 .filter(UserNotification.read == False)\
195 .filter(UserNotification.read == False)\
196 .filter(UserNotification.user == user).count()
196 .filter(UserNotification.user == user).count()
197
197
198 def get_unread_for_user(self, user):
198 def get_unread_for_user(self, user):
199 user = self._get_user(user)
199 user = self._get_user(user)
200 return [x.notification for x in UserNotification.query()\
200 return [x.notification for x in UserNotification.query()\
201 .filter(UserNotification.read == False)\
201 .filter(UserNotification.read == False)\
202 .filter(UserNotification.user == user).all()]
202 .filter(UserNotification.user == user).all()]
203
203
204 def get_user_notification(self, user, notification):
204 def get_user_notification(self, user, notification):
205 user = self._get_user(user)
205 user = self._get_user(user)
206 notification = self.__get_notification(notification)
206 notification = self.__get_notification(notification)
207
207
208 return UserNotification.query()\
208 return UserNotification.query()\
209 .filter(UserNotification.notification == notification)\
209 .filter(UserNotification.notification == notification)\
210 .filter(UserNotification.user == user).scalar()
210 .filter(UserNotification.user == user).scalar()
211
211
212 def make_description(self, notification, show_age=True):
212 def make_description(self, notification, show_age=True):
213 """
213 """
214 Creates a human readable description based on properties
214 Creates a human readable description based on properties
215 of notification object
215 of notification object
216 """
216 """
217 #alias
217 #alias
218 _n = notification
218 _n = notification
219 _map = {
219 _map = {
220 _n.TYPE_CHANGESET_COMMENT: _('commented on commit at %(when)s'),
220 _n.TYPE_CHANGESET_COMMENT: _('commented on commit at %(when)s'),
221 _n.TYPE_MESSAGE: _('sent message at %(when)s'),
221 _n.TYPE_MESSAGE: _('sent message at %(when)s'),
222 _n.TYPE_MENTION: _('mentioned you at %(when)s'),
222 _n.TYPE_MENTION: _('mentioned you at %(when)s'),
223 _n.TYPE_REGISTRATION: _('registered in RhodeCode at %(when)s'),
223 _n.TYPE_REGISTRATION: _('registered in RhodeCode at %(when)s'),
224 _n.TYPE_PULL_REQUEST: _('opened new pull request at %(when)s'),
224 _n.TYPE_PULL_REQUEST: _('opened new pull request at %(when)s'),
225 _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request at %(when)s')
225 _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request at %(when)s')
226 }
226 }
227
227
228 # action == _map string
228 # action == _map string
229 tmpl = "%(user)s %(action)s "
229 tmpl = "%(user)s %(action)s "
230 if show_age:
230 if show_age:
231 when = h.age(notification.created_on)
231 when = h.age(notification.created_on)
232 else:
232 else:
233 when = h.fmt_date(notification.created_on)
233 when = h.fmt_date(notification.created_on)
234
234
235 data = dict(
235 data = dict(
236 user=notification.created_by_user.username,
236 user=notification.created_by_user.username,
237 action=_map[notification.type_]
237 action=_map[notification.type_]
238 )
238 )
239 return (tmpl % data) % {'when': when}
239 return (tmpl % data) % {'when': when}
240
240
241
241
242 class EmailNotificationModel(BaseModel):
242 class EmailNotificationModel(BaseModel):
243
243
244 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
244 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
245 TYPE_PASSWORD_RESET = 'passoword_link'
245 TYPE_PASSWORD_RESET = 'passoword_link'
246 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
246 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
247 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
247 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
248 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
248 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
249 TYPE_DEFAULT = 'default'
249 TYPE_DEFAULT = 'default'
250
250
251 def __init__(self):
251 def __init__(self):
252 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
252 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
253 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
253 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
254
254
255 self.email_types = {
255 self.email_types = {
256 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
256 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
257 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
257 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
258 self.TYPE_REGISTRATION: 'email_templates/registration.html',
258 self.TYPE_REGISTRATION: 'email_templates/registration.html',
259 self.TYPE_DEFAULT: 'email_templates/default.html',
259 self.TYPE_DEFAULT: 'email_templates/default.html',
260 self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html',
260 self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html',
261 self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html',
261 self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html',
262 }
262 }
263
263
264 def get_email_tmpl(self, type_, **kwargs):
264 def get_email_tmpl(self, type_, **kwargs):
265 """
265 """
266 return generated template for email based on given type
266 return generated template for email based on given type
267
267
268 :param type_:
268 :param type_:
269 """
269 """
270
270
271 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
271 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
272 email_template = self._tmpl_lookup.get_template(base)
272 email_template = self._tmpl_lookup.get_template(base)
273 # translator inject
273 # translator and helpers inject
274 _kwargs = {'_': _}
274 _kwargs = {'_': _,
275 'h': h}
275 _kwargs.update(kwargs)
276 _kwargs.update(kwargs)
276 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
277 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
277 return email_template.render(**_kwargs)
278 return email_template.render(**_kwargs)
@@ -1,18 +1,19 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
2 <%inherit file="main.html"/>
3
3
4 ${_('User %s opened pull request for repository %s and wants you to review changes.') % ('<b>%s</b>' % pr_user_created,pr_repo_url)}
4 ${_('User %s opened pull request for repository %s and wants you to review changes.') % (('<b>%s</b>' % pr_user_created),pr_repo_url) |n}
5 <div>${_('title')}: ${pr_title}</div>
5 <div>${_('title')}: ${pr_title}</div>
6 <div>${_('description')}:</div>
6 <div>${_('description')}:</div>
7 <div>${_('View this pull request here')}: ${pr_url}</div>
7 <p>
8 <p>
8 ${body}
9 ${body}
9 </p>
10 </p>
10
11
11 <div>${_('revisions for reviewing')}</div>
12 <div>${_('revisions for reviewing')}</div>
12 <ul>
13 <ul>
13 %for r in pr_revisions:
14 %for r in pr_revisions:
14 <li>${r}</li>
15 <li>${r}</li>
15 %endfor
16 %endfor
16 </ul>
17 </ul>
17
18
18 ${_('View this pull request here')}: ${pr_url}
19
General Comments 0
You need to be logged in to leave comments. Login now