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