##// END OF EJS Templates
pull requests: make title optional - generate one automatically
Mads Kiilerich -
r4058:a2218bdb default
parent child Browse files
Show More
@@ -1,550 +1,552 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.pullrequests
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull requests controller for rhodecode for initializing pull requests
7 7
8 8 :created_on: May 7, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2010-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 import logging
26 26 import traceback
27 27 import formencode
28 28
29 29 from webob.exc import HTTPNotFound, HTTPForbidden
30 30 from collections import defaultdict
31 31 from itertools import groupby
32 32
33 33 from pylons import request, tmpl_context as c, url
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode.lib.compat import json
38 38 from rhodecode.lib.base import BaseRepoController, render
39 39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 40 NotAnonymous
41 41 from rhodecode.lib.helpers import Page
42 42 from rhodecode.lib import helpers as h
43 43 from rhodecode.lib import diffs
44 44 from rhodecode.lib.utils import action_logger, jsonify
45 45 from rhodecode.lib.vcs.utils import safe_str
46 46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 47 from rhodecode.lib.diffs import LimitedDiffContainer
48 48 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment
49 49 from rhodecode.model.pull_request import PullRequestModel
50 50 from rhodecode.model.meta import Session
51 51 from rhodecode.model.repo import RepoModel
52 52 from rhodecode.model.comment import ChangesetCommentsModel
53 53 from rhodecode.model.changeset_status import ChangesetStatusModel
54 54 from rhodecode.model.forms import PullRequestForm
55 55 from rhodecode.lib.utils2 import safe_int
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class PullrequestsController(BaseRepoController):
61 61
62 62 def __before__(self):
63 63 super(PullrequestsController, self).__before__()
64 64 repo_model = RepoModel()
65 65 c.users_array = repo_model.get_users_js()
66 66 c.users_groups_array = repo_model.get_users_groups_js()
67 67
68 68 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
69 69 """return a structure with repo's interesting changesets, suitable for
70 70 the selectors in pullrequest.html
71 71
72 72 rev: a revision that must be in the list somehow and selected by default
73 73 branch: a branch that must be in the list and selected by default - even if closed
74 74 branch_rev: a revision of which peers should be preferred and available."""
75 75 # list named branches that has been merged to this named branch - it should probably merge back
76 76 peers = []
77 77
78 78 if rev:
79 79 rev = safe_str(rev)
80 80
81 81 if branch:
82 82 branch = safe_str(branch)
83 83
84 84 if branch_rev:
85 85 branch_rev = safe_str(branch_rev)
86 86 # not restricting to merge() would also get branch point and be better
87 87 # (especially because it would get the branch point) ... but is currently too expensive
88 88 otherbranches = {}
89 89 for i in repo._repo.revs(
90 90 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
91 91 branch_rev, branch_rev):
92 92 cs = repo.get_changeset(i)
93 93 otherbranches[cs.branch] = cs.raw_id
94 94 for abranch, node in otherbranches.iteritems():
95 95 selected = 'branch:%s:%s' % (abranch, node)
96 96 peers.append((selected, abranch))
97 97
98 98 selected = None
99 99
100 100 branches = []
101 101 for abranch, branchrev in repo.branches.iteritems():
102 102 n = 'branch:%s:%s' % (abranch, branchrev)
103 103 branches.append((n, abranch))
104 104 if rev == branchrev:
105 105 selected = n
106 106 if branch == abranch:
107 107 selected = n
108 108 branch = None
109 109 if branch: # branch not in list - it is probably closed
110 110 revs = repo._repo.revs('max(branch(%s))', branch)
111 111 if revs:
112 112 cs = repo.get_changeset(revs[0])
113 113 selected = 'branch:%s:%s' % (branch, cs.raw_id)
114 114 branches.append((selected, branch))
115 115
116 116 bookmarks = []
117 117 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
118 118 n = 'book:%s:%s' % (bookmark, bookmarkrev)
119 119 bookmarks.append((n, bookmark))
120 120 if rev == bookmarkrev:
121 121 selected = n
122 122
123 123 tags = []
124 124 for tag, tagrev in repo.tags.iteritems():
125 125 n = 'tag:%s:%s' % (tag, tagrev)
126 126 tags.append((n, tag))
127 127 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
128 128 selected = n
129 129
130 130 # prio 1: rev was selected as existing entry above
131 131
132 132 # prio 2: create special entry for rev; rev _must_ be used
133 133 specials = []
134 134 if rev and selected is None:
135 135 selected = 'rev:%s:%s' % (rev, rev)
136 136 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
137 137
138 138 # prio 3: most recent peer branch
139 139 if peers and not selected:
140 140 selected = peers[0][0][0]
141 141
142 142 # prio 4: tip revision
143 143 if not selected:
144 144 selected = 'tag:tip:%s' % repo.tags['tip']
145 145
146 146 groups = [(specials, _("Special")),
147 147 (peers, _("Peer branches")),
148 148 (bookmarks, _("Bookmarks")),
149 149 (branches, _("Branches")),
150 150 (tags, _("Tags")),
151 151 ]
152 152 return [g for g in groups if g[0]], selected
153 153
154 154 def _get_is_allowed_change_status(self, pull_request):
155 155 owner = self.rhodecode_user.user_id == pull_request.user_id
156 156 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
157 157 pull_request.reviewers]
158 158 return (self.rhodecode_user.admin or owner or reviewer)
159 159
160 160 def _load_compare_data(self, pull_request, enable_comments=True):
161 161 """
162 162 Load context data needed for generating compare diff
163 163
164 164 :param pull_request:
165 165 """
166 166 org_repo = pull_request.org_repo
167 167 (org_ref_type,
168 168 org_ref_name,
169 169 org_ref_rev) = pull_request.org_ref.split(':')
170 170
171 171 other_repo = org_repo
172 172 (other_ref_type,
173 173 other_ref_name,
174 174 other_ref_rev) = pull_request.other_ref.split(':')
175 175
176 176 # despite opening revisions for bookmarks/branches/tags, we always
177 177 # convert this to rev to prevent changes after bookmark or branch change
178 178 org_ref = ('rev', org_ref_rev)
179 179 other_ref = ('rev', other_ref_rev)
180 180
181 181 c.org_repo = org_repo
182 182 c.other_repo = other_repo
183 183
184 184 c.fulldiff = fulldiff = request.GET.get('fulldiff')
185 185
186 186 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
187 187
188 188 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
189 189
190 190 c.org_ref = org_ref[1]
191 191 c.org_ref_type = org_ref[0]
192 192 c.other_ref = other_ref[1]
193 193 c.other_ref_type = other_ref[0]
194 194
195 195 diff_limit = self.cut_off_limit if not fulldiff else None
196 196
197 197 # we swap org/other ref since we run a simple diff on one repo
198 198 log.debug('running diff between %s and %s in %s'
199 199 % (other_ref, org_ref, org_repo.scm_instance.path))
200 200 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
201 201
202 202 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
203 203 diff_limit=diff_limit)
204 204 _parsed = diff_processor.prepare()
205 205
206 206 c.limited_diff = False
207 207 if isinstance(_parsed, LimitedDiffContainer):
208 208 c.limited_diff = True
209 209
210 210 c.files = []
211 211 c.changes = {}
212 212 c.lines_added = 0
213 213 c.lines_deleted = 0
214 214
215 215 for f in _parsed:
216 216 st = f['stats']
217 217 c.lines_added += st['added']
218 218 c.lines_deleted += st['deleted']
219 219 fid = h.FID('', f['filename'])
220 220 c.files.append([fid, f['operation'], f['filename'], f['stats']])
221 221 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
222 222 parsed_lines=[f])
223 223 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
224 224
225 225 @LoginRequired()
226 226 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
227 227 'repository.admin')
228 228 def show_all(self, repo_name):
229 229 c.from_ = request.GET.get('from_') or ''
230 230 c.closed = request.GET.get('closed') or ''
231 231 c.pull_requests = PullRequestModel().get_all(repo_name, from_=c.from_, closed=c.closed)
232 232 c.repo_name = repo_name
233 233 p = safe_int(request.GET.get('page', 1), 1)
234 234
235 235 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
236 236
237 237 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
238 238
239 239 if request.environ.get('HTTP_X_PARTIAL_XHR'):
240 240 return c.pullrequest_data
241 241
242 242 return render('/pullrequests/pullrequest_show_all.html')
243 243
244 244 @LoginRequired()
245 245 @NotAnonymous()
246 246 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
247 247 'repository.admin')
248 248 def index(self):
249 249 org_repo = c.rhodecode_db_repo
250 250
251 251 if org_repo.scm_instance.alias != 'hg':
252 252 log.error('Review not available for GIT REPOS')
253 253 raise HTTPNotFound
254 254
255 255 try:
256 256 org_repo.scm_instance.get_changeset()
257 257 except EmptyRepositoryError, e:
258 258 h.flash(h.literal(_('There are no changesets yet')),
259 259 category='warning')
260 260 redirect(url('summary_home', repo_name=org_repo.repo_name))
261 261
262 262 org_rev = request.GET.get('rev_end')
263 263 # rev_start is not directly useful - its parent could however be used
264 264 # as default for other and thus give a simple compare view
265 265 #other_rev = request.POST.get('rev_start')
266 266 branch = request.GET.get('branch')
267 267
268 268 c.org_repos = []
269 269 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
270 270 c.default_org_repo = org_repo.repo_name
271 271 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
272 272
273 273 c.other_repos = []
274 274 other_repos_info = {}
275 275
276 276 def add_other_repo(repo, branch_rev=None):
277 277 if repo.repo_name in other_repos_info: # shouldn't happen
278 278 return
279 279 c.other_repos.append((repo.repo_name, repo.repo_name))
280 280 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
281 281 other_repos_info[repo.repo_name] = {
282 282 'user': dict(user_id=repo.user.user_id,
283 283 username=repo.user.username,
284 284 firstname=repo.user.firstname,
285 285 lastname=repo.user.lastname,
286 286 gravatar_link=h.gravatar_url(repo.user.email, 14)),
287 287 'description': repo.description.split('\n', 1)[0],
288 288 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
289 289 }
290 290
291 291 # add org repo to other so we can open pull request against peer branches on itself
292 292 add_other_repo(org_repo, branch_rev=org_rev)
293 293 c.default_other_repo = org_repo.repo_name
294 294
295 295 # gather forks and add to this list ... even though it is rare to
296 296 # request forks to pull from their parent
297 297 for fork in org_repo.forks:
298 298 add_other_repo(fork)
299 299
300 300 # add parents of this fork also, but only if it's not empty
301 301 if org_repo.parent and org_repo.parent.scm_instance.revisions:
302 302 add_other_repo(org_repo.parent)
303 303 c.default_other_repo = org_repo.parent.repo_name
304 304
305 305 c.default_other_repo_info = other_repos_info[c.default_other_repo]
306 306 c.other_repos_info = json.dumps(other_repos_info)
307 307
308 308 return render('/pullrequests/pullrequest.html')
309 309
310 310 @LoginRequired()
311 311 @NotAnonymous()
312 312 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
313 313 'repository.admin')
314 314 def create(self, repo_name):
315 315 repo = RepoModel()._get_repo(repo_name)
316 316 try:
317 317 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
318 318 except formencode.Invalid, errors:
319 319 log.error(traceback.format_exc())
320 320 if errors.error_dict.get('revisions'):
321 321 msg = 'Revisions: %s' % errors.error_dict['revisions']
322 322 elif errors.error_dict.get('pullrequest_title'):
323 323 msg = _('Pull request requires a title with min. 3 chars')
324 324 else:
325 325 msg = _('Error creating pull request')
326 326
327 327 h.flash(msg, 'error')
328 328 return redirect(url('pullrequest_home', repo_name=repo_name))
329 329
330 330 org_repo = _form['org_repo']
331 331 org_ref = _form['org_ref'] # will end with merge_rev but have symbolic name
332 332 other_repo = _form['other_repo']
333 333 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev'] # could be calculated from other_ref ...
334 334 revisions = [x for x in reversed(_form['revisions'])]
335 335 reviewers = _form['review_members']
336 336
337 337 title = _form['pullrequest_title']
338 if not title:
339 title = '%s#%s to %s' % (org_repo, org_ref.split(':', 2)[1], other_repo)
338 340 description = _form['pullrequest_desc']
339 341 try:
340 342 pull_request = PullRequestModel().create(
341 343 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
342 344 other_ref, revisions, reviewers, title, description
343 345 )
344 346 Session().commit()
345 347 h.flash(_('Successfully opened new pull request'),
346 348 category='success')
347 349 except Exception:
348 350 h.flash(_('Error occurred during sending pull request'),
349 351 category='error')
350 352 log.error(traceback.format_exc())
351 353 return redirect(url('pullrequest_home', repo_name=repo_name))
352 354
353 355 return redirect(url('pullrequest_show', repo_name=other_repo,
354 356 pull_request_id=pull_request.pull_request_id))
355 357
356 358 @LoginRequired()
357 359 @NotAnonymous()
358 360 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
359 361 'repository.admin')
360 362 @jsonify
361 363 def update(self, repo_name, pull_request_id):
362 364 pull_request = PullRequest.get_or_404(pull_request_id)
363 365 if pull_request.is_closed():
364 366 raise HTTPForbidden()
365 367 #only owner or admin can update it
366 368 owner = pull_request.author.user_id == c.rhodecode_user.user_id
367 369 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
368 370 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
369 371 request.POST.get('reviewers_ids', '').split(',')))
370 372
371 373 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
372 374 Session().commit()
373 375 return True
374 376 raise HTTPForbidden()
375 377
376 378 @LoginRequired()
377 379 @NotAnonymous()
378 380 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
379 381 'repository.admin')
380 382 @jsonify
381 383 def delete(self, repo_name, pull_request_id):
382 384 pull_request = PullRequest.get_or_404(pull_request_id)
383 385 #only owner can delete it !
384 386 if pull_request.author.user_id == c.rhodecode_user.user_id:
385 387 PullRequestModel().delete(pull_request)
386 388 Session().commit()
387 389 h.flash(_('Successfully deleted pull request'),
388 390 category='success')
389 391 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
390 392 raise HTTPForbidden()
391 393
392 394 @LoginRequired()
393 395 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
394 396 'repository.admin')
395 397 def show(self, repo_name, pull_request_id):
396 398 repo_model = RepoModel()
397 399 c.users_array = repo_model.get_users_js()
398 400 c.users_groups_array = repo_model.get_users_groups_js()
399 401 c.pull_request = PullRequest.get_or_404(pull_request_id)
400 402 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
401 403 cc_model = ChangesetCommentsModel()
402 404 cs_model = ChangesetStatusModel()
403 405 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
404 406 pull_request=c.pull_request,
405 407 with_revisions=True)
406 408
407 409 cs_statuses = defaultdict(list)
408 410 for st in _cs_statuses:
409 411 cs_statuses[st.author.username] += [st]
410 412
411 413 c.pull_request_reviewers = []
412 414 c.pull_request_pending_reviewers = []
413 415 for o in c.pull_request.reviewers:
414 416 st = cs_statuses.get(o.user.username, None)
415 417 if st:
416 418 sorter = lambda k: k.version
417 419 st = [(x, list(y)[0])
418 420 for x, y in (groupby(sorted(st, key=sorter), sorter))]
419 421 else:
420 422 c.pull_request_pending_reviewers.append(o.user)
421 423 c.pull_request_reviewers.append([o.user, st])
422 424
423 425 # pull_requests repo_name we opened it against
424 426 # ie. other_repo must match
425 427 if repo_name != c.pull_request.other_repo.repo_name:
426 428 raise HTTPNotFound
427 429
428 430 # load compare data into template context
429 431 enable_comments = not c.pull_request.is_closed()
430 432 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
431 433
432 434 # inline comments
433 435 c.inline_cnt = 0
434 436 c.inline_comments = cc_model.get_inline_comments(
435 437 c.rhodecode_db_repo.repo_id,
436 438 pull_request=pull_request_id)
437 439 # count inline comments
438 440 for __, lines in c.inline_comments:
439 441 for comments in lines.values():
440 442 c.inline_cnt += len(comments)
441 443 # comments
442 444 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
443 445 pull_request=pull_request_id)
444 446
445 447 # (badly named) pull-request status calculation based on reviewer votes
446 448 c.current_changeset_status = cs_model.calculate_status(
447 449 c.pull_request_reviewers,
448 450 )
449 451 c.changeset_statuses = ChangesetStatus.STATUSES
450 452
451 453 c.as_form = False
452 454 c.ancestor = None # there is one - but right here we don't know which
453 455 return render('/pullrequests/pullrequest_show.html')
454 456
455 457 @LoginRequired()
456 458 @NotAnonymous()
457 459 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
458 460 'repository.admin')
459 461 @jsonify
460 462 def comment(self, repo_name, pull_request_id):
461 463 pull_request = PullRequest.get_or_404(pull_request_id)
462 464 if pull_request.is_closed():
463 465 raise HTTPForbidden()
464 466
465 467 status = request.POST.get('changeset_status')
466 468 change_status = request.POST.get('change_changeset_status')
467 469 text = request.POST.get('text')
468 470 close_pr = request.POST.get('save_close')
469 471
470 472 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
471 473 if status and change_status and allowed_to_change_status:
472 474 _def = (_('Status change -> %s')
473 475 % ChangesetStatus.get_status_lbl(status))
474 476 if close_pr:
475 477 _def = _('Closing with') + ' ' + _def
476 478 text = text or _def
477 479 comm = ChangesetCommentsModel().create(
478 480 text=text,
479 481 repo=c.rhodecode_db_repo.repo_id,
480 482 user=c.rhodecode_user.user_id,
481 483 pull_request=pull_request_id,
482 484 f_path=request.POST.get('f_path'),
483 485 line_no=request.POST.get('line'),
484 486 status_change=(ChangesetStatus.get_status_lbl(status)
485 487 if status and change_status
486 488 and allowed_to_change_status else None),
487 489 closing_pr=close_pr
488 490 )
489 491
490 492 action_logger(self.rhodecode_user,
491 493 'user_commented_pull_request:%s' % pull_request_id,
492 494 c.rhodecode_db_repo, self.ip_addr, self.sa)
493 495
494 496 if allowed_to_change_status:
495 497 # get status if set !
496 498 if status and change_status:
497 499 ChangesetStatusModel().set_status(
498 500 c.rhodecode_db_repo.repo_id,
499 501 status,
500 502 c.rhodecode_user.user_id,
501 503 comm,
502 504 pull_request=pull_request_id
503 505 )
504 506
505 507 if close_pr:
506 508 if status in ['rejected', 'approved']:
507 509 PullRequestModel().close_pull_request(pull_request_id)
508 510 action_logger(self.rhodecode_user,
509 511 'user_closed_pull_request:%s' % pull_request_id,
510 512 c.rhodecode_db_repo, self.ip_addr, self.sa)
511 513 else:
512 514 h.flash(_('Closing pull request on other statuses than '
513 515 'rejected or approved forbidden'),
514 516 category='warning')
515 517
516 518 Session().commit()
517 519
518 520 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
519 521 return redirect(h.url('pullrequest_show', repo_name=repo_name,
520 522 pull_request_id=pull_request_id))
521 523
522 524 data = {
523 525 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
524 526 }
525 527 if comm:
526 528 c.co = comm
527 529 data.update(comm.get_dict())
528 530 data.update({'rendered_text':
529 531 render('changeset/changeset_comment_block.html')})
530 532
531 533 return data
532 534
533 535 @LoginRequired()
534 536 @NotAnonymous()
535 537 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
536 538 'repository.admin')
537 539 @jsonify
538 540 def delete_comment(self, repo_name, comment_id):
539 541 co = ChangesetComment.get(comment_id)
540 542 if co.pull_request.is_closed():
541 543 #don't allow deleting comments on closed pull request
542 544 raise HTTPForbidden()
543 545
544 546 owner = co.author.user_id == c.rhodecode_user.user_id
545 547 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
546 548 ChangesetCommentsModel().delete(comment=co)
547 549 Session().commit()
548 550 return True
549 551 else:
550 552 raise HTTPForbidden()
@@ -1,439 +1,439 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import logging
23 23
24 24 import formencode
25 25 from formencode import All
26 26
27 27 from pylons.i18n.translation import _
28 28
29 29 from rhodecode.model import validators as v
30 30 from rhodecode import BACKENDS
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class LoginForm(formencode.Schema):
36 36 allow_extra_fields = True
37 37 filter_extra_fields = True
38 38 username = v.UnicodeString(
39 39 strip=True,
40 40 min=1,
41 41 not_empty=True,
42 42 messages={
43 43 'empty': _(u'Please enter a login'),
44 44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 45 )
46 46
47 47 password = v.UnicodeString(
48 48 strip=False,
49 49 min=3,
50 50 not_empty=True,
51 51 messages={
52 52 'empty': _(u'Please enter a password'),
53 53 'tooShort': _(u'Enter %(min)i characters or more')}
54 54 )
55 55
56 56 remember = v.StringBoolean(if_missing=False)
57 57
58 58 chained_validators = [v.ValidAuth()]
59 59
60 60
61 61 def UserForm(edit=False, old_data={}):
62 62 class _UserForm(formencode.Schema):
63 63 allow_extra_fields = True
64 64 filter_extra_fields = True
65 65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 66 v.ValidUsername(edit, old_data))
67 67 if edit:
68 68 new_password = All(
69 69 v.ValidPassword(),
70 70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 71 )
72 72 password_confirmation = All(
73 73 v.ValidPassword(),
74 74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 75 )
76 76 admin = v.StringBoolean(if_missing=False)
77 77 else:
78 78 password = All(
79 79 v.ValidPassword(),
80 80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 81 )
82 82 password_confirmation = All(
83 83 v.ValidPassword(),
84 84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 85 )
86 86
87 87 active = v.StringBoolean(if_missing=False)
88 88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91 91
92 92 chained_validators = [v.ValidPasswordsMatch()]
93 93
94 94 return _UserForm
95 95
96 96
97 97 def UserGroupForm(edit=False, old_data={}, available_members=[]):
98 98 class _UserGroupForm(formencode.Schema):
99 99 allow_extra_fields = True
100 100 filter_extra_fields = True
101 101
102 102 users_group_name = All(
103 103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 104 v.ValidUserGroup(edit, old_data)
105 105 )
106 106
107 107 users_group_active = v.StringBoolean(if_missing=False)
108 108
109 109 if edit:
110 110 users_group_members = v.OneOf(
111 111 available_members, hideList=False, testValueList=True,
112 112 if_missing=None, not_empty=False
113 113 )
114 114
115 115 return _UserGroupForm
116 116
117 117
118 118 def ReposGroupForm(edit=False, old_data={}, available_groups=[],
119 119 can_create_in_root=False):
120 120 class _ReposGroupForm(formencode.Schema):
121 121 allow_extra_fields = True
122 122 filter_extra_fields = False
123 123
124 124 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
125 125 v.SlugifyName())
126 126 group_description = v.UnicodeString(strip=True, min=1,
127 127 not_empty=False)
128 128 if edit:
129 129 #FIXME: do a special check that we cannot move a group to one of
130 130 #it's children
131 131 pass
132 132 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
133 133 v.OneOf(available_groups, hideList=False,
134 134 testValueList=True,
135 135 if_missing=None, not_empty=True))
136 136 enable_locking = v.StringBoolean(if_missing=False)
137 137 chained_validators = [v.ValidReposGroup(edit, old_data)]
138 138
139 139 return _ReposGroupForm
140 140
141 141
142 142 def RegisterForm(edit=False, old_data={}):
143 143 class _RegisterForm(formencode.Schema):
144 144 allow_extra_fields = True
145 145 filter_extra_fields = True
146 146 username = All(
147 147 v.ValidUsername(edit, old_data),
148 148 v.UnicodeString(strip=True, min=1, not_empty=True)
149 149 )
150 150 password = All(
151 151 v.ValidPassword(),
152 152 v.UnicodeString(strip=False, min=6, not_empty=True)
153 153 )
154 154 password_confirmation = All(
155 155 v.ValidPassword(),
156 156 v.UnicodeString(strip=False, min=6, not_empty=True)
157 157 )
158 158 active = v.StringBoolean(if_missing=False)
159 159 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
160 160 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
161 161 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
162 162
163 163 chained_validators = [v.ValidPasswordsMatch()]
164 164
165 165 return _RegisterForm
166 166
167 167
168 168 def PasswordResetForm():
169 169 class _PasswordResetForm(formencode.Schema):
170 170 allow_extra_fields = True
171 171 filter_extra_fields = True
172 172 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
173 173 return _PasswordResetForm
174 174
175 175
176 176 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
177 177 repo_groups=[], landing_revs=[]):
178 178 class _RepoForm(formencode.Schema):
179 179 allow_extra_fields = True
180 180 filter_extra_fields = False
181 181 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
182 182 v.SlugifyName())
183 183 repo_group = All(v.CanWriteGroup(old_data),
184 184 v.OneOf(repo_groups, hideList=True))
185 185 repo_type = v.OneOf(supported_backends, required=False,
186 186 if_missing=old_data.get('repo_type'))
187 187 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
188 188 repo_private = v.StringBoolean(if_missing=False)
189 189 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
190 190 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
191 191
192 192 repo_enable_statistics = v.StringBoolean(if_missing=False)
193 193 repo_enable_downloads = v.StringBoolean(if_missing=False)
194 194 repo_enable_locking = v.StringBoolean(if_missing=False)
195 195
196 196 if edit:
197 197 #this is repo owner
198 198 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
199 199
200 200 chained_validators = [v.ValidCloneUri(),
201 201 v.ValidRepoName(edit, old_data)]
202 202 return _RepoForm
203 203
204 204
205 205 def RepoPermsForm():
206 206 class _RepoPermsForm(formencode.Schema):
207 207 allow_extra_fields = True
208 208 filter_extra_fields = False
209 209 chained_validators = [v.ValidPerms(type_='repo')]
210 210 return _RepoPermsForm
211 211
212 212
213 213 def RepoGroupPermsForm():
214 214 class _RepoGroupPermsForm(formencode.Schema):
215 215 allow_extra_fields = True
216 216 filter_extra_fields = False
217 217 recursive = v.StringBoolean(if_missing=False)
218 218 chained_validators = [v.ValidPerms(type_='repo_group')]
219 219 return _RepoGroupPermsForm
220 220
221 221
222 222 def UserGroupPermsForm():
223 223 class _UserPermsForm(formencode.Schema):
224 224 allow_extra_fields = True
225 225 filter_extra_fields = False
226 226 chained_validators = [v.ValidPerms(type_='user_group')]
227 227 return _UserPermsForm
228 228
229 229
230 230 def RepoFieldForm():
231 231 class _RepoFieldForm(formencode.Schema):
232 232 filter_extra_fields = True
233 233 allow_extra_fields = True
234 234
235 235 new_field_key = All(v.FieldKey(),
236 236 v.UnicodeString(strip=True, min=3, not_empty=True))
237 237 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
238 238 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
239 239 if_missing='str')
240 240 new_field_label = v.UnicodeString(not_empty=False)
241 241 new_field_desc = v.UnicodeString(not_empty=False)
242 242
243 243 return _RepoFieldForm
244 244
245 245
246 246 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
247 247 repo_groups=[], landing_revs=[]):
248 248 class _RepoForkForm(formencode.Schema):
249 249 allow_extra_fields = True
250 250 filter_extra_fields = False
251 251 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
252 252 v.SlugifyName())
253 253 repo_group = All(v.CanWriteGroup(),
254 254 v.OneOf(repo_groups, hideList=True))
255 255 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
256 256 description = v.UnicodeString(strip=True, min=1, not_empty=True)
257 257 private = v.StringBoolean(if_missing=False)
258 258 copy_permissions = v.StringBoolean(if_missing=False)
259 259 update_after_clone = v.StringBoolean(if_missing=False)
260 260 fork_parent_id = v.UnicodeString()
261 261 chained_validators = [v.ValidForkName(edit, old_data)]
262 262 landing_rev = v.OneOf(landing_revs, hideList=True)
263 263
264 264 return _RepoForkForm
265 265
266 266
267 267 def ApplicationSettingsForm():
268 268 class _ApplicationSettingsForm(formencode.Schema):
269 269 allow_extra_fields = True
270 270 filter_extra_fields = False
271 271 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
272 272 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
273 273 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
274 274
275 275 return _ApplicationSettingsForm
276 276
277 277
278 278 def ApplicationVisualisationForm():
279 279 class _ApplicationVisualisationForm(formencode.Schema):
280 280 allow_extra_fields = True
281 281 filter_extra_fields = False
282 282 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
283 283 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
284 284 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
285 285
286 286 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
287 287 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
288 288 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
289 289 rhodecode_show_version = v.StringBoolean(if_missing=False)
290 290
291 291 return _ApplicationVisualisationForm
292 292
293 293
294 294 def ApplicationUiSettingsForm():
295 295 class _ApplicationUiSettingsForm(formencode.Schema):
296 296 allow_extra_fields = True
297 297 filter_extra_fields = False
298 298 web_push_ssl = v.StringBoolean(if_missing=False)
299 299 paths_root_path = All(
300 300 v.ValidPath(),
301 301 v.UnicodeString(strip=True, min=1, not_empty=True)
302 302 )
303 303 hooks_changegroup_update = v.StringBoolean(if_missing=False)
304 304 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
305 305 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
306 306 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
307 307
308 308 extensions_largefiles = v.StringBoolean(if_missing=False)
309 309 extensions_hgsubversion = v.StringBoolean(if_missing=False)
310 310 extensions_hggit = v.StringBoolean(if_missing=False)
311 311
312 312 return _ApplicationUiSettingsForm
313 313
314 314
315 315 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
316 316 user_group_perms_choices, create_choices,
317 317 repo_group_create_choices, user_group_create_choices,
318 318 fork_choices, register_choices, extern_activate_choices):
319 319 class _DefaultPermissionsForm(formencode.Schema):
320 320 allow_extra_fields = True
321 321 filter_extra_fields = True
322 322 overwrite_default_repo = v.StringBoolean(if_missing=False)
323 323 overwrite_default_group = v.StringBoolean(if_missing=False)
324 324 overwrite_default_user_group = v.StringBoolean(if_missing=False)
325 325 anonymous = v.StringBoolean(if_missing=False)
326 326 default_repo_perm = v.OneOf(repo_perms_choices)
327 327 default_group_perm = v.OneOf(group_perms_choices)
328 328 default_user_group_perm = v.OneOf(user_group_perms_choices)
329 329
330 330 default_repo_create = v.OneOf(create_choices)
331 331 default_user_group_create = v.OneOf(user_group_create_choices)
332 332 #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet
333 333 default_fork = v.OneOf(fork_choices)
334 334
335 335 default_register = v.OneOf(register_choices)
336 336 default_extern_activate = v.OneOf(extern_activate_choices)
337 337 return _DefaultPermissionsForm
338 338
339 339
340 340 def CustomDefaultPermissionsForm():
341 341 class _CustomDefaultPermissionsForm(formencode.Schema):
342 342 filter_extra_fields = True
343 343 allow_extra_fields = True
344 344 inherit_default_permissions = v.StringBoolean(if_missing=False)
345 345
346 346 create_repo_perm = v.StringBoolean(if_missing=False)
347 347 create_user_group_perm = v.StringBoolean(if_missing=False)
348 348 #create_repo_group_perm Impl. later
349 349
350 350 fork_repo_perm = v.StringBoolean(if_missing=False)
351 351
352 352 return _CustomDefaultPermissionsForm
353 353
354 354
355 355 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
356 356 class _DefaultsForm(formencode.Schema):
357 357 allow_extra_fields = True
358 358 filter_extra_fields = True
359 359 default_repo_type = v.OneOf(supported_backends)
360 360 default_repo_private = v.StringBoolean(if_missing=False)
361 361 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
362 362 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
363 363 default_repo_enable_locking = v.StringBoolean(if_missing=False)
364 364
365 365 return _DefaultsForm
366 366
367 367
368 368 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
369 369 tls_kind_choices):
370 370 class _LdapSettingsForm(formencode.Schema):
371 371 allow_extra_fields = True
372 372 filter_extra_fields = True
373 373 #pre_validators = [LdapLibValidator]
374 374 ldap_active = v.StringBoolean(if_missing=False)
375 375 ldap_host = v.UnicodeString(strip=True,)
376 376 ldap_port = v.Number(strip=True,)
377 377 ldap_tls_kind = v.OneOf(tls_kind_choices)
378 378 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
379 379 ldap_dn_user = v.UnicodeString(strip=True,)
380 380 ldap_dn_pass = v.UnicodeString(strip=True,)
381 381 ldap_base_dn = v.UnicodeString(strip=True,)
382 382 ldap_filter = v.UnicodeString(strip=True,)
383 383 ldap_search_scope = v.OneOf(search_scope_choices)
384 384 ldap_attr_login = v.AttrLoginValidator()(not_empty=True)
385 385 ldap_attr_firstname = v.UnicodeString(strip=True,)
386 386 ldap_attr_lastname = v.UnicodeString(strip=True,)
387 387 ldap_attr_email = v.UnicodeString(strip=True,)
388 388
389 389 return _LdapSettingsForm
390 390
391 391
392 392 def UserExtraEmailForm():
393 393 class _UserExtraEmailForm(formencode.Schema):
394 394 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
395 395 return _UserExtraEmailForm
396 396
397 397
398 398 def UserExtraIpForm():
399 399 class _UserExtraIpForm(formencode.Schema):
400 400 ip = v.ValidIp()(not_empty=True)
401 401 return _UserExtraIpForm
402 402
403 403
404 404 def PullRequestForm(repo_id):
405 405 class _PullRequestForm(formencode.Schema):
406 406 allow_extra_fields = True
407 407 filter_extra_fields = True
408 408
409 409 user = v.UnicodeString(strip=True, required=True)
410 410 org_repo = v.UnicodeString(strip=True, required=True)
411 411 org_ref = v.UnicodeString(strip=True, required=True)
412 412 other_repo = v.UnicodeString(strip=True, required=True)
413 413 other_ref = v.UnicodeString(strip=True, required=True)
414 414 revisions = All(#v.NotReviewedRevisions(repo_id)(),
415 415 v.UniqueList(not_empty=True))
416 416 review_members = v.UniqueList(not_empty=True)
417 417
418 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
418 pullrequest_title = v.UnicodeString(strip=True, required=True)
419 419 pullrequest_desc = v.UnicodeString(strip=True, required=False)
420 420
421 421 ancestor_rev = v.UnicodeString(strip=True, required=True)
422 422 merge_rev = v.UnicodeString(strip=True, required=True)
423 423
424 424 return _PullRequestForm
425 425
426 426
427 427 def GistForm(lifetime_options):
428 428 class _GistForm(formencode.Schema):
429 429
430 430 filename = All(v.BasePath()(),
431 431 v.UnicodeString(strip=True, required=False))
432 432 description = v.UnicodeString(required=False, if_missing='')
433 433 lifetime = v.OneOf(lifetime_options)
434 434 mimetype = v.UnicodeString(required=False, if_missing=None)
435 435 content = v.UnicodeString(required=True, not_empty=True)
436 436 public = v.UnicodeString(required=False, if_missing='')
437 437 private = v.UnicodeString(required=False, if_missing='')
438 438
439 439 return _GistForm
General Comments 0
You need to be logged in to leave comments. Login now