##// END OF EJS Templates
emails: try to improve wording and layout - 1st iteration
Mads Kiilerich -
r3780:1de8abd9 beta
parent child Browse files
Show More
@@ -1,284 +1,285 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 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class ChangesetCommentsModel(BaseModel):
43 class ChangesetCommentsModel(BaseModel):
44
44
45 cls = ChangesetComment
45 cls = ChangesetComment
46
46
47 def __get_changeset_comment(self, changeset_comment):
47 def __get_changeset_comment(self, changeset_comment):
48 return self._get_instance(ChangesetComment, changeset_comment)
48 return self._get_instance(ChangesetComment, changeset_comment)
49
49
50 def __get_pull_request(self, pull_request):
50 def __get_pull_request(self, pull_request):
51 return self._get_instance(PullRequest, pull_request)
51 return self._get_instance(PullRequest, pull_request)
52
52
53 def _extract_mentions(self, s):
53 def _extract_mentions(self, s):
54 user_objects = []
54 user_objects = []
55 for username in extract_mentioned_users(s):
55 for username in extract_mentioned_users(s):
56 user_obj = User.get_by_username(username, case_insensitive=True)
56 user_obj = User.get_by_username(username, case_insensitive=True)
57 if user_obj:
57 if user_obj:
58 user_objects.append(user_obj)
58 user_objects.append(user_obj)
59 return user_objects
59 return user_objects
60
60
61 def _get_notification_data(self, repo, comment, user, comment_text,
61 def _get_notification_data(self, repo, comment, user, comment_text,
62 line_no=None, revision=None, pull_request=None,
62 line_no=None, revision=None, pull_request=None,
63 status_change=None, closing_pr=False):
63 status_change=None, closing_pr=False):
64 """
64 """
65 Get notification data
65 Get notification data
66
66
67 :param comment_text:
67 :param comment_text:
68 :param line:
68 :param line:
69 :returns: tuple (subj,body,recipients,notification_type,email_kwargs)
69 :returns: tuple (subj,body,recipients,notification_type,email_kwargs)
70 """
70 """
71 # make notification
71 # make notification
72 body = comment_text # text of the comment
72 body = comment_text # text of the comment
73 line = ''
73 line = ''
74 if line_no:
74 if line_no:
75 line = _('on line %s') % line_no
75 line = _('on line %s') % line_no
76
76
77 #changeset
77 #changeset
78 if revision:
78 if revision:
79 notification_type = Notification.TYPE_CHANGESET_COMMENT
79 notification_type = Notification.TYPE_CHANGESET_COMMENT
80 cs = repo.scm_instance.get_changeset(revision)
80 cs = repo.scm_instance.get_changeset(revision)
81 desc = "%s" % (cs.short_id)
81 desc = "%s" % (cs.short_id)
82
82
83 _url = h.url('changeset_home',
83 _url = h.url('changeset_home',
84 repo_name=repo.repo_name,
84 repo_name=repo.repo_name,
85 revision=revision,
85 revision=revision,
86 anchor='comment-%s' % comment.comment_id,
86 anchor='comment-%s' % comment.comment_id,
87 qualified=True,
87 qualified=True,
88 )
88 )
89 subj = safe_unicode(
89 subj = safe_unicode(
90 h.link_to('Re changeset: %(desc)s %(line)s' % \
90 h.link_to('Re changeset: %(desc)s %(line)s' % \
91 {'desc': desc, 'line': line},
91 {'desc': desc, 'line': line},
92 _url)
92 _url)
93 )
93 )
94 email_subject = 'User %s commented on changeset %s' % \
94 email_subject = '%s commented on changeset %s' % \
95 (user.username, h.short_id(revision))
95 (user.username, h.short_id(revision))
96 # get the current participants of this changeset
96 # get the current participants of this changeset
97 recipients = ChangesetComment.get_users(revision=revision)
97 recipients = ChangesetComment.get_users(revision=revision)
98 # add changeset author if it's in rhodecode system
98 # add changeset author if it's in rhodecode system
99 cs_author = User.get_from_cs_author(cs.author)
99 cs_author = User.get_from_cs_author(cs.author)
100 if not cs_author:
100 if not cs_author:
101 #use repo owner if we cannot extract the author correctly
101 #use repo owner if we cannot extract the author correctly
102 cs_author = repo.user
102 cs_author = repo.user
103 recipients += [cs_author]
103 recipients += [cs_author]
104 email_kwargs = {
104 email_kwargs = {
105 'status_change': status_change,
105 'status_change': status_change,
106 'cs_comment_user': h.person(user.email),
106 'cs_comment_user': h.person(user.email),
107 'cs_target_repo': h.url('summary_home', repo_name=repo.repo_name,
107 'cs_target_repo': h.url('summary_home', repo_name=repo.repo_name,
108 qualified=True),
108 qualified=True),
109 'cs_comment_url': _url,
109 'cs_comment_url': _url,
110 'raw_id': revision,
110 'raw_id': revision,
111 'message': cs.message
111 'message': cs.message
112 }
112 }
113 #pull request
113 #pull request
114 elif pull_request:
114 elif pull_request:
115 notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
115 notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
116 desc = comment.pull_request.title
116 desc = comment.pull_request.title
117 _url = h.url('pullrequest_show',
117 _url = h.url('pullrequest_show',
118 repo_name=pull_request.other_repo.repo_name,
118 repo_name=pull_request.other_repo.repo_name,
119 pull_request_id=pull_request.pull_request_id,
119 pull_request_id=pull_request.pull_request_id,
120 anchor='comment-%s' % comment.comment_id,
120 anchor='comment-%s' % comment.comment_id,
121 qualified=True,
121 qualified=True,
122 )
122 )
123 subj = safe_unicode(
123 subj = safe_unicode(
124 h.link_to('Re pull request #%(pr_id)s: %(desc)s %(line)s' % \
124 h.link_to('Re pull request #%(pr_id)s: %(desc)s %(line)s' % \
125 {'desc': desc,
125 {'desc': desc,
126 'pr_id': comment.pull_request.pull_request_id,
126 'pr_id': comment.pull_request.pull_request_id,
127 'line': line},
127 'line': line},
128 _url)
128 _url)
129 )
129 )
130 email_subject = 'User %s commented on pull request #%s' % \
130 email_subject = '%s commented on pull request #%s' % \
131 (user.username, comment.pull_request.pull_request_id)
131 (user.username, comment.pull_request.pull_request_id)
132 # get the current participants of this pull request
132 # get the current participants of this pull request
133 recipients = ChangesetComment.get_users(pull_request_id=
133 recipients = ChangesetComment.get_users(pull_request_id=
134 pull_request.pull_request_id)
134 pull_request.pull_request_id)
135 # add pull request author
135 # add pull request author
136 recipients += [pull_request.author]
136 recipients += [pull_request.author]
137
137
138 # add the reviewers to notification
138 # add the reviewers to notification
139 recipients += [x.user for x in pull_request.reviewers]
139 recipients += [x.user for x in pull_request.reviewers]
140
140
141 #set some variables for email notification
141 #set some variables for email notification
142 email_kwargs = {
142 email_kwargs = {
143 'pr_title': pull_request.title,
143 'pr_id': pull_request.pull_request_id,
144 'pr_id': pull_request.pull_request_id,
144 'status_change': status_change,
145 'status_change': status_change,
145 'closing_pr': closing_pr,
146 'closing_pr': closing_pr,
146 'pr_comment_url': _url,
147 'pr_comment_url': _url,
147 'pr_comment_user': h.person(user.email),
148 'pr_comment_user': h.person(user.email),
148 'pr_target_repo': h.url('summary_home',
149 'pr_target_repo': h.url('summary_home',
149 repo_name=pull_request.other_repo.repo_name,
150 repo_name=pull_request.other_repo.repo_name,
150 qualified=True)
151 qualified=True)
151 }
152 }
152
153
153 return subj, body, recipients, notification_type, email_kwargs, email_subject
154 return subj, body, recipients, notification_type, email_kwargs, email_subject
154
155
155 def create(self, text, repo, user, revision=None, pull_request=None,
156 def create(self, text, repo, user, revision=None, pull_request=None,
156 f_path=None, line_no=None, status_change=None, closing_pr=False,
157 f_path=None, line_no=None, status_change=None, closing_pr=False,
157 send_email=True):
158 send_email=True):
158 """
159 """
159 Creates new comment for changeset or pull request.
160 Creates new comment for changeset or pull request.
160 IF status_change is not none this comment is associated with a
161 IF status_change is not none this comment is associated with a
161 status change of changeset or changesets associated with pull request
162 status change of changeset or changesets associated with pull request
162
163
163 :param text:
164 :param text:
164 :param repo:
165 :param repo:
165 :param user:
166 :param user:
166 :param revision:
167 :param revision:
167 :param pull_request:
168 :param pull_request:
168 :param f_path:
169 :param f_path:
169 :param line_no:
170 :param line_no:
170 :param status_change:
171 :param status_change:
171 :param closing_pr:
172 :param closing_pr:
172 :param send_email:
173 :param send_email:
173 """
174 """
174 if not text:
175 if not text:
175 log.warning('Missing text for comment, skipping...')
176 log.warning('Missing text for comment, skipping...')
176 return
177 return
177
178
178 repo = self._get_repo(repo)
179 repo = self._get_repo(repo)
179 user = self._get_user(user)
180 user = self._get_user(user)
180 comment = ChangesetComment()
181 comment = ChangesetComment()
181 comment.repo = repo
182 comment.repo = repo
182 comment.author = user
183 comment.author = user
183 comment.text = text
184 comment.text = text
184 comment.f_path = f_path
185 comment.f_path = f_path
185 comment.line_no = line_no
186 comment.line_no = line_no
186
187
187 if revision:
188 if revision:
188 comment.revision = revision
189 comment.revision = revision
189 elif pull_request:
190 elif pull_request:
190 pull_request = self.__get_pull_request(pull_request)
191 pull_request = self.__get_pull_request(pull_request)
191 comment.pull_request = pull_request
192 comment.pull_request = pull_request
192 else:
193 else:
193 raise Exception('Please specify revision or pull_request_id')
194 raise Exception('Please specify revision or pull_request_id')
194
195
195 Session().add(comment)
196 Session().add(comment)
196 Session().flush()
197 Session().flush()
197
198
198 if send_email:
199 if send_email:
199 (subj, body, recipients, notification_type,
200 (subj, body, recipients, notification_type,
200 email_kwargs, email_subject) = self._get_notification_data(
201 email_kwargs, email_subject) = self._get_notification_data(
201 repo, comment, user,
202 repo, comment, user,
202 comment_text=text,
203 comment_text=text,
203 line_no=line_no,
204 line_no=line_no,
204 revision=revision,
205 revision=revision,
205 pull_request=pull_request,
206 pull_request=pull_request,
206 status_change=status_change,
207 status_change=status_change,
207 closing_pr=closing_pr)
208 closing_pr=closing_pr)
208 # create notification objects, and emails
209 # create notification objects, and emails
209 NotificationModel().create(
210 NotificationModel().create(
210 created_by=user, subject=subj, body=body,
211 created_by=user, subject=subj, body=body,
211 recipients=recipients, type_=notification_type,
212 recipients=recipients, type_=notification_type,
212 email_kwargs=email_kwargs, email_subject=email_subject
213 email_kwargs=email_kwargs, email_subject=email_subject
213 )
214 )
214
215
215 mention_recipients = set(self._extract_mentions(body))\
216 mention_recipients = set(self._extract_mentions(body))\
216 .difference(recipients)
217 .difference(recipients)
217 if mention_recipients:
218 if mention_recipients:
218 email_kwargs.update({'pr_mention': True})
219 email_kwargs.update({'pr_mention': True})
219 subj = _('[Mention]') + ' ' + subj
220 subj = _('[Mention]') + ' ' + subj
220 NotificationModel().create(
221 NotificationModel().create(
221 created_by=user, subject=subj, body=body,
222 created_by=user, subject=subj, body=body,
222 recipients=mention_recipients,
223 recipients=mention_recipients,
223 type_=notification_type,
224 type_=notification_type,
224 email_kwargs=email_kwargs
225 email_kwargs=email_kwargs
225 )
226 )
226
227
227 return comment
228 return comment
228
229
229 def delete(self, comment):
230 def delete(self, comment):
230 """
231 """
231 Deletes given comment
232 Deletes given comment
232
233
233 :param comment_id:
234 :param comment_id:
234 """
235 """
235 comment = self.__get_changeset_comment(comment)
236 comment = self.__get_changeset_comment(comment)
236 Session().delete(comment)
237 Session().delete(comment)
237
238
238 return comment
239 return comment
239
240
240 def get_comments(self, repo_id, revision=None, pull_request=None):
241 def get_comments(self, repo_id, revision=None, pull_request=None):
241 """
242 """
242 Get's main comments based on revision or pull_request_id
243 Get's main comments based on revision or pull_request_id
243
244
244 :param repo_id:
245 :param repo_id:
245 :param revision:
246 :param revision:
246 :param pull_request:
247 :param pull_request:
247 """
248 """
248
249
249 q = ChangesetComment.query()\
250 q = ChangesetComment.query()\
250 .filter(ChangesetComment.repo_id == repo_id)\
251 .filter(ChangesetComment.repo_id == repo_id)\
251 .filter(ChangesetComment.line_no == None)\
252 .filter(ChangesetComment.line_no == None)\
252 .filter(ChangesetComment.f_path == None)
253 .filter(ChangesetComment.f_path == None)
253 if revision:
254 if revision:
254 q = q.filter(ChangesetComment.revision == revision)
255 q = q.filter(ChangesetComment.revision == revision)
255 elif pull_request:
256 elif pull_request:
256 pull_request = self.__get_pull_request(pull_request)
257 pull_request = self.__get_pull_request(pull_request)
257 q = q.filter(ChangesetComment.pull_request == pull_request)
258 q = q.filter(ChangesetComment.pull_request == pull_request)
258 else:
259 else:
259 raise Exception('Please specify revision or pull_request')
260 raise Exception('Please specify revision or pull_request')
260 q = q.order_by(ChangesetComment.created_on)
261 q = q.order_by(ChangesetComment.created_on)
261 return q.all()
262 return q.all()
262
263
263 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
264 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
264 q = Session().query(ChangesetComment)\
265 q = Session().query(ChangesetComment)\
265 .filter(ChangesetComment.repo_id == repo_id)\
266 .filter(ChangesetComment.repo_id == repo_id)\
266 .filter(ChangesetComment.line_no != None)\
267 .filter(ChangesetComment.line_no != None)\
267 .filter(ChangesetComment.f_path != None)\
268 .filter(ChangesetComment.f_path != None)\
268 .order_by(ChangesetComment.comment_id.asc())\
269 .order_by(ChangesetComment.comment_id.asc())\
269
270
270 if revision:
271 if revision:
271 q = q.filter(ChangesetComment.revision == revision)
272 q = q.filter(ChangesetComment.revision == revision)
272 elif pull_request:
273 elif pull_request:
273 pull_request = self.__get_pull_request(pull_request)
274 pull_request = self.__get_pull_request(pull_request)
274 q = q.filter(ChangesetComment.pull_request == pull_request)
275 q = q.filter(ChangesetComment.pull_request == pull_request)
275 else:
276 else:
276 raise Exception('Please specify revision or pull_request_id')
277 raise Exception('Please specify revision or pull_request_id')
277
278
278 comments = q.all()
279 comments = q.all()
279
280
280 paths = defaultdict(lambda: defaultdict(list))
281 paths = defaultdict(lambda: defaultdict(list))
281
282
282 for co in comments:
283 for co in comments:
283 paths[co.f_path][co.line_no].append(co)
284 paths[co.f_path][co.line_no].append(co)
284 return paths.items()
285 return paths.items()
@@ -1,17 +1,18 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
2 <%inherit file="main.html"/>
3 ##message from user goes here
3
4 <p>
4 <p>${_('URL')}: <a href="${cs_comment_url}">${cs_comment_url}</a></p>
5 ${cs_comment_user}: <br/>
5
6 <h4>${_('%s commented on a %s changeset.') % (cs_comment_user,cs_target_repo) |n}</h4>
7
8 <p>${_('Changeset')}: ${h.short_id(raw_id)}</p>
9 <p>${_('Description')}:<br/>
10 ${h.shorter(message, 256)}
11 </p>
12
13 %if status_change:
14 <p>${_('The changeset status was changed to')}: <b>${status_change}</b></p>
15 %endif
16 <p>${_('Comment')}:<br/>
6 ${body}
17 ${body}
7 </p>
18 </p>
8 %if status_change:
9 <span>${_('New status')} -&gt; ${status_change}</span>
10 %endif
11 <div>${_('View this comment here')}: ${cs_comment_url}</div>
12
13 <pre>
14 ${_('Repo')}: ${cs_target_repo}
15 ${_('Changeset')}: ${h.short_id(raw_id)}
16 ${_('desc')}: ${h.shorter(message, 256)}
17 </pre>
@@ -1,11 +1,10 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
2 <%inherit file="main.html"/>
3
3
4 <h4>${_('Hello %s') % user}</h4>
4 <h4>${_('Hello %s') % user}</h4>
5 <div>${_('We received a request to create a new password for your account.')}</div>
5 <p>${_('We received a request to create a new password for your account.')}</p>
6 <div>${_('You can generate it by clicking following URL')}:</div>
6 <p>${_('You can generate it by clicking following URL')}:</p>
7 <pre>
7 <pre>
8 ${reset_url}
8 ${reset_url}
9 </pre>
9 </pre>
10 <br/>
10 <p>${_("Please ignore this email if you did not request a new password .")}</p>
11 ${_("If you did not request new password please ignore this email.")}
@@ -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) |n}
4 <p>${_('URL')}: <a href="${pr_url}">${pr_url}</a></p>
5 <div>${_('View this pull request here')}: ${pr_url}</div>
5
6 <div>${_('title')}: ${pr_title}</div>
6 <h4>${_('%s opened a pull request for repository %s and wants you to review changes.') % (pr_user_created,pr_repo_url) |n}</h4>
7 <div>${_('description')}:</div>
8 <p>
9 ${body}
10 </p>
11
7
12 <div>${_('revisions for reviewing')}</div>
8 <p>${_('Title')}: <b>${pr_title}</b></p>
9 <p>${_('Description')}:</p>
10 <p style="white-space: pre-wrap;">${body}</p>
11
12 <p>${_('Changesets')}:</p>
13 <p style="white-space: pre-wrap;">
13 <p style="white-space: pre-wrap;">
14 %for r,r_msg in pr_revisions:
14 %for r,r_msg in pr_revisions:
15 <b>${h.short_id(r)}</b>:
15 <b>${h.short_id(r)}</b>:
16 ${h.shorter(r_msg, 256)}
16 ${h.shorter(r_msg, 256)}
17
17 %endfor
18 %endfor
18 </p>
19 </p>
@@ -1,18 +1,17 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
2 <%inherit file="main.html"/>
3 ${_('Pull request #%s for repository %s') % (pr_id, pr_target_repo) |n}
3
4 ##message from user goes here
4 <p>${_('URL')}: <a href="${pr_comment_url}">${pr_comment_url}</a></p>
5 <p>
5
6 ${pr_comment_user}: <br/>
6 <h4>${_('%s commented on pull request "%s"') % (pr_comment_user,pr_title) |n}</h4>
7 ${body}
8 </p>
9 <div>${_('View this comment here')}: ${pr_comment_url}</div>
10
7
11 %if status_change:
8 %if status_change:
12 %if closing_pr:
9 %if closing_pr:
13 <span>${_('Closing pull request with status')} -&gt; ${status_change}</span>
10 <p>${_('Pull request was closed with status')}: <b>${status_change}</b></p>
14 %else:
11 %else:
15 <span>${_('New status')} -&gt; ${status_change}</span>
12 <p>${_('Pull request changed status')}: <b>${status_change}</b></p>
16 %endif
13 %endif
17 %endif
14 %endif
18 </p>
15
16 <p>${_('Comment')}:</p>
17 <p>${body}</p>
@@ -1,9 +1,6 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
2 <%inherit file="main.html"/>
3
3
4 ${_('A new user have registered in RhodeCode')}
5
6 ${body}
4 ${body}
7
5
8
9 ${_('View this user here')}: ${registered_user_url}
6 ${_('View this user here')}: ${registered_user_url}
General Comments 0
You need to be logged in to leave comments. Login now