##// END OF EJS Templates
part2 of pull-request notification improvements
marcink -
r2802:0a623ec2 beta
parent child Browse files
Show More
@@ -0,0 +1,15 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
3
4 User <b>${pr_comment_user}</b> commented on pull request #${pr_id} for
5 repository ${pr_target_repo}
6
7 <p>
8 ${body}
9
10 %if status_change:
11 <span>New status -> ${status_change}</span>
12 %endif
13 </p>
14
15 View this comment here: ${pr_comment_url}
@@ -1,225 +1,238 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.comment
3 rhodecode.model.comment
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 comments model for RhodeCode
6 comments model for RhodeCode
7
7
8 :created_on: Nov 11, 2011
8 :created_on: Nov 11, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
30 from sqlalchemy.util.compat import defaultdict
31
31
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, \
35 from rhodecode.model.db import ChangesetComment, User, Repository, \
36 Notification, PullRequest
36 Notification, PullRequest
37 from rhodecode.model.notification import NotificationModel
37 from rhodecode.model.notification import NotificationModel
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class ChangesetCommentsModel(BaseModel):
42 class ChangesetCommentsModel(BaseModel):
43
43
44 cls = ChangesetComment
44 cls = ChangesetComment
45
45
46 def __get_changeset_comment(self, changeset_comment):
46 def __get_changeset_comment(self, changeset_comment):
47 return self._get_instance(ChangesetComment, changeset_comment)
47 return self._get_instance(ChangesetComment, changeset_comment)
48
48
49 def __get_pull_request(self, pull_request):
49 def __get_pull_request(self, pull_request):
50 return self._get_instance(PullRequest, pull_request)
50 return self._get_instance(PullRequest, pull_request)
51
51
52 def _extract_mentions(self, s):
52 def _extract_mentions(self, s):
53 user_objects = []
53 user_objects = []
54 for username in extract_mentioned_users(s):
54 for username in extract_mentioned_users(s):
55 user_obj = User.get_by_username(username, case_insensitive=True)
55 user_obj = User.get_by_username(username, case_insensitive=True)
56 if user_obj:
56 if user_obj:
57 user_objects.append(user_obj)
57 user_objects.append(user_obj)
58 return user_objects
58 return user_objects
59
59
60 def create(self, text, repo, user, revision=None, pull_request=None,
60 def create(self, text, repo, user, revision=None, pull_request=None,
61 f_path=None, line_no=None, status_change=None):
61 f_path=None, line_no=None, status_change=None):
62 """
62 """
63 Creates new comment for changeset or pull request.
63 Creates new comment for changeset or pull request.
64 IF status_change is not none this comment is associated with a
64 IF status_change is not none this comment is associated with a
65 status change of changeset or changesets associated with pull request
65 status change of changeset or changesets associated with pull request
66
66
67 :param text:
67 :param text:
68 :param repo:
68 :param repo:
69 :param user:
69 :param user:
70 :param revision:
70 :param revision:
71 :param pull_request:
71 :param pull_request:
72 :param f_path:
72 :param f_path:
73 :param line_no:
73 :param line_no:
74 :param status_change:
74 :param status_change:
75 """
75 """
76 if not text:
76 if not text:
77 return
77 return
78
78
79 repo = self._get_repo(repo)
79 repo = self._get_repo(repo)
80 user = self._get_user(user)
80 user = self._get_user(user)
81 comment = ChangesetComment()
81 comment = ChangesetComment()
82 comment.repo = repo
82 comment.repo = repo
83 comment.author = user
83 comment.author = user
84 comment.text = text
84 comment.text = text
85 comment.f_path = f_path
85 comment.f_path = f_path
86 comment.line_no = line_no
86 comment.line_no = line_no
87
87
88 if revision:
88 if revision:
89 cs = repo.scm_instance.get_changeset(revision)
89 cs = repo.scm_instance.get_changeset(revision)
90 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
90 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
91 author_email = cs.author_email
91 author_email = cs.author_email
92 comment.revision = revision
92 comment.revision = revision
93 elif pull_request:
93 elif pull_request:
94 pull_request = self.__get_pull_request(pull_request)
94 pull_request = self.__get_pull_request(pull_request)
95 comment.pull_request = pull_request
95 comment.pull_request = pull_request
96 desc = pull_request.pull_request_id
96 desc = pull_request.pull_request_id
97 else:
97 else:
98 raise Exception('Please specify revision or pull_request_id')
98 raise Exception('Please specify revision or pull_request_id')
99
99
100 self.sa.add(comment)
100 self.sa.add(comment)
101 self.sa.flush()
101 self.sa.flush()
102
102
103 # make notification
103 # make notification
104 line = ''
104 line = ''
105 body = text
105 body = text
106
106
107 #changeset
107 #changeset
108 if revision:
108 if revision:
109 if line_no:
109 if line_no:
110 line = _('on line %s') % line_no
110 line = _('on line %s') % line_no
111 subj = safe_unicode(
111 subj = safe_unicode(
112 h.link_to('Re commit: %(desc)s %(line)s' % \
112 h.link_to('Re commit: %(desc)s %(line)s' % \
113 {'desc': desc, 'line': line},
113 {'desc': desc, 'line': line},
114 h.url('changeset_home', repo_name=repo.repo_name,
114 h.url('changeset_home', repo_name=repo.repo_name,
115 revision=revision,
115 revision=revision,
116 anchor='comment-%s' % comment.comment_id,
116 anchor='comment-%s' % comment.comment_id,
117 qualified=True,
117 qualified=True,
118 )
118 )
119 )
119 )
120 )
120 )
121 notification_type = Notification.TYPE_CHANGESET_COMMENT
121 notification_type = Notification.TYPE_CHANGESET_COMMENT
122 # get the current participants of this changeset
122 # get the current participants of this changeset
123 recipients = ChangesetComment.get_users(revision=revision)
123 recipients = ChangesetComment.get_users(revision=revision)
124 # add changeset author if it's in rhodecode system
124 # add changeset author if it's in rhodecode system
125 recipients += [User.get_by_email(author_email)]
125 recipients += [User.get_by_email(author_email)]
126 #pull request
126 #pull request
127 elif pull_request:
127 elif pull_request:
128 subj = safe_unicode(
128 _url = h.url('pullrequest_show',
129 h.link_to('Re pull request: %(desc)s %(line)s' % \
130 {'desc': desc, 'line': line},
131 h.url('pullrequest_show',
132 repo_name=pull_request.other_repo.repo_name,
129 repo_name=pull_request.other_repo.repo_name,
133 pull_request_id=pull_request.pull_request_id,
130 pull_request_id=pull_request.pull_request_id,
134 anchor='comment-%s' % comment.comment_id,
131 anchor='comment-%s' % comment.comment_id,
135 qualified=True,
132 qualified=True,
136 )
133 )
137 )
134 subj = safe_unicode(
135 h.link_to('Re pull request: %(desc)s %(line)s' % \
136 {'desc': desc, 'line': line}, _url)
138 )
137 )
139
138
140 notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
139 notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
141 # get the current participants of this pull request
140 # get the current participants of this pull request
142 recipients = ChangesetComment.get_users(pull_request_id=
141 recipients = ChangesetComment.get_users(pull_request_id=
143 pull_request.pull_request_id)
142 pull_request.pull_request_id)
144 # add pull request author
143 # add pull request author
145 recipients += [pull_request.author]
144 recipients += [pull_request.author]
146
145
146 # add the reviewers to notification
147 recipients += [x.user for x in pull_request.reviewers]
148
149 #set some variables for email notification
150 kwargs = {
151 'pr_id': pull_request.pull_request_id,
152 'status_change': status_change,
153 'pr_comment_url': _url,
154 'pr_comment_user': h.person(user.email),
155 'pr_target_repo': h.url('summary_home',
156 repo_name=pull_request.other_repo.repo_name,
157 qualified=True)
158 }
147 # create notification objects, and emails
159 # create notification objects, and emails
148 NotificationModel().create(
160 NotificationModel().create(
149 created_by=user, subject=subj, body=body,
161 created_by=user, subject=subj, body=body,
150 recipients=recipients, type_=notification_type,
162 recipients=recipients, type_=notification_type,
151 email_kwargs={'status_change': status_change}
163 email_kwargs=kwargs
152 )
164 )
153
165
154 mention_recipients = set(self._extract_mentions(body))\
166 mention_recipients = set(self._extract_mentions(body))\
155 .difference(recipients)
167 .difference(recipients)
156 if mention_recipients:
168 if mention_recipients:
169 kwargs.update({'pr_mention': True})
157 subj = _('[Mention]') + ' ' + subj
170 subj = _('[Mention]') + ' ' + subj
158 NotificationModel().create(
171 NotificationModel().create(
159 created_by=user, subject=subj, body=body,
172 created_by=user, subject=subj, body=body,
160 recipients=mention_recipients,
173 recipients=mention_recipients,
161 type_=notification_type,
174 type_=notification_type,
162 email_kwargs={'status_change': status_change}
175 email_kwargs=kwargs
163 )
176 )
164
177
165 return comment
178 return comment
166
179
167 def delete(self, comment):
180 def delete(self, comment):
168 """
181 """
169 Deletes given comment
182 Deletes given comment
170
183
171 :param comment_id:
184 :param comment_id:
172 """
185 """
173 comment = self.__get_changeset_comment(comment)
186 comment = self.__get_changeset_comment(comment)
174 self.sa.delete(comment)
187 self.sa.delete(comment)
175
188
176 return comment
189 return comment
177
190
178 def get_comments(self, repo_id, revision=None, pull_request=None):
191 def get_comments(self, repo_id, revision=None, pull_request=None):
179 """
192 """
180 Get's main comments based on revision or pull_request_id
193 Get's main comments based on revision or pull_request_id
181
194
182 :param repo_id:
195 :param repo_id:
183 :type repo_id:
196 :type repo_id:
184 :param revision:
197 :param revision:
185 :type revision:
198 :type revision:
186 :param pull_request:
199 :param pull_request:
187 :type pull_request:
200 :type pull_request:
188 """
201 """
189
202
190 q = ChangesetComment.query()\
203 q = ChangesetComment.query()\
191 .filter(ChangesetComment.repo_id == repo_id)\
204 .filter(ChangesetComment.repo_id == repo_id)\
192 .filter(ChangesetComment.line_no == None)\
205 .filter(ChangesetComment.line_no == None)\
193 .filter(ChangesetComment.f_path == None)
206 .filter(ChangesetComment.f_path == None)
194 if revision:
207 if revision:
195 q = q.filter(ChangesetComment.revision == revision)
208 q = q.filter(ChangesetComment.revision == revision)
196 elif pull_request:
209 elif pull_request:
197 pull_request = self.__get_pull_request(pull_request)
210 pull_request = self.__get_pull_request(pull_request)
198 q = q.filter(ChangesetComment.pull_request == pull_request)
211 q = q.filter(ChangesetComment.pull_request == pull_request)
199 else:
212 else:
200 raise Exception('Please specify revision or pull_request')
213 raise Exception('Please specify revision or pull_request')
201 q = q.order_by(ChangesetComment.created_on)
214 q = q.order_by(ChangesetComment.created_on)
202 return q.all()
215 return q.all()
203
216
204 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
217 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
205 q = self.sa.query(ChangesetComment)\
218 q = self.sa.query(ChangesetComment)\
206 .filter(ChangesetComment.repo_id == repo_id)\
219 .filter(ChangesetComment.repo_id == repo_id)\
207 .filter(ChangesetComment.line_no != None)\
220 .filter(ChangesetComment.line_no != None)\
208 .filter(ChangesetComment.f_path != None)\
221 .filter(ChangesetComment.f_path != None)\
209 .order_by(ChangesetComment.comment_id.asc())\
222 .order_by(ChangesetComment.comment_id.asc())\
210
223
211 if revision:
224 if revision:
212 q = q.filter(ChangesetComment.revision == revision)
225 q = q.filter(ChangesetComment.revision == revision)
213 elif pull_request:
226 elif pull_request:
214 pull_request = self.__get_pull_request(pull_request)
227 pull_request = self.__get_pull_request(pull_request)
215 q = q.filter(ChangesetComment.pull_request == pull_request)
228 q = q.filter(ChangesetComment.pull_request == pull_request)
216 else:
229 else:
217 raise Exception('Please specify revision or pull_request_id')
230 raise Exception('Please specify revision or pull_request_id')
218
231
219 comments = q.all()
232 comments = q.all()
220
233
221 paths = defaultdict(lambda: defaultdict(list))
234 paths = defaultdict(lambda: defaultdict(list))
222
235
223 for co in comments:
236 for co in comments:
224 paths[co.f_path][co.line_no].append(co)
237 paths[co.f_path][co.line_no].append(co)
225 return paths.items()
238 return paths.items()
@@ -1,275 +1,277 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'),
220 _n.TYPE_CHANGESET_COMMENT: _('commented on commit'),
221 _n.TYPE_MESSAGE: _('sent message'),
221 _n.TYPE_MESSAGE: _('sent message'),
222 _n.TYPE_MENTION: _('mentioned you'),
222 _n.TYPE_MENTION: _('mentioned you'),
223 _n.TYPE_REGISTRATION: _('registered in RhodeCode'),
223 _n.TYPE_REGISTRATION: _('registered in RhodeCode'),
224 _n.TYPE_PULL_REQUEST: _('opened new pull request'),
224 _n.TYPE_PULL_REQUEST: _('opened new pull request'),
225 _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request')
225 _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request')
226 }
226 }
227
227
228 # action == _map string
228 # action == _map string
229 tmpl = "%(user)s %(action)s at %(when)s"
229 tmpl = "%(user)s %(action)s at %(when)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_], when=when,
237 action=_map[notification.type_], when=when,
238 )
238 )
239 return tmpl % data
239 return tmpl % data
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_DEFAULT = 'default'
249 TYPE_DEFAULT = 'default'
249
250
250 def __init__(self):
251 def __init__(self):
251 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
252 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
252 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
253 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
253
254
254 self.email_types = {
255 self.email_types = {
255 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
256 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
256 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
257 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
257 self.TYPE_REGISTRATION: 'email_templates/registration.html',
258 self.TYPE_REGISTRATION: 'email_templates/registration.html',
258 self.TYPE_DEFAULT: 'email_templates/default.html',
259 self.TYPE_DEFAULT: 'email_templates/default.html',
259 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',
260 }
262 }
261
263
262 def get_email_tmpl(self, type_, **kwargs):
264 def get_email_tmpl(self, type_, **kwargs):
263 """
265 """
264 return generated template for email based on given type
266 return generated template for email based on given type
265
267
266 :param type_:
268 :param type_:
267 """
269 """
268
270
269 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])
270 email_template = self._tmpl_lookup.get_template(base)
272 email_template = self._tmpl_lookup.get_template(base)
271 # translator inject
273 # translator inject
272 _kwargs = {'_': _}
274 _kwargs = {'_': _}
273 _kwargs.update(kwargs)
275 _kwargs.update(kwargs)
274 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
276 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
275 return email_template.render(**_kwargs)
277 return email_template.render(**_kwargs)
General Comments 0
You need to be logged in to leave comments. Login now