##// END OF EJS Templates
moved permission management into separate entity....
marcink -
r3628:c734686b beta
parent child Browse files
Show More
@@ -1,665 +1,670
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 if match_dict.get('f_path'):
36 36 #fix for multiple initial slashes that causes errors
37 37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
38 38
39 39 try:
40 40 by_id = repo_name.split('_')
41 41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
42 42 repo_name = Repository.get(by_id[1]).repo_name
43 43 match_dict['repo_name'] = repo_name
44 44 except:
45 45 pass
46 46
47 47 return is_valid_repo(repo_name, config['base_path'])
48 48
49 49 def check_group(environ, match_dict):
50 50 """
51 51 check for valid repository group for proper 404 handling
52 52
53 53 :param environ:
54 54 :param match_dict:
55 55 """
56 56 repos_group_name = match_dict.get('group_name')
57 57 return is_valid_repos_group(repos_group_name, config['base_path'])
58 58
59 59 def check_group_skip_path(environ, match_dict):
60 60 """
61 61 check for valid repository group for proper 404 handling, but skips
62 62 verification of existing path
63 63
64 64 :param environ:
65 65 :param match_dict:
66 66 """
67 67 repos_group_name = match_dict.get('group_name')
68 68 return is_valid_repos_group(repos_group_name, config['base_path'],
69 69 skip_path_check=True)
70 70
71 71 def check_int(environ, match_dict):
72 72 return match_dict.get('id').isdigit()
73 73
74 74 # The ErrorController route (handles 404/500 error pages); it should
75 75 # likely stay at the top, ensuring it can always be resolved
76 76 rmap.connect('/error/{action}', controller='error')
77 77 rmap.connect('/error/{action}/{id}', controller='error')
78 78
79 79 #==========================================================================
80 80 # CUSTOM ROUTES HERE
81 81 #==========================================================================
82 82
83 83 #MAIN PAGE
84 84 rmap.connect('home', '/', controller='home', action='index')
85 85 rmap.connect('repo_switcher', '/repos', controller='home',
86 86 action='repo_switcher')
87 87 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
88 88 controller='home', action='branch_tag_switcher')
89 89 rmap.connect('bugtracker',
90 90 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
91 91 _static=True)
92 92 rmap.connect('rst_help',
93 93 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
94 94 _static=True)
95 95 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
96 96
97 97 #ADMIN REPOSITORY REST ROUTES
98 98 with rmap.submapper(path_prefix=ADMIN_PREFIX,
99 99 controller='admin/repos') as m:
100 100 m.connect("repos", "/repos",
101 101 action="create", conditions=dict(method=["POST"]))
102 102 m.connect("repos", "/repos",
103 103 action="index", conditions=dict(method=["GET"]))
104 104 m.connect("formatted_repos", "/repos.{format}",
105 105 action="index",
106 106 conditions=dict(method=["GET"]))
107 107 m.connect("new_repo", "/repos/new",
108 108 action="new", conditions=dict(method=["GET"]))
109 109 m.connect("formatted_new_repo", "/repos/new.{format}",
110 110 action="new", conditions=dict(method=["GET"]))
111 111 m.connect("/repos/{repo_name:.*?}",
112 112 action="update", conditions=dict(method=["PUT"],
113 113 function=check_repo))
114 114 m.connect("/repos/{repo_name:.*?}",
115 115 action="delete", conditions=dict(method=["DELETE"],
116 116 function=check_repo))
117 117 # no longer used:
118 118 m.connect("edit_repo_admin", "/repos/{repo_name:.*?}/edit",
119 119 action="edit", conditions=dict(method=["GET"],
120 120 function=check_repo))
121 121 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
122 122 action="edit", conditions=dict(method=["GET"],
123 123 function=check_repo))
124 124 m.connect("repo", "/repos/{repo_name:.*?}",
125 125 action="show", conditions=dict(method=["GET"],
126 126 function=check_repo))
127 127 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
128 128 action="show", conditions=dict(method=["GET"],
129 129 function=check_repo))
130 #add repo perm member
131 m.connect('set_repo_perm_member', "/set_repo_perm_member/{repo_name:.*?}",
132 action="set_repo_perm_member",
133 conditions=dict(method=["POST"], function=check_repo))
134
130 135 #ajax delete repo perm user
131 136 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
132 137 action="delete_perm_user",
133 138 conditions=dict(method=["DELETE"], function=check_repo))
134 139
135 140 #ajax delete repo perm users_group
136 141 m.connect('delete_repo_users_group',
137 142 "/repos_delete_users_group/{repo_name:.*?}",
138 143 action="delete_perm_users_group",
139 144 conditions=dict(method=["DELETE"], function=check_repo))
140 145
141 146 #settings actions
142 147 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
143 148 action="repo_stats", conditions=dict(method=["DELETE"],
144 149 function=check_repo))
145 150 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
146 151 action="repo_cache", conditions=dict(method=["DELETE"],
147 152 function=check_repo))
148 153 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
149 154 action="repo_public_journal", conditions=dict(method=["PUT"],
150 155 function=check_repo))
151 156 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
152 157 action="repo_pull", conditions=dict(method=["PUT"],
153 158 function=check_repo))
154 159 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
155 160 action="repo_as_fork", conditions=dict(method=["PUT"],
156 161 function=check_repo))
157 162 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
158 163 action="repo_locking", conditions=dict(method=["PUT"],
159 164 function=check_repo))
160 165 #repo fields
161 166 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
162 167 action="create_repo_field", conditions=dict(method=["PUT"],
163 168 function=check_repo))
164 169
165 170 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
166 171 action="delete_repo_field", conditions=dict(method=["DELETE"],
167 172 function=check_repo))
168 173
169 174 with rmap.submapper(path_prefix=ADMIN_PREFIX,
170 175 controller='admin/repos_groups') as m:
171 176 m.connect("repos_groups", "/repos_groups",
172 177 action="create", conditions=dict(method=["POST"]))
173 178 m.connect("repos_groups", "/repos_groups",
174 179 action="index", conditions=dict(method=["GET"]))
175 180 m.connect("formatted_repos_groups", "/repos_groups.{format}",
176 181 action="index", conditions=dict(method=["GET"]))
177 182 m.connect("new_repos_group", "/repos_groups/new",
178 183 action="new", conditions=dict(method=["GET"]))
179 184 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
180 185 action="new", conditions=dict(method=["GET"]))
181 186 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
182 187 action="update", conditions=dict(method=["PUT"],
183 188 function=check_group))
184 189 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
185 190 action="delete", conditions=dict(method=["DELETE"],
186 191 function=check_group_skip_path))
187 192 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
188 193 action="edit", conditions=dict(method=["GET"],
189 194 function=check_group))
190 195 m.connect("formatted_edit_repos_group",
191 196 "/repos_groups/{group_name:.*?}.{format}/edit",
192 197 action="edit", conditions=dict(method=["GET"],
193 198 function=check_group))
194 199 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
195 200 action="show", conditions=dict(method=["GET"],
196 201 function=check_group))
197 202 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
198 203 action="show", conditions=dict(method=["GET"],
199 204 function=check_group))
200 205 # ajax delete repos group perm user
201 206 m.connect('delete_repos_group_user_perm',
202 207 "/delete_repos_group_user_perm/{group_name:.*?}",
203 208 action="delete_repos_group_user_perm",
204 209 conditions=dict(method=["DELETE"], function=check_group))
205 210
206 211 # ajax delete repos group perm users_group
207 212 m.connect('delete_repos_group_users_group_perm',
208 213 "/delete_repos_group_users_group_perm/{group_name:.*?}",
209 214 action="delete_repos_group_users_group_perm",
210 215 conditions=dict(method=["DELETE"], function=check_group))
211 216
212 217 #ADMIN USER REST ROUTES
213 218 with rmap.submapper(path_prefix=ADMIN_PREFIX,
214 219 controller='admin/users') as m:
215 220 m.connect("users", "/users",
216 221 action="create", conditions=dict(method=["POST"]))
217 222 m.connect("users", "/users",
218 223 action="index", conditions=dict(method=["GET"]))
219 224 m.connect("formatted_users", "/users.{format}",
220 225 action="index", conditions=dict(method=["GET"]))
221 226 m.connect("new_user", "/users/new",
222 227 action="new", conditions=dict(method=["GET"]))
223 228 m.connect("formatted_new_user", "/users/new.{format}",
224 229 action="new", conditions=dict(method=["GET"]))
225 230 m.connect("update_user", "/users/{id}",
226 231 action="update", conditions=dict(method=["PUT"]))
227 232 m.connect("delete_user", "/users/{id}",
228 233 action="delete", conditions=dict(method=["DELETE"]))
229 234 m.connect("edit_user", "/users/{id}/edit",
230 235 action="edit", conditions=dict(method=["GET"]))
231 236 m.connect("formatted_edit_user",
232 237 "/users/{id}.{format}/edit",
233 238 action="edit", conditions=dict(method=["GET"]))
234 239 m.connect("user", "/users/{id}",
235 240 action="show", conditions=dict(method=["GET"]))
236 241 m.connect("formatted_user", "/users/{id}.{format}",
237 242 action="show", conditions=dict(method=["GET"]))
238 243
239 244 #EXTRAS USER ROUTES
240 245 m.connect("user_perm", "/users_perm/{id}",
241 246 action="update_perm", conditions=dict(method=["PUT"]))
242 247 m.connect("user_emails", "/users_emails/{id}",
243 248 action="add_email", conditions=dict(method=["PUT"]))
244 249 m.connect("user_emails_delete", "/users_emails/{id}",
245 250 action="delete_email", conditions=dict(method=["DELETE"]))
246 251 m.connect("user_ips", "/users_ips/{id}",
247 252 action="add_ip", conditions=dict(method=["PUT"]))
248 253 m.connect("user_ips_delete", "/users_ips/{id}",
249 254 action="delete_ip", conditions=dict(method=["DELETE"]))
250 255
251 256 #ADMIN USER GROUPS REST ROUTES
252 257 with rmap.submapper(path_prefix=ADMIN_PREFIX,
253 258 controller='admin/users_groups') as m:
254 259 m.connect("users_groups", "/users_groups",
255 260 action="create", conditions=dict(method=["POST"]))
256 261 m.connect("users_groups", "/users_groups",
257 262 action="index", conditions=dict(method=["GET"]))
258 263 m.connect("formatted_users_groups", "/users_groups.{format}",
259 264 action="index", conditions=dict(method=["GET"]))
260 265 m.connect("new_users_group", "/users_groups/new",
261 266 action="new", conditions=dict(method=["GET"]))
262 267 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
263 268 action="new", conditions=dict(method=["GET"]))
264 269 m.connect("update_users_group", "/users_groups/{id}",
265 270 action="update", conditions=dict(method=["PUT"]))
266 271 m.connect("delete_users_group", "/users_groups/{id}",
267 272 action="delete", conditions=dict(method=["DELETE"]))
268 273 m.connect("edit_users_group", "/users_groups/{id}/edit",
269 274 action="edit", conditions=dict(method=["GET"]))
270 275 m.connect("formatted_edit_users_group",
271 276 "/users_groups/{id}.{format}/edit",
272 277 action="edit", conditions=dict(method=["GET"]))
273 278 m.connect("users_group", "/users_groups/{id}",
274 279 action="show", conditions=dict(method=["GET"]))
275 280 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
276 281 action="show", conditions=dict(method=["GET"]))
277 282
278 283 #EXTRAS USER ROUTES
279 284 m.connect("users_group_perm", "/users_groups_perm/{id}",
280 285 action="update_perm", conditions=dict(method=["PUT"]))
281 286
282 287 #ADMIN GROUP REST ROUTES
283 288 rmap.resource('group', 'groups',
284 289 controller='admin/groups', path_prefix=ADMIN_PREFIX)
285 290
286 291 #ADMIN PERMISSIONS REST ROUTES
287 292 rmap.resource('permission', 'permissions',
288 293 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
289 294
290 295 #ADMIN DEFAULTS REST ROUTES
291 296 rmap.resource('default', 'defaults',
292 297 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
293 298
294 299 ##ADMIN LDAP SETTINGS
295 300 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
296 301 controller='admin/ldap_settings', action='ldap_settings',
297 302 conditions=dict(method=["POST"]))
298 303
299 304 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
300 305 controller='admin/ldap_settings')
301 306
302 307 #ADMIN SETTINGS REST ROUTES
303 308 with rmap.submapper(path_prefix=ADMIN_PREFIX,
304 309 controller='admin/settings') as m:
305 310 m.connect("admin_settings", "/settings",
306 311 action="create", conditions=dict(method=["POST"]))
307 312 m.connect("admin_settings", "/settings",
308 313 action="index", conditions=dict(method=["GET"]))
309 314 m.connect("formatted_admin_settings", "/settings.{format}",
310 315 action="index", conditions=dict(method=["GET"]))
311 316 m.connect("admin_new_setting", "/settings/new",
312 317 action="new", conditions=dict(method=["GET"]))
313 318 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
314 319 action="new", conditions=dict(method=["GET"]))
315 320 m.connect("/settings/{setting_id}",
316 321 action="update", conditions=dict(method=["PUT"]))
317 322 m.connect("/settings/{setting_id}",
318 323 action="delete", conditions=dict(method=["DELETE"]))
319 324 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
320 325 action="edit", conditions=dict(method=["GET"]))
321 326 m.connect("formatted_admin_edit_setting",
322 327 "/settings/{setting_id}.{format}/edit",
323 328 action="edit", conditions=dict(method=["GET"]))
324 329 m.connect("admin_setting", "/settings/{setting_id}",
325 330 action="show", conditions=dict(method=["GET"]))
326 331 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
327 332 action="show", conditions=dict(method=["GET"]))
328 333 m.connect("admin_settings_my_account", "/my_account",
329 334 action="my_account", conditions=dict(method=["GET"]))
330 335 m.connect("admin_settings_my_account_update", "/my_account_update",
331 336 action="my_account_update", conditions=dict(method=["PUT"]))
332 337 m.connect("admin_settings_create_repository", "/create_repository",
333 338 action="create_repository", conditions=dict(method=["GET"]))
334 339 m.connect("admin_settings_my_repos", "/my_account/repos",
335 340 action="my_account_my_repos", conditions=dict(method=["GET"]))
336 341 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
337 342 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
338 343
339 344 #NOTIFICATION REST ROUTES
340 345 with rmap.submapper(path_prefix=ADMIN_PREFIX,
341 346 controller='admin/notifications') as m:
342 347 m.connect("notifications", "/notifications",
343 348 action="create", conditions=dict(method=["POST"]))
344 349 m.connect("notifications", "/notifications",
345 350 action="index", conditions=dict(method=["GET"]))
346 351 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
347 352 action="mark_all_read", conditions=dict(method=["GET"]))
348 353 m.connect("formatted_notifications", "/notifications.{format}",
349 354 action="index", conditions=dict(method=["GET"]))
350 355 m.connect("new_notification", "/notifications/new",
351 356 action="new", conditions=dict(method=["GET"]))
352 357 m.connect("formatted_new_notification", "/notifications/new.{format}",
353 358 action="new", conditions=dict(method=["GET"]))
354 359 m.connect("/notification/{notification_id}",
355 360 action="update", conditions=dict(method=["PUT"]))
356 361 m.connect("/notification/{notification_id}",
357 362 action="delete", conditions=dict(method=["DELETE"]))
358 363 m.connect("edit_notification", "/notification/{notification_id}/edit",
359 364 action="edit", conditions=dict(method=["GET"]))
360 365 m.connect("formatted_edit_notification",
361 366 "/notification/{notification_id}.{format}/edit",
362 367 action="edit", conditions=dict(method=["GET"]))
363 368 m.connect("notification", "/notification/{notification_id}",
364 369 action="show", conditions=dict(method=["GET"]))
365 370 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
366 371 action="show", conditions=dict(method=["GET"]))
367 372
368 373 #ADMIN MAIN PAGES
369 374 with rmap.submapper(path_prefix=ADMIN_PREFIX,
370 375 controller='admin/admin') as m:
371 376 m.connect('admin_home', '', action='index')
372 377 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
373 378 action='add_repo')
374 379
375 380 #==========================================================================
376 381 # API V2
377 382 #==========================================================================
378 383 with rmap.submapper(path_prefix=ADMIN_PREFIX,
379 384 controller='api/api') as m:
380 385 m.connect('api', '/api')
381 386
382 387 #USER JOURNAL
383 388 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
384 389 controller='journal', action='index')
385 390 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
386 391 controller='journal', action='journal_rss')
387 392 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
388 393 controller='journal', action='journal_atom')
389 394
390 395 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
391 396 controller='journal', action="public_journal")
392 397
393 398 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
394 399 controller='journal', action="public_journal_rss")
395 400
396 401 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
397 402 controller='journal', action="public_journal_rss")
398 403
399 404 rmap.connect('public_journal_atom',
400 405 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
401 406 action="public_journal_atom")
402 407
403 408 rmap.connect('public_journal_atom_old',
404 409 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
405 410 action="public_journal_atom")
406 411
407 412 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
408 413 controller='journal', action='toggle_following',
409 414 conditions=dict(method=["POST"]))
410 415
411 416 #SEARCH
412 417 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
413 418 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
414 419 controller='search',
415 420 conditions=dict(function=check_repo))
416 421 rmap.connect('search_repo', '/{repo_name:.*?}/search',
417 422 controller='search',
418 423 conditions=dict(function=check_repo),
419 424 )
420 425
421 426 #LOGIN/LOGOUT/REGISTER/SIGN IN
422 427 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
423 428 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
424 429 action='logout')
425 430
426 431 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
427 432 action='register')
428 433
429 434 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
430 435 controller='login', action='password_reset')
431 436
432 437 rmap.connect('reset_password_confirmation',
433 438 '%s/password_reset_confirmation' % ADMIN_PREFIX,
434 439 controller='login', action='password_reset_confirmation')
435 440
436 441 #FEEDS
437 442 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
438 443 controller='feed', action='rss',
439 444 conditions=dict(function=check_repo))
440 445
441 446 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
442 447 controller='feed', action='atom',
443 448 conditions=dict(function=check_repo))
444 449
445 450 #==========================================================================
446 451 # REPOSITORY ROUTES
447 452 #==========================================================================
448 453 rmap.connect('summary_home', '/{repo_name:.*?}',
449 454 controller='summary',
450 455 conditions=dict(function=check_repo))
451 456
452 457 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
453 458 controller='summary', action='repo_size',
454 459 conditions=dict(function=check_repo))
455 460
456 461 rmap.connect('repos_group_home', '/{group_name:.*}',
457 462 controller='admin/repos_groups', action="show_by_name",
458 463 conditions=dict(function=check_group))
459 464
460 465 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
461 466 controller='changeset', revision='tip',
462 467 conditions=dict(function=check_repo))
463 468
464 469 rmap.connect("edit_repo", "/{repo_name:.*?}/edit",
465 470 controller='admin/repos', action="edit",
466 471 conditions=dict(method=["GET"], function=check_repo)
467 472 )
468 473
469 474 #still working url for backward compat.
470 475 rmap.connect('raw_changeset_home_depraced',
471 476 '/{repo_name:.*?}/raw-changeset/{revision}',
472 477 controller='changeset', action='changeset_raw',
473 478 revision='tip', conditions=dict(function=check_repo))
474 479
475 480 ## new URLs
476 481 rmap.connect('changeset_raw_home',
477 482 '/{repo_name:.*?}/changeset-diff/{revision}',
478 483 controller='changeset', action='changeset_raw',
479 484 revision='tip', conditions=dict(function=check_repo))
480 485
481 486 rmap.connect('changeset_patch_home',
482 487 '/{repo_name:.*?}/changeset-patch/{revision}',
483 488 controller='changeset', action='changeset_patch',
484 489 revision='tip', conditions=dict(function=check_repo))
485 490
486 491 rmap.connect('changeset_download_home',
487 492 '/{repo_name:.*?}/changeset-download/{revision}',
488 493 controller='changeset', action='changeset_download',
489 494 revision='tip', conditions=dict(function=check_repo))
490 495
491 496 rmap.connect('changeset_comment',
492 497 '/{repo_name:.*?}/changeset/{revision}/comment',
493 498 controller='changeset', revision='tip', action='comment',
494 499 conditions=dict(function=check_repo))
495 500
496 501 rmap.connect('changeset_comment_delete',
497 502 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
498 503 controller='changeset', action='delete_comment',
499 504 conditions=dict(function=check_repo, method=["DELETE"]))
500 505
501 506 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
502 507 controller='changeset', action='changeset_info')
503 508
504 509 rmap.connect('compare_url',
505 510 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
506 511 controller='compare', action='index',
507 512 conditions=dict(function=check_repo),
508 513 requirements=dict(
509 514 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
510 515 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
511 516 )
512 517
513 518 rmap.connect('pullrequest_home',
514 519 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
515 520 action='index', conditions=dict(function=check_repo,
516 521 method=["GET"]))
517 522
518 523 rmap.connect('pullrequest',
519 524 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
520 525 action='create', conditions=dict(function=check_repo,
521 526 method=["POST"]))
522 527
523 528 rmap.connect('pullrequest_show',
524 529 '/{repo_name:.*?}/pull-request/{pull_request_id}',
525 530 controller='pullrequests',
526 531 action='show', conditions=dict(function=check_repo,
527 532 method=["GET"]))
528 533 rmap.connect('pullrequest_update',
529 534 '/{repo_name:.*?}/pull-request/{pull_request_id}',
530 535 controller='pullrequests',
531 536 action='update', conditions=dict(function=check_repo,
532 537 method=["PUT"]))
533 538 rmap.connect('pullrequest_delete',
534 539 '/{repo_name:.*?}/pull-request/{pull_request_id}',
535 540 controller='pullrequests',
536 541 action='delete', conditions=dict(function=check_repo,
537 542 method=["DELETE"]))
538 543
539 544 rmap.connect('pullrequest_show_all',
540 545 '/{repo_name:.*?}/pull-request',
541 546 controller='pullrequests',
542 547 action='show_all', conditions=dict(function=check_repo,
543 548 method=["GET"]))
544 549
545 550 rmap.connect('pullrequest_comment',
546 551 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
547 552 controller='pullrequests',
548 553 action='comment', conditions=dict(function=check_repo,
549 554 method=["POST"]))
550 555
551 556 rmap.connect('pullrequest_comment_delete',
552 557 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
553 558 controller='pullrequests', action='delete_comment',
554 559 conditions=dict(function=check_repo, method=["DELETE"]))
555 560
556 561 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
557 562 controller='summary', conditions=dict(function=check_repo))
558 563
559 564 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
560 565 controller='shortlog', conditions=dict(function=check_repo))
561 566
562 567 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
563 568 controller='shortlog', f_path=None,
564 569 conditions=dict(function=check_repo))
565 570
566 571 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
567 572 controller='branches', conditions=dict(function=check_repo))
568 573
569 574 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
570 575 controller='tags', conditions=dict(function=check_repo))
571 576
572 577 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
573 578 controller='bookmarks', conditions=dict(function=check_repo))
574 579
575 580 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
576 581 controller='changelog', conditions=dict(function=check_repo))
577 582
578 583 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
579 584 controller='changelog', action='changelog_details',
580 585 conditions=dict(function=check_repo))
581 586
582 587 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
583 588 controller='files', revision='tip', f_path='',
584 589 conditions=dict(function=check_repo))
585 590
586 591 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
587 592 controller='files', revision='tip', f_path='',
588 593 conditions=dict(function=check_repo))
589 594
590 595 rmap.connect('files_history_home',
591 596 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
592 597 controller='files', action='history', revision='tip', f_path='',
593 598 conditions=dict(function=check_repo))
594 599
595 600 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
596 601 controller='files', action='diff', revision='tip', f_path='',
597 602 conditions=dict(function=check_repo))
598 603
599 604 rmap.connect('files_rawfile_home',
600 605 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
601 606 controller='files', action='rawfile', revision='tip',
602 607 f_path='', conditions=dict(function=check_repo))
603 608
604 609 rmap.connect('files_raw_home',
605 610 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
606 611 controller='files', action='raw', revision='tip', f_path='',
607 612 conditions=dict(function=check_repo))
608 613
609 614 rmap.connect('files_annotate_home',
610 615 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
611 616 controller='files', action='index', revision='tip',
612 617 f_path='', annotate=True, conditions=dict(function=check_repo))
613 618
614 619 rmap.connect('files_edit_home',
615 620 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
616 621 controller='files', action='edit', revision='tip',
617 622 f_path='', conditions=dict(function=check_repo))
618 623
619 624 rmap.connect('files_add_home',
620 625 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
621 626 controller='files', action='add', revision='tip',
622 627 f_path='', conditions=dict(function=check_repo))
623 628
624 629 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
625 630 controller='files', action='archivefile',
626 631 conditions=dict(function=check_repo))
627 632
628 633 rmap.connect('files_nodelist_home',
629 634 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
630 635 controller='files', action='nodelist',
631 636 conditions=dict(function=check_repo))
632 637
633 638 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
634 639 controller='settings', action="delete",
635 640 conditions=dict(method=["DELETE"], function=check_repo))
636 641
637 642 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
638 643 controller='settings', action="update",
639 644 conditions=dict(method=["PUT"], function=check_repo))
640 645
641 646 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
642 647 controller='settings', action='index',
643 648 conditions=dict(function=check_repo))
644 649
645 650 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
646 651 controller='settings', action="toggle_locking",
647 652 conditions=dict(method=["GET"], function=check_repo))
648 653
649 654 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
650 655 controller='forks', action='fork_create',
651 656 conditions=dict(function=check_repo, method=["POST"]))
652 657
653 658 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
654 659 controller='forks', action='fork',
655 660 conditions=dict(function=check_repo))
656 661
657 662 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
658 663 controller='forks', action='forks',
659 664 conditions=dict(function=check_repo))
660 665
661 666 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
662 667 controller='followers', action='followers',
663 668 conditions=dict(function=check_repo))
664 669
665 670 return rmap
@@ -1,540 +1,579
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
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
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from webob.exc import HTTPInternalServerError, HTTPForbidden
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 import rhodecode
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\
41 41 HasPermissionAny, HasReposGroupPermissionAny
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
44 44 from rhodecode.lib.helpers import get_token
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
47 47 RhodeCodeSetting, RepositoryField
48 from rhodecode.model.forms import RepoForm, RepoFieldForm
48 from rhodecode.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
49 49 from rhodecode.model.scm import ScmModel, GroupList
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.lib.compat import json
52 52 from sqlalchemy.sql.expression import func
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class ReposController(BaseRepoController):
58 58 """
59 59 REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('repo', 'repos')
63 63
64 64 @LoginRequired()
65 65 def __before__(self):
66 66 c.admin_user = session.get('admin_user')
67 67 c.admin_username = session.get('admin_username')
68 68 super(ReposController, self).__before__()
69 69
70 70 def __load_defaults(self):
71 71 acl_groups = GroupList(RepoGroup.query().all(),
72 72 perm_set=['group.write', 'group.admin'])
73 73 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
74 74 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
75 75
76 76 repo_model = RepoModel()
77 77 c.users_array = repo_model.get_users_js()
78 78 c.users_groups_array = repo_model.get_users_groups_js()
79 79 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
80 80 c.landing_revs_choices = choices
81 81
82 82 def __load_data(self, repo_name=None):
83 83 """
84 84 Load defaults settings for edit, and update
85 85
86 86 :param repo_name:
87 87 """
88 88 self.__load_defaults()
89 89
90 90 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
91 91 repo = db_repo.scm_instance
92 92
93 93 if c.repo_info is None:
94 94 h.not_mapped_error(repo_name)
95 95 return redirect(url('repos'))
96 96
97 97 ##override defaults for exact repo info here git/hg etc
98 98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
99 99 c.landing_revs_choices = choices
100 100
101 101 c.default_user_id = User.get_by_username('default').user_id
102 102 c.in_public_journal = UserFollowing.query()\
103 103 .filter(UserFollowing.user_id == c.default_user_id)\
104 104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
105 105
106 106 if c.repo_info.stats:
107 107 # this is on what revision we ended up so we add +1 for count
108 108 last_rev = c.repo_info.stats.stat_on_revision + 1
109 109 else:
110 110 last_rev = 0
111 111 c.stats_revision = last_rev
112 112
113 113 c.repo_last_rev = repo.count() if repo.revisions else 0
114 114
115 115 if last_rev == 0 or c.repo_last_rev == 0:
116 116 c.stats_percentage = 0
117 117 else:
118 118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
119 119 c.repo_last_rev) * 100)
120 120
121 121 c.repo_fields = RepositoryField.query()\
122 122 .filter(RepositoryField.repository == db_repo).all()
123 123
124 124 defaults = RepoModel()._get_defaults(repo_name)
125 125
126 126 c.repos_list = [('', _('--REMOVE FORK--'))]
127 127 c.repos_list += [(x.repo_id, x.repo_name) for x in
128 128 Repository.query().order_by(Repository.repo_name).all()
129 129 if x.repo_id != c.repo_info.repo_id]
130 130
131 131 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
132 132 return defaults
133 133
134 134 @HasPermissionAllDecorator('hg.admin')
135 135 def index(self, format='html'):
136 136 """GET /repos: All items in the collection"""
137 137 # url('repos')
138 138
139 139 c.repos_list = Repository.query()\
140 140 .order_by(func.lower(Repository.repo_name))\
141 141 .all()
142 142
143 143 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
144 144 admin=True,
145 145 super_user_actions=True)
146 146 #json used to render the grid
147 147 c.data = json.dumps(repos_data)
148 148
149 149 return render('admin/repos/repos.html')
150 150
151 151 @NotAnonymous()
152 152 def create(self):
153 153 """
154 154 POST /repos: Create a new item"""
155 155 # url('repos')
156 156
157 157 self.__load_defaults()
158 158 form_result = {}
159 159 try:
160 160 form_result = RepoForm(repo_groups=c.repo_groups_choices,
161 161 landing_revs=c.landing_revs_choices)()\
162 162 .to_python(dict(request.POST))
163 163
164 164 new_repo = RepoModel().create(form_result,
165 165 self.rhodecode_user.user_id)
166 166 if form_result['clone_uri']:
167 167 h.flash(_('Created repository %s from %s') \
168 168 % (form_result['repo_name'], form_result['clone_uri']),
169 169 category='success')
170 170 else:
171 171 repo_url = h.link_to(form_result['repo_name'],
172 172 h.url('summary_home', repo_name=form_result['repo_name']))
173 173 h.flash(h.literal(_('Created repository %s') % repo_url),
174 174 category='success')
175 175
176 176 if request.POST.get('user_created'):
177 177 # created by regular non admin user
178 178 action_logger(self.rhodecode_user, 'user_created_repo',
179 179 form_result['repo_name_full'], self.ip_addr,
180 180 self.sa)
181 181 else:
182 182 action_logger(self.rhodecode_user, 'admin_created_repo',
183 183 form_result['repo_name_full'], self.ip_addr,
184 184 self.sa)
185 185 Session().commit()
186 186 except formencode.Invalid, errors:
187 187 return htmlfill.render(
188 188 render('admin/repos/repo_add.html'),
189 189 defaults=errors.value,
190 190 errors=errors.error_dict or {},
191 191 prefix_error=False,
192 192 encoding="UTF-8")
193 193
194 194 except Exception:
195 195 log.error(traceback.format_exc())
196 196 msg = _('error occurred during creation of repository %s') \
197 197 % form_result.get('repo_name')
198 198 h.flash(msg, category='error')
199 199 if c.rhodecode_user.is_admin:
200 200 return redirect(url('repos'))
201 201 return redirect(url('home'))
202 202 #redirect to our new repo !
203 203 return redirect(url('summary_home', repo_name=new_repo.repo_name))
204 204
205 205 @HasPermissionAllDecorator('hg.admin')
206 206 def new(self, format='html'):
207 207 """
208 208 WARNING: this function is depracated see settings.create_repo !!
209 209
210 210 GET /repos/new: Form to create a new item
211 211 """
212 212
213 213 parent_group = request.GET.get('parent_group')
214 214 self.__load_defaults()
215 215 ## apply the defaults from defaults page
216 216 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
217 217 if parent_group:
218 218 defaults.update({'repo_group': parent_group})
219 219
220 220 return htmlfill.render(
221 221 render('admin/repos/repo_add.html'),
222 222 defaults=defaults,
223 223 errors={},
224 224 prefix_error=False,
225 225 encoding="UTF-8"
226 226 )
227 227
228 228 @HasPermissionAllDecorator('hg.admin')
229 229 def update(self, repo_name):
230 230 """
231 231 PUT /repos/repo_name: Update an existing item"""
232 232 # Forms posted to this method should contain a hidden field:
233 233 # <input type="hidden" name="_method" value="PUT" />
234 234 # Or using helpers:
235 235 # h.form(url('repo', repo_name=ID),
236 236 # method='put')
237 237 # url('repo', repo_name=ID)
238 238 self.__load_defaults()
239 239 repo_model = RepoModel()
240 240 changed_name = repo_name
241 241 #override the choices with extracted revisions !
242 242 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
243 243 c.landing_revs_choices = choices
244 244 repo = Repository.get_by_repo_name(repo_name)
245 245 _form = RepoForm(edit=True, old_data={'repo_name': repo_name,
246 246 'repo_group': repo.group.get_dict() \
247 247 if repo.group else {}},
248 248 repo_groups=c.repo_groups_choices,
249 249 landing_revs=c.landing_revs_choices)()
250 250 try:
251 251 form_result = _form.to_python(dict(request.POST))
252 252 repo = repo_model.update(repo_name, **form_result)
253 253 invalidate_cache('get_repo_cached_%s' % repo_name)
254 254 h.flash(_('Repository %s updated successfully') % repo_name,
255 255 category='success')
256 256 changed_name = repo.repo_name
257 257 action_logger(self.rhodecode_user, 'admin_updated_repo',
258 258 changed_name, self.ip_addr, self.sa)
259 259 Session().commit()
260 260 except formencode.Invalid, errors:
261 261 defaults = self.__load_data(repo_name)
262 262 defaults.update(errors.value)
263 263 return htmlfill.render(
264 264 render('admin/repos/repo_edit.html'),
265 265 defaults=defaults,
266 266 errors=errors.error_dict or {},
267 267 prefix_error=False,
268 268 encoding="UTF-8")
269 269
270 270 except Exception:
271 271 log.error(traceback.format_exc())
272 272 h.flash(_('Error occurred during update of repository %s') \
273 273 % repo_name, category='error')
274 274 return redirect(url('edit_repo', repo_name=changed_name))
275 275
276 276 @HasPermissionAllDecorator('hg.admin')
277 277 def delete(self, repo_name):
278 278 """
279 279 DELETE /repos/repo_name: Delete an existing item"""
280 280 # Forms posted to this method should contain a hidden field:
281 281 # <input type="hidden" name="_method" value="DELETE" />
282 282 # Or using helpers:
283 283 # h.form(url('repo', repo_name=ID),
284 284 # method='delete')
285 285 # url('repo', repo_name=ID)
286 286
287 287 repo_model = RepoModel()
288 288 repo = repo_model.get_by_repo_name(repo_name)
289 289 if not repo:
290 290 h.not_mapped_error(repo_name)
291 291 return redirect(url('repos'))
292 292 try:
293 293 _forks = repo.forks.count()
294 294 if _forks and request.POST.get('forks'):
295 295 do = request.POST['forks']
296 296 if do == 'detach_forks':
297 297 for r in repo.forks:
298 298 log.debug('Detaching fork %s from repo %s' % (r, repo))
299 299 r.fork = None
300 300 Session().add(r)
301 301 h.flash(_('Detached %s forks') % _forks, category='success')
302 302 elif do == 'delete_forks':
303 303 for r in repo.forks:
304 304 log.debug('Deleting fork %s of repo %s' % (r, repo))
305 305 repo_model.delete(r)
306 306 h.flash(_('Deleted %s forks') % _forks, category='success')
307 307 action_logger(self.rhodecode_user, 'admin_deleted_repo',
308 308 repo_name, self.ip_addr, self.sa)
309 309 repo_model.delete(repo)
310 310 invalidate_cache('get_repo_cached_%s' % repo_name)
311 311 h.flash(_('Deleted repository %s') % repo_name, category='success')
312 312 Session().commit()
313 313 except IntegrityError, e:
314 314 if e.message.find('repositories_fork_id_fkey') != -1:
315 315 log.error(traceback.format_exc())
316 316 h.flash(_('Cannot delete %s it still contains attached '
317 317 'forks') % repo_name,
318 318 category='warning')
319 319 else:
320 320 log.error(traceback.format_exc())
321 321 h.flash(_('An error occurred during '
322 322 'deletion of %s') % repo_name,
323 323 category='error')
324 324
325 325 except Exception, e:
326 326 log.error(traceback.format_exc())
327 327 h.flash(_('An error occurred during deletion of %s') % repo_name,
328 328 category='error')
329 329
330 330 return redirect(url('repos'))
331 331
332 332 @HasRepoPermissionAllDecorator('repository.admin')
333 def set_repo_perm_member(self, repo_name):
334 form = RepoPermsForm()().to_python(request.POST)
335
336 perms_new = form['perms_new']
337 perms_updates = form['perms_updates']
338 cur_repo = repo_name
339
340 # update permissions
341 for member, perm, member_type in perms_updates:
342 if member_type == 'user':
343 # this updates existing one
344 RepoModel().grant_user_permission(
345 repo=cur_repo, user=member, perm=perm
346 )
347 else:
348 RepoModel().grant_users_group_permission(
349 repo=cur_repo, group_name=member, perm=perm
350 )
351 # set new permissions
352 for member, perm, member_type in perms_new:
353 if member_type == 'user':
354 RepoModel().grant_user_permission(
355 repo=cur_repo, user=member, perm=perm
356 )
357 else:
358 RepoModel().grant_users_group_permission(
359 repo=cur_repo, group_name=member, perm=perm
360 )
361 #TODO: implement this
362 #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
363 # repo_name, self.ip_addr, self.sa)
364 Session().commit()
365 h.flash(_('updated repository permissions'), category='success')
366 return redirect(url('edit_repo', repo_name=repo_name))
367
368 @HasRepoPermissionAllDecorator('repository.admin')
333 369 def delete_perm_user(self, repo_name):
334 370 """
335 371 DELETE an existing repository permission user
336 372
337 373 :param repo_name:
338 374 """
339 375 try:
340 376 RepoModel().revoke_user_permission(repo=repo_name,
341 377 user=request.POST['user_id'])
378 #TODO: implement this
379 #action_logger(self.rhodecode_user, 'admin_revoked_repo_permissions',
380 # repo_name, self.ip_addr, self.sa)
342 381 Session().commit()
343 382 except Exception:
344 383 log.error(traceback.format_exc())
345 384 h.flash(_('An error occurred during deletion of repository user'),
346 385 category='error')
347 386 raise HTTPInternalServerError()
348 387
349 388 @HasRepoPermissionAllDecorator('repository.admin')
350 389 def delete_perm_users_group(self, repo_name):
351 390 """
352 391 DELETE an existing repository permission user group
353 392
354 393 :param repo_name:
355 394 """
356 395
357 396 try:
358 397 RepoModel().revoke_users_group_permission(
359 398 repo=repo_name, group_name=request.POST['users_group_id']
360 399 )
361 400 Session().commit()
362 401 except Exception:
363 402 log.error(traceback.format_exc())
364 403 h.flash(_('An error occurred during deletion of repository'
365 404 ' user groups'),
366 405 category='error')
367 406 raise HTTPInternalServerError()
368 407
369 408 @HasPermissionAllDecorator('hg.admin')
370 409 def repo_stats(self, repo_name):
371 410 """
372 411 DELETE an existing repository statistics
373 412
374 413 :param repo_name:
375 414 """
376 415
377 416 try:
378 417 RepoModel().delete_stats(repo_name)
379 418 Session().commit()
380 419 except Exception, e:
381 420 log.error(traceback.format_exc())
382 421 h.flash(_('An error occurred during deletion of repository stats'),
383 422 category='error')
384 423 return redirect(url('edit_repo', repo_name=repo_name))
385 424
386 425 @HasPermissionAllDecorator('hg.admin')
387 426 def repo_cache(self, repo_name):
388 427 """
389 428 INVALIDATE existing repository cache
390 429
391 430 :param repo_name:
392 431 """
393 432
394 433 try:
395 434 ScmModel().mark_for_invalidation(repo_name)
396 435 Session().commit()
397 436 except Exception, e:
398 437 log.error(traceback.format_exc())
399 438 h.flash(_('An error occurred during cache invalidation'),
400 439 category='error')
401 440 return redirect(url('edit_repo', repo_name=repo_name))
402 441
403 442 @HasPermissionAllDecorator('hg.admin')
404 443 def repo_locking(self, repo_name):
405 444 """
406 445 Unlock repository when it is locked !
407 446
408 447 :param repo_name:
409 448 """
410 449
411 450 try:
412 451 repo = Repository.get_by_repo_name(repo_name)
413 452 if request.POST.get('set_lock'):
414 453 Repository.lock(repo, c.rhodecode_user.user_id)
415 454 elif request.POST.get('set_unlock'):
416 455 Repository.unlock(repo)
417 456 except Exception, e:
418 457 log.error(traceback.format_exc())
419 458 h.flash(_('An error occurred during unlocking'),
420 459 category='error')
421 460 return redirect(url('edit_repo', repo_name=repo_name))
422 461
423 462 @HasPermissionAllDecorator('hg.admin')
424 463 def repo_public_journal(self, repo_name):
425 464 """
426 465 Set's this repository to be visible in public journal,
427 466 in other words assing default user to follow this repo
428 467
429 468 :param repo_name:
430 469 """
431 470
432 471 cur_token = request.POST.get('auth_token')
433 472 token = get_token()
434 473 if cur_token == token:
435 474 try:
436 475 repo_id = Repository.get_by_repo_name(repo_name).repo_id
437 476 user_id = User.get_by_username('default').user_id
438 477 self.scm_model.toggle_following_repo(repo_id, user_id)
439 478 h.flash(_('Updated repository visibility in public journal'),
440 479 category='success')
441 480 Session().commit()
442 481 except:
443 482 h.flash(_('An error occurred during setting this'
444 483 ' repository in public journal'),
445 484 category='error')
446 485
447 486 else:
448 487 h.flash(_('Token mismatch'), category='error')
449 488 return redirect(url('edit_repo', repo_name=repo_name))
450 489
451 490 @HasPermissionAllDecorator('hg.admin')
452 491 def repo_pull(self, repo_name):
453 492 """
454 493 Runs task to update given repository with remote changes,
455 494 ie. make pull on remote location
456 495
457 496 :param repo_name:
458 497 """
459 498 try:
460 499 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
461 500 h.flash(_('Pulled from remote location'), category='success')
462 501 except Exception, e:
463 502 h.flash(_('An error occurred during pull from remote location'),
464 503 category='error')
465 504
466 505 return redirect(url('edit_repo', repo_name=repo_name))
467 506
468 507 @HasPermissionAllDecorator('hg.admin')
469 508 def repo_as_fork(self, repo_name):
470 509 """
471 510 Mark given repository as a fork of another
472 511
473 512 :param repo_name:
474 513 """
475 514 try:
476 515 fork_id = request.POST.get('id_fork_of')
477 516 repo = ScmModel().mark_as_fork(repo_name, fork_id,
478 517 self.rhodecode_user.username)
479 518 fork = repo.fork.repo_name if repo.fork else _('Nothing')
480 519 Session().commit()
481 520 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
482 521 category='success')
483 522 except Exception, e:
484 523 log.error(traceback.format_exc())
485 524 h.flash(_('An error occurred during this operation'),
486 525 category='error')
487 526
488 527 return redirect(url('edit_repo', repo_name=repo_name))
489 528
490 529 @HasPermissionAllDecorator('hg.admin')
491 530 def show(self, repo_name, format='html'):
492 531 """GET /repos/repo_name: Show a specific item"""
493 532 # url('repo', repo_name=ID)
494 533
495 534 @HasPermissionAllDecorator('hg.admin')
496 535 def edit(self, repo_name, format='html'):
497 536 """GET /repos/repo_name/edit: Form to edit an existing item"""
498 537 # url('edit_repo', repo_name=ID)
499 538 defaults = self.__load_data(repo_name)
500 539
501 540 return htmlfill.render(
502 541 render('admin/repos/repo_edit.html'),
503 542 defaults=defaults,
504 543 encoding="UTF-8",
505 544 force_defaults=False
506 545 )
507 546
508 547 @HasPermissionAllDecorator('hg.admin')
509 548 def create_repo_field(self, repo_name):
510 549 try:
511 550 form_result = RepoFieldForm()().to_python(dict(request.POST))
512 551 new_field = RepositoryField()
513 552 new_field.repository = Repository.get_by_repo_name(repo_name)
514 553 new_field.field_key = form_result['new_field_key']
515 554 new_field.field_type = form_result['new_field_type'] # python type
516 555 new_field.field_value = form_result['new_field_value'] # set initial blank value
517 556 new_field.field_desc = form_result['new_field_desc']
518 557 new_field.field_label = form_result['new_field_label']
519 558 Session().add(new_field)
520 559 Session().commit()
521 560
522 561 except Exception, e:
523 562 log.error(traceback.format_exc())
524 563 msg = _('An error occurred during creation of field')
525 564 if isinstance(e, formencode.Invalid):
526 565 msg += ". " + e.msg
527 566 h.flash(msg, category='error')
528 567 return redirect(url('edit_repo', repo_name=repo_name))
529 568
530 569 @HasPermissionAllDecorator('hg.admin')
531 570 def delete_repo_field(self, repo_name, field_id):
532 571 field = RepositoryField.get_or_404(field_id)
533 572 try:
534 573 Session().delete(field)
535 574 Session().commit()
536 575 except Exception, e:
537 576 log.error(traceback.format_exc())
538 577 msg = _('An error occurred during removal of field')
539 578 h.flash(msg, category='error')
540 579 return redirect(url('edit_repo', repo_name=repo_name))
@@ -1,401 +1,408
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 recursive = v.StringBoolean(if_missing=False)
138 138 chained_validators = [v.ValidReposGroup(edit, old_data),
139 139 v.ValidPerms('group')]
140 140
141 141 return _ReposGroupForm
142 142
143 143
144 144 def RegisterForm(edit=False, old_data={}):
145 145 class _RegisterForm(formencode.Schema):
146 146 allow_extra_fields = True
147 147 filter_extra_fields = True
148 148 username = All(
149 149 v.ValidUsername(edit, old_data),
150 150 v.UnicodeString(strip=True, min=1, not_empty=True)
151 151 )
152 152 password = All(
153 153 v.ValidPassword(),
154 154 v.UnicodeString(strip=False, min=6, not_empty=True)
155 155 )
156 156 password_confirmation = All(
157 157 v.ValidPassword(),
158 158 v.UnicodeString(strip=False, min=6, not_empty=True)
159 159 )
160 160 active = v.StringBoolean(if_missing=False)
161 161 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
162 162 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
163 163 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
164 164
165 165 chained_validators = [v.ValidPasswordsMatch()]
166 166
167 167 return _RegisterForm
168 168
169 169
170 170 def PasswordResetForm():
171 171 class _PasswordResetForm(formencode.Schema):
172 172 allow_extra_fields = True
173 173 filter_extra_fields = True
174 174 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
175 175 return _PasswordResetForm
176 176
177 177
178 178 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
179 179 repo_groups=[], landing_revs=[]):
180 180 class _RepoForm(formencode.Schema):
181 181 allow_extra_fields = True
182 182 filter_extra_fields = False
183 183 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
184 184 v.SlugifyName())
185 185 repo_group = All(v.CanWriteGroup(old_data),
186 186 v.OneOf(repo_groups, hideList=True))
187 187 repo_type = v.OneOf(supported_backends)
188 188 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
189 189 repo_private = v.StringBoolean(if_missing=False)
190 190 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
191 191 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
192 192
193 193 repo_enable_statistics = v.StringBoolean(if_missing=False)
194 194 repo_enable_downloads = v.StringBoolean(if_missing=False)
195 195 repo_enable_locking = v.StringBoolean(if_missing=False)
196 196
197 197 if edit:
198 198 #this is repo owner
199 199 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
200 200
201 201 chained_validators = [v.ValidCloneUri(),
202 v.ValidRepoName(edit, old_data),
203 v.ValidPerms()]
202 v.ValidRepoName(edit, old_data)]
204 203 return _RepoForm
205 204
206 205
206 def RepoPermsForm():
207 class _RepoPermsForm(formencode.Schema):
208 allow_extra_fields = True
209 filter_extra_fields = False
210 chained_validators = [v.ValidPerms()]
211 return _RepoPermsForm
212
213
207 214 def RepoFieldForm():
208 215 class _RepoFieldForm(formencode.Schema):
209 216 filter_extra_fields = True
210 217 allow_extra_fields = True
211 218
212 219 new_field_key = All(v.FieldKey(),
213 220 v.UnicodeString(strip=True, min=3, not_empty=True))
214 221 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
215 222 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
216 223 if_missing='str')
217 224 new_field_label = v.UnicodeString(not_empty=False)
218 225 new_field_desc = v.UnicodeString(not_empty=False)
219 226
220 227 return _RepoFieldForm
221 228
222 229
223 230 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
224 231 repo_groups=[], landing_revs=[]):
225 232 class _RepoForm(formencode.Schema):
226 233 allow_extra_fields = True
227 234 filter_extra_fields = False
228 235 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
229 236 v.SlugifyName())
230 237 repo_group = All(v.CanWriteGroup(old_data),
231 238 v.OneOf(repo_groups, hideList=True))
232 239 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
233 240 repo_private = v.StringBoolean(if_missing=False)
234 241 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
235 242 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
236 243
237 244 chained_validators = [v.ValidCloneUri(),
238 245 v.ValidRepoName(edit, old_data),
239 246 v.ValidPerms(),
240 247 v.ValidSettings()]
241 248 return _RepoForm
242 249
243 250
244 251 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
245 252 repo_groups=[], landing_revs=[]):
246 253 class _RepoForkForm(formencode.Schema):
247 254 allow_extra_fields = True
248 255 filter_extra_fields = False
249 256 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
250 257 v.SlugifyName())
251 258 repo_group = All(v.CanWriteGroup(),
252 259 v.OneOf(repo_groups, hideList=True))
253 260 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
254 261 description = v.UnicodeString(strip=True, min=1, not_empty=True)
255 262 private = v.StringBoolean(if_missing=False)
256 263 copy_permissions = v.StringBoolean(if_missing=False)
257 264 update_after_clone = v.StringBoolean(if_missing=False)
258 265 fork_parent_id = v.UnicodeString()
259 266 chained_validators = [v.ValidForkName(edit, old_data)]
260 267 landing_rev = v.OneOf(landing_revs, hideList=True)
261 268
262 269 return _RepoForkForm
263 270
264 271
265 272 def ApplicationSettingsForm():
266 273 class _ApplicationSettingsForm(formencode.Schema):
267 274 allow_extra_fields = True
268 275 filter_extra_fields = False
269 276 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
270 277 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
271 278 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
272 279
273 280 return _ApplicationSettingsForm
274 281
275 282
276 283 def ApplicationVisualisationForm():
277 284 class _ApplicationVisualisationForm(formencode.Schema):
278 285 allow_extra_fields = True
279 286 filter_extra_fields = False
280 287 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
281 288 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
282 289 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
283 290
284 291 rhodecode_lightweight_dashboard = v.StringBoolean(if_missing=False)
285 292 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
286 293 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
287 294
288 295 return _ApplicationVisualisationForm
289 296
290 297
291 298 def ApplicationUiSettingsForm():
292 299 class _ApplicationUiSettingsForm(formencode.Schema):
293 300 allow_extra_fields = True
294 301 filter_extra_fields = False
295 302 web_push_ssl = v.StringBoolean(if_missing=False)
296 303 paths_root_path = All(
297 304 v.ValidPath(),
298 305 v.UnicodeString(strip=True, min=1, not_empty=True)
299 306 )
300 307 hooks_changegroup_update = v.StringBoolean(if_missing=False)
301 308 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
302 309 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
303 310 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
304 311
305 312 extensions_largefiles = v.StringBoolean(if_missing=False)
306 313 extensions_hgsubversion = v.StringBoolean(if_missing=False)
307 314 extensions_hggit = v.StringBoolean(if_missing=False)
308 315
309 316 return _ApplicationUiSettingsForm
310 317
311 318
312 319 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
313 320 register_choices, create_choices, fork_choices):
314 321 class _DefaultPermissionsForm(formencode.Schema):
315 322 allow_extra_fields = True
316 323 filter_extra_fields = True
317 324 overwrite_default_repo = v.StringBoolean(if_missing=False)
318 325 overwrite_default_group = v.StringBoolean(if_missing=False)
319 326 anonymous = v.StringBoolean(if_missing=False)
320 327 default_repo_perm = v.OneOf(repo_perms_choices)
321 328 default_group_perm = v.OneOf(group_perms_choices)
322 329 default_register = v.OneOf(register_choices)
323 330 default_create = v.OneOf(create_choices)
324 331 default_fork = v.OneOf(fork_choices)
325 332
326 333 return _DefaultPermissionsForm
327 334
328 335
329 336 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
330 337 class _DefaultsForm(formencode.Schema):
331 338 allow_extra_fields = True
332 339 filter_extra_fields = True
333 340 default_repo_type = v.OneOf(supported_backends)
334 341 default_repo_private = v.StringBoolean(if_missing=False)
335 342 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
336 343 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
337 344 default_repo_enable_locking = v.StringBoolean(if_missing=False)
338 345
339 346 return _DefaultsForm
340 347
341 348
342 349 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
343 350 tls_kind_choices):
344 351 class _LdapSettingsForm(formencode.Schema):
345 352 allow_extra_fields = True
346 353 filter_extra_fields = True
347 354 #pre_validators = [LdapLibValidator]
348 355 ldap_active = v.StringBoolean(if_missing=False)
349 356 ldap_host = v.UnicodeString(strip=True,)
350 357 ldap_port = v.Number(strip=True,)
351 358 ldap_tls_kind = v.OneOf(tls_kind_choices)
352 359 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
353 360 ldap_dn_user = v.UnicodeString(strip=True,)
354 361 ldap_dn_pass = v.UnicodeString(strip=True,)
355 362 ldap_base_dn = v.UnicodeString(strip=True,)
356 363 ldap_filter = v.UnicodeString(strip=True,)
357 364 ldap_search_scope = v.OneOf(search_scope_choices)
358 365 ldap_attr_login = All(
359 366 v.AttrLoginValidator(),
360 367 v.UnicodeString(strip=True,)
361 368 )
362 369 ldap_attr_firstname = v.UnicodeString(strip=True,)
363 370 ldap_attr_lastname = v.UnicodeString(strip=True,)
364 371 ldap_attr_email = v.UnicodeString(strip=True,)
365 372
366 373 return _LdapSettingsForm
367 374
368 375
369 376 def UserExtraEmailForm():
370 377 class _UserExtraEmailForm(formencode.Schema):
371 378 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
372 379 return _UserExtraEmailForm
373 380
374 381
375 382 def UserExtraIpForm():
376 383 class _UserExtraIpForm(formencode.Schema):
377 384 ip = v.ValidIp()(not_empty=True)
378 385 return _UserExtraIpForm
379 386
380 387
381 388 def PullRequestForm(repo_id):
382 389 class _PullRequestForm(formencode.Schema):
383 390 allow_extra_fields = True
384 391 filter_extra_fields = True
385 392
386 393 user = v.UnicodeString(strip=True, required=True)
387 394 org_repo = v.UnicodeString(strip=True, required=True)
388 395 org_ref = v.UnicodeString(strip=True, required=True)
389 396 other_repo = v.UnicodeString(strip=True, required=True)
390 397 other_ref = v.UnicodeString(strip=True, required=True)
391 398 revisions = All(#v.NotReviewedRevisions(repo_id)(),
392 399 v.UniqueList(not_empty=True))
393 400 review_members = v.UniqueList(not_empty=True)
394 401
395 402 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
396 403 pullrequest_desc = v.UnicodeString(strip=True, required=False)
397 404
398 405 ancestor_rev = v.UnicodeString(strip=True, required=True)
399 406 merge_rev = v.UnicodeString(strip=True, required=True)
400 407
401 408 return _PullRequestForm
@@ -1,700 +1,678
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
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 from __future__ import with_statement
26 26 import os
27 27 import shutil
28 28 import logging
29 29 import traceback
30 30 from datetime import datetime
31 31
32 32 from rhodecode.lib.vcs.backends import get_backend
33 33 from rhodecode.lib.compat import json
34 34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\
35 35 remove_prefix, obfuscate_url_pw
36 36 from rhodecode.lib.caching_query import FromCache
37 37 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
38 38
39 39 from rhodecode.model import BaseModel
40 40 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
41 41 Statistics, UserGroup, UserGroupRepoToPerm, RhodeCodeUi, RepoGroup,\
42 42 RhodeCodeSetting, RepositoryField
43 43 from rhodecode.lib import helpers as h
44 44 from rhodecode.lib.auth import HasRepoPermissionAny
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class RepoModel(BaseModel):
50 50
51 51 cls = Repository
52 52 URL_SEPARATOR = Repository.url_sep()
53 53
54 54 def __get_users_group(self, users_group):
55 55 return self._get_instance(UserGroup, users_group,
56 56 callback=UserGroup.get_by_group_name)
57 57
58 58 def _get_repos_group(self, repos_group):
59 59 return self._get_instance(RepoGroup, repos_group,
60 60 callback=RepoGroup.get_by_group_name)
61 61
62 62 @LazyProperty
63 63 def repos_path(self):
64 64 """
65 65 Get's the repositories root path from database
66 66 """
67 67
68 68 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
69 69 return q.ui_value
70 70
71 71 def get(self, repo_id, cache=False):
72 72 repo = self.sa.query(Repository)\
73 73 .filter(Repository.repo_id == repo_id)
74 74
75 75 if cache:
76 76 repo = repo.options(FromCache("sql_cache_short",
77 77 "get_repo_%s" % repo_id))
78 78 return repo.scalar()
79 79
80 80 def get_repo(self, repository):
81 81 return self._get_repo(repository)
82 82
83 83 def get_by_repo_name(self, repo_name, cache=False):
84 84 repo = self.sa.query(Repository)\
85 85 .filter(Repository.repo_name == repo_name)
86 86
87 87 if cache:
88 88 repo = repo.options(FromCache("sql_cache_short",
89 89 "get_repo_%s" % repo_name))
90 90 return repo.scalar()
91 91
92 92 def get_all_user_repos(self, user):
93 93 """
94 94 Get's all repositories that user have at least read access
95 95
96 96 :param user:
97 97 :type user:
98 98 """
99 99 from rhodecode.lib.auth import AuthUser
100 100 user = self._get_user(user)
101 101 repos = AuthUser(user_id=user.user_id).permissions['repositories']
102 102 access_check = lambda r: r[1] in ['repository.read',
103 103 'repository.write',
104 104 'repository.admin']
105 105 repos = [x[0] for x in filter(access_check, repos.items())]
106 106 return Repository.query().filter(Repository.repo_name.in_(repos))
107 107
108 108 def get_users_js(self):
109 109 users = self.sa.query(User).filter(User.active == True).all()
110 110 return json.dumps([
111 111 {
112 112 'id': u.user_id,
113 113 'fname': u.name,
114 114 'lname': u.lastname,
115 115 'nname': u.username,
116 116 'gravatar_lnk': h.gravatar_url(u.email, 14)
117 117 } for u in users]
118 118 )
119 119
120 120 def get_users_groups_js(self):
121 121 users_groups = self.sa.query(UserGroup)\
122 122 .filter(UserGroup.users_group_active == True).all()
123 123
124 124 return json.dumps([
125 125 {
126 126 'id': gr.users_group_id,
127 127 'grname': gr.users_group_name,
128 128 'grmembers': len(gr.members),
129 129 } for gr in users_groups]
130 130 )
131 131
132 132 @classmethod
133 133 def _render_datatable(cls, tmpl, *args, **kwargs):
134 134 import rhodecode
135 135 from pylons import tmpl_context as c
136 136 from pylons.i18n.translation import _
137 137
138 138 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
139 139 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
140 140
141 141 tmpl = template.get_def(tmpl)
142 142 kwargs.update(dict(_=_, h=h, c=c))
143 143 return tmpl.render(*args, **kwargs)
144 144
145 145 @classmethod
146 146 def update_repoinfo(cls, repositories=None):
147 147 if not repositories:
148 148 repositories = Repository.getAll()
149 149 for repo in repositories:
150 150 repo.update_changeset_cache()
151 151
152 152 def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
153 153 super_user_actions=False):
154 154 _render = self._render_datatable
155 155
156 156 def quick_menu(repo_name):
157 157 return _render('quick_menu', repo_name)
158 158
159 159 def repo_lnk(name, rtype, private, fork_of):
160 160 return _render('repo_name', name, rtype, private, fork_of,
161 161 short_name=not admin, admin=False)
162 162
163 163 def last_change(last_change):
164 164 return _render("last_change", last_change)
165 165
166 166 def rss_lnk(repo_name):
167 167 return _render("rss", repo_name)
168 168
169 169 def atom_lnk(repo_name):
170 170 return _render("atom", repo_name)
171 171
172 172 def last_rev(repo_name, cs_cache):
173 173 return _render('revision', repo_name, cs_cache.get('revision'),
174 174 cs_cache.get('raw_id'), cs_cache.get('author'),
175 175 cs_cache.get('message'))
176 176
177 177 def desc(desc):
178 178 from pylons import tmpl_context as c
179 179 if c.visual.stylify_metatags:
180 180 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
181 181 else:
182 182 return h.urlify_text(h.truncate(desc, 60))
183 183
184 184 def repo_actions(repo_name):
185 185 return _render('repo_actions', repo_name, super_user_actions)
186 186
187 187 def owner_actions(user_id, username):
188 188 return _render('user_name', user_id, username)
189 189
190 190 repos_data = []
191 191 for repo in repos_list:
192 192 if perm_check:
193 193 # check permission at this level
194 194 if not HasRepoPermissionAny(
195 195 'repository.read', 'repository.write', 'repository.admin'
196 196 )(repo.repo_name, 'get_repos_as_dict check'):
197 197 continue
198 198 cs_cache = repo.changeset_cache
199 199 row = {
200 200 "menu": quick_menu(repo.repo_name),
201 201 "raw_name": repo.repo_name.lower(),
202 202 "name": repo_lnk(repo.repo_name, repo.repo_type,
203 203 repo.private, repo.fork),
204 204 "last_change": last_change(repo.last_db_change),
205 205 "last_changeset": last_rev(repo.repo_name, cs_cache),
206 206 "raw_tip": cs_cache.get('revision'),
207 207 "desc": desc(repo.description),
208 208 "owner": h.person(repo.user.username),
209 209 "rss": rss_lnk(repo.repo_name),
210 210 "atom": atom_lnk(repo.repo_name),
211 211
212 212 }
213 213 if admin:
214 214 row.update({
215 215 "action": repo_actions(repo.repo_name),
216 216 "owner": owner_actions(repo.user.user_id,
217 217 h.person(repo.user.username))
218 218 })
219 219 repos_data.append(row)
220 220
221 221 return {
222 222 "totalRecords": len(repos_list),
223 223 "startIndex": 0,
224 224 "sort": "name",
225 225 "dir": "asc",
226 226 "records": repos_data
227 227 }
228 228
229 229 def _get_defaults(self, repo_name):
230 230 """
231 231 Get's information about repository, and returns a dict for
232 232 usage in forms
233 233
234 234 :param repo_name:
235 235 """
236 236
237 237 repo_info = Repository.get_by_repo_name(repo_name)
238 238
239 239 if repo_info is None:
240 240 return None
241 241
242 242 defaults = repo_info.get_dict()
243 243 group, repo_name, repo_name_full = repo_info.groups_and_repo
244 244 defaults['repo_name'] = repo_name
245 245 defaults['repo_group'] = getattr(group[-1] if group else None,
246 246 'group_id', None)
247 247
248 248 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
249 249 (1, 'repo_description'), (1, 'repo_enable_locking'),
250 250 (1, 'repo_landing_rev'), (0, 'clone_uri'),
251 251 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
252 252 attr = k
253 253 if strip:
254 254 attr = remove_prefix(k, 'repo_')
255 255
256 256 defaults[k] = defaults[attr]
257 257
258 258 # fill owner
259 259 if repo_info.user:
260 260 defaults.update({'user': repo_info.user.username})
261 261 else:
262 262 replacement_user = User.query().filter(User.admin ==
263 263 True).first().username
264 264 defaults.update({'user': replacement_user})
265 265
266 266 # fill repository users
267 267 for p in repo_info.repo_to_perm:
268 268 defaults.update({'u_perm_%s' % p.user.username:
269 269 p.permission.permission_name})
270 270
271 271 # fill repository groups
272 272 for p in repo_info.users_group_to_perm:
273 273 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
274 274 p.permission.permission_name})
275 275
276 276 return defaults
277 277
278 278 def update(self, org_repo_name, **kwargs):
279 279 try:
280 280 cur_repo = self.get_by_repo_name(org_repo_name, cache=False)
281 281
282 # update permissions
283 for member, perm, member_type in kwargs['perms_updates']:
284 if member_type == 'user':
285 # this updates existing one
286 RepoModel().grant_user_permission(
287 repo=cur_repo, user=member, perm=perm
288 )
289 else:
290 RepoModel().grant_users_group_permission(
291 repo=cur_repo, group_name=member, perm=perm
292 )
293 # set new permissions
294 for member, perm, member_type in kwargs['perms_new']:
295 if member_type == 'user':
296 RepoModel().grant_user_permission(
297 repo=cur_repo, user=member, perm=perm
298 )
299 else:
300 RepoModel().grant_users_group_permission(
301 repo=cur_repo, group_name=member, perm=perm
302 )
303
304 282 if 'user' in kwargs:
305 283 cur_repo.user = User.get_by_username(kwargs['user'])
306 284
307 285 if 'repo_group' in kwargs:
308 286 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
309 287
310 288 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
311 289 (1, 'repo_description'), (1, 'repo_enable_locking'),
312 290 (1, 'repo_landing_rev'), (0, 'clone_uri'),
313 291 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
314 292 if k in kwargs:
315 293 val = kwargs[k]
316 294 if strip:
317 295 k = remove_prefix(k, 'repo_')
318 296 setattr(cur_repo, k, val)
319 297
320 298 new_name = cur_repo.get_new_name(kwargs['repo_name'])
321 299 cur_repo.repo_name = new_name
322 300
323 301 #handle extra fields
324 302 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
325 303 k = RepositoryField.un_prefix_key(field)
326 304 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
327 305 if ex_field:
328 306 ex_field.field_value = kwargs[field]
329 307 self.sa.add(ex_field)
330 308 self.sa.add(cur_repo)
331 309
332 310 if org_repo_name != new_name:
333 311 # rename repository
334 312 self.__rename_repo(old=org_repo_name, new=new_name)
335 313
336 314 return cur_repo
337 315 except:
338 316 log.error(traceback.format_exc())
339 317 raise
340 318
341 319 def create_repo(self, repo_name, repo_type, description, owner,
342 320 private=False, clone_uri=None, repos_group=None,
343 321 landing_rev='tip', just_db=False, fork_of=None,
344 322 copy_fork_permissions=False, enable_statistics=False,
345 323 enable_locking=False, enable_downloads=False):
346 324 """
347 325 Create repository
348 326
349 327 """
350 328 from rhodecode.model.scm import ScmModel
351 329
352 330 owner = self._get_user(owner)
353 331 fork_of = self._get_repo(fork_of)
354 332 repos_group = self._get_repos_group(repos_group)
355 333 try:
356 334
357 335 # repo name is just a name of repository
358 336 # while repo_name_full is a full qualified name that is combined
359 337 # with name and path of group
360 338 repo_name_full = repo_name
361 339 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
362 340
363 341 new_repo = Repository()
364 342 new_repo.enable_statistics = False
365 343 new_repo.repo_name = repo_name_full
366 344 new_repo.repo_type = repo_type
367 345 new_repo.user = owner
368 346 new_repo.group = repos_group
369 347 new_repo.description = description or repo_name
370 348 new_repo.private = private
371 349 new_repo.clone_uri = clone_uri
372 350 new_repo.landing_rev = landing_rev
373 351
374 352 new_repo.enable_statistics = enable_statistics
375 353 new_repo.enable_locking = enable_locking
376 354 new_repo.enable_downloads = enable_downloads
377 355
378 356 if repos_group:
379 357 new_repo.enable_locking = repos_group.enable_locking
380 358
381 359 if fork_of:
382 360 parent_repo = fork_of
383 361 new_repo.fork = parent_repo
384 362
385 363 self.sa.add(new_repo)
386 364
387 365 def _create_default_perms():
388 366 # create default permission
389 367 repo_to_perm = UserRepoToPerm()
390 368 default = 'repository.read'
391 369 for p in User.get_by_username('default').user_perms:
392 370 if p.permission.permission_name.startswith('repository.'):
393 371 default = p.permission.permission_name
394 372 break
395 373
396 374 default_perm = 'repository.none' if private else default
397 375
398 376 repo_to_perm.permission_id = self.sa.query(Permission)\
399 377 .filter(Permission.permission_name == default_perm)\
400 378 .one().permission_id
401 379
402 380 repo_to_perm.repository = new_repo
403 381 repo_to_perm.user_id = User.get_by_username('default').user_id
404 382
405 383 self.sa.add(repo_to_perm)
406 384
407 385 if fork_of:
408 386 if copy_fork_permissions:
409 387 repo = fork_of
410 388 user_perms = UserRepoToPerm.query()\
411 389 .filter(UserRepoToPerm.repository == repo).all()
412 390 group_perms = UserGroupRepoToPerm.query()\
413 391 .filter(UserGroupRepoToPerm.repository == repo).all()
414 392
415 393 for perm in user_perms:
416 394 UserRepoToPerm.create(perm.user, new_repo,
417 395 perm.permission)
418 396
419 397 for perm in group_perms:
420 398 UserGroupRepoToPerm.create(perm.users_group, new_repo,
421 399 perm.permission)
422 400 else:
423 401 _create_default_perms()
424 402 else:
425 403 _create_default_perms()
426 404
427 405 if not just_db:
428 406 self.__create_repo(repo_name, repo_type,
429 407 repos_group,
430 408 clone_uri)
431 409 log_create_repository(new_repo.get_dict(),
432 410 created_by=owner.username)
433 411
434 412 # now automatically start following this repository as owner
435 413 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
436 414 owner.user_id)
437 415 return new_repo
438 416 except:
439 417 log.error(traceback.format_exc())
440 418 raise
441 419
442 420 def create(self, form_data, cur_user, just_db=False, fork=None):
443 421 """
444 422 Backward compatibility function, just a wrapper on top of create_repo
445 423
446 424 :param form_data:
447 425 :param cur_user:
448 426 :param just_db:
449 427 :param fork:
450 428 """
451 429 owner = cur_user
452 430 repo_name = form_data['repo_name_full']
453 431 repo_type = form_data['repo_type']
454 432 description = form_data['repo_description']
455 433 private = form_data['repo_private']
456 434 clone_uri = form_data.get('clone_uri')
457 435 repos_group = form_data['repo_group']
458 436 landing_rev = form_data['repo_landing_rev']
459 437 copy_fork_permissions = form_data.get('copy_permissions')
460 438 fork_of = form_data.get('fork_parent_id')
461 439
462 440 ## repo creation defaults, private and repo_type are filled in form
463 441 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
464 442 enable_statistics = defs.get('repo_enable_statistics')
465 443 enable_locking = defs.get('repo_enable_locking')
466 444 enable_downloads = defs.get('repo_enable_downloads')
467 445
468 446 return self.create_repo(
469 447 repo_name, repo_type, description, owner, private, clone_uri,
470 448 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions,
471 449 enable_statistics, enable_locking, enable_downloads
472 450 )
473 451
474 452 def create_fork(self, form_data, cur_user):
475 453 """
476 454 Simple wrapper into executing celery task for fork creation
477 455
478 456 :param form_data:
479 457 :param cur_user:
480 458 """
481 459 from rhodecode.lib.celerylib import tasks, run_task
482 460 run_task(tasks.create_repo_fork, form_data, cur_user)
483 461
484 462 def delete(self, repo):
485 463 repo = self._get_repo(repo)
486 464 if repo:
487 465 old_repo_dict = repo.get_dict()
488 466 owner = repo.user
489 467 try:
490 468 self.sa.delete(repo)
491 469 self.__delete_repo(repo)
492 470 log_delete_repository(old_repo_dict,
493 471 deleted_by=owner.username)
494 472 except:
495 473 log.error(traceback.format_exc())
496 474 raise
497 475
498 476 def grant_user_permission(self, repo, user, perm):
499 477 """
500 478 Grant permission for user on given repository, or update existing one
501 479 if found
502 480
503 481 :param repo: Instance of Repository, repository_id, or repository name
504 482 :param user: Instance of User, user_id or username
505 483 :param perm: Instance of Permission, or permission_name
506 484 """
507 485 user = self._get_user(user)
508 486 repo = self._get_repo(repo)
509 487 permission = self._get_perm(perm)
510 488
511 489 # check if we have that permission already
512 490 obj = self.sa.query(UserRepoToPerm)\
513 491 .filter(UserRepoToPerm.user == user)\
514 492 .filter(UserRepoToPerm.repository == repo)\
515 493 .scalar()
516 494 if obj is None:
517 495 # create new !
518 496 obj = UserRepoToPerm()
519 497 obj.repository = repo
520 498 obj.user = user
521 499 obj.permission = permission
522 500 self.sa.add(obj)
523 501 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
524 502
525 503 def revoke_user_permission(self, repo, user):
526 504 """
527 505 Revoke permission for user on given repository
528 506
529 507 :param repo: Instance of Repository, repository_id, or repository name
530 508 :param user: Instance of User, user_id or username
531 509 """
532 510
533 511 user = self._get_user(user)
534 512 repo = self._get_repo(repo)
535 513
536 514 obj = self.sa.query(UserRepoToPerm)\
537 515 .filter(UserRepoToPerm.repository == repo)\
538 516 .filter(UserRepoToPerm.user == user)\
539 517 .scalar()
540 518 if obj:
541 519 self.sa.delete(obj)
542 520 log.debug('Revoked perm on %s on %s' % (repo, user))
543 521
544 522 def grant_users_group_permission(self, repo, group_name, perm):
545 523 """
546 524 Grant permission for user group on given repository, or update
547 525 existing one if found
548 526
549 527 :param repo: Instance of Repository, repository_id, or repository name
550 528 :param group_name: Instance of UserGroup, users_group_id,
551 529 or user group name
552 530 :param perm: Instance of Permission, or permission_name
553 531 """
554 532 repo = self._get_repo(repo)
555 533 group_name = self.__get_users_group(group_name)
556 534 permission = self._get_perm(perm)
557 535
558 536 # check if we have that permission already
559 537 obj = self.sa.query(UserGroupRepoToPerm)\
560 538 .filter(UserGroupRepoToPerm.users_group == group_name)\
561 539 .filter(UserGroupRepoToPerm.repository == repo)\
562 540 .scalar()
563 541
564 542 if obj is None:
565 543 # create new
566 544 obj = UserGroupRepoToPerm()
567 545
568 546 obj.repository = repo
569 547 obj.users_group = group_name
570 548 obj.permission = permission
571 549 self.sa.add(obj)
572 550 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
573 551
574 552 def revoke_users_group_permission(self, repo, group_name):
575 553 """
576 554 Revoke permission for user group on given repository
577 555
578 556 :param repo: Instance of Repository, repository_id, or repository name
579 557 :param group_name: Instance of UserGroup, users_group_id,
580 558 or user group name
581 559 """
582 560 repo = self._get_repo(repo)
583 561 group_name = self.__get_users_group(group_name)
584 562
585 563 obj = self.sa.query(UserGroupRepoToPerm)\
586 564 .filter(UserGroupRepoToPerm.repository == repo)\
587 565 .filter(UserGroupRepoToPerm.users_group == group_name)\
588 566 .scalar()
589 567 if obj:
590 568 self.sa.delete(obj)
591 569 log.debug('Revoked perm to %s on %s' % (repo, group_name))
592 570
593 571 def delete_stats(self, repo_name):
594 572 """
595 573 removes stats for given repo
596 574
597 575 :param repo_name:
598 576 """
599 577 repo = self._get_repo(repo_name)
600 578 try:
601 579 obj = self.sa.query(Statistics)\
602 580 .filter(Statistics.repository == repo).scalar()
603 581 if obj:
604 582 self.sa.delete(obj)
605 583 except:
606 584 log.error(traceback.format_exc())
607 585 raise
608 586
609 587 def __create_repo(self, repo_name, alias, parent, clone_uri=False):
610 588 """
611 589 makes repository on filesystem. It's group aware means it'll create
612 590 a repository within a group, and alter the paths accordingly of
613 591 group location
614 592
615 593 :param repo_name:
616 594 :param alias:
617 595 :param parent_id:
618 596 :param clone_uri:
619 597 """
620 598 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
621 599 from rhodecode.model.scm import ScmModel
622 600
623 601 if parent:
624 602 new_parent_path = os.sep.join(parent.full_path_splitted)
625 603 else:
626 604 new_parent_path = ''
627 605
628 606 # we need to make it str for mercurial
629 607 repo_path = os.path.join(*map(lambda x: safe_str(x),
630 608 [self.repos_path, new_parent_path, repo_name]))
631 609
632 610 # check if this path is not a repository
633 611 if is_valid_repo(repo_path, self.repos_path):
634 612 raise Exception('This path %s is a valid repository' % repo_path)
635 613
636 614 # check if this path is a group
637 615 if is_valid_repos_group(repo_path, self.repos_path):
638 616 raise Exception('This path %s is a valid group' % repo_path)
639 617
640 618 log.info('creating repo %s in %s @ %s' % (
641 619 repo_name, safe_unicode(repo_path),
642 620 obfuscate_url_pw(clone_uri)
643 621 )
644 622 )
645 623 backend = get_backend(alias)
646 624 if alias == 'hg':
647 625 backend(repo_path, create=True, src_url=clone_uri)
648 626 elif alias == 'git':
649 627 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
650 628 # add rhodecode hook into this repo
651 629 ScmModel().install_git_hook(repo=r)
652 630 else:
653 631 raise Exception('Undefined alias %s' % alias)
654 632
655 633 def __rename_repo(self, old, new):
656 634 """
657 635 renames repository on filesystem
658 636
659 637 :param old: old name
660 638 :param new: new name
661 639 """
662 640 log.info('renaming repo from %s to %s' % (old, new))
663 641
664 642 old_path = os.path.join(self.repos_path, old)
665 643 new_path = os.path.join(self.repos_path, new)
666 644 if os.path.isdir(new_path):
667 645 raise Exception(
668 646 'Was trying to rename to already existing dir %s' % new_path
669 647 )
670 648 shutil.move(old_path, new_path)
671 649
672 650 def __delete_repo(self, repo):
673 651 """
674 652 removes repo from filesystem, the removal is acctually made by
675 653 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
676 654 repository is no longer valid for rhodecode, can be undeleted later on
677 655 by reverting the renames on this repository
678 656
679 657 :param repo: repo object
680 658 """
681 659 rm_path = os.path.join(self.repos_path, repo.repo_name)
682 660 log.info("Removing %s" % (rm_path))
683 661 # disable hg/git internal that it doesn't get detected as repo
684 662 alias = repo.repo_type
685 663
686 664 bare = getattr(repo.scm_instance, 'bare', False)
687 665
688 666 if not bare:
689 667 # skip this for bare git repos
690 668 shutil.move(os.path.join(rm_path, '.%s' % alias),
691 669 os.path.join(rm_path, 'rm__.%s' % alias))
692 670 # disable repo
693 671 _now = datetime.now()
694 672 _ms = str(_now.microsecond).rjust(6, '0')
695 673 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
696 674 repo.just_name)
697 675 if repo.group:
698 676 args = repo.group.full_path_splitted + [_d]
699 677 _d = os.path.join(*args)
700 678 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,378 +1,394
1 1 ## -*- coding: utf-8 -*-
2 2 ##
3 3 ## See also repo_settings.html
4 4 ##
5 5 <%inherit file="/base/base.html"/>
6 6
7 7 <%def name="title()">
8 8 ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${_('Settings')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('admin')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 ${self.context_bar('options')}
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="repo_name">${_('Name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('repo_name',class_="medium")}
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="clone_uri">${_('Clone uri')}:</label>
41 41 </div>
42 42 <div class="input">
43 43 ${h.text('clone_uri',class_="medium")}
44 44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 45 </div>
46 46 </div>
47 47 <div class="field">
48 48 <div class="label">
49 49 <label for="repo_group">${_('Repository group')}:</label>
50 50 </div>
51 51 <div class="input">
52 52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 54 </div>
55 55 </div>
56 56 <div class="field">
57 57 <div class="label">
58 58 <label for="repo_type">${_('Type')}:</label>
59 59 </div>
60 60 <div class="input">
61 61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 62 </div>
63 63 </div>
64 64 <div class="field">
65 65 <div class="label">
66 66 <label for="repo_landing_rev">${_('Landing revision')}:</label>
67 67 </div>
68 68 <div class="input">
69 69 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
70 70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 71 </div>
72 72 </div>
73 73 <div class="field">
74 74 <div class="label label-textarea">
75 75 <label for="repo_description">${_('Description')}:</label>
76 76 </div>
77 77 <div class="textarea text-area editor">
78 78 ${h.textarea('repo_description')}
79 79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
80 80 </div>
81 81 </div>
82 82
83 83 <div class="field">
84 84 <div class="label label-checkbox">
85 85 <label for="repo_private">${_('Private repository')}:</label>
86 86 </div>
87 87 <div class="checkboxes">
88 88 ${h.checkbox('repo_private',value="True")}
89 89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
90 90 </div>
91 91 </div>
92 92 <div class="field">
93 93 <div class="label label-checkbox">
94 94 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
95 95 </div>
96 96 <div class="checkboxes">
97 97 ${h.checkbox('repo_enable_statistics',value="True")}
98 98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
99 99 </div>
100 100 </div>
101 101 <div class="field">
102 102 <div class="label label-checkbox">
103 103 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
104 104 </div>
105 105 <div class="checkboxes">
106 106 ${h.checkbox('repo_enable_downloads',value="True")}
107 107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
108 108 </div>
109 109 </div>
110 110 <div class="field">
111 111 <div class="label label-checkbox">
112 112 <label for="repo_enable_locking">${_('Enable locking')}:</label>
113 113 </div>
114 114 <div class="checkboxes">
115 115 ${h.checkbox('repo_enable_locking',value="True")}
116 116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 117 </div>
118 118 </div>
119 119 <div class="field">
120 120 <div class="label">
121 121 <label for="user">${_('Owner')}:</label>
122 122 </div>
123 123 <div class="input input-medium ac">
124 124 <div class="perm_ac">
125 125 ${h.text('user',class_='yui-ac-input')}
126 126 <span class="help-block">${_('Change owner of this repository.')}</span>
127 127 <div id="owner_container"></div>
128 128 </div>
129 129 </div>
130 130 </div>
131 131 %if c.visual.repository_fields:
132 132 ## EXTRA FIELDS
133 133 %for field in c.repo_fields:
134 134 <div class="field">
135 135 <div class="label">
136 136 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
137 137 </div>
138 138 <div class="input input-medium">
139 139 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
140 140 %if field.field_desc:
141 141 <span class="help-block">${field.field_desc}</span>
142 142 %endif
143 143 </div>
144 144 </div>
145 145 %endfor
146 146 %endif
147 <div class="field">
148 <div class="label">
149 <label for="input">${_('Permissions')}:</label>
150 </div>
151 <div class="input">
152 <%include file="repo_edit_perms.html"/>
153 </div>
154 </div>
155
156 147 <div class="buttons">
157 148 ${h.submit('save',_('Save'),class_="ui-btn large")}
158 149 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
159 150 </div>
160 151 </div>
161 152 </div>
162 153 ${h.end_form()}
163 154 </div>
164 155
165 156 <div class="box box-right">
166 157 <div class="title">
158 <h5>${_('Permissions')}</h5>
159 </div>
160 ${h.form(url('set_repo_perm_member', repo_name=c.repo_info.repo_name),method='post')}
161 <div class="form">
162 <div class="fields">
163 <div class="field">
164 <div class="label">
165 <label for="input">${_('Permissions')}:</label>
166 </div>
167 <div class="input">
168 <%include file="repo_edit_perms.html"/>
169 </div>
170 </div>
171 <div class="buttons">
172 ${h.submit('save',_('Save'),class_="ui-btn large")}
173 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
174 </div>
175 </div>
176 </div>
177 ${h.end_form()}
178 </div>
179
180
181 <div class="box box-right" style="clear:right">
182 <div class="title">
167 183 <h5>${_('Advanced settings')}</h5>
168 184 </div>
169 185
170 186 <h3>${_('Statistics')}</h3>
171 187 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
172 188 <div class="form">
173 189 <div class="fields">
174 190 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
175 191 <div class="field" style="border:none;color:#888">
176 192 <ul>
177 193 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
178 194 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
179 195 </ul>
180 196 </div>
181 197 </div>
182 198 </div>
183 199 ${h.end_form()}
184 200
185 201 %if c.repo_info.clone_uri:
186 202 <h3>${_('Remote')}</h3>
187 203 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
188 204 <div class="form">
189 205 <div class="fields">
190 206 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
191 207 <div class="field" style="border:none">
192 208 <ul>
193 209 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
194 210 </ul>
195 211 </div>
196 212 </div>
197 213 </div>
198 214 ${h.end_form()}
199 215 %endif
200 216
201 217 <h3>${_('Cache')}</h3>
202 218 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
203 219 <div class="form">
204 220 <div class="fields">
205 221 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
206 222 <div class="field" style="border:none;color:#888">
207 223 <ul>
208 224 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
209 225 </li>
210 226 </ul>
211 227 </div>
212 228 <div class="field" style="border:none;">
213 229 ${_('List of cached values')}
214 230 <table>
215 231 <tr>
216 232 <th>${_('Prefix')}</th>
217 233 <th>${_('Key')}</th>
218 234 <th>${_('Active')}</th>
219 235 </tr>
220 236 %for cache in c.repo_info.cache_keys:
221 237 <tr>
222 238 <td>${cache.get_prefix() or '-'}</td>
223 239 <td>${cache.cache_key}</td>
224 240 <td>${h.boolicon(cache.cache_active)}</td>
225 241 </tr>
226 242 %endfor
227 243 </table>
228 244 </div>
229 245 </div>
230 246 </div>
231 247 ${h.end_form()}
232 248
233 249 <h3>${_('Public journal')}</h3>
234 250 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
235 251 <div class="form">
236 252 ${h.hidden('auth_token',str(h.get_token()))}
237 253 <div class="field">
238 254 %if c.in_public_journal:
239 255 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
240 256 %else:
241 257 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
242 258 %endif
243 259 </div>
244 260 <div class="field" style="border:none;color:#888">
245 261 <ul>
246 262 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
247 263 </li>
248 264 </ul>
249 265 </div>
250 266 </div>
251 267 ${h.end_form()}
252 268
253 269 <h3>${_('Locking')}</h3>
254 270 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
255 271 <div class="form">
256 272 <div class="fields">
257 273 %if c.repo_info.locked[0]:
258 274 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
259 275 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
260 276 %else:
261 277 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
262 278 ${_('Repository is not locked')}
263 279 %endif
264 280 </div>
265 281 <div class="field" style="border:none;color:#888">
266 282 <ul>
267 283 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
268 284 </li>
269 285 </ul>
270 286 </div>
271 287 </div>
272 288 ${h.end_form()}
273 289
274 290 <h3>${_('Set as fork of')}</h3>
275 291 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
276 292 <div class="form">
277 293 <div class="fields">
278 294 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
279 295 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
280 296 </div>
281 297 <div class="field" style="border:none;color:#888">
282 298 <ul>
283 299 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
284 300 </ul>
285 301 </div>
286 302 </div>
287 303 ${h.end_form()}
288 304
289 305 <h3>${_('Delete')}</h3>
290 306 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
291 307 <div class="form">
292 308 <div class="fields">
293 309 <div class="field" style="border:none;color:#888">
294 310 ## <div class="label">
295 311 ## <label for="">${_('Remove repository')}:</label>
296 312 ## </div>
297 313 <div class="checkboxes">
298 314 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
299 315 %if c.repo_info.forks.count():
300 316 - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
301 317 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
302 318 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
303 319 %endif
304 320 <ul>
305 321 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
306 322 </ul>
307 323 </div>
308 324 </div>
309 325 </div>
310 326 </div>
311 327 ${h.end_form()}
312 328 </div>
313 329
314 330 ##TODO: this should be controlled by the VISUAL setting
315 331 %if c.visual.repository_fields:
316 332 <div class="box box-left" style="clear:left">
317 333 <!-- box / title -->
318 334 <div class="title">
319 335 <h5>${_('Extra fields')}</h5>
320 336 </div>
321 337
322 338 <div class="emails_wrap">
323 339 <table class="noborder">
324 340 %for field in c.repo_fields:
325 341 <tr>
326 342 <td>${field.field_label} (${field.field_key})</td>
327 343 <td>${field.field_type}</td>
328 344 <td>
329 345 ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
330 346 ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
331 347 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
332 348 ${h.end_form()}
333 349 </td>
334 350 </tr>
335 351 %endfor
336 352 </table>
337 353 </div>
338 354
339 355 ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
340 356 <div class="form">
341 357 <!-- fields -->
342 358 <div class="fields">
343 359 <div class="field">
344 360 <div class="label">
345 361 <label for="new_field_key">${_('New field key')}:</label>
346 362 </div>
347 363 <div class="input">
348 364 ${h.text('new_field_key', class_='small')}
349 365 </div>
350 366 </div>
351 367 <div class="field">
352 368 <div class="label">
353 369 <label for="new_field_label">${_('New field label')}:</label>
354 370 </div>
355 371 <div class="input">
356 372 ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
357 373 </div>
358 374 </div>
359 375
360 376 <div class="field">
361 377 <div class="label">
362 378 <label for="new_field_desc">${_('New field description')}:</label>
363 379 </div>
364 380 <div class="input">
365 381 ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
366 382 </div>
367 383 </div>
368 384
369 385 <div class="buttons">
370 386 ${h.submit('save',_('Add'),class_="ui-btn large")}
371 387 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
372 388 </div>
373 389 </div>
374 390 </div>
375 391 ${h.end_form()}
376 392 </div>
377 393 %endif
378 394 </%def>
General Comments 0
You need to be logged in to leave comments. Login now