##// END OF EJS Templates
refactored url.resource to full definition of routes...
marcink -
r3866:1fdec7e3 beta
parent child Browse files
Show More
@@ -1,683 +1,706 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11 11 # prefix for non repository related links needs to be prefixed with `/`
12 12 ADMIN_PREFIX = '/_admin'
13 13
14 14
15 15 def make_map(config):
16 16 """Create, configure and return the routes Mapper"""
17 17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 18 always_scan=config['debug'])
19 19 rmap.minimization = False
20 20 rmap.explicit = False
21 21
22 22 from rhodecode.lib.utils import is_valid_repo
23 23 from rhodecode.lib.utils import is_valid_repos_group
24 24
25 25 def check_repo(environ, match_dict):
26 26 """
27 27 check for valid repository for proper 404 handling
28 28
29 29 :param environ:
30 30 :param match_dict:
31 31 """
32 32 from rhodecode.model.db import Repository
33 33 repo_name = match_dict.get('repo_name')
34 34
35 35 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 Exception:
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_user_group(environ, match_dict):
72 72 """
73 73 check for valid user group for proper 404 handling
74 74
75 75 :param environ:
76 76 :param match_dict:
77 77 """
78 78 return True
79 79
80 80 def check_int(environ, match_dict):
81 81 return match_dict.get('id').isdigit()
82 82
83 83 # The ErrorController route (handles 404/500 error pages); it should
84 84 # likely stay at the top, ensuring it can always be resolved
85 85 rmap.connect('/error/{action}', controller='error')
86 86 rmap.connect('/error/{action}/{id}', controller='error')
87 87
88 88 #==========================================================================
89 89 # CUSTOM ROUTES HERE
90 90 #==========================================================================
91 91
92 92 #MAIN PAGE
93 93 rmap.connect('home', '/', controller='home', action='index')
94 94 rmap.connect('repo_switcher', '/repos', controller='home',
95 95 action='repo_switcher')
96 96 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
97 97 controller='home', action='branch_tag_switcher')
98 98 rmap.connect('bugtracker',
99 99 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
100 100 _static=True)
101 101 rmap.connect('rst_help',
102 102 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
103 103 _static=True)
104 104 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
105 105
106 106 #ADMIN REPOSITORY REST ROUTES
107 107 with rmap.submapper(path_prefix=ADMIN_PREFIX,
108 108 controller='admin/repos') as m:
109 109 m.connect("repos", "/repos",
110 110 action="create", conditions=dict(method=["POST"]))
111 111 m.connect("repos", "/repos",
112 112 action="index", conditions=dict(method=["GET"]))
113 113 m.connect("formatted_repos", "/repos.{format}",
114 114 action="index",
115 115 conditions=dict(method=["GET"]))
116 116 m.connect("new_repo", "/create_repository",
117 117 action="create_repository", conditions=dict(method=["GET"]))
118 118 m.connect("/repos/{repo_name:.*?}",
119 119 action="update", conditions=dict(method=["PUT"],
120 120 function=check_repo))
121 121 m.connect("/repos/{repo_name:.*?}",
122 122 action="delete", conditions=dict(method=["DELETE"],
123 123 function=check_repo))
124 124 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
125 125 action="edit", conditions=dict(method=["GET"],
126 126 function=check_repo))
127 127 m.connect("repo", "/repos/{repo_name:.*?}",
128 128 action="show", conditions=dict(method=["GET"],
129 129 function=check_repo))
130 130 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
131 131 action="show", conditions=dict(method=["GET"],
132 132 function=check_repo))
133 133 #add repo perm member
134 134 m.connect('set_repo_perm_member',
135 135 "/repos/{repo_name:.*?}/grant_perm",
136 136 action="set_repo_perm_member",
137 137 conditions=dict(method=["POST"], function=check_repo))
138 138
139 139 #ajax delete repo perm user
140 140 m.connect('delete_repo_perm_member',
141 141 "/repos/{repo_name:.*?}/revoke_perm",
142 142 action="delete_repo_perm_member",
143 143 conditions=dict(method=["DELETE"], function=check_repo))
144 144
145 145 #settings actions
146 146 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
147 147 action="repo_stats", conditions=dict(method=["DELETE"],
148 148 function=check_repo))
149 149 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
150 150 action="repo_cache", conditions=dict(method=["DELETE"],
151 151 function=check_repo))
152 152 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
153 153 action="repo_public_journal", conditions=dict(method=["PUT"],
154 154 function=check_repo))
155 155 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
156 156 action="repo_pull", conditions=dict(method=["PUT"],
157 157 function=check_repo))
158 158 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
159 159 action="repo_as_fork", conditions=dict(method=["PUT"],
160 160 function=check_repo))
161 161 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
162 162 action="repo_locking", conditions=dict(method=["PUT"],
163 163 function=check_repo))
164 164 m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}",
165 165 action="toggle_locking", conditions=dict(method=["GET"],
166 166 function=check_repo))
167 167
168 168 #repo fields
169 169 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
170 170 action="create_repo_field", conditions=dict(method=["PUT"],
171 171 function=check_repo))
172 172
173 173 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
174 174 action="delete_repo_field", conditions=dict(method=["DELETE"],
175 175 function=check_repo))
176 176
177 177 with rmap.submapper(path_prefix=ADMIN_PREFIX,
178 178 controller='admin/repos_groups') as m:
179 179 m.connect("repos_groups", "/repos_groups",
180 180 action="create", conditions=dict(method=["POST"]))
181 181 m.connect("repos_groups", "/repos_groups",
182 182 action="index", conditions=dict(method=["GET"]))
183 183 m.connect("formatted_repos_groups", "/repos_groups.{format}",
184 184 action="index", conditions=dict(method=["GET"]))
185 185 m.connect("new_repos_group", "/repos_groups/new",
186 186 action="new", conditions=dict(method=["GET"]))
187 187 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
188 188 action="new", conditions=dict(method=["GET"]))
189 189 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
190 190 action="update", conditions=dict(method=["PUT"],
191 191 function=check_group))
192 192 #add repo group perm member
193 193 m.connect('set_repo_group_perm_member',
194 194 "/repos_groups/{group_name:.*?}/grant_perm",
195 195 action="set_repo_group_perm_member",
196 196 conditions=dict(method=["POST"], function=check_group))
197 197
198 198 #ajax delete repo group perm
199 199 m.connect('delete_repo_group_perm_member',
200 200 "/repos_groups/{group_name:.*?}/revoke_perm",
201 201 action="delete_repo_group_perm_member",
202 202 conditions=dict(method=["DELETE"], function=check_group))
203 203
204 204 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
205 205 action="delete", conditions=dict(method=["DELETE"],
206 206 function=check_group_skip_path))
207 207 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
208 208 action="edit", conditions=dict(method=["GET"],
209 209 function=check_group))
210 210 m.connect("formatted_edit_repos_group",
211 211 "/repos_groups/{group_name:.*?}.{format}/edit",
212 212 action="edit", conditions=dict(method=["GET"],
213 213 function=check_group))
214 214 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
215 215 action="show", conditions=dict(method=["GET"],
216 216 function=check_group))
217 217 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
218 218 action="show", conditions=dict(method=["GET"],
219 219 function=check_group))
220 220
221 221 #ADMIN USER REST ROUTES
222 222 with rmap.submapper(path_prefix=ADMIN_PREFIX,
223 223 controller='admin/users') as m:
224 224 m.connect("users", "/users",
225 225 action="create", conditions=dict(method=["POST"]))
226 226 m.connect("users", "/users",
227 227 action="index", conditions=dict(method=["GET"]))
228 228 m.connect("formatted_users", "/users.{format}",
229 229 action="index", conditions=dict(method=["GET"]))
230 230 m.connect("new_user", "/users/new",
231 231 action="new", conditions=dict(method=["GET"]))
232 232 m.connect("formatted_new_user", "/users/new.{format}",
233 233 action="new", conditions=dict(method=["GET"]))
234 234 m.connect("update_user", "/users/{id}",
235 235 action="update", conditions=dict(method=["PUT"]))
236 236 m.connect("delete_user", "/users/{id}",
237 237 action="delete", conditions=dict(method=["DELETE"]))
238 238 m.connect("edit_user", "/users/{id}/edit",
239 239 action="edit", conditions=dict(method=["GET"]))
240 240 m.connect("formatted_edit_user",
241 241 "/users/{id}.{format}/edit",
242 242 action="edit", conditions=dict(method=["GET"]))
243 243 m.connect("user", "/users/{id}",
244 244 action="show", conditions=dict(method=["GET"]))
245 245 m.connect("formatted_user", "/users/{id}.{format}",
246 246 action="show", conditions=dict(method=["GET"]))
247 247
248 248 #EXTRAS USER ROUTES
249 249 m.connect("user_perm", "/users_perm/{id}",
250 250 action="update_perm", conditions=dict(method=["PUT"]))
251 251 m.connect("user_emails", "/users_emails/{id}",
252 252 action="add_email", conditions=dict(method=["PUT"]))
253 253 m.connect("user_emails_delete", "/users_emails/{id}",
254 254 action="delete_email", conditions=dict(method=["DELETE"]))
255 255 m.connect("user_ips", "/users_ips/{id}",
256 256 action="add_ip", conditions=dict(method=["PUT"]))
257 257 m.connect("user_ips_delete", "/users_ips/{id}",
258 258 action="delete_ip", conditions=dict(method=["DELETE"]))
259 259
260 260 #ADMIN USER GROUPS REST ROUTES
261 261 with rmap.submapper(path_prefix=ADMIN_PREFIX,
262 262 controller='admin/users_groups') as m:
263 263 m.connect("users_groups", "/users_groups",
264 264 action="create", conditions=dict(method=["POST"]))
265 265 m.connect("users_groups", "/users_groups",
266 266 action="index", conditions=dict(method=["GET"]))
267 267 m.connect("formatted_users_groups", "/users_groups.{format}",
268 268 action="index", conditions=dict(method=["GET"]))
269 269 m.connect("new_users_group", "/users_groups/new",
270 270 action="new", conditions=dict(method=["GET"]))
271 271 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
272 272 action="new", conditions=dict(method=["GET"]))
273 273 m.connect("update_users_group", "/users_groups/{id}",
274 274 action="update", conditions=dict(method=["PUT"]))
275 275 m.connect("delete_users_group", "/users_groups/{id}",
276 276 action="delete", conditions=dict(method=["DELETE"]))
277 277 m.connect("edit_users_group", "/users_groups/{id}/edit",
278 278 action="edit", conditions=dict(method=["GET"]),
279 279 function=check_user_group)
280 280 m.connect("formatted_edit_users_group",
281 281 "/users_groups/{id}.{format}/edit",
282 282 action="edit", conditions=dict(method=["GET"]))
283 283 m.connect("users_group", "/users_groups/{id}",
284 284 action="show", conditions=dict(method=["GET"]))
285 285 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
286 286 action="show", conditions=dict(method=["GET"]))
287 287
288 288 #EXTRAS USER ROUTES
289 289 # update
290 290 m.connect("users_group_perm", "/users_groups/{id}/update_global_perm",
291 291 action="update_perm", conditions=dict(method=["PUT"]))
292 292
293 293 #add user group perm member
294 294 m.connect('set_user_group_perm_member', "/users_groups/{id}/grant_perm",
295 295 action="set_user_group_perm_member",
296 296 conditions=dict(method=["POST"]))
297 297
298 298 #ajax delete user group perm
299 299 m.connect('delete_user_group_perm_member', "/users_groups/{id}/revoke_perm",
300 300 action="delete_user_group_perm_member",
301 301 conditions=dict(method=["DELETE"]))
302 302
303 303 #ADMIN GROUP REST ROUTES
304 304 rmap.resource('group', 'groups',
305 305 controller='admin/groups', path_prefix=ADMIN_PREFIX)
306 306
307 307 #ADMIN PERMISSIONS REST ROUTES
308 308 rmap.resource('permission', 'permissions',
309 309 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
310 310
311 311 #ADMIN DEFAULTS REST ROUTES
312 312 rmap.resource('default', 'defaults',
313 313 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
314 314
315 315 ##ADMIN LDAP SETTINGS
316 316 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
317 317 controller='admin/ldap_settings', action='ldap_settings',
318 318 conditions=dict(method=["POST"]))
319 319
320 320 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
321 321 controller='admin/ldap_settings')
322 322
323 323 #ADMIN SETTINGS REST ROUTES
324 324 with rmap.submapper(path_prefix=ADMIN_PREFIX,
325 325 controller='admin/settings') as m:
326 326 m.connect("admin_settings", "/settings",
327 327 action="create", conditions=dict(method=["POST"]))
328 328 m.connect("admin_settings", "/settings",
329 329 action="index", conditions=dict(method=["GET"]))
330 330 m.connect("formatted_admin_settings", "/settings.{format}",
331 331 action="index", conditions=dict(method=["GET"]))
332 332 m.connect("admin_new_setting", "/settings/new",
333 333 action="new", conditions=dict(method=["GET"]))
334 334 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
335 335 action="new", conditions=dict(method=["GET"]))
336 336 m.connect("/settings/{setting_id}",
337 337 action="update", conditions=dict(method=["PUT"]))
338 338 m.connect("/settings/{setting_id}",
339 339 action="delete", conditions=dict(method=["DELETE"]))
340 340 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
341 341 action="edit", conditions=dict(method=["GET"]))
342 342 m.connect("formatted_admin_edit_setting",
343 343 "/settings/{setting_id}.{format}/edit",
344 344 action="edit", conditions=dict(method=["GET"]))
345 345 m.connect("admin_setting", "/settings/{setting_id}",
346 346 action="show", conditions=dict(method=["GET"]))
347 347 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
348 348 action="show", conditions=dict(method=["GET"]))
349 349 m.connect("admin_settings_my_account", "/my_account",
350 350 action="my_account", conditions=dict(method=["GET"]))
351 351 m.connect("admin_settings_my_account_update", "/my_account_update",
352 352 action="my_account_update", conditions=dict(method=["PUT"]))
353 353 m.connect("admin_settings_my_repos", "/my_account/repos",
354 354 action="my_account_my_repos", conditions=dict(method=["GET"]))
355 355 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
356 356 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
357 357
358 358 #NOTIFICATION REST ROUTES
359 359 with rmap.submapper(path_prefix=ADMIN_PREFIX,
360 360 controller='admin/notifications') as m:
361 361 m.connect("notifications", "/notifications",
362 362 action="create", conditions=dict(method=["POST"]))
363 363 m.connect("notifications", "/notifications",
364 364 action="index", conditions=dict(method=["GET"]))
365 365 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
366 366 action="mark_all_read", conditions=dict(method=["GET"]))
367 367 m.connect("formatted_notifications", "/notifications.{format}",
368 368 action="index", conditions=dict(method=["GET"]))
369 369 m.connect("new_notification", "/notifications/new",
370 370 action="new", conditions=dict(method=["GET"]))
371 371 m.connect("formatted_new_notification", "/notifications/new.{format}",
372 372 action="new", conditions=dict(method=["GET"]))
373 373 m.connect("/notification/{notification_id}",
374 374 action="update", conditions=dict(method=["PUT"]))
375 375 m.connect("/notification/{notification_id}",
376 376 action="delete", conditions=dict(method=["DELETE"]))
377 377 m.connect("edit_notification", "/notification/{notification_id}/edit",
378 378 action="edit", conditions=dict(method=["GET"]))
379 379 m.connect("formatted_edit_notification",
380 380 "/notification/{notification_id}.{format}/edit",
381 381 action="edit", conditions=dict(method=["GET"]))
382 382 m.connect("notification", "/notification/{notification_id}",
383 383 action="show", conditions=dict(method=["GET"]))
384 384 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
385 385 action="show", conditions=dict(method=["GET"]))
386 386
387 #ADMIN GIST
388 with rmap.submapper(path_prefix=ADMIN_PREFIX,
389 controller='admin/gists') as m:
390 m.connect("gists", "/gists",
391 action="create", conditions=dict(method=["POST"]))
392 m.connect("gists", "/gists",
393 action="index", conditions=dict(method=["GET"]))
394 m.connect("formatted_gists", "/gists.{format}",
395 action="index", conditions=dict(method=["GET"]))
396 m.connect("new_gist", "/gists/new",
397 action="new", conditions=dict(method=["GET"]))
398 m.connect("formatted_new_gist", "/gists/new.{format}",
399 action="new", conditions=dict(method=["GET"]))
400 m.connect("/gist/{gist_id}",
401 action="update", conditions=dict(method=["PUT"]))
402 m.connect("/gist/{gist_id}",
403 action="delete", conditions=dict(method=["DELETE"]))
404 m.connect("edit_gist", "/gist/{gist_id}/edit",
405 action="edit", conditions=dict(method=["GET"]))
406 m.connect("formatted_edit_gist",
407 "/gist/{gist_id}.{format}/edit",
408 action="edit", conditions=dict(method=["GET"]))
409 m.connect("gist", "/gist/{gist_id}",
410 action="show", conditions=dict(method=["GET"]))
411 m.connect("formatted_gist", "/gists/{gist_id}.{format}",
412 action="show", conditions=dict(method=["GET"]))
413
387 414 #ADMIN MAIN PAGES
388 415 with rmap.submapper(path_prefix=ADMIN_PREFIX,
389 416 controller='admin/admin') as m:
390 417 m.connect('admin_home', '', action='index')
391 418 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
392 419 action='add_repo')
393
394 #ADMIN GIST
395 rmap.resource('gist', 'gists', controller='admin/gists',
396 path_prefix=ADMIN_PREFIX)
397 420 #==========================================================================
398 421 # API V2
399 422 #==========================================================================
400 423 with rmap.submapper(path_prefix=ADMIN_PREFIX,
401 424 controller='api/api') as m:
402 425 m.connect('api', '/api')
403 426
404 427 #USER JOURNAL
405 428 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
406 429 controller='journal', action='index')
407 430 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
408 431 controller='journal', action='journal_rss')
409 432 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
410 433 controller='journal', action='journal_atom')
411 434
412 435 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
413 436 controller='journal', action="public_journal")
414 437
415 438 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
416 439 controller='journal', action="public_journal_rss")
417 440
418 441 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
419 442 controller='journal', action="public_journal_rss")
420 443
421 444 rmap.connect('public_journal_atom',
422 445 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
423 446 action="public_journal_atom")
424 447
425 448 rmap.connect('public_journal_atom_old',
426 449 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
427 450 action="public_journal_atom")
428 451
429 452 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
430 453 controller='journal', action='toggle_following',
431 454 conditions=dict(method=["POST"]))
432 455
433 456 #SEARCH
434 457 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
435 458 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
436 459 controller='search',
437 460 conditions=dict(function=check_repo))
438 461 rmap.connect('search_repo', '/{repo_name:.*?}/search',
439 462 controller='search',
440 463 conditions=dict(function=check_repo),
441 464 )
442 465
443 466 #LOGIN/LOGOUT/REGISTER/SIGN IN
444 467 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
445 468 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
446 469 action='logout')
447 470
448 471 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
449 472 action='register')
450 473
451 474 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
452 475 controller='login', action='password_reset')
453 476
454 477 rmap.connect('reset_password_confirmation',
455 478 '%s/password_reset_confirmation' % ADMIN_PREFIX,
456 479 controller='login', action='password_reset_confirmation')
457 480
458 481 #FEEDS
459 482 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
460 483 controller='feed', action='rss',
461 484 conditions=dict(function=check_repo))
462 485
463 486 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
464 487 controller='feed', action='atom',
465 488 conditions=dict(function=check_repo))
466 489
467 490 #==========================================================================
468 491 # REPOSITORY ROUTES
469 492 #==========================================================================
470 493 rmap.connect('summary_home', '/{repo_name:.*?}',
471 494 controller='summary',
472 495 conditions=dict(function=check_repo))
473 496
474 497 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
475 498 controller='summary', action='repo_size',
476 499 conditions=dict(function=check_repo))
477 500
478 501 rmap.connect('repos_group_home', '/{group_name:.*}',
479 502 controller='admin/repos_groups', action="show_by_name",
480 503 conditions=dict(function=check_group))
481 504
482 505 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
483 506 controller='changeset', revision='tip',
484 507 conditions=dict(function=check_repo))
485 508
486 509 # no longer user, but kept for routes to work
487 510 rmap.connect("_edit_repo", "/{repo_name:.*?}/edit",
488 511 controller='admin/repos', action="edit",
489 512 conditions=dict(method=["GET"], function=check_repo)
490 513 )
491 514
492 515 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
493 516 controller='admin/repos', action="edit",
494 517 conditions=dict(method=["GET"], function=check_repo)
495 518 )
496 519
497 520 #still working url for backward compat.
498 521 rmap.connect('raw_changeset_home_depraced',
499 522 '/{repo_name:.*?}/raw-changeset/{revision}',
500 523 controller='changeset', action='changeset_raw',
501 524 revision='tip', conditions=dict(function=check_repo))
502 525
503 526 ## new URLs
504 527 rmap.connect('changeset_raw_home',
505 528 '/{repo_name:.*?}/changeset-diff/{revision}',
506 529 controller='changeset', action='changeset_raw',
507 530 revision='tip', conditions=dict(function=check_repo))
508 531
509 532 rmap.connect('changeset_patch_home',
510 533 '/{repo_name:.*?}/changeset-patch/{revision}',
511 534 controller='changeset', action='changeset_patch',
512 535 revision='tip', conditions=dict(function=check_repo))
513 536
514 537 rmap.connect('changeset_download_home',
515 538 '/{repo_name:.*?}/changeset-download/{revision}',
516 539 controller='changeset', action='changeset_download',
517 540 revision='tip', conditions=dict(function=check_repo))
518 541
519 542 rmap.connect('changeset_comment',
520 543 '/{repo_name:.*?}/changeset/{revision}/comment',
521 544 controller='changeset', revision='tip', action='comment',
522 545 conditions=dict(function=check_repo))
523 546
524 547 rmap.connect('changeset_comment_preview',
525 548 '/{repo_name:.*?}/changeset/comment/preview',
526 549 controller='changeset', action='preview_comment',
527 550 conditions=dict(function=check_repo, method=["POST"]))
528 551
529 552 rmap.connect('changeset_comment_delete',
530 553 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
531 554 controller='changeset', action='delete_comment',
532 555 conditions=dict(function=check_repo, method=["DELETE"]))
533 556
534 557 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
535 558 controller='changeset', action='changeset_info')
536 559
537 560 rmap.connect('compare_url',
538 561 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
539 562 controller='compare', action='index',
540 563 conditions=dict(function=check_repo),
541 564 requirements=dict(
542 565 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
543 566 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
544 567 )
545 568
546 569 rmap.connect('pullrequest_home',
547 570 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
548 571 action='index', conditions=dict(function=check_repo,
549 572 method=["GET"]))
550 573
551 574 rmap.connect('pullrequest',
552 575 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
553 576 action='create', conditions=dict(function=check_repo,
554 577 method=["POST"]))
555 578
556 579 rmap.connect('pullrequest_show',
557 580 '/{repo_name:.*?}/pull-request/{pull_request_id}',
558 581 controller='pullrequests',
559 582 action='show', conditions=dict(function=check_repo,
560 583 method=["GET"]))
561 584 rmap.connect('pullrequest_update',
562 585 '/{repo_name:.*?}/pull-request/{pull_request_id}',
563 586 controller='pullrequests',
564 587 action='update', conditions=dict(function=check_repo,
565 588 method=["PUT"]))
566 589 rmap.connect('pullrequest_delete',
567 590 '/{repo_name:.*?}/pull-request/{pull_request_id}',
568 591 controller='pullrequests',
569 592 action='delete', conditions=dict(function=check_repo,
570 593 method=["DELETE"]))
571 594
572 595 rmap.connect('pullrequest_show_all',
573 596 '/{repo_name:.*?}/pull-request',
574 597 controller='pullrequests',
575 598 action='show_all', conditions=dict(function=check_repo,
576 599 method=["GET"]))
577 600
578 601 rmap.connect('pullrequest_comment',
579 602 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
580 603 controller='pullrequests',
581 604 action='comment', conditions=dict(function=check_repo,
582 605 method=["POST"]))
583 606
584 607 rmap.connect('pullrequest_comment_delete',
585 608 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
586 609 controller='pullrequests', action='delete_comment',
587 610 conditions=dict(function=check_repo, method=["DELETE"]))
588 611
589 612 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
590 613 controller='summary', conditions=dict(function=check_repo))
591 614
592 615 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
593 616 controller='branches', conditions=dict(function=check_repo))
594 617
595 618 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
596 619 controller='tags', conditions=dict(function=check_repo))
597 620
598 621 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
599 622 controller='bookmarks', conditions=dict(function=check_repo))
600 623
601 624 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
602 625 controller='changelog', conditions=dict(function=check_repo))
603 626
604 627 rmap.connect('changelog_summary_home', '/{repo_name:.*?}/changelog_summary',
605 628 controller='changelog', action='changelog_summary',
606 629 conditions=dict(function=check_repo))
607 630
608 631 rmap.connect('changelog_file_home', '/{repo_name:.*?}/changelog/{revision}/{f_path:.*}',
609 632 controller='changelog', f_path=None,
610 633 conditions=dict(function=check_repo))
611 634
612 635 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
613 636 controller='changelog', action='changelog_details',
614 637 conditions=dict(function=check_repo))
615 638
616 639 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
617 640 controller='files', revision='tip', f_path='',
618 641 conditions=dict(function=check_repo))
619 642
620 643 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
621 644 controller='files', revision='tip', f_path='',
622 645 conditions=dict(function=check_repo))
623 646
624 647 rmap.connect('files_history_home',
625 648 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
626 649 controller='files', action='history', revision='tip', f_path='',
627 650 conditions=dict(function=check_repo))
628 651
629 652 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
630 653 controller='files', action='diff', revision='tip', f_path='',
631 654 conditions=dict(function=check_repo))
632 655
633 656 rmap.connect('files_rawfile_home',
634 657 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
635 658 controller='files', action='rawfile', revision='tip',
636 659 f_path='', conditions=dict(function=check_repo))
637 660
638 661 rmap.connect('files_raw_home',
639 662 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
640 663 controller='files', action='raw', revision='tip', f_path='',
641 664 conditions=dict(function=check_repo))
642 665
643 666 rmap.connect('files_annotate_home',
644 667 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
645 668 controller='files', action='index', revision='tip',
646 669 f_path='', annotate=True, conditions=dict(function=check_repo))
647 670
648 671 rmap.connect('files_edit_home',
649 672 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
650 673 controller='files', action='edit', revision='tip',
651 674 f_path='', conditions=dict(function=check_repo))
652 675
653 676 rmap.connect('files_add_home',
654 677 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
655 678 controller='files', action='add', revision='tip',
656 679 f_path='', conditions=dict(function=check_repo))
657 680
658 681 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
659 682 controller='files', action='archivefile',
660 683 conditions=dict(function=check_repo))
661 684
662 685 rmap.connect('files_nodelist_home',
663 686 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
664 687 controller='files', action='nodelist',
665 688 conditions=dict(function=check_repo))
666 689
667 690 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
668 691 controller='forks', action='fork_create',
669 692 conditions=dict(function=check_repo, method=["POST"]))
670 693
671 694 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
672 695 controller='forks', action='fork',
673 696 conditions=dict(function=check_repo))
674 697
675 698 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
676 699 controller='forks', action='forks',
677 700 conditions=dict(function=check_repo))
678 701
679 702 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
680 703 controller='followers', action='followers',
681 704 conditions=dict(function=check_repo))
682 705
683 706 return rmap
@@ -1,196 +1,195 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.gist
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 gist controller for RhodeCode
7 7
8 8 :created_on: May 9, 2013
9 9 :author: marcink
10 10 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import time
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.model.forms import GistForm
36 36 from rhodecode.model.gist import GistModel
37 37 from rhodecode.model.meta import Session
38 38 from rhodecode.model.db import Gist
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.auth import LoginRequired, NotAnonymous
42 42 from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
43 43 from rhodecode.lib.helpers import Page
44 44 from webob.exc import HTTPNotFound, HTTPForbidden
45 45 from sqlalchemy.sql.expression import or_
46 46 from rhodecode.lib.vcs.exceptions import VCSError
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class GistsController(BaseController):
52 52 """REST Controller styled on the Atom Publishing Protocol"""
53 53
54 54 def __load_defaults(self):
55 55 c.lifetime_values = [
56 56 (str(-1), _('forever')),
57 57 (str(5), _('5 minutes')),
58 58 (str(60), _('1 hour')),
59 59 (str(60 * 24), _('1 day')),
60 60 (str(60 * 24 * 30), _('1 month')),
61 61 ]
62 62 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
63 63
64 64 @LoginRequired()
65 65 def index(self, format='html'):
66 66 """GET /admin/gists: All items in the collection"""
67 67 # url('gists')
68 68 c.show_private = request.GET.get('private') and c.rhodecode_user.username != 'default'
69 69 c.show_public = request.GET.get('public') and c.rhodecode_user.username != 'default'
70 70
71 71 gists = Gist().query()\
72 72 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
73 73 .order_by(Gist.created_on.desc())
74 74 if c.show_private:
75 75 c.gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
76 76 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
77 77 elif c.show_public:
78 78 c.gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
79 79 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
80 80
81 81 else:
82 82 c.gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
83 83 p = safe_int(request.GET.get('page', 1), 1)
84 84 c.gists_pager = Page(c.gists, page=p, items_per_page=10)
85 85 return render('admin/gists/index.html')
86 86
87 87 @LoginRequired()
88 88 @NotAnonymous()
89 89 def create(self):
90 90 """POST /admin/gists: Create a new item"""
91 91 # url('gists')
92 92 self.__load_defaults()
93 93 gist_form = GistForm([x[0] for x in c.lifetime_values])()
94 94 try:
95 95 form_result = gist_form.to_python(dict(request.POST))
96 96 #TODO: multiple files support, from the form
97 97 nodes = {
98 98 form_result['filename'] or 'gistfile1.txt': {
99 99 'content': form_result['content'],
100 100 'lexer': None # autodetect
101 101 }
102 102 }
103 103 _public = form_result['public']
104 104 gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE
105 105 gist = GistModel().create(
106 106 description=form_result['description'],
107 107 owner=c.rhodecode_user,
108 108 gist_mapping=nodes,
109 109 gist_type=gist_type,
110 110 lifetime=form_result['lifetime']
111 111 )
112 112 Session().commit()
113 113 new_gist_id = gist.gist_access_id
114 114 except formencode.Invalid, errors:
115 115 defaults = errors.value
116 116
117 117 return formencode.htmlfill.render(
118 118 render('admin/gists/new.html'),
119 119 defaults=defaults,
120 120 errors=errors.error_dict or {},
121 121 prefix_error=False,
122 122 encoding="UTF-8"
123 123 )
124 124
125 125 except Exception, e:
126 126 log.error(traceback.format_exc())
127 127 h.flash(_('Error occurred during gist creation'), category='error')
128 128 return redirect(url('new_gist'))
129 return redirect(url('gist', id=new_gist_id))
129 return redirect(url('gist', gist_id=new_gist_id))
130 130
131 131 @LoginRequired()
132 132 @NotAnonymous()
133 133 def new(self, format='html'):
134 134 """GET /admin/gists/new: Form to create a new item"""
135 135 # url('new_gist')
136 136 self.__load_defaults()
137 137 return render('admin/gists/new.html')
138 138
139 139 @LoginRequired()
140 140 @NotAnonymous()
141 def update(self, id):
142 """PUT /admin/gists/id: Update an existing item"""
141 def update(self, gist_id):
142 """PUT /admin/gists/gist_id: Update an existing item"""
143 143 # Forms posted to this method should contain a hidden field:
144 144 # <input type="hidden" name="_method" value="PUT" />
145 145 # Or using helpers:
146 # h.form(url('gist', id=ID),
146 # h.form(url('gist', gist_id=ID),
147 147 # method='put')
148 # url('gist', id=ID)
148 # url('gist', gist_id=ID)
149 149
150 150 @LoginRequired()
151 151 @NotAnonymous()
152 def delete(self, id):
153 """DELETE /admin/gists/id: Delete an existing item"""
152 def delete(self, gist_id):
153 """DELETE /admin/gists/gist_id: Delete an existing item"""
154 154 # Forms posted to this method should contain a hidden field:
155 155 # <input type="hidden" name="_method" value="DELETE" />
156 156 # Or using helpers:
157 # h.form(url('gist', id=ID),
157 # h.form(url('gist', gist_id=ID),
158 158 # method='delete')
159 # url('gist', id=ID)
160 gist = GistModel().get_gist(id)
159 # url('gist', gist_id=ID)
160 gist = GistModel().get_gist(gist_id)
161 161 owner = gist.gist_owner == c.rhodecode_user.user_id
162 162 if h.HasPermissionAny('hg.admin')() or owner:
163 163 GistModel().delete(gist)
164 164 Session().commit()
165 165 h.flash(_('Deleted gist %s') % gist.gist_access_id, category='success')
166 166 else:
167 167 raise HTTPForbidden()
168 168
169 169 return redirect(url('gists'))
170 170
171 171 @LoginRequired()
172 def show(self, id, format='html'):
173 """GET /admin/gists/id: Show a specific item"""
174 # url('gist', id=ID)
175 gist_id = id
172 def show(self, gist_id, format='html'):
173 """GET /admin/gists/gist_id: Show a specific item"""
174 # url('gist', gist_id=ID)
176 175 c.gist = Gist.get_or_404(gist_id)
177 176
178 177 #check if this gist is not expired
179 178 if c.gist.gist_expires != -1:
180 179 if time.time() > c.gist.gist_expires:
181 180 log.error('Gist expired at %s' %
182 181 (time_to_datetime(c.gist.gist_expires)))
183 182 raise HTTPNotFound()
184 183 try:
185 184 c.file_changeset, c.files = GistModel().get_gist_files(gist_id)
186 185 except VCSError:
187 186 log.error(traceback.format_exc())
188 187 raise HTTPNotFound()
189 188
190 189 return render('admin/gists/show.html')
191 190
192 191 @LoginRequired()
193 192 @NotAnonymous()
194 def edit(self, id, format='html'):
195 """GET /admin/gists/id/edit: Form to edit an existing item"""
196 # url('edit_gist', id=ID)
193 def edit(self, gist_id, format='html'):
194 """GET /admin/gists/gist_id/edit: Form to edit an existing item"""
195 # url('edit_gist', gist_id=ID)
@@ -1,2218 +1,2218 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 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 os
27 27 import time
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import hashlib
32 32 import collections
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48
49 49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 51 from rhodecode.lib.compat import json
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model.meta import Base, Session
55 55
56 56 URL_SEP = '/'
57 57 log = logging.getLogger(__name__)
58 58
59 59 #==============================================================================
60 60 # BASE CLASSES
61 61 #==============================================================================
62 62
63 63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64 64
65 65
66 66 class BaseModel(object):
67 67 """
68 68 Base Model for all classess
69 69 """
70 70
71 71 @classmethod
72 72 def _get_keys(cls):
73 73 """return column names for this model """
74 74 return class_mapper(cls).c.keys()
75 75
76 76 def get_dict(self):
77 77 """
78 78 return dict with keys and values corresponding
79 79 to this model data """
80 80
81 81 d = {}
82 82 for k in self._get_keys():
83 83 d[k] = getattr(self, k)
84 84
85 85 # also use __json__() if present to get additional fields
86 86 _json_attr = getattr(self, '__json__', None)
87 87 if _json_attr:
88 88 # update with attributes from __json__
89 89 if callable(_json_attr):
90 90 _json_attr = _json_attr()
91 91 for k, val in _json_attr.iteritems():
92 92 d[k] = val
93 93 return d
94 94
95 95 def get_appstruct(self):
96 96 """return list with keys and values tupples corresponding
97 97 to this model data """
98 98
99 99 l = []
100 100 for k in self._get_keys():
101 101 l.append((k, getattr(self, k),))
102 102 return l
103 103
104 104 def populate_obj(self, populate_dict):
105 105 """populate model with data from given populate_dict"""
106 106
107 107 for k in self._get_keys():
108 108 if k in populate_dict:
109 109 setattr(self, k, populate_dict[k])
110 110
111 111 @classmethod
112 112 def query(cls):
113 113 return Session().query(cls)
114 114
115 115 @classmethod
116 116 def get(cls, id_):
117 117 if id_:
118 118 return cls.query().get(id_)
119 119
120 120 @classmethod
121 121 def get_or_404(cls, id_):
122 122 try:
123 123 id_ = int(id_)
124 124 except (TypeError, ValueError):
125 125 raise HTTPNotFound
126 126
127 127 res = cls.query().get(id_)
128 128 if not res:
129 129 raise HTTPNotFound
130 130 return res
131 131
132 132 @classmethod
133 133 def getAll(cls):
134 134 # deprecated and left for backward compatibility
135 135 return cls.get_all()
136 136
137 137 @classmethod
138 138 def get_all(cls):
139 139 return cls.query().all()
140 140
141 141 @classmethod
142 142 def delete(cls, id_):
143 143 obj = cls.query().get(id_)
144 144 Session().delete(obj)
145 145
146 146 def __repr__(self):
147 147 if hasattr(self, '__unicode__'):
148 148 # python repr needs to return str
149 149 return safe_str(self.__unicode__())
150 150 return '<DB:%s>' % (self.__class__.__name__)
151 151
152 152
153 153 class RhodeCodeSetting(Base, BaseModel):
154 154 __tablename__ = 'rhodecode_settings'
155 155 __table_args__ = (
156 156 UniqueConstraint('app_settings_name'),
157 157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
158 158 'mysql_charset': 'utf8'}
159 159 )
160 160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
161 161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163 163
164 164 def __init__(self, k='', v=''):
165 165 self.app_settings_name = k
166 166 self.app_settings_value = v
167 167
168 168 @validates('_app_settings_value')
169 169 def validate_settings_value(self, key, val):
170 170 assert type(val) == unicode
171 171 return val
172 172
173 173 @hybrid_property
174 174 def app_settings_value(self):
175 175 v = self._app_settings_value
176 176 if self.app_settings_name in ["ldap_active",
177 177 "default_repo_enable_statistics",
178 178 "default_repo_enable_locking",
179 179 "default_repo_private",
180 180 "default_repo_enable_downloads"]:
181 181 v = str2bool(v)
182 182 return v
183 183
184 184 @app_settings_value.setter
185 185 def app_settings_value(self, val):
186 186 """
187 187 Setter that will always make sure we use unicode in app_settings_value
188 188
189 189 :param val:
190 190 """
191 191 self._app_settings_value = safe_unicode(val)
192 192
193 193 def __unicode__(self):
194 194 return u"<%s('%s:%s')>" % (
195 195 self.__class__.__name__,
196 196 self.app_settings_name, self.app_settings_value
197 197 )
198 198
199 199 @classmethod
200 200 def get_by_name(cls, key):
201 201 return cls.query()\
202 202 .filter(cls.app_settings_name == key).scalar()
203 203
204 204 @classmethod
205 205 def get_by_name_or_create(cls, key):
206 206 res = cls.get_by_name(key)
207 207 if not res:
208 208 res = cls(key)
209 209 return res
210 210
211 211 @classmethod
212 212 def get_app_settings(cls, cache=False):
213 213
214 214 ret = cls.query()
215 215
216 216 if cache:
217 217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
218 218
219 219 if not ret:
220 220 raise Exception('Could not get application settings !')
221 221 settings = {}
222 222 for each in ret:
223 223 settings['rhodecode_' + each.app_settings_name] = \
224 224 each.app_settings_value
225 225
226 226 return settings
227 227
228 228 @classmethod
229 229 def get_ldap_settings(cls, cache=False):
230 230 ret = cls.query()\
231 231 .filter(cls.app_settings_name.startswith('ldap_')).all()
232 232 fd = {}
233 233 for row in ret:
234 234 fd.update({row.app_settings_name: row.app_settings_value})
235 235
236 236 return fd
237 237
238 238 @classmethod
239 239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
240 240 ret = cls.query()\
241 241 .filter(cls.app_settings_name.startswith('default_')).all()
242 242 fd = {}
243 243 for row in ret:
244 244 key = row.app_settings_name
245 245 if strip_prefix:
246 246 key = remove_prefix(key, prefix='default_')
247 247 fd.update({key: row.app_settings_value})
248 248
249 249 return fd
250 250
251 251
252 252 class RhodeCodeUi(Base, BaseModel):
253 253 __tablename__ = 'rhodecode_ui'
254 254 __table_args__ = (
255 255 UniqueConstraint('ui_key'),
256 256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
257 257 'mysql_charset': 'utf8'}
258 258 )
259 259
260 260 HOOK_UPDATE = 'changegroup.update'
261 261 HOOK_REPO_SIZE = 'changegroup.repo_size'
262 262 HOOK_PUSH = 'changegroup.push_logger'
263 263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
264 264 HOOK_PULL = 'outgoing.pull_logger'
265 265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
266 266
267 267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
268 268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
272 272
273 273 @classmethod
274 274 def get_by_key(cls, key):
275 275 return cls.query().filter(cls.ui_key == key).scalar()
276 276
277 277 @classmethod
278 278 def get_builtin_hooks(cls):
279 279 q = cls.query()
280 280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
281 281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
282 282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
283 283 return q.all()
284 284
285 285 @classmethod
286 286 def get_custom_hooks(cls):
287 287 q = cls.query()
288 288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
289 289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
290 290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
291 291 q = q.filter(cls.ui_section == 'hooks')
292 292 return q.all()
293 293
294 294 @classmethod
295 295 def get_repos_location(cls):
296 296 return cls.get_by_key('/').ui_value
297 297
298 298 @classmethod
299 299 def create_or_update_hook(cls, key, val):
300 300 new_ui = cls.get_by_key(key) or cls()
301 301 new_ui.ui_section = 'hooks'
302 302 new_ui.ui_active = True
303 303 new_ui.ui_key = key
304 304 new_ui.ui_value = val
305 305
306 306 Session().add(new_ui)
307 307
308 308 def __repr__(self):
309 309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
310 310 self.ui_value)
311 311
312 312
313 313 class User(Base, BaseModel):
314 314 __tablename__ = 'users'
315 315 __table_args__ = (
316 316 UniqueConstraint('username'), UniqueConstraint('email'),
317 317 Index('u_username_idx', 'username'),
318 318 Index('u_email_idx', 'email'),
319 319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
320 320 'mysql_charset': 'utf8'}
321 321 )
322 322 DEFAULT_USER = 'default'
323 323
324 324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
325 325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
328 328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
329 329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
333 333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
335 335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
336 336
337 337 user_log = relationship('UserLog')
338 338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
339 339
340 340 repositories = relationship('Repository')
341 341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
342 342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
343 343
344 344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
345 345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
346 346
347 347 group_member = relationship('UserGroupMember', cascade='all')
348 348
349 349 notifications = relationship('UserNotification', cascade='all')
350 350 # notifications assigned to this user
351 351 user_created_notifications = relationship('Notification', cascade='all')
352 352 # comments created by this user
353 353 user_comments = relationship('ChangesetComment', cascade='all')
354 354 #extra emails for this user
355 355 user_emails = relationship('UserEmailMap', cascade='all')
356 356
357 357 @hybrid_property
358 358 def email(self):
359 359 return self._email
360 360
361 361 @email.setter
362 362 def email(self, val):
363 363 self._email = val.lower() if val else None
364 364
365 365 @property
366 366 def firstname(self):
367 367 # alias for future
368 368 return self.name
369 369
370 370 @property
371 371 def emails(self):
372 372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
373 373 return [self.email] + [x.email for x in other]
374 374
375 375 @property
376 376 def ip_addresses(self):
377 377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
378 378 return [x.ip_addr for x in ret]
379 379
380 380 @property
381 381 def username_and_name(self):
382 382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
383 383
384 384 @property
385 385 def full_name(self):
386 386 return '%s %s' % (self.firstname, self.lastname)
387 387
388 388 @property
389 389 def full_name_or_username(self):
390 390 return ('%s %s' % (self.firstname, self.lastname)
391 391 if (self.firstname and self.lastname) else self.username)
392 392
393 393 @property
394 394 def full_contact(self):
395 395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
396 396
397 397 @property
398 398 def short_contact(self):
399 399 return '%s %s' % (self.firstname, self.lastname)
400 400
401 401 @property
402 402 def is_admin(self):
403 403 return self.admin
404 404
405 405 @property
406 406 def AuthUser(self):
407 407 """
408 408 Returns instance of AuthUser for this user
409 409 """
410 410 from rhodecode.lib.auth import AuthUser
411 411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
412 412 username=self.username)
413 413
414 414 def __unicode__(self):
415 415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
416 416 self.user_id, self.username)
417 417
418 418 @classmethod
419 419 def get_by_username(cls, username, case_insensitive=False, cache=False):
420 420 if case_insensitive:
421 421 q = cls.query().filter(cls.username.ilike(username))
422 422 else:
423 423 q = cls.query().filter(cls.username == username)
424 424
425 425 if cache:
426 426 q = q.options(FromCache(
427 427 "sql_cache_short",
428 428 "get_user_%s" % _hash_key(username)
429 429 )
430 430 )
431 431 return q.scalar()
432 432
433 433 @classmethod
434 434 def get_by_api_key(cls, api_key, cache=False):
435 435 q = cls.query().filter(cls.api_key == api_key)
436 436
437 437 if cache:
438 438 q = q.options(FromCache("sql_cache_short",
439 439 "get_api_key_%s" % api_key))
440 440 return q.scalar()
441 441
442 442 @classmethod
443 443 def get_by_email(cls, email, case_insensitive=False, cache=False):
444 444 if case_insensitive:
445 445 q = cls.query().filter(cls.email.ilike(email))
446 446 else:
447 447 q = cls.query().filter(cls.email == email)
448 448
449 449 if cache:
450 450 q = q.options(FromCache("sql_cache_short",
451 451 "get_email_key_%s" % email))
452 452
453 453 ret = q.scalar()
454 454 if ret is None:
455 455 q = UserEmailMap.query()
456 456 # try fetching in alternate email map
457 457 if case_insensitive:
458 458 q = q.filter(UserEmailMap.email.ilike(email))
459 459 else:
460 460 q = q.filter(UserEmailMap.email == email)
461 461 q = q.options(joinedload(UserEmailMap.user))
462 462 if cache:
463 463 q = q.options(FromCache("sql_cache_short",
464 464 "get_email_map_key_%s" % email))
465 465 ret = getattr(q.scalar(), 'user', None)
466 466
467 467 return ret
468 468
469 469 @classmethod
470 470 def get_from_cs_author(cls, author):
471 471 """
472 472 Tries to get User objects out of commit author string
473 473
474 474 :param author:
475 475 """
476 476 from rhodecode.lib.helpers import email, author_name
477 477 # Valid email in the attribute passed, see if they're in the system
478 478 _email = email(author)
479 479 if _email:
480 480 user = cls.get_by_email(_email, case_insensitive=True)
481 481 if user:
482 482 return user
483 483 # Maybe we can match by username?
484 484 _author = author_name(author)
485 485 user = cls.get_by_username(_author, case_insensitive=True)
486 486 if user:
487 487 return user
488 488
489 489 def update_lastlogin(self):
490 490 """Update user lastlogin"""
491 491 self.last_login = datetime.datetime.now()
492 492 Session().add(self)
493 493 log.debug('updated user %s lastlogin' % self.username)
494 494
495 495 @classmethod
496 496 def get_first_admin(cls):
497 497 user = User.query().filter(User.admin == True).first()
498 498 if user is None:
499 499 raise Exception('Missing administrative account!')
500 500 return user
501 501
502 502 @classmethod
503 503 def get_default_user(cls, cache=False):
504 504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
505 505 if user is None:
506 506 raise Exception('Missing default account!')
507 507 return user
508 508
509 509 def get_api_data(self):
510 510 """
511 511 Common function for generating user related data for API
512 512 """
513 513 user = self
514 514 data = dict(
515 515 user_id=user.user_id,
516 516 username=user.username,
517 517 firstname=user.name,
518 518 lastname=user.lastname,
519 519 email=user.email,
520 520 emails=user.emails,
521 521 api_key=user.api_key,
522 522 active=user.active,
523 523 admin=user.admin,
524 524 ldap_dn=user.ldap_dn,
525 525 last_login=user.last_login,
526 526 ip_addresses=user.ip_addresses
527 527 )
528 528 return data
529 529
530 530 def __json__(self):
531 531 data = dict(
532 532 full_name=self.full_name,
533 533 full_name_or_username=self.full_name_or_username,
534 534 short_contact=self.short_contact,
535 535 full_contact=self.full_contact
536 536 )
537 537 data.update(self.get_api_data())
538 538 return data
539 539
540 540
541 541 class UserEmailMap(Base, BaseModel):
542 542 __tablename__ = 'user_email_map'
543 543 __table_args__ = (
544 544 Index('uem_email_idx', 'email'),
545 545 UniqueConstraint('email'),
546 546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 547 'mysql_charset': 'utf8'}
548 548 )
549 549 __mapper_args__ = {}
550 550
551 551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
553 553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 554 user = relationship('User', lazy='joined')
555 555
556 556 @validates('_email')
557 557 def validate_email(self, key, email):
558 558 # check if this email is not main one
559 559 main_email = Session().query(User).filter(User.email == email).scalar()
560 560 if main_email is not None:
561 561 raise AttributeError('email %s is present is user table' % email)
562 562 return email
563 563
564 564 @hybrid_property
565 565 def email(self):
566 566 return self._email
567 567
568 568 @email.setter
569 569 def email(self, val):
570 570 self._email = val.lower() if val else None
571 571
572 572
573 573 class UserIpMap(Base, BaseModel):
574 574 __tablename__ = 'user_ip_map'
575 575 __table_args__ = (
576 576 UniqueConstraint('user_id', 'ip_addr'),
577 577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 578 'mysql_charset': 'utf8'}
579 579 )
580 580 __mapper_args__ = {}
581 581
582 582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
583 583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
584 584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
585 585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
586 586 user = relationship('User', lazy='joined')
587 587
588 588 @classmethod
589 589 def _get_ip_range(cls, ip_addr):
590 590 from rhodecode.lib import ipaddr
591 591 net = ipaddr.IPNetwork(address=ip_addr)
592 592 return [str(net.network), str(net.broadcast)]
593 593
594 594 def __json__(self):
595 595 return dict(
596 596 ip_addr=self.ip_addr,
597 597 ip_range=self._get_ip_range(self.ip_addr)
598 598 )
599 599
600 600
601 601 class UserLog(Base, BaseModel):
602 602 __tablename__ = 'user_logs'
603 603 __table_args__ = (
604 604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
605 605 'mysql_charset': 'utf8'},
606 606 )
607 607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
609 609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
610 610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
611 611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
614 614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
615 615
616 616 @property
617 617 def action_as_day(self):
618 618 return datetime.date(*self.action_date.timetuple()[:3])
619 619
620 620 user = relationship('User')
621 621 repository = relationship('Repository', cascade='')
622 622
623 623
624 624 class UserGroup(Base, BaseModel):
625 625 __tablename__ = 'users_groups'
626 626 __table_args__ = (
627 627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
628 628 'mysql_charset': 'utf8'},
629 629 )
630 630
631 631 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
632 632 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
633 633 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
634 634 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
635 635 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
636 636
637 637 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
638 638 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
639 639 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
640 640 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
641 641 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
642 642 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
643 643
644 644 user = relationship('User')
645 645
646 646 def __unicode__(self):
647 647 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
648 648 self.users_group_id,
649 649 self.users_group_name)
650 650
651 651 @classmethod
652 652 def get_by_group_name(cls, group_name, cache=False,
653 653 case_insensitive=False):
654 654 if case_insensitive:
655 655 q = cls.query().filter(cls.users_group_name.ilike(group_name))
656 656 else:
657 657 q = cls.query().filter(cls.users_group_name == group_name)
658 658 if cache:
659 659 q = q.options(FromCache(
660 660 "sql_cache_short",
661 661 "get_user_%s" % _hash_key(group_name)
662 662 )
663 663 )
664 664 return q.scalar()
665 665
666 666 @classmethod
667 667 def get(cls, users_group_id, cache=False):
668 668 users_group = cls.query()
669 669 if cache:
670 670 users_group = users_group.options(FromCache("sql_cache_short",
671 671 "get_users_group_%s" % users_group_id))
672 672 return users_group.get(users_group_id)
673 673
674 674 def get_api_data(self):
675 675 users_group = self
676 676
677 677 data = dict(
678 678 users_group_id=users_group.users_group_id,
679 679 group_name=users_group.users_group_name,
680 680 active=users_group.users_group_active,
681 681 )
682 682
683 683 return data
684 684
685 685
686 686 class UserGroupMember(Base, BaseModel):
687 687 __tablename__ = 'users_groups_members'
688 688 __table_args__ = (
689 689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
690 690 'mysql_charset': 'utf8'},
691 691 )
692 692
693 693 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
694 694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
695 695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
696 696
697 697 user = relationship('User', lazy='joined')
698 698 users_group = relationship('UserGroup')
699 699
700 700 def __init__(self, gr_id='', u_id=''):
701 701 self.users_group_id = gr_id
702 702 self.user_id = u_id
703 703
704 704
705 705 class RepositoryField(Base, BaseModel):
706 706 __tablename__ = 'repositories_fields'
707 707 __table_args__ = (
708 708 UniqueConstraint('repository_id', 'field_key'), # no-multi field
709 709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
710 710 'mysql_charset': 'utf8'},
711 711 )
712 712 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
713 713
714 714 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
715 715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
716 716 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
717 717 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
718 718 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
719 719 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
720 720 field_type = Column("field_type", String(256), nullable=False, unique=None)
721 721 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
722 722
723 723 repository = relationship('Repository')
724 724
725 725 @property
726 726 def field_key_prefixed(self):
727 727 return 'ex_%s' % self.field_key
728 728
729 729 @classmethod
730 730 def un_prefix_key(cls, key):
731 731 if key.startswith(cls.PREFIX):
732 732 return key[len(cls.PREFIX):]
733 733 return key
734 734
735 735 @classmethod
736 736 def get_by_key_name(cls, key, repo):
737 737 row = cls.query()\
738 738 .filter(cls.repository == repo)\
739 739 .filter(cls.field_key == key).scalar()
740 740 return row
741 741
742 742
743 743 class Repository(Base, BaseModel):
744 744 __tablename__ = 'repositories'
745 745 __table_args__ = (
746 746 UniqueConstraint('repo_name'),
747 747 Index('r_repo_name_idx', 'repo_name'),
748 748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
749 749 'mysql_charset': 'utf8'},
750 750 )
751 751
752 752 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
753 753 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
754 754 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
755 755 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
756 756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
757 757 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
758 758 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
759 759 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
760 760 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
761 761 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
762 762 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
763 763 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
764 764 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
765 765 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
766 766 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
767 767
768 768 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
769 769 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
770 770
771 771 user = relationship('User')
772 772 fork = relationship('Repository', remote_side=repo_id)
773 773 group = relationship('RepoGroup')
774 774 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
775 775 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
776 776 stats = relationship('Statistics', cascade='all', uselist=False)
777 777
778 778 followers = relationship('UserFollowing',
779 779 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
780 780 cascade='all')
781 781 extra_fields = relationship('RepositoryField',
782 782 cascade="all, delete, delete-orphan")
783 783
784 784 logs = relationship('UserLog')
785 785 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
786 786
787 787 pull_requests_org = relationship('PullRequest',
788 788 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
789 789 cascade="all, delete, delete-orphan")
790 790
791 791 pull_requests_other = relationship('PullRequest',
792 792 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
793 793 cascade="all, delete, delete-orphan")
794 794
795 795 def __unicode__(self):
796 796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
797 797 self.repo_name)
798 798
799 799 @hybrid_property
800 800 def locked(self):
801 801 # always should return [user_id, timelocked]
802 802 if self._locked:
803 803 _lock_info = self._locked.split(':')
804 804 return int(_lock_info[0]), _lock_info[1]
805 805 return [None, None]
806 806
807 807 @locked.setter
808 808 def locked(self, val):
809 809 if val and isinstance(val, (list, tuple)):
810 810 self._locked = ':'.join(map(str, val))
811 811 else:
812 812 self._locked = None
813 813
814 814 @hybrid_property
815 815 def changeset_cache(self):
816 816 from rhodecode.lib.vcs.backends.base import EmptyChangeset
817 817 dummy = EmptyChangeset().__json__()
818 818 if not self._changeset_cache:
819 819 return dummy
820 820 try:
821 821 return json.loads(self._changeset_cache)
822 822 except TypeError:
823 823 return dummy
824 824
825 825 @changeset_cache.setter
826 826 def changeset_cache(self, val):
827 827 try:
828 828 self._changeset_cache = json.dumps(val)
829 829 except Exception:
830 830 log.error(traceback.format_exc())
831 831
832 832 @classmethod
833 833 def url_sep(cls):
834 834 return URL_SEP
835 835
836 836 @classmethod
837 837 def normalize_repo_name(cls, repo_name):
838 838 """
839 839 Normalizes os specific repo_name to the format internally stored inside
840 840 dabatabase using URL_SEP
841 841
842 842 :param cls:
843 843 :param repo_name:
844 844 """
845 845 return cls.url_sep().join(repo_name.split(os.sep))
846 846
847 847 @classmethod
848 848 def get_by_repo_name(cls, repo_name):
849 849 q = Session().query(cls).filter(cls.repo_name == repo_name)
850 850 q = q.options(joinedload(Repository.fork))\
851 851 .options(joinedload(Repository.user))\
852 852 .options(joinedload(Repository.group))
853 853 return q.scalar()
854 854
855 855 @classmethod
856 856 def get_by_full_path(cls, repo_full_path):
857 857 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
858 858 repo_name = cls.normalize_repo_name(repo_name)
859 859 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
860 860
861 861 @classmethod
862 862 def get_repo_forks(cls, repo_id):
863 863 return cls.query().filter(Repository.fork_id == repo_id)
864 864
865 865 @classmethod
866 866 def base_path(cls):
867 867 """
868 868 Returns base path when all repos are stored
869 869
870 870 :param cls:
871 871 """
872 872 q = Session().query(RhodeCodeUi)\
873 873 .filter(RhodeCodeUi.ui_key == cls.url_sep())
874 874 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
875 875 return q.one().ui_value
876 876
877 877 @property
878 878 def forks(self):
879 879 """
880 880 Return forks of this repo
881 881 """
882 882 return Repository.get_repo_forks(self.repo_id)
883 883
884 884 @property
885 885 def parent(self):
886 886 """
887 887 Returns fork parent
888 888 """
889 889 return self.fork
890 890
891 891 @property
892 892 def just_name(self):
893 893 return self.repo_name.split(Repository.url_sep())[-1]
894 894
895 895 @property
896 896 def groups_with_parents(self):
897 897 groups = []
898 898 if self.group is None:
899 899 return groups
900 900
901 901 cur_gr = self.group
902 902 groups.insert(0, cur_gr)
903 903 while 1:
904 904 gr = getattr(cur_gr, 'parent_group', None)
905 905 cur_gr = cur_gr.parent_group
906 906 if gr is None:
907 907 break
908 908 groups.insert(0, gr)
909 909
910 910 return groups
911 911
912 912 @property
913 913 def groups_and_repo(self):
914 914 return self.groups_with_parents, self.just_name, self.repo_name
915 915
916 916 @LazyProperty
917 917 def repo_path(self):
918 918 """
919 919 Returns base full path for that repository means where it actually
920 920 exists on a filesystem
921 921 """
922 922 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
923 923 Repository.url_sep())
924 924 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
925 925 return q.one().ui_value
926 926
927 927 @property
928 928 def repo_full_path(self):
929 929 p = [self.repo_path]
930 930 # we need to split the name by / since this is how we store the
931 931 # names in the database, but that eventually needs to be converted
932 932 # into a valid system path
933 933 p += self.repo_name.split(Repository.url_sep())
934 934 return os.path.join(*map(safe_unicode, p))
935 935
936 936 @property
937 937 def cache_keys(self):
938 938 """
939 939 Returns associated cache keys for that repo
940 940 """
941 941 return CacheInvalidation.query()\
942 942 .filter(CacheInvalidation.cache_args == self.repo_name)\
943 943 .order_by(CacheInvalidation.cache_key)\
944 944 .all()
945 945
946 946 def get_new_name(self, repo_name):
947 947 """
948 948 returns new full repository name based on assigned group and new new
949 949
950 950 :param group_name:
951 951 """
952 952 path_prefix = self.group.full_path_splitted if self.group else []
953 953 return Repository.url_sep().join(path_prefix + [repo_name])
954 954
955 955 @property
956 956 def _ui(self):
957 957 """
958 958 Creates an db based ui object for this repository
959 959 """
960 960 from rhodecode.lib.utils import make_ui
961 961 return make_ui('db', clear_session=False)
962 962
963 963 @classmethod
964 964 def is_valid(cls, repo_name):
965 965 """
966 966 returns True if given repo name is a valid filesystem repository
967 967
968 968 :param cls:
969 969 :param repo_name:
970 970 """
971 971 from rhodecode.lib.utils import is_valid_repo
972 972
973 973 return is_valid_repo(repo_name, cls.base_path())
974 974
975 975 def get_api_data(self):
976 976 """
977 977 Common function for generating repo api data
978 978
979 979 """
980 980 repo = self
981 981 data = dict(
982 982 repo_id=repo.repo_id,
983 983 repo_name=repo.repo_name,
984 984 repo_type=repo.repo_type,
985 985 clone_uri=repo.clone_uri,
986 986 private=repo.private,
987 987 created_on=repo.created_on,
988 988 description=repo.description,
989 989 landing_rev=repo.landing_rev,
990 990 owner=repo.user.username,
991 991 fork_of=repo.fork.repo_name if repo.fork else None,
992 992 enable_statistics=repo.enable_statistics,
993 993 enable_locking=repo.enable_locking,
994 994 enable_downloads=repo.enable_downloads,
995 995 last_changeset=repo.changeset_cache,
996 996 locked_by=User.get(self.locked[0]).get_api_data() \
997 997 if self.locked[0] else None,
998 998 locked_date=time_to_datetime(self.locked[1]) \
999 999 if self.locked[1] else None
1000 1000 )
1001 1001 rc_config = RhodeCodeSetting.get_app_settings()
1002 1002 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1003 1003 if repository_fields:
1004 1004 for f in self.extra_fields:
1005 1005 data[f.field_key_prefixed] = f.field_value
1006 1006
1007 1007 return data
1008 1008
1009 1009 @classmethod
1010 1010 def lock(cls, repo, user_id, lock_time=None):
1011 1011 if not lock_time:
1012 1012 lock_time = time.time()
1013 1013 repo.locked = [user_id, lock_time]
1014 1014 Session().add(repo)
1015 1015 Session().commit()
1016 1016
1017 1017 @classmethod
1018 1018 def unlock(cls, repo):
1019 1019 repo.locked = None
1020 1020 Session().add(repo)
1021 1021 Session().commit()
1022 1022
1023 1023 @classmethod
1024 1024 def getlock(cls, repo):
1025 1025 return repo.locked
1026 1026
1027 1027 @property
1028 1028 def last_db_change(self):
1029 1029 return self.updated_on
1030 1030
1031 1031 def clone_url(self, **override):
1032 1032 from pylons import url
1033 1033 from urlparse import urlparse
1034 1034 import urllib
1035 1035 parsed_url = urlparse(url('home', qualified=True))
1036 1036 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1037 1037 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1038 1038 args = {
1039 1039 'user': '',
1040 1040 'pass': '',
1041 1041 'scheme': parsed_url.scheme,
1042 1042 'netloc': parsed_url.netloc,
1043 1043 'prefix': decoded_path,
1044 1044 'path': self.repo_name
1045 1045 }
1046 1046
1047 1047 args.update(override)
1048 1048 return default_clone_uri % args
1049 1049
1050 1050 #==========================================================================
1051 1051 # SCM PROPERTIES
1052 1052 #==========================================================================
1053 1053
1054 1054 def get_changeset(self, rev=None):
1055 1055 return get_changeset_safe(self.scm_instance, rev)
1056 1056
1057 1057 def get_landing_changeset(self):
1058 1058 """
1059 1059 Returns landing changeset, or if that doesn't exist returns the tip
1060 1060 """
1061 1061 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1062 1062 return cs
1063 1063
1064 1064 def update_changeset_cache(self, cs_cache=None):
1065 1065 """
1066 1066 Update cache of last changeset for repository, keys should be::
1067 1067
1068 1068 short_id
1069 1069 raw_id
1070 1070 revision
1071 1071 message
1072 1072 date
1073 1073 author
1074 1074
1075 1075 :param cs_cache:
1076 1076 """
1077 1077 from rhodecode.lib.vcs.backends.base import BaseChangeset
1078 1078 if cs_cache is None:
1079 1079 cs_cache = EmptyChangeset()
1080 1080 # use no-cache version here
1081 1081 scm_repo = self.scm_instance_no_cache()
1082 1082 if scm_repo:
1083 1083 cs_cache = scm_repo.get_changeset()
1084 1084
1085 1085 if isinstance(cs_cache, BaseChangeset):
1086 1086 cs_cache = cs_cache.__json__()
1087 1087
1088 1088 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1089 1089 _default = datetime.datetime.fromtimestamp(0)
1090 1090 last_change = cs_cache.get('date') or _default
1091 1091 log.debug('updated repo %s with new cs cache %s'
1092 1092 % (self.repo_name, cs_cache))
1093 1093 self.updated_on = last_change
1094 1094 self.changeset_cache = cs_cache
1095 1095 Session().add(self)
1096 1096 Session().commit()
1097 1097 else:
1098 1098 log.debug('Skipping repo:%s already with latest changes'
1099 1099 % self.repo_name)
1100 1100
1101 1101 @property
1102 1102 def tip(self):
1103 1103 return self.get_changeset('tip')
1104 1104
1105 1105 @property
1106 1106 def author(self):
1107 1107 return self.tip.author
1108 1108
1109 1109 @property
1110 1110 def last_change(self):
1111 1111 return self.scm_instance.last_change
1112 1112
1113 1113 def get_comments(self, revisions=None):
1114 1114 """
1115 1115 Returns comments for this repository grouped by revisions
1116 1116
1117 1117 :param revisions: filter query by revisions only
1118 1118 """
1119 1119 cmts = ChangesetComment.query()\
1120 1120 .filter(ChangesetComment.repo == self)
1121 1121 if revisions:
1122 1122 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1123 1123 grouped = collections.defaultdict(list)
1124 1124 for cmt in cmts.all():
1125 1125 grouped[cmt.revision].append(cmt)
1126 1126 return grouped
1127 1127
1128 1128 def statuses(self, revisions=None):
1129 1129 """
1130 1130 Returns statuses for this repository
1131 1131
1132 1132 :param revisions: list of revisions to get statuses for
1133 1133 """
1134 1134
1135 1135 statuses = ChangesetStatus.query()\
1136 1136 .filter(ChangesetStatus.repo == self)\
1137 1137 .filter(ChangesetStatus.version == 0)
1138 1138 if revisions:
1139 1139 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1140 1140 grouped = {}
1141 1141
1142 1142 #maybe we have open new pullrequest without a status ?
1143 1143 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1144 1144 status_lbl = ChangesetStatus.get_status_lbl(stat)
1145 1145 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1146 1146 for rev in pr.revisions:
1147 1147 pr_id = pr.pull_request_id
1148 1148 pr_repo = pr.other_repo.repo_name
1149 1149 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1150 1150
1151 1151 for stat in statuses.all():
1152 1152 pr_id = pr_repo = None
1153 1153 if stat.pull_request:
1154 1154 pr_id = stat.pull_request.pull_request_id
1155 1155 pr_repo = stat.pull_request.other_repo.repo_name
1156 1156 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1157 1157 pr_id, pr_repo]
1158 1158 return grouped
1159 1159
1160 1160 def _repo_size(self):
1161 1161 from rhodecode.lib import helpers as h
1162 1162 log.debug('calculating repository size...')
1163 1163 return h.format_byte_size(self.scm_instance.size)
1164 1164
1165 1165 #==========================================================================
1166 1166 # SCM CACHE INSTANCE
1167 1167 #==========================================================================
1168 1168
1169 1169 def set_invalidate(self):
1170 1170 """
1171 1171 Mark caches of this repo as invalid.
1172 1172 """
1173 1173 CacheInvalidation.set_invalidate(self.repo_name)
1174 1174
1175 1175 def scm_instance_no_cache(self):
1176 1176 return self.__get_instance()
1177 1177
1178 1178 @property
1179 1179 def scm_instance(self):
1180 1180 import rhodecode
1181 1181 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1182 1182 if full_cache:
1183 1183 return self.scm_instance_cached()
1184 1184 return self.__get_instance()
1185 1185
1186 1186 def scm_instance_cached(self, valid_cache_keys=None):
1187 1187 @cache_region('long_term')
1188 1188 def _c(repo_name):
1189 1189 return self.__get_instance()
1190 1190 rn = self.repo_name
1191 1191
1192 1192 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1193 1193 if not valid:
1194 1194 log.debug('Cache for %s invalidated, getting new object' % (rn))
1195 1195 region_invalidate(_c, None, rn)
1196 1196 else:
1197 1197 log.debug('Getting obj for %s from cache' % (rn))
1198 1198 return _c(rn)
1199 1199
1200 1200 def __get_instance(self):
1201 1201 repo_full_path = self.repo_full_path
1202 1202 try:
1203 1203 alias = get_scm(repo_full_path)[0]
1204 1204 log.debug('Creating instance of %s repository from %s'
1205 1205 % (alias, repo_full_path))
1206 1206 backend = get_backend(alias)
1207 1207 except VCSError:
1208 1208 log.error(traceback.format_exc())
1209 1209 log.error('Perhaps this repository is in db and not in '
1210 1210 'filesystem run rescan repositories with '
1211 1211 '"destroy old data " option from admin panel')
1212 1212 return
1213 1213
1214 1214 if alias == 'hg':
1215 1215
1216 1216 repo = backend(safe_str(repo_full_path), create=False,
1217 1217 baseui=self._ui)
1218 1218 # skip hidden web repository
1219 1219 if repo._get_hidden():
1220 1220 return
1221 1221 else:
1222 1222 repo = backend(repo_full_path, create=False)
1223 1223
1224 1224 return repo
1225 1225
1226 1226
1227 1227 class RepoGroup(Base, BaseModel):
1228 1228 __tablename__ = 'groups'
1229 1229 __table_args__ = (
1230 1230 UniqueConstraint('group_name', 'group_parent_id'),
1231 1231 CheckConstraint('group_id != group_parent_id'),
1232 1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1233 1233 'mysql_charset': 'utf8'},
1234 1234 )
1235 1235 __mapper_args__ = {'order_by': 'group_name'}
1236 1236
1237 1237 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 1238 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1239 1239 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1240 1240 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1241 1241 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1242 1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1243 1243
1244 1244 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1245 1245 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1246 1246 parent_group = relationship('RepoGroup', remote_side=group_id)
1247 1247 user = relationship('User')
1248 1248
1249 1249 def __init__(self, group_name='', parent_group=None):
1250 1250 self.group_name = group_name
1251 1251 self.parent_group = parent_group
1252 1252
1253 1253 def __unicode__(self):
1254 1254 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1255 1255 self.group_name)
1256 1256
1257 1257 @classmethod
1258 1258 def groups_choices(cls, groups=None, show_empty_group=True):
1259 1259 from webhelpers.html import literal as _literal
1260 1260 if not groups:
1261 1261 groups = cls.query().all()
1262 1262
1263 1263 repo_groups = []
1264 1264 if show_empty_group:
1265 1265 repo_groups = [('-1', '-- %s --' % _('top level'))]
1266 1266 sep = ' &raquo; '
1267 1267 _name = lambda k: _literal(sep.join(k))
1268 1268
1269 1269 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1270 1270 for x in groups])
1271 1271
1272 1272 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1273 1273 return repo_groups
1274 1274
1275 1275 @classmethod
1276 1276 def url_sep(cls):
1277 1277 return URL_SEP
1278 1278
1279 1279 @classmethod
1280 1280 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1281 1281 if case_insensitive:
1282 1282 gr = cls.query()\
1283 1283 .filter(cls.group_name.ilike(group_name))
1284 1284 else:
1285 1285 gr = cls.query()\
1286 1286 .filter(cls.group_name == group_name)
1287 1287 if cache:
1288 1288 gr = gr.options(FromCache(
1289 1289 "sql_cache_short",
1290 1290 "get_group_%s" % _hash_key(group_name)
1291 1291 )
1292 1292 )
1293 1293 return gr.scalar()
1294 1294
1295 1295 @property
1296 1296 def parents(self):
1297 1297 parents_recursion_limit = 5
1298 1298 groups = []
1299 1299 if self.parent_group is None:
1300 1300 return groups
1301 1301 cur_gr = self.parent_group
1302 1302 groups.insert(0, cur_gr)
1303 1303 cnt = 0
1304 1304 while 1:
1305 1305 cnt += 1
1306 1306 gr = getattr(cur_gr, 'parent_group', None)
1307 1307 cur_gr = cur_gr.parent_group
1308 1308 if gr is None:
1309 1309 break
1310 1310 if cnt == parents_recursion_limit:
1311 1311 # this will prevent accidental infinit loops
1312 1312 log.error('group nested more than %s' %
1313 1313 parents_recursion_limit)
1314 1314 break
1315 1315
1316 1316 groups.insert(0, gr)
1317 1317 return groups
1318 1318
1319 1319 @property
1320 1320 def children(self):
1321 1321 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1322 1322
1323 1323 @property
1324 1324 def name(self):
1325 1325 return self.group_name.split(RepoGroup.url_sep())[-1]
1326 1326
1327 1327 @property
1328 1328 def full_path(self):
1329 1329 return self.group_name
1330 1330
1331 1331 @property
1332 1332 def full_path_splitted(self):
1333 1333 return self.group_name.split(RepoGroup.url_sep())
1334 1334
1335 1335 @property
1336 1336 def repositories(self):
1337 1337 return Repository.query()\
1338 1338 .filter(Repository.group == self)\
1339 1339 .order_by(Repository.repo_name)
1340 1340
1341 1341 @property
1342 1342 def repositories_recursive_count(self):
1343 1343 cnt = self.repositories.count()
1344 1344
1345 1345 def children_count(group):
1346 1346 cnt = 0
1347 1347 for child in group.children:
1348 1348 cnt += child.repositories.count()
1349 1349 cnt += children_count(child)
1350 1350 return cnt
1351 1351
1352 1352 return cnt + children_count(self)
1353 1353
1354 1354 def _recursive_objects(self, include_repos=True):
1355 1355 all_ = []
1356 1356
1357 1357 def _get_members(root_gr):
1358 1358 if include_repos:
1359 1359 for r in root_gr.repositories:
1360 1360 all_.append(r)
1361 1361 childs = root_gr.children.all()
1362 1362 if childs:
1363 1363 for gr in childs:
1364 1364 all_.append(gr)
1365 1365 _get_members(gr)
1366 1366
1367 1367 _get_members(self)
1368 1368 return [self] + all_
1369 1369
1370 1370 def recursive_groups_and_repos(self):
1371 1371 """
1372 1372 Recursive return all groups, with repositories in those groups
1373 1373 """
1374 1374 return self._recursive_objects()
1375 1375
1376 1376 def recursive_groups(self):
1377 1377 """
1378 1378 Returns all children groups for this group including children of children
1379 1379 """
1380 1380 return self._recursive_objects(include_repos=False)
1381 1381
1382 1382 def get_new_name(self, group_name):
1383 1383 """
1384 1384 returns new full group name based on parent and new name
1385 1385
1386 1386 :param group_name:
1387 1387 """
1388 1388 path_prefix = (self.parent_group.full_path_splitted if
1389 1389 self.parent_group else [])
1390 1390 return RepoGroup.url_sep().join(path_prefix + [group_name])
1391 1391
1392 1392
1393 1393 class Permission(Base, BaseModel):
1394 1394 __tablename__ = 'permissions'
1395 1395 __table_args__ = (
1396 1396 Index('p_perm_name_idx', 'permission_name'),
1397 1397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1398 1398 'mysql_charset': 'utf8'},
1399 1399 )
1400 1400 PERMS = [
1401 1401 ('hg.admin', _('RhodeCode Administrator')),
1402 1402
1403 1403 ('repository.none', _('Repository no access')),
1404 1404 ('repository.read', _('Repository read access')),
1405 1405 ('repository.write', _('Repository write access')),
1406 1406 ('repository.admin', _('Repository admin access')),
1407 1407
1408 1408 ('group.none', _('Repository group no access')),
1409 1409 ('group.read', _('Repository group read access')),
1410 1410 ('group.write', _('Repository group write access')),
1411 1411 ('group.admin', _('Repository group admin access')),
1412 1412
1413 1413 ('usergroup.none', _('User group no access')),
1414 1414 ('usergroup.read', _('User group read access')),
1415 1415 ('usergroup.write', _('User group write access')),
1416 1416 ('usergroup.admin', _('User group admin access')),
1417 1417
1418 1418 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1419 1419 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1420 1420
1421 1421 ('hg.usergroup.create.false', _('User Group creation disabled')),
1422 1422 ('hg.usergroup.create.true', _('User Group creation enabled')),
1423 1423
1424 1424 ('hg.create.none', _('Repository creation disabled')),
1425 1425 ('hg.create.repository', _('Repository creation enabled')),
1426 1426
1427 1427 ('hg.fork.none', _('Repository forking disabled')),
1428 1428 ('hg.fork.repository', _('Repository forking enabled')),
1429 1429
1430 1430 ('hg.register.none', _('Registration disabled')),
1431 1431 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1432 1432 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1433 1433
1434 1434 ('hg.extern_activate.manual', _('Manual activation of external account')),
1435 1435 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1436 1436
1437 1437 ]
1438 1438
1439 1439 #definition of system default permissions for DEFAULT user
1440 1440 DEFAULT_USER_PERMISSIONS = [
1441 1441 'repository.read',
1442 1442 'group.read',
1443 1443 'usergroup.read',
1444 1444 'hg.create.repository',
1445 1445 'hg.fork.repository',
1446 1446 'hg.register.manual_activate',
1447 1447 'hg.extern_activate.auto',
1448 1448 ]
1449 1449
1450 1450 # defines which permissions are more important higher the more important
1451 1451 # Weight defines which permissions are more important.
1452 1452 # The higher number the more important.
1453 1453 PERM_WEIGHTS = {
1454 1454 'repository.none': 0,
1455 1455 'repository.read': 1,
1456 1456 'repository.write': 3,
1457 1457 'repository.admin': 4,
1458 1458
1459 1459 'group.none': 0,
1460 1460 'group.read': 1,
1461 1461 'group.write': 3,
1462 1462 'group.admin': 4,
1463 1463
1464 1464 'usergroup.none': 0,
1465 1465 'usergroup.read': 1,
1466 1466 'usergroup.write': 3,
1467 1467 'usergroup.admin': 4,
1468 1468 'hg.repogroup.create.false': 0,
1469 1469 'hg.repogroup.create.true': 1,
1470 1470
1471 1471 'hg.usergroup.create.false': 0,
1472 1472 'hg.usergroup.create.true': 1,
1473 1473
1474 1474 'hg.fork.none': 0,
1475 1475 'hg.fork.repository': 1,
1476 1476 'hg.create.none': 0,
1477 1477 'hg.create.repository': 1
1478 1478 }
1479 1479
1480 1480 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1481 1481 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1482 1482 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1483 1483
1484 1484 def __unicode__(self):
1485 1485 return u"<%s('%s:%s')>" % (
1486 1486 self.__class__.__name__, self.permission_id, self.permission_name
1487 1487 )
1488 1488
1489 1489 @classmethod
1490 1490 def get_by_key(cls, key):
1491 1491 return cls.query().filter(cls.permission_name == key).scalar()
1492 1492
1493 1493 @classmethod
1494 1494 def get_default_perms(cls, default_user_id):
1495 1495 q = Session().query(UserRepoToPerm, Repository, cls)\
1496 1496 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1497 1497 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1498 1498 .filter(UserRepoToPerm.user_id == default_user_id)
1499 1499
1500 1500 return q.all()
1501 1501
1502 1502 @classmethod
1503 1503 def get_default_group_perms(cls, default_user_id):
1504 1504 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1505 1505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1506 1506 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1507 1507 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1508 1508
1509 1509 return q.all()
1510 1510
1511 1511 @classmethod
1512 1512 def get_default_user_group_perms(cls, default_user_id):
1513 1513 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1514 1514 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1515 1515 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1516 1516 .filter(UserUserGroupToPerm.user_id == default_user_id)
1517 1517
1518 1518 return q.all()
1519 1519
1520 1520
1521 1521 class UserRepoToPerm(Base, BaseModel):
1522 1522 __tablename__ = 'repo_to_perm'
1523 1523 __table_args__ = (
1524 1524 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1525 1525 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1526 1526 'mysql_charset': 'utf8'}
1527 1527 )
1528 1528 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 1529 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1530 1530 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1531 1531 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1532 1532
1533 1533 user = relationship('User')
1534 1534 repository = relationship('Repository')
1535 1535 permission = relationship('Permission')
1536 1536
1537 1537 @classmethod
1538 1538 def create(cls, user, repository, permission):
1539 1539 n = cls()
1540 1540 n.user = user
1541 1541 n.repository = repository
1542 1542 n.permission = permission
1543 1543 Session().add(n)
1544 1544 return n
1545 1545
1546 1546 def __unicode__(self):
1547 1547 return u'<%s => %s >' % (self.user, self.repository)
1548 1548
1549 1549
1550 1550 class UserUserGroupToPerm(Base, BaseModel):
1551 1551 __tablename__ = 'user_user_group_to_perm'
1552 1552 __table_args__ = (
1553 1553 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1554 1554 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1555 1555 'mysql_charset': 'utf8'}
1556 1556 )
1557 1557 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1558 1558 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1559 1559 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1560 1560 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1561 1561
1562 1562 user = relationship('User')
1563 1563 user_group = relationship('UserGroup')
1564 1564 permission = relationship('Permission')
1565 1565
1566 1566 @classmethod
1567 1567 def create(cls, user, user_group, permission):
1568 1568 n = cls()
1569 1569 n.user = user
1570 1570 n.user_group = user_group
1571 1571 n.permission = permission
1572 1572 Session().add(n)
1573 1573 return n
1574 1574
1575 1575 def __unicode__(self):
1576 1576 return u'<%s => %s >' % (self.user, self.user_group)
1577 1577
1578 1578
1579 1579 class UserToPerm(Base, BaseModel):
1580 1580 __tablename__ = 'user_to_perm'
1581 1581 __table_args__ = (
1582 1582 UniqueConstraint('user_id', 'permission_id'),
1583 1583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1584 1584 'mysql_charset': 'utf8'}
1585 1585 )
1586 1586 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1587 1587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1588 1588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1589 1589
1590 1590 user = relationship('User')
1591 1591 permission = relationship('Permission', lazy='joined')
1592 1592
1593 1593 def __unicode__(self):
1594 1594 return u'<%s => %s >' % (self.user, self.permission)
1595 1595
1596 1596
1597 1597 class UserGroupRepoToPerm(Base, BaseModel):
1598 1598 __tablename__ = 'users_group_repo_to_perm'
1599 1599 __table_args__ = (
1600 1600 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1601 1601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1602 1602 'mysql_charset': 'utf8'}
1603 1603 )
1604 1604 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1605 1605 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1606 1606 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1607 1607 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1608 1608
1609 1609 users_group = relationship('UserGroup')
1610 1610 permission = relationship('Permission')
1611 1611 repository = relationship('Repository')
1612 1612
1613 1613 @classmethod
1614 1614 def create(cls, users_group, repository, permission):
1615 1615 n = cls()
1616 1616 n.users_group = users_group
1617 1617 n.repository = repository
1618 1618 n.permission = permission
1619 1619 Session().add(n)
1620 1620 return n
1621 1621
1622 1622 def __unicode__(self):
1623 1623 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1624 1624
1625 1625
1626 1626 class UserGroupUserGroupToPerm(Base, BaseModel):
1627 1627 __tablename__ = 'user_group_user_group_to_perm'
1628 1628 __table_args__ = (
1629 1629 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1630 1630 CheckConstraint('target_user_group_id != user_group_id'),
1631 1631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1632 1632 'mysql_charset': 'utf8'}
1633 1633 )
1634 1634 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 1635 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1636 1636 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1637 1637 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1638 1638
1639 1639 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1640 1640 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1641 1641 permission = relationship('Permission')
1642 1642
1643 1643 @classmethod
1644 1644 def create(cls, target_user_group, user_group, permission):
1645 1645 n = cls()
1646 1646 n.target_user_group = target_user_group
1647 1647 n.user_group = user_group
1648 1648 n.permission = permission
1649 1649 Session().add(n)
1650 1650 return n
1651 1651
1652 1652 def __unicode__(self):
1653 1653 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1654 1654
1655 1655
1656 1656 class UserGroupToPerm(Base, BaseModel):
1657 1657 __tablename__ = 'users_group_to_perm'
1658 1658 __table_args__ = (
1659 1659 UniqueConstraint('users_group_id', 'permission_id',),
1660 1660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1661 1661 'mysql_charset': 'utf8'}
1662 1662 )
1663 1663 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1664 1664 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1665 1665 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1666 1666
1667 1667 users_group = relationship('UserGroup')
1668 1668 permission = relationship('Permission')
1669 1669
1670 1670
1671 1671 class UserRepoGroupToPerm(Base, BaseModel):
1672 1672 __tablename__ = 'user_repo_group_to_perm'
1673 1673 __table_args__ = (
1674 1674 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1675 1675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1676 1676 'mysql_charset': 'utf8'}
1677 1677 )
1678 1678
1679 1679 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1680 1680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1681 1681 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1682 1682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1683 1683
1684 1684 user = relationship('User')
1685 1685 group = relationship('RepoGroup')
1686 1686 permission = relationship('Permission')
1687 1687
1688 1688
1689 1689 class UserGroupRepoGroupToPerm(Base, BaseModel):
1690 1690 __tablename__ = 'users_group_repo_group_to_perm'
1691 1691 __table_args__ = (
1692 1692 UniqueConstraint('users_group_id', 'group_id'),
1693 1693 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1694 1694 'mysql_charset': 'utf8'}
1695 1695 )
1696 1696
1697 1697 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1698 1698 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1699 1699 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1700 1700 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1701 1701
1702 1702 users_group = relationship('UserGroup')
1703 1703 permission = relationship('Permission')
1704 1704 group = relationship('RepoGroup')
1705 1705
1706 1706
1707 1707 class Statistics(Base, BaseModel):
1708 1708 __tablename__ = 'statistics'
1709 1709 __table_args__ = (
1710 1710 UniqueConstraint('repository_id'),
1711 1711 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1712 1712 'mysql_charset': 'utf8'}
1713 1713 )
1714 1714 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1715 1715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1716 1716 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1717 1717 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1718 1718 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1719 1719 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1720 1720
1721 1721 repository = relationship('Repository', single_parent=True)
1722 1722
1723 1723
1724 1724 class UserFollowing(Base, BaseModel):
1725 1725 __tablename__ = 'user_followings'
1726 1726 __table_args__ = (
1727 1727 UniqueConstraint('user_id', 'follows_repository_id'),
1728 1728 UniqueConstraint('user_id', 'follows_user_id'),
1729 1729 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1730 1730 'mysql_charset': 'utf8'}
1731 1731 )
1732 1732
1733 1733 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1734 1734 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1735 1735 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1736 1736 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1737 1737 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1738 1738
1739 1739 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1740 1740
1741 1741 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1742 1742 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1743 1743
1744 1744 @classmethod
1745 1745 def get_repo_followers(cls, repo_id):
1746 1746 return cls.query().filter(cls.follows_repo_id == repo_id)
1747 1747
1748 1748
1749 1749 class CacheInvalidation(Base, BaseModel):
1750 1750 __tablename__ = 'cache_invalidation'
1751 1751 __table_args__ = (
1752 1752 UniqueConstraint('cache_key'),
1753 1753 Index('key_idx', 'cache_key'),
1754 1754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1755 1755 'mysql_charset': 'utf8'},
1756 1756 )
1757 1757 # cache_id, not used
1758 1758 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1759 1759 # cache_key as created by _get_cache_key
1760 1760 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1761 1761 # cache_args is a repo_name
1762 1762 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1763 1763 # instance sets cache_active True when it is caching,
1764 1764 # other instances set cache_active to False to indicate that this cache is invalid
1765 1765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1766 1766
1767 1767 def __init__(self, cache_key, repo_name=''):
1768 1768 self.cache_key = cache_key
1769 1769 self.cache_args = repo_name
1770 1770 self.cache_active = False
1771 1771
1772 1772 def __unicode__(self):
1773 1773 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1774 1774 self.cache_id, self.cache_key, self.cache_active)
1775 1775
1776 1776 def _cache_key_partition(self):
1777 1777 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1778 1778 return prefix, repo_name, suffix
1779 1779
1780 1780 def get_prefix(self):
1781 1781 """
1782 1782 get prefix that might have been used in _get_cache_key to
1783 1783 generate self.cache_key. Only used for informational purposes
1784 1784 in repo_edit.html.
1785 1785 """
1786 1786 # prefix, repo_name, suffix
1787 1787 return self._cache_key_partition()[0]
1788 1788
1789 1789 def get_suffix(self):
1790 1790 """
1791 1791 get suffix that might have been used in _get_cache_key to
1792 1792 generate self.cache_key. Only used for informational purposes
1793 1793 in repo_edit.html.
1794 1794 """
1795 1795 # prefix, repo_name, suffix
1796 1796 return self._cache_key_partition()[2]
1797 1797
1798 1798 @classmethod
1799 1799 def clear_cache(cls):
1800 1800 """
1801 1801 Delete all cache keys from database.
1802 1802 Should only be run when all instances are down and all entries thus stale.
1803 1803 """
1804 1804 cls.query().delete()
1805 1805 Session().commit()
1806 1806
1807 1807 @classmethod
1808 1808 def _get_cache_key(cls, key):
1809 1809 """
1810 1810 Wrapper for generating a unique cache key for this instance and "key".
1811 1811 key must / will start with a repo_name which will be stored in .cache_args .
1812 1812 """
1813 1813 import rhodecode
1814 1814 prefix = rhodecode.CONFIG.get('instance_id', '')
1815 1815 return "%s%s" % (prefix, key)
1816 1816
1817 1817 @classmethod
1818 1818 def set_invalidate(cls, repo_name):
1819 1819 """
1820 1820 Mark all caches of a repo as invalid in the database.
1821 1821 """
1822 1822 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1823 1823
1824 1824 try:
1825 1825 for inv_obj in inv_objs:
1826 1826 log.debug('marking %s key for invalidation based on repo_name=%s'
1827 1827 % (inv_obj, safe_str(repo_name)))
1828 1828 inv_obj.cache_active = False
1829 1829 Session().add(inv_obj)
1830 1830 Session().commit()
1831 1831 except Exception:
1832 1832 log.error(traceback.format_exc())
1833 1833 Session().rollback()
1834 1834
1835 1835 @classmethod
1836 1836 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1837 1837 """
1838 1838 Mark this cache key as active and currently cached.
1839 1839 Return True if the existing cache registration still was valid.
1840 1840 Return False to indicate that it had been invalidated and caches should be refreshed.
1841 1841 """
1842 1842
1843 1843 key = (repo_name + '_' + kind) if kind else repo_name
1844 1844 cache_key = cls._get_cache_key(key)
1845 1845
1846 1846 if valid_cache_keys and cache_key in valid_cache_keys:
1847 1847 return True
1848 1848
1849 1849 try:
1850 1850 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1851 1851 if not inv_obj:
1852 1852 inv_obj = CacheInvalidation(cache_key, repo_name)
1853 1853 was_valid = inv_obj.cache_active
1854 1854 inv_obj.cache_active = True
1855 1855 Session().add(inv_obj)
1856 1856 Session().commit()
1857 1857 return was_valid
1858 1858 except Exception:
1859 1859 log.error(traceback.format_exc())
1860 1860 Session().rollback()
1861 1861 return False
1862 1862
1863 1863 @classmethod
1864 1864 def get_valid_cache_keys(cls):
1865 1865 """
1866 1866 Return opaque object with information of which caches still are valid
1867 1867 and can be used without checking for invalidation.
1868 1868 """
1869 1869 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1870 1870
1871 1871
1872 1872 class ChangesetComment(Base, BaseModel):
1873 1873 __tablename__ = 'changeset_comments'
1874 1874 __table_args__ = (
1875 1875 Index('cc_revision_idx', 'revision'),
1876 1876 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1877 1877 'mysql_charset': 'utf8'},
1878 1878 )
1879 1879 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1880 1880 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1881 1881 revision = Column('revision', String(40), nullable=True)
1882 1882 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1883 1883 line_no = Column('line_no', Unicode(10), nullable=True)
1884 1884 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1885 1885 f_path = Column('f_path', Unicode(1000), nullable=True)
1886 1886 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1887 1887 text = Column('text', UnicodeText(25000), nullable=False)
1888 1888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1889 1889 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1890 1890
1891 1891 author = relationship('User', lazy='joined')
1892 1892 repo = relationship('Repository')
1893 1893 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1894 1894 pull_request = relationship('PullRequest', lazy='joined')
1895 1895
1896 1896 @classmethod
1897 1897 def get_users(cls, revision=None, pull_request_id=None):
1898 1898 """
1899 1899 Returns user associated with this ChangesetComment. ie those
1900 1900 who actually commented
1901 1901
1902 1902 :param cls:
1903 1903 :param revision:
1904 1904 """
1905 1905 q = Session().query(User)\
1906 1906 .join(ChangesetComment.author)
1907 1907 if revision:
1908 1908 q = q.filter(cls.revision == revision)
1909 1909 elif pull_request_id:
1910 1910 q = q.filter(cls.pull_request_id == pull_request_id)
1911 1911 return q.all()
1912 1912
1913 1913
1914 1914 class ChangesetStatus(Base, BaseModel):
1915 1915 __tablename__ = 'changeset_statuses'
1916 1916 __table_args__ = (
1917 1917 Index('cs_revision_idx', 'revision'),
1918 1918 Index('cs_version_idx', 'version'),
1919 1919 UniqueConstraint('repo_id', 'revision', 'version'),
1920 1920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1921 1921 'mysql_charset': 'utf8'}
1922 1922 )
1923 1923 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1924 1924 STATUS_APPROVED = 'approved'
1925 1925 STATUS_REJECTED = 'rejected'
1926 1926 STATUS_UNDER_REVIEW = 'under_review'
1927 1927
1928 1928 STATUSES = [
1929 1929 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1930 1930 (STATUS_APPROVED, _("Approved")),
1931 1931 (STATUS_REJECTED, _("Rejected")),
1932 1932 (STATUS_UNDER_REVIEW, _("Under Review")),
1933 1933 ]
1934 1934
1935 1935 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1936 1936 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1937 1937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1938 1938 revision = Column('revision', String(40), nullable=False)
1939 1939 status = Column('status', String(128), nullable=False, default=DEFAULT)
1940 1940 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1941 1941 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1942 1942 version = Column('version', Integer(), nullable=False, default=0)
1943 1943 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1944 1944
1945 1945 author = relationship('User', lazy='joined')
1946 1946 repo = relationship('Repository')
1947 1947 comment = relationship('ChangesetComment', lazy='joined')
1948 1948 pull_request = relationship('PullRequest', lazy='joined')
1949 1949
1950 1950 def __unicode__(self):
1951 1951 return u"<%s('%s:%s')>" % (
1952 1952 self.__class__.__name__,
1953 1953 self.status, self.author
1954 1954 )
1955 1955
1956 1956 @classmethod
1957 1957 def get_status_lbl(cls, value):
1958 1958 return dict(cls.STATUSES).get(value)
1959 1959
1960 1960 @property
1961 1961 def status_lbl(self):
1962 1962 return ChangesetStatus.get_status_lbl(self.status)
1963 1963
1964 1964
1965 1965 class PullRequest(Base, BaseModel):
1966 1966 __tablename__ = 'pull_requests'
1967 1967 __table_args__ = (
1968 1968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1969 1969 'mysql_charset': 'utf8'},
1970 1970 )
1971 1971
1972 1972 STATUS_NEW = u'new'
1973 1973 STATUS_OPEN = u'open'
1974 1974 STATUS_CLOSED = u'closed'
1975 1975
1976 1976 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1977 1977 title = Column('title', Unicode(256), nullable=True)
1978 1978 description = Column('description', UnicodeText(10240), nullable=True)
1979 1979 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1980 1980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1981 1981 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1982 1982 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1983 1983 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1984 1984 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1985 1985 org_ref = Column('org_ref', Unicode(256), nullable=False)
1986 1986 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1987 1987 other_ref = Column('other_ref', Unicode(256), nullable=False)
1988 1988
1989 1989 @hybrid_property
1990 1990 def revisions(self):
1991 1991 return self._revisions.split(':')
1992 1992
1993 1993 @revisions.setter
1994 1994 def revisions(self, val):
1995 1995 self._revisions = ':'.join(val)
1996 1996
1997 1997 @property
1998 1998 def org_ref_parts(self):
1999 1999 return self.org_ref.split(':')
2000 2000
2001 2001 @property
2002 2002 def other_ref_parts(self):
2003 2003 return self.other_ref.split(':')
2004 2004
2005 2005 author = relationship('User', lazy='joined')
2006 2006 reviewers = relationship('PullRequestReviewers',
2007 2007 cascade="all, delete, delete-orphan")
2008 2008 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2009 2009 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2010 2010 statuses = relationship('ChangesetStatus')
2011 2011 comments = relationship('ChangesetComment',
2012 2012 cascade="all, delete, delete-orphan")
2013 2013
2014 2014 def is_closed(self):
2015 2015 return self.status == self.STATUS_CLOSED
2016 2016
2017 2017 @property
2018 2018 def last_review_status(self):
2019 2019 return self.statuses[-1].status if self.statuses else ''
2020 2020
2021 2021 def __json__(self):
2022 2022 return dict(
2023 2023 revisions=self.revisions
2024 2024 )
2025 2025
2026 2026
2027 2027 class PullRequestReviewers(Base, BaseModel):
2028 2028 __tablename__ = 'pull_request_reviewers'
2029 2029 __table_args__ = (
2030 2030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2031 2031 'mysql_charset': 'utf8'},
2032 2032 )
2033 2033
2034 2034 def __init__(self, user=None, pull_request=None):
2035 2035 self.user = user
2036 2036 self.pull_request = pull_request
2037 2037
2038 2038 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2039 2039 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2040 2040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2041 2041
2042 2042 user = relationship('User')
2043 2043 pull_request = relationship('PullRequest')
2044 2044
2045 2045
2046 2046 class Notification(Base, BaseModel):
2047 2047 __tablename__ = 'notifications'
2048 2048 __table_args__ = (
2049 2049 Index('notification_type_idx', 'type'),
2050 2050 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2051 2051 'mysql_charset': 'utf8'},
2052 2052 )
2053 2053
2054 2054 TYPE_CHANGESET_COMMENT = u'cs_comment'
2055 2055 TYPE_MESSAGE = u'message'
2056 2056 TYPE_MENTION = u'mention'
2057 2057 TYPE_REGISTRATION = u'registration'
2058 2058 TYPE_PULL_REQUEST = u'pull_request'
2059 2059 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2060 2060
2061 2061 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2062 2062 subject = Column('subject', Unicode(512), nullable=True)
2063 2063 body = Column('body', UnicodeText(50000), nullable=True)
2064 2064 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2065 2065 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2066 2066 type_ = Column('type', Unicode(256))
2067 2067
2068 2068 created_by_user = relationship('User')
2069 2069 notifications_to_users = relationship('UserNotification', lazy='joined',
2070 2070 cascade="all, delete, delete-orphan")
2071 2071
2072 2072 @property
2073 2073 def recipients(self):
2074 2074 return [x.user for x in UserNotification.query()\
2075 2075 .filter(UserNotification.notification == self)\
2076 2076 .order_by(UserNotification.user_id.asc()).all()]
2077 2077
2078 2078 @classmethod
2079 2079 def create(cls, created_by, subject, body, recipients, type_=None):
2080 2080 if type_ is None:
2081 2081 type_ = Notification.TYPE_MESSAGE
2082 2082
2083 2083 notification = cls()
2084 2084 notification.created_by_user = created_by
2085 2085 notification.subject = subject
2086 2086 notification.body = body
2087 2087 notification.type_ = type_
2088 2088 notification.created_on = datetime.datetime.now()
2089 2089
2090 2090 for u in recipients:
2091 2091 assoc = UserNotification()
2092 2092 assoc.notification = notification
2093 2093 u.notifications.append(assoc)
2094 2094 Session().add(notification)
2095 2095 return notification
2096 2096
2097 2097 @property
2098 2098 def description(self):
2099 2099 from rhodecode.model.notification import NotificationModel
2100 2100 return NotificationModel().make_description(self)
2101 2101
2102 2102
2103 2103 class UserNotification(Base, BaseModel):
2104 2104 __tablename__ = 'user_to_notification'
2105 2105 __table_args__ = (
2106 2106 UniqueConstraint('user_id', 'notification_id'),
2107 2107 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2108 2108 'mysql_charset': 'utf8'}
2109 2109 )
2110 2110 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2111 2111 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2112 2112 read = Column('read', Boolean, default=False)
2113 2113 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2114 2114
2115 2115 user = relationship('User', lazy="joined")
2116 2116 notification = relationship('Notification', lazy="joined",
2117 2117 order_by=lambda: Notification.created_on.desc(),)
2118 2118
2119 2119 def mark_as_read(self):
2120 2120 self.read = True
2121 2121 Session().add(self)
2122 2122
2123 2123
2124 2124 class Gist(Base, BaseModel):
2125 2125 __tablename__ = 'gists'
2126 2126 __table_args__ = (
2127 2127 Index('g_gist_access_id_idx', 'gist_access_id'),
2128 2128 Index('g_created_on_idx', 'created_on'),
2129 2129 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2130 2130 'mysql_charset': 'utf8'}
2131 2131 )
2132 2132 GIST_PUBLIC = u'public'
2133 2133 GIST_PRIVATE = u'private'
2134 2134
2135 2135 gist_id = Column('gist_id', Integer(), primary_key=True)
2136 2136 gist_access_id = Column('gist_access_id', UnicodeText(1024))
2137 2137 gist_description = Column('gist_description', UnicodeText(1024))
2138 2138 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2139 2139 gist_expires = Column('gist_expires', Float(), nullable=False)
2140 2140 gist_type = Column('gist_type', Unicode(128), nullable=False)
2141 2141 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2142 2142 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2143 2143
2144 2144 owner = relationship('User')
2145 2145
2146 2146 @classmethod
2147 2147 def get_or_404(cls, id_):
2148 2148 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2149 2149 if not res:
2150 2150 raise HTTPNotFound
2151 2151 return res
2152 2152
2153 2153 @classmethod
2154 2154 def get_by_access_id(cls, gist_access_id):
2155 2155 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2156 2156
2157 2157 def gist_url(self):
2158 2158 import rhodecode
2159 2159 alias_url = rhodecode.CONFIG.get('gist_alias_url')
2160 2160 if alias_url:
2161 2161 return alias_url.replace('{gistid}', self.gist_access_id)
2162 2162
2163 2163 from pylons import url
2164 return url('gist', id=self.gist_access_id, qualified=True)
2164 return url('gist', gist_id=self.gist_access_id, qualified=True)
2165 2165
2166 2166 @classmethod
2167 2167 def base_path(cls):
2168 2168 """
2169 2169 Returns base path when all gists are stored
2170 2170
2171 2171 :param cls:
2172 2172 """
2173 2173 from rhodecode.model.gist import GIST_STORE_LOC
2174 2174 q = Session().query(RhodeCodeUi)\
2175 2175 .filter(RhodeCodeUi.ui_key == URL_SEP)
2176 2176 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2177 2177 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2178 2178
2179 2179 def get_api_data(self):
2180 2180 """
2181 2181 Common function for generating gist related data for API
2182 2182 """
2183 2183 gist = self
2184 2184 data = dict(
2185 2185 gist_id=gist.gist_id,
2186 2186 type=gist.gist_type,
2187 2187 access_id=gist.gist_access_id,
2188 2188 description=gist.gist_description,
2189 2189 url=gist.gist_url(),
2190 2190 expires=gist.gist_expires,
2191 2191 created_on=gist.created_on,
2192 2192 )
2193 2193 return data
2194 2194
2195 2195 def __json__(self):
2196 2196 data = dict(
2197 2197 )
2198 2198 data.update(self.get_api_data())
2199 2199 return data
2200 2200 ## SCM functions
2201 2201
2202 2202 @property
2203 2203 def scm_instance(self):
2204 2204 from rhodecode.lib.vcs import get_repo
2205 2205 base_path = self.base_path()
2206 2206 return get_repo(os.path.join(*map(safe_str,
2207 2207 [base_path, self.gist_access_id])))
2208 2208
2209 2209
2210 2210 class DbMigrateVersion(Base, BaseModel):
2211 2211 __tablename__ = 'db_migrate_version'
2212 2212 __table_args__ = (
2213 2213 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2214 2214 'mysql_charset': 'utf8'},
2215 2215 )
2216 2216 repository_id = Column('repository_id', String(250), primary_key=True)
2217 2217 repository_path = Column('repository_path', Text)
2218 2218 version = Column('version', Integer)
@@ -1,71 +1,71 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Gists')} &middot; ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 %if c.show_private:
10 10 ${_('Private Gists for user %s') % c.rhodecode_user.username}
11 11 %elif c.show_public:
12 12 ${_('Public Gists for user %s') % c.rhodecode_user.username}
13 13 %else:
14 14 ${_('Public Gists')}
15 15 %endif
16 16 - ${c.gists_pager.item_count}
17 17 </%def>
18 18
19 19 <%def name="page_nav()">
20 20 ${self.menu('gists')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <!-- box / title -->
26 26 <div class="title">
27 27 ${self.breadcrumbs()}
28 28 %if c.rhodecode_user.username != 'default':
29 29 <ul class="links">
30 30 <li>
31 31 <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span>
32 32 </li>
33 33 </ul>
34 34 %endif
35 35 </div>
36 36 %if c.gists_pager.item_count>0:
37 37 % for gist in c.gists_pager:
38 38 <div class="gist-item" style="padding:10px 20px 10px 15px">
39 39
40 40 <div class="gravatar">
41 41 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(gist.owner.full_contact),28)}"/>
42 42 </div>
43 43 <div title="${gist.owner.full_contact}" class="user" style="font-size: 16px">
44 44 <b>${h.person(gist.owner.full_contact)}</b> /
45 <b><a href="${h.url('gist',id=gist.gist_access_id)}">gist:${gist.gist_access_id}</a></b>
45 <b><a href="${h.url('gist',gist_id=gist.gist_access_id)}">gist:${gist.gist_access_id}</a></b>
46 46 </div>
47 47 <div style="padding: 4px 0px 0px 0px">
48 48 ${_('Created')} ${h.age(gist.created_on)} /
49 49 <span style="color: #AAA">
50 50 %if gist.gist_expires == -1:
51 51 ${_('Expires')}: ${_('never')}
52 52 %else:
53 53 ${_('Expires')}: ${h.age(h.time_to_datetime(gist.gist_expires))}
54 54 %endif
55 55 </span>
56 56 </div>
57 57
58 58 <div style="border:0px;padding:10px 0px 0px 40px;color:#AAA">${gist.gist_description}</div>
59 59 </div>
60 60 % endfor
61 61
62 62 <div class="notification-paginator">
63 63 <div class="pagination-wh pagination-left">
64 64 ${c.gists_pager.pager('$link_previous ~2~ $link_next')}
65 65 </div>
66 66 </div>
67 67 %else:
68 68 <div class="table">${_('There are no gists yet')}</div>
69 69 %endif
70 70 </div>
71 71 </%def>
@@ -1,92 +1,92 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('gist')}:${c.gist.gist_access_id} &middot; ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${_('Gist')} &middot; gist:${c.gist.gist_access_id}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('gists')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 %if c.rhodecode_user.username != 'default':
22 22 <ul class="links">
23 23 <li>
24 24 <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span>
25 25 </li>
26 26 </ul>
27 27 %endif
28 28 </div>
29 29 <div class="table">
30 30 <div id="files_data">
31 31 <div id="body" class="codeblock">
32 32 <div class="code-header">
33 33 <div class="stats">
34 34 <div class="left" style="margin: -4px 0px 0px 0px">
35 35 %if c.gist.gist_type == 'public':
36 36 <div class="ui-btn green badge">${_('Public gist')}</div>
37 37 %else:
38 38 <div class="ui-btn yellow badge">${_('Private gist')}</div>
39 39 %endif
40 40 </div>
41 41 <div class="left item ${'' if c.gist.gist_description else 'last'}" style="color: #AAA">
42 42 %if c.gist.gist_expires == -1:
43 43 ${_('Expires')}: ${_('never')}
44 44 %else:
45 45 ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
46 46 %endif
47 47 </div>
48 48 <div class="left item last">
49 49 ${c.gist.gist_description}
50 50 </div>
51 51 <div class="buttons">
52 52 ## only owner should see that
53 53 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
54 54 ##${h.link_to(_('Edit'),h.url(''),class_="ui-btn")}
55 ${h.form(url('gist', id=c.gist.gist_id),method='delete')}
55 ${h.form(url('gist', gist_id=c.gist.gist_id),method='delete')}
56 56 ${h.submit('remove_gist', _('Delete'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this gist')+"');")}
57 57 ${h.end_form()}
58 58 %endif
59 59 </div>
60 60 </div>
61 61
62 62 <div class="author">
63 63 <div class="gravatar">
64 64 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.file_changeset.author),16)}"/>
65 65 </div>
66 66 <div title="${c.file_changeset.author}" class="user">${h.person(c.file_changeset.author)} - ${_('created')} ${h.age(c.file_changeset.date)}</div>
67 67 </div>
68 68 <div class="commit">${h.urlify_commit(c.file_changeset.message,c.repo_name)}</div>
69 69 </div>
70 70 </div>
71 71
72 72 ## iterate over the files
73 73 % for file in c.files:
74 74 <div style="border: 1px solid #EEE;margin-top:20px">
75 75 <div id="${h.FID('G', file.path)}" class="stats" style="border-bottom: 1px solid #DDD;padding: 8px 14px;">
76 76 <a href="${c.gist.gist_url()}">ΒΆ</a>
77 77 <b style="margin:0px 0px 0px 4px">${file.path}</b>
78 78 ##<div class="buttons">
79 79 ## ${h.link_to(_('Show as raw'),h.url(''),class_="ui-btn")}
80 80 ##</div>
81 81 </div>
82 82 <div class="code-body">
83 83 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
84 84 </div>
85 85 </div>
86 86 %endfor
87 87 </div>
88 88 </div>
89 89
90 90
91 91 </div>
92 92 </%def>
@@ -1,160 +1,160 b''
1 1 import datetime
2 2
3 3 from rhodecode.tests import *
4 4 from rhodecode.model.gist import GistModel
5 5 from rhodecode.model.meta import Session
6 6 from rhodecode.model.db import User, Gist
7 7
8 8
9 9 def _create_gist(f_name, content='some gist', lifetime=-1,
10 10 description='gist-desc', gist_type='public',
11 11 owner=TEST_USER_ADMIN_LOGIN):
12 12 gist_mapping = {
13 13 f_name: {'content': content}
14 14 }
15 15 user = User.get_by_username(owner)
16 16 gist = GistModel().create(description, owner=user,
17 17 gist_mapping=gist_mapping, gist_type=gist_type,
18 18 lifetime=lifetime)
19 19 Session().commit()
20 20 return gist
21 21
22 22
23 23 class TestGistsController(TestController):
24 24
25 25 def tearDown(self):
26 26 for g in Gist.get_all():
27 27 GistModel().delete(g)
28 28 Session().commit()
29 29
30 30 def test_index(self):
31 31 self.log_user()
32 32 response = self.app.get(url('gists'))
33 33 # Test response...
34 34 response.mustcontain('There are no gists yet')
35 35
36 36 g1 = _create_gist('gist1').gist_access_id
37 37 g2 = _create_gist('gist2', lifetime=1400).gist_access_id
38 38 g3 = _create_gist('gist3', description='gist3-desc').gist_access_id
39 39 g4 = _create_gist('gist4', gist_type='private').gist_access_id
40 40 response = self.app.get(url('gists'))
41 41 # Test response...
42 42 response.mustcontain('gist:%s' % g1)
43 43 response.mustcontain('gist:%s' % g2)
44 44 response.mustcontain('Expires: in 23 hours') # we don't care about the end
45 45 response.mustcontain('gist:%s' % g3)
46 46 response.mustcontain('gist3-desc')
47 47 response.mustcontain(no=['gist:%s' % g4])
48 48
49 49 def test_index_private_gists(self):
50 50 self.log_user()
51 51 gist = _create_gist('gist5', gist_type='private')
52 52 response = self.app.get(url('gists', private=1))
53 53 # Test response...
54 54
55 55 #and privates
56 56 response.mustcontain('gist:%s' % gist.gist_access_id)
57 57
58 58 def test_create_missing_description(self):
59 59 self.log_user()
60 60 response = self.app.post(url('gists'),
61 61 params={'lifetime': -1}, status=200)
62 62
63 63 response.mustcontain('Missing value')
64 64
65 65 def test_create(self):
66 66 self.log_user()
67 67 response = self.app.post(url('gists'),
68 68 params={'lifetime': -1,
69 69 'content': 'gist test',
70 70 'filename': 'foo',
71 71 'public': 'public'},
72 72 status=302)
73 73 response = response.follow()
74 74 response.mustcontain('added file: foo')
75 75 response.mustcontain('gist test')
76 76 response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
77 77
78 78 def test_create_with_path_with_dirs(self):
79 79 self.log_user()
80 80 response = self.app.post(url('gists'),
81 81 params={'lifetime': -1,
82 82 'content': 'gist test',
83 83 'filename': '/home/foo',
84 84 'public': 'public'},
85 85 status=200)
86 86 response.mustcontain('Filename cannot be inside a directory')
87 87
88 88 def test_access_expired_gist(self):
89 89 self.log_user()
90 90 gist = _create_gist('never-see-me')
91 91 gist.gist_expires = 0 # 1970
92 92 Session().add(gist)
93 93 Session().commit()
94 94
95 response = self.app.get(url('gist', id=gist.gist_access_id), status=404)
95 response = self.app.get(url('gist', gist_id=gist.gist_access_id), status=404)
96 96
97 97 def test_create_private(self):
98 98 self.log_user()
99 99 response = self.app.post(url('gists'),
100 100 params={'lifetime': -1,
101 101 'content': 'private gist test',
102 102 'filename': 'private-foo',
103 103 'private': 'private'},
104 104 status=302)
105 105 response = response.follow()
106 106 response.mustcontain('added file: private-foo<')
107 107 response.mustcontain('private gist test')
108 108 response.mustcontain('<div class="ui-btn yellow badge">Private gist</div>')
109 109
110 110 def test_create_with_description(self):
111 111 self.log_user()
112 112 response = self.app.post(url('gists'),
113 113 params={'lifetime': -1,
114 114 'content': 'gist test',
115 115 'filename': 'foo-desc',
116 116 'description': 'gist-desc',
117 117 'public': 'public'},
118 118 status=302)
119 119 response = response.follow()
120 120 response.mustcontain('added file: foo-desc')
121 121 response.mustcontain('gist test')
122 122 response.mustcontain('gist-desc')
123 123 response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
124 124
125 125 def test_new(self):
126 126 self.log_user()
127 127 response = self.app.get(url('new_gist'))
128 128
129 129 def test_update(self):
130 130 self.skipTest('not implemented')
131 response = self.app.put(url('gist', id=1))
131 response = self.app.put(url('gist', gist_id=1))
132 132
133 133 def test_delete(self):
134 134 self.log_user()
135 135 gist = _create_gist('delete-me')
136 response = self.app.delete(url('gist', id=gist.gist_id))
136 response = self.app.delete(url('gist', gist_id=gist.gist_id))
137 137 self.checkSessionFlash(response, 'Deleted gist %s' % gist.gist_id)
138 138
139 139 def test_delete_normal_user_his_gist(self):
140 140 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
141 141 gist = _create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
142 response = self.app.delete(url('gist', id=gist.gist_id))
142 response = self.app.delete(url('gist', gist_id=gist.gist_id))
143 143 self.checkSessionFlash(response, 'Deleted gist %s' % gist.gist_id)
144 144
145 145 def test_delete_normal_user_not_his_own_gist(self):
146 146 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
147 147 gist = _create_gist('delete-me')
148 response = self.app.delete(url('gist', id=gist.gist_id), status=403)
148 response = self.app.delete(url('gist', gist_id=gist.gist_id), status=403)
149 149
150 150 def test_show(self):
151 151 gist = _create_gist('gist-show-me')
152 response = self.app.get(url('gist', id=gist.gist_access_id))
152 response = self.app.get(url('gist', gist_id=gist.gist_access_id))
153 153 response.mustcontain('added file: gist-show-me<')
154 154 response.mustcontain('test_admin (RhodeCode Admin) - created')
155 155 response.mustcontain('gist-desc')
156 156 response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
157 157
158 158 def test_edit(self):
159 159 self.skipTest('not implemented')
160 response = self.app.get(url('edit_gist', id=1))
160 response = self.app.get(url('edit_gist', gist_id=1))
General Comments 0
You need to be logged in to leave comments. Login now