##// END OF EJS Templates
pull requests: prevent adding DEFAULT user as reviewer...
Søren Løvborg -
r5841:1658beb2 default
parent child Browse files
Show More
@@ -1,213 +1,223 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.pull_request
15 kallithea.model.pull_request
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 pull request model for Kallithea
18 pull request model for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Jun 6, 2012
22 :created_on: Jun 6, 2012
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import datetime
29 import datetime
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from kallithea.model.meta import Session
33 from kallithea.model.meta import Session
34 from kallithea.lib import helpers as h
34 from kallithea.lib import helpers as h
35 from kallithea.lib.exceptions import UserInvalidException
35 from kallithea.lib.exceptions import UserInvalidException
36 from kallithea.model import BaseModel
36 from kallithea.model import BaseModel
37 from kallithea.model.db import PullRequest, PullRequestReviewers, Notification, \
37 from kallithea.model.db import PullRequest, PullRequestReviewers, Notification, \
38 ChangesetStatus
38 ChangesetStatus, User
39 from kallithea.model.notification import NotificationModel
39 from kallithea.model.notification import NotificationModel
40 from kallithea.lib.utils2 import extract_mentioned_users, safe_unicode
40 from kallithea.lib.utils2 import extract_mentioned_users, safe_unicode
41
41
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class PullRequestModel(BaseModel):
46 class PullRequestModel(BaseModel):
47
47
48 cls = PullRequest
48 cls = PullRequest
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 get_pullrequest_cnt_for_user(self, user):
53 def get_pullrequest_cnt_for_user(self, user):
54 return PullRequest.query() \
54 return PullRequest.query() \
55 .join(PullRequestReviewers) \
55 .join(PullRequestReviewers) \
56 .filter(PullRequestReviewers.user_id == user) \
56 .filter(PullRequestReviewers.user_id == user) \
57 .filter(PullRequest.status != PullRequest.STATUS_CLOSED) \
57 .filter(PullRequest.status != PullRequest.STATUS_CLOSED) \
58 .count()
58 .count()
59
59
60 def get_all(self, repo_name, from_=False, closed=False):
60 def get_all(self, repo_name, from_=False, closed=False):
61 """Get all PRs for repo.
61 """Get all PRs for repo.
62 Default is all PRs to the repo, PRs from the repo if from_.
62 Default is all PRs to the repo, PRs from the repo if from_.
63 Closed PRs are only included if closed is true."""
63 Closed PRs are only included if closed is true."""
64 repo = self._get_repo(repo_name)
64 repo = self._get_repo(repo_name)
65 q = PullRequest.query()
65 q = PullRequest.query()
66 if from_:
66 if from_:
67 q = q.filter(PullRequest.org_repo == repo)
67 q = q.filter(PullRequest.org_repo == repo)
68 else:
68 else:
69 q = q.filter(PullRequest.other_repo == repo)
69 q = q.filter(PullRequest.other_repo == repo)
70 if not closed:
70 if not closed:
71 q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED)
71 q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED)
72 return q.order_by(PullRequest.created_on.desc()).all()
72 return q.order_by(PullRequest.created_on.desc()).all()
73
73
74 def _get_valid_reviewers(self, seq):
75 """ Generate User objects from a sequence of user IDs, usernames or
76 User objects. Raises UserInvalidException if the DEFAULT user is
77 specified, or if a given ID or username does not match any user.
78 """
79 for user_spec in seq:
80 user = self._get_user(user_spec)
81 if user is None or user.username == User.DEFAULT_USER:
82 raise UserInvalidException(user_spec)
83 yield user
84
74 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
85 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
75 revisions, reviewers, title, description=None):
86 revisions, reviewers, title, description=None):
76 from kallithea.model.changeset_status import ChangesetStatusModel
87 from kallithea.model.changeset_status import ChangesetStatusModel
77
88
78 created_by_user = self._get_user(created_by)
89 created_by_user = self._get_user(created_by)
79 org_repo = self._get_repo(org_repo)
90 org_repo = self._get_repo(org_repo)
80 other_repo = self._get_repo(other_repo)
91 other_repo = self._get_repo(other_repo)
81
92
82 new = PullRequest()
93 new = PullRequest()
83 new.org_repo = org_repo
94 new.org_repo = org_repo
84 new.org_ref = org_ref
95 new.org_ref = org_ref
85 new.other_repo = other_repo
96 new.other_repo = other_repo
86 new.other_ref = other_ref
97 new.other_ref = other_ref
87 new.revisions = revisions
98 new.revisions = revisions
88 new.title = title
99 new.title = title
89 new.description = description
100 new.description = description
90 new.owner = created_by_user
101 new.owner = created_by_user
91 Session().add(new)
102 Session().add(new)
92 Session().flush()
103 Session().flush()
93
104
94 #reset state to under-review
105 #reset state to under-review
95 from kallithea.model.comment import ChangesetCommentsModel
106 from kallithea.model.comment import ChangesetCommentsModel
96 comment = ChangesetCommentsModel().create(
107 comment = ChangesetCommentsModel().create(
97 text=u'',
108 text=u'',
98 repo=org_repo,
109 repo=org_repo,
99 user=new.owner,
110 user=new.owner,
100 pull_request=new,
111 pull_request=new,
101 send_email=False,
112 send_email=False,
102 status_change=ChangesetStatus.STATUS_UNDER_REVIEW,
113 status_change=ChangesetStatus.STATUS_UNDER_REVIEW,
103 )
114 )
104 ChangesetStatusModel().set_status(
115 ChangesetStatusModel().set_status(
105 org_repo,
116 org_repo,
106 ChangesetStatus.STATUS_UNDER_REVIEW,
117 ChangesetStatus.STATUS_UNDER_REVIEW,
107 new.owner,
118 new.owner,
108 comment,
119 comment,
109 pull_request=new
120 pull_request=new
110 )
121 )
111
122
123 reviewers = set(self._get_valid_reviewers(reviewers))
112 mention_recipients = extract_mentioned_users(new.description)
124 mention_recipients = extract_mentioned_users(new.description)
113 self.__add_reviewers(created_by_user, new, reviewers, mention_recipients)
125 self.__add_reviewers(created_by_user, new, reviewers, mention_recipients)
114
126
115 return new
127 return new
116
128
117 def __add_reviewers(self, user, pr, reviewers, mention_recipients=None):
129 def __add_reviewers(self, user, pr, reviewers, mention_recipients):
130 # reviewers and mention_recipients should be sets of User objects.
118 #members
131 #members
119 for member in set(reviewers):
132 for reviewer in reviewers:
120 _usr = self._get_user(member)
133 reviewer = PullRequestReviewers(reviewer, pr)
121 if _usr is None:
122 raise UserInvalidException(member)
123 reviewer = PullRequestReviewers(_usr, pr)
124 Session().add(reviewer)
134 Session().add(reviewer)
125
135
126 revision_data = [(x.raw_id, x.message)
136 revision_data = [(x.raw_id, x.message)
127 for x in map(pr.org_repo.get_changeset, pr.revisions)]
137 for x in map(pr.org_repo.get_changeset, pr.revisions)]
128
138
129 #notification to reviewers
139 #notification to reviewers
130 pr_url = pr.url(canonical=True)
140 pr_url = pr.url(canonical=True)
131 threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name,
141 threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name,
132 pr.pull_request_id,
142 pr.pull_request_id,
133 h.canonical_hostname())]
143 h.canonical_hostname())]
134 subject = safe_unicode(
144 subject = safe_unicode(
135 h.link_to(
145 h.link_to(
136 _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') % \
146 _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') % \
137 {'user': user.username,
147 {'user': user.username,
138 'pr_title': pr.title,
148 'pr_title': pr.title,
139 'pr_nice_id': pr.nice_id()},
149 'pr_nice_id': pr.nice_id()},
140 pr_url)
150 pr_url)
141 )
151 )
142 body = pr.description
152 body = pr.description
143 _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':')
153 _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':')
144 email_kwargs = {
154 email_kwargs = {
145 'pr_title': pr.title,
155 'pr_title': pr.title,
146 'pr_user_created': user.full_name_and_username,
156 'pr_user_created': user.full_name_and_username,
147 'pr_repo_url': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name),
157 'pr_repo_url': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name),
148 'pr_url': pr_url,
158 'pr_url': pr_url,
149 'pr_revisions': revision_data,
159 'pr_revisions': revision_data,
150 'repo_name': pr.other_repo.repo_name,
160 'repo_name': pr.other_repo.repo_name,
151 'org_repo_name': pr.org_repo.repo_name,
161 'org_repo_name': pr.org_repo.repo_name,
152 'pr_nice_id': pr.nice_id(),
162 'pr_nice_id': pr.nice_id(),
153 'ref': org_ref_name,
163 'ref': org_ref_name,
154 'pr_username': user.username,
164 'pr_username': user.username,
155 'threading': threading,
165 'threading': threading,
156 'is_mention': False,
166 'is_mention': False,
157 }
167 }
158 if reviewers:
168 if reviewers:
159 NotificationModel().create(created_by=user, subject=subject, body=body,
169 NotificationModel().create(created_by=user, subject=subject, body=body,
160 recipients=reviewers,
170 recipients=reviewers,
161 type_=Notification.TYPE_PULL_REQUEST,
171 type_=Notification.TYPE_PULL_REQUEST,
162 email_kwargs=email_kwargs)
172 email_kwargs=email_kwargs)
163
173
164 if mention_recipients:
174 if mention_recipients:
165 mention_recipients.difference_update(reviewers)
175 mention_recipients.difference_update(reviewers)
166 if mention_recipients:
176 if mention_recipients:
167 email_kwargs['is_mention'] = True
177 email_kwargs['is_mention'] = True
168 subject = _('[Mention]') + ' ' + subject
178 subject = _('[Mention]') + ' ' + subject
169 NotificationModel().create(created_by=user, subject=subject, body=body,
179 NotificationModel().create(created_by=user, subject=subject, body=body,
170 recipients=mention_recipients,
180 recipients=mention_recipients,
171 type_=Notification.TYPE_PULL_REQUEST,
181 type_=Notification.TYPE_PULL_REQUEST,
172 email_kwargs=email_kwargs)
182 email_kwargs=email_kwargs)
173
183
174 def mention_from_description(self, user, pr, old_description=''):
184 def mention_from_description(self, user, pr, old_description=''):
175 mention_recipients = (extract_mentioned_users(pr.description) -
185 mention_recipients = (extract_mentioned_users(pr.description) -
176 extract_mentioned_users(old_description))
186 extract_mentioned_users(old_description))
177
187
178 log.debug("Mentioning %s", mention_recipients)
188 log.debug("Mentioning %s", mention_recipients)
179 self.__add_reviewers(user, pr, [], mention_recipients)
189 self.__add_reviewers(user, pr, set(), mention_recipients)
180
190
181 def update_reviewers(self, user, pull_request, reviewers_ids):
191 def update_reviewers(self, user, pull_request, reviewers_ids):
182 reviewers_ids = set(reviewers_ids)
192 reviewers_ids = set(reviewers_ids)
183 pull_request = self.__get_pull_request(pull_request)
193 pull_request = self.__get_pull_request(pull_request)
184 current_reviewers = PullRequestReviewers.query() \
194 current_reviewers = PullRequestReviewers.query() \
185 .filter(PullRequestReviewers.pull_request==
195 .filter(PullRequestReviewers.pull_request==
186 pull_request) \
196 pull_request) \
187 .all()
197 .all()
188 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
198 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
189
199
190 to_add = reviewers_ids.difference(current_reviewers_ids)
200 to_add = reviewers_ids.difference(current_reviewers_ids)
191 to_remove = current_reviewers_ids.difference(reviewers_ids)
201 to_remove = current_reviewers_ids.difference(reviewers_ids)
192
202
193 log.debug("Adding %s reviewers", to_add)
203 log.debug("Adding %s reviewers", to_add)
194 self.__add_reviewers(user, pull_request, to_add)
204 self.__add_reviewers(user, pull_request, set(self._get_valid_reviewers(to_add)), set())
195
205
196 log.debug("Removing %s reviewers", to_remove)
206 log.debug("Removing %s reviewers", to_remove)
197 for uid in to_remove:
207 for uid in to_remove:
198 reviewer = PullRequestReviewers.query() \
208 reviewer = PullRequestReviewers.query() \
199 .filter(PullRequestReviewers.user_id==uid,
209 .filter(PullRequestReviewers.user_id==uid,
200 PullRequestReviewers.pull_request==pull_request) \
210 PullRequestReviewers.pull_request==pull_request) \
201 .scalar()
211 .scalar()
202 if reviewer:
212 if reviewer:
203 Session().delete(reviewer)
213 Session().delete(reviewer)
204
214
205 def delete(self, pull_request):
215 def delete(self, pull_request):
206 pull_request = self.__get_pull_request(pull_request)
216 pull_request = self.__get_pull_request(pull_request)
207 Session().delete(pull_request)
217 Session().delete(pull_request)
208
218
209 def close_pull_request(self, pull_request):
219 def close_pull_request(self, pull_request):
210 pull_request = self.__get_pull_request(pull_request)
220 pull_request = self.__get_pull_request(pull_request)
211 pull_request.status = PullRequest.STATUS_CLOSED
221 pull_request.status = PullRequest.STATUS_CLOSED
212 pull_request.updated_on = datetime.datetime.now()
222 pull_request.updated_on = datetime.datetime.now()
213 Session().add(pull_request)
223 Session().add(pull_request)
General Comments 0
You need to be logged in to leave comments. Login now