##// END OF EJS Templates
Enabled inline comments in pull-requests
marcink -
r2489:a0adf8db beta
parent child Browse files
Show More
@@ -1,558 +1,563 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11 11 # prefix for non repository related links needs to be prefixed with `/`
12 12 ADMIN_PREFIX = '/_admin'
13 13
14 14
15 15 def make_map(config):
16 16 """Create, configure and return the routes Mapper"""
17 17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 18 always_scan=config['debug'])
19 19 rmap.minimization = False
20 20 rmap.explicit = False
21 21
22 22 from rhodecode.lib.utils import is_valid_repo
23 23 from rhodecode.lib.utils import is_valid_repos_group
24 24
25 25 def check_repo(environ, match_dict):
26 26 """
27 27 check for valid repository for proper 404 handling
28 28
29 29 :param environ:
30 30 :param match_dict:
31 31 """
32 32 from rhodecode.model.db import Repository
33 33 repo_name = match_dict.get('repo_name')
34 34
35 35 try:
36 36 by_id = repo_name.split('_')
37 37 if len(by_id) == 2 and by_id[1].isdigit():
38 38 repo_name = Repository.get(by_id[1]).repo_name
39 39 match_dict['repo_name'] = repo_name
40 40 except:
41 41 pass
42 42
43 43 return is_valid_repo(repo_name, config['base_path'])
44 44
45 45 def check_group(environ, match_dict):
46 46 """
47 47 check for valid repositories group for proper 404 handling
48 48
49 49 :param environ:
50 50 :param match_dict:
51 51 """
52 52 repos_group_name = match_dict.get('group_name')
53 53
54 54 return is_valid_repos_group(repos_group_name, config['base_path'])
55 55
56 56 def check_int(environ, match_dict):
57 57 return match_dict.get('id').isdigit()
58 58
59 59 # The ErrorController route (handles 404/500 error pages); it should
60 60 # likely stay at the top, ensuring it can always be resolved
61 61 rmap.connect('/error/{action}', controller='error')
62 62 rmap.connect('/error/{action}/{id}', controller='error')
63 63
64 64 #==========================================================================
65 65 # CUSTOM ROUTES HERE
66 66 #==========================================================================
67 67
68 68 #MAIN PAGE
69 69 rmap.connect('home', '/', controller='home', action='index')
70 70 rmap.connect('repo_switcher', '/repos', controller='home',
71 71 action='repo_switcher')
72 72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
73 73 controller='home', action='branch_tag_switcher')
74 74 rmap.connect('bugtracker',
75 75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
76 76 _static=True)
77 77 rmap.connect('rst_help',
78 78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 79 _static=True)
80 80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
81 81
82 82 #ADMIN REPOSITORY REST ROUTES
83 83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
84 84 controller='admin/repos') as m:
85 85 m.connect("repos", "/repos",
86 86 action="create", conditions=dict(method=["POST"]))
87 87 m.connect("repos", "/repos",
88 88 action="index", conditions=dict(method=["GET"]))
89 89 m.connect("formatted_repos", "/repos.{format}",
90 90 action="index",
91 91 conditions=dict(method=["GET"]))
92 92 m.connect("new_repo", "/repos/new",
93 93 action="new", conditions=dict(method=["GET"]))
94 94 m.connect("formatted_new_repo", "/repos/new.{format}",
95 95 action="new", conditions=dict(method=["GET"]))
96 96 m.connect("/repos/{repo_name:.*}",
97 97 action="update", conditions=dict(method=["PUT"],
98 98 function=check_repo))
99 99 m.connect("/repos/{repo_name:.*}",
100 100 action="delete", conditions=dict(method=["DELETE"],
101 101 function=check_repo))
102 102 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
103 103 action="edit", conditions=dict(method=["GET"],
104 104 function=check_repo))
105 105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
106 106 action="edit", conditions=dict(method=["GET"],
107 107 function=check_repo))
108 108 m.connect("repo", "/repos/{repo_name:.*}",
109 109 action="show", conditions=dict(method=["GET"],
110 110 function=check_repo))
111 111 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
112 112 action="show", conditions=dict(method=["GET"],
113 113 function=check_repo))
114 114 #ajax delete repo perm user
115 115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
116 116 action="delete_perm_user",
117 117 conditions=dict(method=["DELETE"], function=check_repo))
118 118
119 119 #ajax delete repo perm users_group
120 120 m.connect('delete_repo_users_group',
121 121 "/repos_delete_users_group/{repo_name:.*}",
122 122 action="delete_perm_users_group",
123 123 conditions=dict(method=["DELETE"], function=check_repo))
124 124
125 125 #settings actions
126 126 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
127 127 action="repo_stats", conditions=dict(method=["DELETE"],
128 128 function=check_repo))
129 129 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
130 130 action="repo_cache", conditions=dict(method=["DELETE"],
131 131 function=check_repo))
132 132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}",
133 133 action="repo_public_journal", conditions=dict(method=["PUT"],
134 134 function=check_repo))
135 135 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
136 136 action="repo_pull", conditions=dict(method=["PUT"],
137 137 function=check_repo))
138 138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}",
139 139 action="repo_as_fork", conditions=dict(method=["PUT"],
140 140 function=check_repo))
141 141
142 142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
143 143 controller='admin/repos_groups') as m:
144 144 m.connect("repos_groups", "/repos_groups",
145 145 action="create", conditions=dict(method=["POST"]))
146 146 m.connect("repos_groups", "/repos_groups",
147 147 action="index", conditions=dict(method=["GET"]))
148 148 m.connect("formatted_repos_groups", "/repos_groups.{format}",
149 149 action="index", conditions=dict(method=["GET"]))
150 150 m.connect("new_repos_group", "/repos_groups/new",
151 151 action="new", conditions=dict(method=["GET"]))
152 152 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
153 153 action="new", conditions=dict(method=["GET"]))
154 154 m.connect("update_repos_group", "/repos_groups/{id}",
155 155 action="update", conditions=dict(method=["PUT"],
156 156 function=check_int))
157 157 m.connect("delete_repos_group", "/repos_groups/{id}",
158 158 action="delete", conditions=dict(method=["DELETE"],
159 159 function=check_int))
160 160 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
161 161 action="edit", conditions=dict(method=["GET"],
162 162 function=check_int))
163 163 m.connect("formatted_edit_repos_group",
164 164 "/repos_groups/{id}.{format}/edit",
165 165 action="edit", conditions=dict(method=["GET"],
166 166 function=check_int))
167 167 m.connect("repos_group", "/repos_groups/{id}",
168 168 action="show", conditions=dict(method=["GET"],
169 169 function=check_int))
170 170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
171 171 action="show", conditions=dict(method=["GET"],
172 172 function=check_int))
173 173 # ajax delete repos group perm user
174 174 m.connect('delete_repos_group_user_perm',
175 175 "/delete_repos_group_user_perm/{group_name:.*}",
176 176 action="delete_repos_group_user_perm",
177 177 conditions=dict(method=["DELETE"], function=check_group))
178 178
179 179 # ajax delete repos group perm users_group
180 180 m.connect('delete_repos_group_users_group_perm',
181 181 "/delete_repos_group_users_group_perm/{group_name:.*}",
182 182 action="delete_repos_group_users_group_perm",
183 183 conditions=dict(method=["DELETE"], function=check_group))
184 184
185 185 #ADMIN USER REST ROUTES
186 186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
187 187 controller='admin/users') as m:
188 188 m.connect("users", "/users",
189 189 action="create", conditions=dict(method=["POST"]))
190 190 m.connect("users", "/users",
191 191 action="index", conditions=dict(method=["GET"]))
192 192 m.connect("formatted_users", "/users.{format}",
193 193 action="index", conditions=dict(method=["GET"]))
194 194 m.connect("new_user", "/users/new",
195 195 action="new", conditions=dict(method=["GET"]))
196 196 m.connect("formatted_new_user", "/users/new.{format}",
197 197 action="new", conditions=dict(method=["GET"]))
198 198 m.connect("update_user", "/users/{id}",
199 199 action="update", conditions=dict(method=["PUT"]))
200 200 m.connect("delete_user", "/users/{id}",
201 201 action="delete", conditions=dict(method=["DELETE"]))
202 202 m.connect("edit_user", "/users/{id}/edit",
203 203 action="edit", conditions=dict(method=["GET"]))
204 204 m.connect("formatted_edit_user",
205 205 "/users/{id}.{format}/edit",
206 206 action="edit", conditions=dict(method=["GET"]))
207 207 m.connect("user", "/users/{id}",
208 208 action="show", conditions=dict(method=["GET"]))
209 209 m.connect("formatted_user", "/users/{id}.{format}",
210 210 action="show", conditions=dict(method=["GET"]))
211 211
212 212 #EXTRAS USER ROUTES
213 213 m.connect("user_perm", "/users_perm/{id}",
214 214 action="update_perm", conditions=dict(method=["PUT"]))
215 215 m.connect("user_emails", "/users_emails/{id}",
216 216 action="add_email", conditions=dict(method=["PUT"]))
217 217 m.connect("user_emails_delete", "/users_emails/{id}",
218 218 action="delete_email", conditions=dict(method=["DELETE"]))
219 219
220 220 #ADMIN USERS GROUPS REST ROUTES
221 221 with rmap.submapper(path_prefix=ADMIN_PREFIX,
222 222 controller='admin/users_groups') as m:
223 223 m.connect("users_groups", "/users_groups",
224 224 action="create", conditions=dict(method=["POST"]))
225 225 m.connect("users_groups", "/users_groups",
226 226 action="index", conditions=dict(method=["GET"]))
227 227 m.connect("formatted_users_groups", "/users_groups.{format}",
228 228 action="index", conditions=dict(method=["GET"]))
229 229 m.connect("new_users_group", "/users_groups/new",
230 230 action="new", conditions=dict(method=["GET"]))
231 231 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
232 232 action="new", conditions=dict(method=["GET"]))
233 233 m.connect("update_users_group", "/users_groups/{id}",
234 234 action="update", conditions=dict(method=["PUT"]))
235 235 m.connect("delete_users_group", "/users_groups/{id}",
236 236 action="delete", conditions=dict(method=["DELETE"]))
237 237 m.connect("edit_users_group", "/users_groups/{id}/edit",
238 238 action="edit", conditions=dict(method=["GET"]))
239 239 m.connect("formatted_edit_users_group",
240 240 "/users_groups/{id}.{format}/edit",
241 241 action="edit", conditions=dict(method=["GET"]))
242 242 m.connect("users_group", "/users_groups/{id}",
243 243 action="show", conditions=dict(method=["GET"]))
244 244 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
245 245 action="show", conditions=dict(method=["GET"]))
246 246
247 247 #EXTRAS USER ROUTES
248 248 m.connect("users_group_perm", "/users_groups_perm/{id}",
249 249 action="update_perm", conditions=dict(method=["PUT"]))
250 250
251 251 #ADMIN GROUP REST ROUTES
252 252 rmap.resource('group', 'groups',
253 253 controller='admin/groups', path_prefix=ADMIN_PREFIX)
254 254
255 255 #ADMIN PERMISSIONS REST ROUTES
256 256 rmap.resource('permission', 'permissions',
257 257 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
258 258
259 259 ##ADMIN LDAP SETTINGS
260 260 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
261 261 controller='admin/ldap_settings', action='ldap_settings',
262 262 conditions=dict(method=["POST"]))
263 263
264 264 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
265 265 controller='admin/ldap_settings')
266 266
267 267 #ADMIN SETTINGS REST ROUTES
268 268 with rmap.submapper(path_prefix=ADMIN_PREFIX,
269 269 controller='admin/settings') as m:
270 270 m.connect("admin_settings", "/settings",
271 271 action="create", conditions=dict(method=["POST"]))
272 272 m.connect("admin_settings", "/settings",
273 273 action="index", conditions=dict(method=["GET"]))
274 274 m.connect("formatted_admin_settings", "/settings.{format}",
275 275 action="index", conditions=dict(method=["GET"]))
276 276 m.connect("admin_new_setting", "/settings/new",
277 277 action="new", conditions=dict(method=["GET"]))
278 278 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
279 279 action="new", conditions=dict(method=["GET"]))
280 280 m.connect("/settings/{setting_id}",
281 281 action="update", conditions=dict(method=["PUT"]))
282 282 m.connect("/settings/{setting_id}",
283 283 action="delete", conditions=dict(method=["DELETE"]))
284 284 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
285 285 action="edit", conditions=dict(method=["GET"]))
286 286 m.connect("formatted_admin_edit_setting",
287 287 "/settings/{setting_id}.{format}/edit",
288 288 action="edit", conditions=dict(method=["GET"]))
289 289 m.connect("admin_setting", "/settings/{setting_id}",
290 290 action="show", conditions=dict(method=["GET"]))
291 291 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
292 292 action="show", conditions=dict(method=["GET"]))
293 293 m.connect("admin_settings_my_account", "/my_account",
294 294 action="my_account", conditions=dict(method=["GET"]))
295 295 m.connect("admin_settings_my_account_update", "/my_account_update",
296 296 action="my_account_update", conditions=dict(method=["PUT"]))
297 297 m.connect("admin_settings_create_repository", "/create_repository",
298 298 action="create_repository", conditions=dict(method=["GET"]))
299 299
300 300 #NOTIFICATION REST ROUTES
301 301 with rmap.submapper(path_prefix=ADMIN_PREFIX,
302 302 controller='admin/notifications') as m:
303 303 m.connect("notifications", "/notifications",
304 304 action="create", conditions=dict(method=["POST"]))
305 305 m.connect("notifications", "/notifications",
306 306 action="index", conditions=dict(method=["GET"]))
307 307 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
308 308 action="mark_all_read", conditions=dict(method=["GET"]))
309 309 m.connect("formatted_notifications", "/notifications.{format}",
310 310 action="index", conditions=dict(method=["GET"]))
311 311 m.connect("new_notification", "/notifications/new",
312 312 action="new", conditions=dict(method=["GET"]))
313 313 m.connect("formatted_new_notification", "/notifications/new.{format}",
314 314 action="new", conditions=dict(method=["GET"]))
315 315 m.connect("/notification/{notification_id}",
316 316 action="update", conditions=dict(method=["PUT"]))
317 317 m.connect("/notification/{notification_id}",
318 318 action="delete", conditions=dict(method=["DELETE"]))
319 319 m.connect("edit_notification", "/notification/{notification_id}/edit",
320 320 action="edit", conditions=dict(method=["GET"]))
321 321 m.connect("formatted_edit_notification",
322 322 "/notification/{notification_id}.{format}/edit",
323 323 action="edit", conditions=dict(method=["GET"]))
324 324 m.connect("notification", "/notification/{notification_id}",
325 325 action="show", conditions=dict(method=["GET"]))
326 326 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
327 327 action="show", conditions=dict(method=["GET"]))
328 328
329 329 #ADMIN MAIN PAGES
330 330 with rmap.submapper(path_prefix=ADMIN_PREFIX,
331 331 controller='admin/admin') as m:
332 332 m.connect('admin_home', '', action='index')
333 333 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
334 334 action='add_repo')
335 335
336 336 #==========================================================================
337 337 # API V2
338 338 #==========================================================================
339 339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
340 340 controller='api/api') as m:
341 341 m.connect('api', '/api')
342 342
343 343 #USER JOURNAL
344 344 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
345 345 controller='journal', action='index')
346 346 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
347 347 controller='journal', action='journal_rss')
348 348 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
349 349 controller='journal', action='journal_atom')
350 350
351 351 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
352 352 controller='journal', action="public_journal")
353 353
354 354 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
355 355 controller='journal', action="public_journal_rss")
356 356
357 357 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
358 358 controller='journal', action="public_journal_rss")
359 359
360 360 rmap.connect('public_journal_atom',
361 361 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
362 362 action="public_journal_atom")
363 363
364 364 rmap.connect('public_journal_atom_old',
365 365 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
366 366 action="public_journal_atom")
367 367
368 368 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
369 369 controller='journal', action='toggle_following',
370 370 conditions=dict(method=["POST"]))
371 371
372 372 #SEARCH
373 373 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
374 374 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
375 375 controller='search')
376 376
377 377 #LOGIN/LOGOUT/REGISTER/SIGN IN
378 378 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
379 379 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
380 380 action='logout')
381 381
382 382 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
383 383 action='register')
384 384
385 385 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
386 386 controller='login', action='password_reset')
387 387
388 388 rmap.connect('reset_password_confirmation',
389 389 '%s/password_reset_confirmation' % ADMIN_PREFIX,
390 390 controller='login', action='password_reset_confirmation')
391 391
392 392 #FEEDS
393 393 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
394 394 controller='feed', action='rss',
395 395 conditions=dict(function=check_repo))
396 396
397 397 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
398 398 controller='feed', action='atom',
399 399 conditions=dict(function=check_repo))
400 400
401 401 #==========================================================================
402 402 # REPOSITORY ROUTES
403 403 #==========================================================================
404 404 rmap.connect('summary_home', '/{repo_name:.*}',
405 405 controller='summary',
406 406 conditions=dict(function=check_repo))
407 407
408 408 rmap.connect('repos_group_home', '/{group_name:.*}',
409 409 controller='admin/repos_groups', action="show_by_name",
410 410 conditions=dict(function=check_group))
411 411
412 412 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
413 413 controller='changeset', revision='tip',
414 414 conditions=dict(function=check_repo))
415 415
416 416 rmap.connect('changeset_comment',
417 417 '/{repo_name:.*}/changeset/{revision}/comment',
418 418 controller='changeset', revision='tip', action='comment',
419 419 conditions=dict(function=check_repo))
420 420
421 421 rmap.connect('changeset_comment_delete',
422 422 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
423 423 controller='changeset', action='delete_comment',
424 424 conditions=dict(function=check_repo, method=["DELETE"]))
425 425
426 426 rmap.connect('raw_changeset_home',
427 427 '/{repo_name:.*}/raw-changeset/{revision}',
428 428 controller='changeset', action='raw_changeset',
429 429 revision='tip', conditions=dict(function=check_repo))
430 430
431 431 rmap.connect('compare_url',
432 432 '/{repo_name:.*}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
433 433 controller='compare', action='index',
434 434 conditions=dict(function=check_repo),
435 435 requirements=dict(org_ref_type='(branch|book|tag)',
436 436 other_ref_type='(branch|book|tag)'))
437 437
438 438 rmap.connect('pullrequest_home',
439 439 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
440 440 action='index', conditions=dict(function=check_repo,
441 441 method=["GET"]))
442 442
443 443 rmap.connect('pullrequest',
444 444 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
445 445 action='create', conditions=dict(function=check_repo,
446 446 method=["POST"]))
447 447
448 448 rmap.connect('pullrequest_show',
449 449 '/{repo_name:.*}/pull-request/{pull_request_id}',
450 450 controller='pullrequests',
451 451 action='show', conditions=dict(function=check_repo,
452 452 method=["GET"]))
453 453
454 454 rmap.connect('pullrequest_show_all',
455 455 '/{repo_name:.*}/pull-request',
456 456 controller='pullrequests',
457 457 action='show_all', conditions=dict(function=check_repo,
458 458 method=["GET"]))
459 459
460 460 rmap.connect('pullrequest_comment',
461 461 '/{repo_name:.*}/pull-request-comment/{pull_request_id}',
462 462 controller='pullrequests',
463 463 action='comment', conditions=dict(function=check_repo,
464 464 method=["POST"]))
465 465
466 rmap.connect('pullrequest_comment_delete',
467 '/{repo_name:.*}/pull-request-comment/{comment_id}/delete',
468 controller='pullrequests', action='delete_comment',
469 conditions=dict(function=check_repo, method=["DELETE"]))
470
466 471 rmap.connect('summary_home', '/{repo_name:.*}/summary',
467 472 controller='summary', conditions=dict(function=check_repo))
468 473
469 474 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
470 475 controller='shortlog', conditions=dict(function=check_repo))
471 476
472 477 rmap.connect('branches_home', '/{repo_name:.*}/branches',
473 478 controller='branches', conditions=dict(function=check_repo))
474 479
475 480 rmap.connect('tags_home', '/{repo_name:.*}/tags',
476 481 controller='tags', conditions=dict(function=check_repo))
477 482
478 483 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
479 484 controller='bookmarks', conditions=dict(function=check_repo))
480 485
481 486 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
482 487 controller='changelog', conditions=dict(function=check_repo))
483 488
484 489 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
485 490 controller='changelog', action='changelog_details',
486 491 conditions=dict(function=check_repo))
487 492
488 493 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
489 494 controller='files', revision='tip', f_path='',
490 495 conditions=dict(function=check_repo))
491 496
492 497 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
493 498 controller='files', action='diff', revision='tip', f_path='',
494 499 conditions=dict(function=check_repo))
495 500
496 501 rmap.connect('files_rawfile_home',
497 502 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
498 503 controller='files', action='rawfile', revision='tip',
499 504 f_path='', conditions=dict(function=check_repo))
500 505
501 506 rmap.connect('files_raw_home',
502 507 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
503 508 controller='files', action='raw', revision='tip', f_path='',
504 509 conditions=dict(function=check_repo))
505 510
506 511 rmap.connect('files_annotate_home',
507 512 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
508 513 controller='files', action='index', revision='tip',
509 514 f_path='', annotate=True, conditions=dict(function=check_repo))
510 515
511 516 rmap.connect('files_edit_home',
512 517 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
513 518 controller='files', action='edit', revision='tip',
514 519 f_path='', conditions=dict(function=check_repo))
515 520
516 521 rmap.connect('files_add_home',
517 522 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
518 523 controller='files', action='add', revision='tip',
519 524 f_path='', conditions=dict(function=check_repo))
520 525
521 526 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
522 527 controller='files', action='archivefile',
523 528 conditions=dict(function=check_repo))
524 529
525 530 rmap.connect('files_nodelist_home',
526 531 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
527 532 controller='files', action='nodelist',
528 533 conditions=dict(function=check_repo))
529 534
530 535 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
531 536 controller='settings', action="delete",
532 537 conditions=dict(method=["DELETE"], function=check_repo))
533 538
534 539 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
535 540 controller='settings', action="update",
536 541 conditions=dict(method=["PUT"], function=check_repo))
537 542
538 543 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
539 544 controller='settings', action='index',
540 545 conditions=dict(function=check_repo))
541 546
542 547 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
543 548 controller='forks', action='fork_create',
544 549 conditions=dict(function=check_repo, method=["POST"]))
545 550
546 551 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
547 552 controller='forks', action='fork',
548 553 conditions=dict(function=check_repo))
549 554
550 555 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
551 556 controller='forks', action='forks',
552 557 conditions=dict(function=check_repo))
553 558
554 559 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
555 560 controller='followers', action='followers',
556 561 conditions=dict(function=check_repo))
557 562
558 563 return rmap
@@ -1,297 +1,310 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
28 from webob.exc import HTTPNotFound
28 from webob.exc import HTTPNotFound, HTTPForbidden
29 29 from collections import defaultdict
30 30 from itertools import groupby
31 31
32 32 from pylons import request, response, session, tmpl_context as c, url
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35 from pylons.decorators import jsonify
36 36
37 37 from rhodecode.lib.base import BaseRepoController, render
38 38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib import diffs
41 41 from rhodecode.lib.utils import action_logger
42 from rhodecode.model.db import User, PullRequest, ChangesetStatus
42 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
43 ChangesetComment
43 44 from rhodecode.model.pull_request import PullRequestModel
44 45 from rhodecode.model.meta import Session
45 46 from rhodecode.model.repo import RepoModel
46 47 from rhodecode.model.comment import ChangesetCommentsModel
47 48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 49
49 50 log = logging.getLogger(__name__)
50 51
51 52
52 53 class PullrequestsController(BaseRepoController):
53 54
54 55 @LoginRequired()
55 56 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
56 57 'repository.admin')
57 58 def __before__(self):
58 59 super(PullrequestsController, self).__before__()
59 60
60 61 def _get_repo_refs(self, repo):
61 62 hist_l = []
62 63
63 64 branches_group = ([('branch:%s:%s' % (k, v), k) for
64 65 k, v in repo.branches.iteritems()], _("Branches"))
65 66 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
66 67 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
67 68 tags_group = ([('tag:%s:%s' % (k, v), k) for
68 69 k, v in repo.tags.iteritems()], _("Tags"))
69 70
70 71 hist_l.append(bookmarks_group)
71 72 hist_l.append(branches_group)
72 73 hist_l.append(tags_group)
73 74
74 75 return hist_l
75 76
76 77 def show_all(self, repo_name):
77 78 c.pull_requests = PullRequestModel().get_all(repo_name)
78 79 c.repo_name = repo_name
79 80 return render('/pullrequests/pullrequest_show_all.html')
80 81
81 82 def index(self):
82 83 org_repo = c.rhodecode_db_repo
83 84
84 85 if org_repo.scm_instance.alias != 'hg':
85 86 log.error('Review not available for GIT REPOS')
86 87 raise HTTPNotFound
87 88
88 89 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
89 90 c.org_repos = []
90 91 c.other_repos = []
91 92 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
92 93 org_repo.user.username, c.repo_name))
93 94 )
94 95
95 96 c.other_refs = c.org_refs
96 97 c.other_repos.extend(c.org_repos)
97 98 c.default_pull_request = org_repo.repo_name
98 99 #gather forks and add to this list
99 100 for fork in org_repo.forks:
100 101 c.other_repos.append((fork.repo_name, '%s/%s' % (
101 102 fork.user.username, fork.repo_name))
102 103 )
103 104 #add parents of this fork also
104 105 if org_repo.parent:
105 106 c.default_pull_request = org_repo.parent.repo_name
106 107 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
107 108 org_repo.parent.user.username,
108 109 org_repo.parent.repo_name))
109 110 )
110 111
111 112 c.review_members = []
112 113 c.available_members = []
113 114 for u in User.query().filter(User.username != 'default').all():
114 115 uname = u.username
115 116 if org_repo.user == u:
116 117 uname = _('%s (owner)' % u.username)
117 118 # auto add owner to pull-request recipients
118 119 c.review_members.append([u.user_id, uname])
119 120 c.available_members.append([u.user_id, uname])
120 121 return render('/pullrequests/pullrequest.html')
121 122
122 123 def create(self, repo_name):
123 124 req_p = request.POST
124 125 org_repo = req_p['org_repo']
125 126 org_ref = req_p['org_ref']
126 127 other_repo = req_p['other_repo']
127 128 other_ref = req_p['other_ref']
128 129 revisions = req_p.getall('revisions')
129 130 reviewers = req_p.getall('review_members')
130 131 #TODO: wrap this into a FORM !!!
131 132
132 133 title = req_p['pullrequest_title']
133 134 description = req_p['pullrequest_desc']
134 135
135 136 try:
136 137 model = PullRequestModel()
137 138 model.create(self.rhodecode_user.user_id, org_repo,
138 139 org_ref, other_repo, other_ref, revisions,
139 140 reviewers, title, description)
140 141 Session.commit()
141 142 h.flash(_('Pull request send'), category='success')
142 143 except Exception:
143 144 raise
144 145 h.flash(_('Error occured during sending pull request'),
145 146 category='error')
146 147 log.error(traceback.format_exc())
147 148
148 149 return redirect(url('changelog_home', repo_name=repo_name))
149 150
150 151 def _load_compare_data(self, pull_request):
151 152 """
152 153 Load context data needed for generating compare diff
153 154
154 155 :param pull_request:
155 156 :type pull_request:
156 157 """
157 158
158 159 org_repo = pull_request.org_repo
159 160 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
160 161 other_repo = pull_request.other_repo
161 162 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
162 163
163 164 org_ref = (org_ref_type, org_ref)
164 165 other_ref = (other_ref_type, other_ref)
165 166
166 167 c.org_repo = org_repo
167 168 c.other_repo = other_repo
168 169
169 170 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
170 171 org_repo, org_ref, other_repo, other_ref
171 172 )
172 173
173 174 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
174 175 c.cs_ranges])
175 176 # defines that we need hidden inputs with changesets
176 177 c.as_form = request.GET.get('as_form', False)
177 178
178 179 c.org_ref = org_ref[1]
179 180 c.other_ref = other_ref[1]
180 181 # diff needs to have swapped org with other to generate proper diff
181 182 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
182 183 discovery_data)
183 184 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
184 185 _parsed = diff_processor.prepare()
185 186
186 187 c.files = []
187 188 c.changes = {}
188 189
189 190 for f in _parsed:
190 191 fid = h.FID('', f['filename'])
191 192 c.files.append([fid, f['operation'], f['filename'], f['stats']])
192 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
193 diff = diff_processor.as_html(enable_comments=True,
194 diff_lines=[f])
193 195 c.changes[fid] = [f['operation'], f['filename'], diff]
194 196
195 197 def show(self, repo_name, pull_request_id):
196 198 repo_model = RepoModel()
197 199 c.users_array = repo_model.get_users_js()
198 200 c.users_groups_array = repo_model.get_users_groups_js()
199 201 c.pull_request = PullRequest.get(pull_request_id)
200 202
201 203 # valid ID
202 204 if not c.pull_request:
203 205 raise HTTPNotFound
204 206 cc_model = ChangesetCommentsModel()
205 207 cs_model = ChangesetStatusModel()
206 208 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
207 209 pull_request=c.pull_request,
208 210 with_revisions=True)
209 211
210 212 cs_statuses = defaultdict(list)
211 213 for st in _cs_statuses:
212 214 cs_statuses[st.author.username] += [st]
213 215
214 216 c.pull_request_reviewers = []
215 217 for o in c.pull_request.reviewers:
216 218 st = cs_statuses.get(o.user.username, None)
217 219 if st:
218 220 sorter = lambda k: k.version
219 221 st = [(x, list(y)[0])
220 222 for x, y in (groupby(sorted(st, key=sorter), sorter))]
221 223 c.pull_request_reviewers.append([o.user, st])
222 224
223 225 # pull_requests repo_name we opened it against
224 226 # ie. other_repo must match
225 227 if repo_name != c.pull_request.other_repo.repo_name:
226 228 raise HTTPNotFound
227 229
228 230 # load compare data into template context
229 231 self._load_compare_data(c.pull_request)
230 232
231 233 # inline comments
232 234 c.inline_cnt = 0
233 235 c.inline_comments = cc_model.get_inline_comments(
234 236 c.rhodecode_db_repo.repo_id,
235 237 pull_request=pull_request_id)
236 238 # count inline comments
237 239 for __, lines in c.inline_comments:
238 240 for comments in lines.values():
239 241 c.inline_cnt += len(comments)
240 242 # comments
241 243 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
242 244 pull_request=pull_request_id)
243 245
244 246 # changeset(pull-request) status
245 247 c.current_changeset_status = cs_model.calculate_status(
246 248 c.pull_request_reviewers
247 249 )
248 250 c.changeset_statuses = ChangesetStatus.STATUSES
249 251 c.target_repo = c.pull_request.org_repo.repo_name
250 252 return render('/pullrequests/pullrequest_show.html')
251 253
252 254 @jsonify
253 255 def comment(self, repo_name, pull_request_id):
254 256
255 257 status = request.POST.get('changeset_status')
256 258 change_status = request.POST.get('change_changeset_status')
257 259
258 260 comm = ChangesetCommentsModel().create(
259 261 text=request.POST.get('text'),
260 262 repo_id=c.rhodecode_db_repo.repo_id,
261 263 user_id=c.rhodecode_user.user_id,
262 264 pull_request=pull_request_id,
263 265 f_path=request.POST.get('f_path'),
264 266 line_no=request.POST.get('line'),
265 267 status_change=(ChangesetStatus.get_status_lbl(status)
266 268 if status and change_status else None)
267 269 )
268 270
269 271 # get status if set !
270 272 if status and change_status:
271 273 ChangesetStatusModel().set_status(
272 274 c.rhodecode_db_repo.repo_id,
273 275 status,
274 276 c.rhodecode_user.user_id,
275 277 comm,
276 278 pull_request=pull_request_id
277 279 )
278 280 action_logger(self.rhodecode_user,
279 281 'user_commented_pull_request:%s' % pull_request_id,
280 282 c.rhodecode_db_repo, self.ip_addr, self.sa)
281 283
282 284 Session.commit()
283 285
284 286 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
285 287 return redirect(h.url('pullrequest_show', repo_name=repo_name,
286 288 pull_request_id=pull_request_id))
287 289
288 290 data = {
289 291 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
290 292 }
291 293 if comm:
292 294 c.co = comm
293 295 data.update(comm.get_dict())
294 296 data.update({'rendered_text':
295 297 render('changeset/changeset_comment_block.html')})
296 298
297 299 return data
300
301 @jsonify
302 def delete_comment(self, repo_name, comment_id):
303 co = ChangesetComment.get(comment_id)
304 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
305 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
306 ChangesetCommentsModel().delete(comment=co)
307 Session.commit()
308 return True
309 else:
310 raise HTTPForbidden() No newline at end of file
@@ -1,176 +1,176 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
7 7 </%def>
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(u'Home',h.url('/'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23 <!-- box / title -->
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27 <div class="table">
28 28 <div class="diffblock">
29 29 <div class="code-header">
30 30 <div class="hash">
31 31 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
32 32 </div>
33 33 <div class="date">
34 34 ${h.fmt_date(c.changeset.date)}
35 35 </div>
36 36 <div class="changeset-status-container">
37 37 %if c.statuses:
38 38 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
39 39 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
40 40 %endif
41 41 </div>
42 42 <div class="diff-actions">
43 43 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
44 44 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
45 45 ${c.ignorews_url(request.GET)}
46 46 ${c.context_url(request.GET)}
47 47 </div>
48 48 <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
49 49 </div>
50 50 </div>
51 51 <div id="changeset_content">
52 52 <div class="container">
53 53 <div class="left">
54 54 <div class="author">
55 55 <div class="gravatar">
56 56 <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
57 57 </div>
58 58 <span>${h.person(c.changeset.author)}</span><br/>
59 59 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
60 60 </div>
61 61 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
62 62 </div>
63 63 <div class="right">
64 64 <div class="changes">
65 65 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
66 66 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
67 67 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
68 68 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
69 69 % else:
70 70 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
71 71 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
72 72 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
73 73 % endif
74 74 </div>
75 75
76 76 %if c.changeset.parents:
77 77 %for p_cs in reversed(c.changeset.parents):
78 78 <div class="parent">${_('Parent')}
79 79 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
80 80 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
81 81 </div>
82 82 %endfor
83 83 %else:
84 84 <div class="parent">${_('No parents')}</div>
85 85 %endif
86 86 <span class="logtags">
87 87 %if len(c.changeset.parents)>1:
88 88 <span class="merge">${_('merge')}</span>
89 89 %endif
90 90 %if c.changeset.branch:
91 91 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
92 92 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
93 93 </span>
94 94 %endif
95 95 %for tag in c.changeset.tags:
96 96 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
97 97 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
98 98 %endfor
99 99 </span>
100 100 </div>
101 101 </div>
102 102 <span>
103 103 ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
104 104 </span>
105 105 <div class="cs_files">
106 106 %for change,filenode,diff,cs1,cs2,stat in c.changes:
107 107 <div class="cs_${change}">
108 108 <div class="node">
109 109 %if change != 'removed':
110 110 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")}
111 111 %else:
112 112 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
113 113 %endif
114 114 </div>
115 115 <div class="changes">${h.fancy_file_stats(stat)}</div>
116 116 </div>
117 117 %endfor
118 118 % if c.cut_off:
119 119 ${_('Changeset was too big and was cut off...')}
120 120 % endif
121 121 </div>
122 122 </div>
123 123
124 124 </div>
125 125 <script>
126 126 var _USERS_AC_DATA = ${c.users_array|n};
127 127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
128 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
129 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
128 130 </script>
129 131 ## diff block
130 132 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
131 133 ${diff_block.diff_block(c.changes)}
132 134
133 135 ## template for inline comment form
134 136 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
135 ${comment.comment_inline_form(c.changeset)}
137 ${comment.comment_inline_form()}
136 138
137 139 ## render comments main comments form and it status
138 140 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
139 141 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
140 142
143 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
141 144 <script type="text/javascript">
142 145 YUE.onDOMReady(function(){
143 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
144 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"
145 146 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
146 147 var show = 'none';
147 148 var target = e.currentTarget;
148 149 if(target.checked){
149 150 var show = ''
150 151 }
151 152 var boxid = YUD.getAttribute(target,'id_for');
152 153 var comments = YUQ('#{0} .inline-comments'.format(boxid));
153 154 for(c in comments){
154 155 YUD.setStyle(comments[c],'display',show);
155 156 }
156 157 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
157 158 for(c in btns){
158 159 YUD.setStyle(btns[c],'display',show);
159 160 }
160 161 })
161 162
162 163 YUE.on(YUQ('.line'),'click',function(e){
163 164 var tr = e.currentTarget;
164 165 injectInlineForm(tr);
165 166 });
166 167
167 168 // inject comments into they proper positions
168 169 var file_comments = YUQ('.inline-comment-placeholder');
169 170 renderInlineComments(file_comments);
170
171 171 })
172 172
173 173 </script>
174 174
175 175 </div>
176 176 </%def>
@@ -1,154 +1,154 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ## usage:
3 3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
4 4 ## ${comment.comment_block(co)}
5 5 ##
6 6 <%def name="comment_block(co)">
7 7 <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
8 8 <div class="comment-wrapp">
9 9 <div class="meta">
10 10 <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div>
11 11 <div class="user">
12 12 ${co.author.username}
13 13 </div>
14 14 <div class="date">
15 15 ${h.age(co.modified_at)}
16 16 </div>
17 17 %if co.status_change:
18 18 <div style="float:left" class="changeset-status-container">
19 19 <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
20 20 <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change.status_lbl}</div>
21 21 <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change.status))}" /></div>
22 22 </div>
23 23 %endif
24 24 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
25 25 <div class="buttons">
26 26 <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
27 27 </div>
28 28 %endif
29 29 </div>
30 30 <div class="text">
31 31 ${h.rst_w_mentions(co.text)|n}
32 32 </div>
33 33 </div>
34 34 </div>
35 35 </%def>
36 36
37 37
38 <%def name="comment_inline_form(changeset)">
38 <%def name="comment_inline_form()">
39 39 <div id='comment-inline-form-template' style="display:none">
40 40 <div class="comment-inline-form ac">
41 41 %if c.rhodecode_user.username != 'default':
42 42 <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
43 ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id),class_='inline-form')}
43 ${h.form('#', class_='inline-form')}
44 44 <div class="clearfix">
45 45 <div class="comment-help">${_('Commenting on line {1}.')}
46 46 ${(_('Comments parsed using %s syntax with %s support.') % (
47 47 ('<a href="%s">RST</a>' % h.url('rst_help')),
48 48 ('<span style="color:#003367" class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
49 49 )
50 50 )|n
51 51 }
52 52 </div>
53 53 <div class="mentions-container" id="mentions_container_{1}"></div>
54 54 <textarea id="text_{1}" name="text" class="yui-ac-input"></textarea>
55 55 </div>
56 56 <div class="comment-button">
57 57 <input type="hidden" name="f_path" value="{0}">
58 58 <input type="hidden" name="line" value="{1}">
59 59 ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
60 60 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
61 61 </div>
62 62 ${h.end_form()}
63 63 %else:
64 64 ${h.form('')}
65 65 <div class="clearfix">
66 66 <div class="comment-help">
67 67 ${_('You need to be logged in to comment.')} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
68 68 </div>
69 69 </div>
70 70 <div class="comment-button">
71 71 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
72 72 </div>
73 73 ${h.end_form()}
74 74 %endif
75 75 </div>
76 76 </div>
77 77 </%def>
78 78
79 79
80 80 ## generates inlines taken from c.comments var
81 81 <%def name="inlines()">
82 82 <div class="comments-number">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
83 83 %for path, lines in c.inline_comments:
84 84 % for line,comments in lines.iteritems():
85 85 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
86 86 %for co in comments:
87 87 ${comment_block(co)}
88 88 %endfor
89 89 </div>
90 90 %endfor
91 91 %endfor
92 92
93 93 </%def>
94 94
95 95 ## MAIN COMMENT FORM
96 96 <%def name="comments(post_url, cur_status)">
97 97
98 98 <div class="comments">
99 99 <div id="inline-comments-container">
100 100 ## generate inlines for this changeset
101 101 ${inlines()}
102 102 </div>
103 103
104 104 %for co in c.comments:
105 105 <div id="comment-tr-${co.comment_id}">
106 106 ${comment_block(co)}
107 107 </div>
108 108 %endfor
109 109 %if c.rhodecode_user.username != 'default':
110 110 <div class="comment-form ac">
111 111 ${h.form(post_url)}
112 112 <strong>${_('Leave a comment')}</strong>
113 113 <div class="clearfix">
114 114 <div class="comment-help">
115 115 ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')),
116 116 '<span style="color:#003367" class="tooltip" title="%s">@mention</span>' %
117 117 _('Use @username inside this text to send notification to this RhodeCode user')))|n}
118 118 | <label for="show_changeset_status_box" class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')}</label>
119 119 <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" />
120 120 </div>
121 121 <div id="status_block_container" class="status-block" style="display:none">
122 122 %for status,lbl in c.changeset_statuses:
123 123 <div class="">
124 124 <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" name="changeset_status" value="${status}"> <label>${lbl}</label>
125 125 </div>
126 126 %endfor
127 127 </div>
128 128 <div class="mentions-container" id="mentions_container"></div>
129 129 ${h.textarea('text')}
130 130 </div>
131 131 <div class="comment-button">
132 132 ${h.submit('save', _('Comment'), class_='ui-button')}
133 133 </div>
134 134 ${h.end_form()}
135 135 </div>
136 136 %endif
137 137 </div>
138 138 <script>
139 139 YUE.onDOMReady(function () {
140 140 MentionsAutoComplete('text', 'mentions_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
141 141
142 142 // changeset status box listener
143 143 YUE.on(YUD.get('show_changeset_status_box'),'change',function(e){
144 144 if(e.currentTarget.checked){
145 145 YUD.setStyle('status_block_container','display','');
146 146 }
147 147 else{
148 148 YUD.setStyle('status_block_container','display','none');
149 149 }
150 150 })
151 151
152 152 });
153 153 </script>
154 154 </%def>
@@ -1,104 +1,140 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 13 </%def>
14 14
15 15 <%def name="main()">
16 16
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22
23 23 <h3>${_('Title')}: ${c.pull_request.title}
24 24 <div class="changeset-status-container" style="float:none">
25 25 %if c.current_changeset_status:
26 26 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
27 27 <div class="changeset-status-ico" style="padding:4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
28 28 %endif
29 29 </div>
30 30 </h3>
31 31 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
32 32 <div style="padding:4px 4px 10px 20px">
33 33 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
34 34 </div>
35 35
36 36 ## REVIEWERS
37 37 <div>
38 38 <div class="table" style="float:right;width:46%;clear:none">
39 39 <div id="body" class="diffblock">
40 40 <div style="white-space:pre-wrap;padding:5px">${_('Pull request reviewers')}</div>
41 41 </div>
42 42 <div style="border: 1px solid #CCC">
43 43 <div class="group_members_wrap">
44 44 <ul class="group_members">
45 45 %for user,status in c.pull_request_reviewers:
46 46 <li>
47 47 <div class="group_member">
48 48 <div style="float:left;padding:3px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
49 49 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
50 50 </div>
51 51 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,20)}"/> </div>
52 52 <div style="float:left">${user.username}</div>
53 53 </div>
54 54 </li>
55 55 %endfor
56 56 </ul>
57 57 </div>
58 58 </div>
59 59 </div>
60 60 ##DIFF
61 61 <div class="table" style="float:left;width:46%;clear:none">
62 62 <div id="body" class="diffblock">
63 63 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
64 64 </div>
65 65 <div id="changeset_compare_view_content">
66 66 ##CS
67 67 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
68 68 <%include file="/compare/compare_cs.html" />
69 69
70 70 ## FILES
71 71 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
72 72 <div class="cs_files">
73 73 %for fid, change, f, stat in c.files:
74 74 <div class="cs_${change}">
75 75 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
76 76 <div class="changes">${h.fancy_file_stats(stat)}</div>
77 77 </div>
78 78 %endfor
79 79 </div>
80 80 </div>
81 81 </div>
82 82 </div>
83 83 <script>
84 84 var _USERS_AC_DATA = ${c.users_array|n};
85 85 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
86 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
87 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
86 88 </script>
87 89
88 90 ## diff block
89 91 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
90 92 %for fid, change, f, stat in c.files:
91 93 ${diff_block.diff_block_simple([c.changes[fid]])}
92 94 %endfor
93 95
94 96 ## template for inline comment form
95 97 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
96 ##${comment.comment_inline_form(c.changeset)}
98 ${comment.comment_inline_form()}
97 99
98 100 ## render comments main comments form and it status
99 101 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
100 102 c.current_changeset_status)}
101 103
104
105 <script type="text/javascript">
106 YUE.onDOMReady(function(){
107
108 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
109 var show = 'none';
110 var target = e.currentTarget;
111 if(target.checked){
112 var show = ''
113 }
114 var boxid = YUD.getAttribute(target,'id_for');
115 var comments = YUQ('#{0} .inline-comments'.format(boxid));
116 for(c in comments){
117 YUD.setStyle(comments[c],'display',show);
118 }
119 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
120 for(c in btns){
121 YUD.setStyle(btns[c],'display',show);
122 }
123 })
124
125 YUE.on(YUQ('.line'),'click',function(e){
126 var tr = e.currentTarget;
127 injectInlineForm(tr);
128 });
129
130 // inject comments into they proper positions
131 var file_comments = YUQ('.inline-comment-placeholder');
132 renderInlineComments(file_comments);
133 })
134
135 </script>
136
137
102 138 </div>
103 139
104 140 </%def>
General Comments 0
You need to be logged in to leave comments. Login now