##// END OF EJS Templates
Cleanup leftover print statements
marcink -
r3024:1361ddff beta
parent child Browse files
Show More
@@ -1,812 +1,811 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.api
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 API controller for RhodeCode
7 7
8 8 :created_on: Aug 20, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-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
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import traceback
29 29 import logging
30 30
31 31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
33 33 HasPermissionAnyDecorator, PasswordGenerator, AuthUser
34 34 from rhodecode.lib.utils import map_groups, repo2db_mapper
35 35 from rhodecode.model.meta import Session
36 36 from rhodecode.model.scm import ScmModel
37 37 from rhodecode.model.repo import RepoModel
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.users_group import UsersGroupModel
40 40 from rhodecode.model.permission import PermissionModel
41 41 from rhodecode.model.db import Repository
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class Optional(object):
47 47 """
48 48 Defines an optional parameter::
49 49
50 50 param = param.getval() if isinstance(param, Optional) else param
51 51 param = param() if isinstance(param, Optional) else param
52 52
53 53 is equivalent of::
54 54
55 55 param = Optional.extract(param)
56 56
57 57 """
58 58 def __init__(self, type_):
59 59 self.type_ = type_
60 60
61 61 def __repr__(self):
62 62 return '<Optional:%s>' % self.type_.__repr__()
63 63
64 64 def __call__(self):
65 65 return self.getval()
66 66
67 67 def getval(self):
68 68 """
69 69 returns value from this Optional instance
70 70 """
71 71 return self.type_
72 72
73 73 @classmethod
74 74 def extract(cls, val):
75 75 if isinstance(val, cls):
76 76 return val.getval()
77 77 return val
78 78
79 79
80 80 def get_user_or_error(userid):
81 81 """
82 82 Get user by id or name or return JsonRPCError if not found
83 83
84 84 :param userid:
85 85 """
86 86 user = UserModel().get_user(userid)
87 87 if user is None:
88 88 raise JSONRPCError("user `%s` does not exist" % userid)
89 89 return user
90 90
91 91
92 92 def get_repo_or_error(repoid):
93 93 """
94 94 Get repo by id or name or return JsonRPCError if not found
95 95
96 96 :param userid:
97 97 """
98 98 repo = RepoModel().get_repo(repoid)
99 99 if repo is None:
100 100 raise JSONRPCError('repository `%s` does not exist' % (repoid))
101 101 return repo
102 102
103 103
104 104 def get_users_group_or_error(usersgroupid):
105 105 """
106 106 Get users group by id or name or return JsonRPCError if not found
107 107
108 108 :param userid:
109 109 """
110 110 users_group = UsersGroupModel().get_group(usersgroupid)
111 111 if users_group is None:
112 112 raise JSONRPCError('users group `%s` does not exist' % usersgroupid)
113 113 return users_group
114 114
115 115
116 116 def get_perm_or_error(permid):
117 117 """
118 118 Get permission by id or name or return JsonRPCError if not found
119 119
120 120 :param userid:
121 121 """
122 122 perm = PermissionModel().get_permission_by_name(permid)
123 123 if perm is None:
124 124 raise JSONRPCError('permission `%s` does not exist' % (permid))
125 125 return perm
126 126
127 127
128 128 class ApiController(JSONRPCController):
129 129 """
130 130 API Controller
131 131
132 132
133 133 Each method needs to have USER as argument this is then based on given
134 134 API_KEY propagated as instance of user object
135 135
136 136 Preferably this should be first argument also
137 137
138 138
139 139 Each function should also **raise** JSONRPCError for any
140 140 errors that happens
141 141
142 142 """
143 143
144 144 @HasPermissionAllDecorator('hg.admin')
145 145 def pull(self, apiuser, repoid):
146 146 """
147 147 Dispatch pull action on given repo
148 148
149 149 :param apiuser:
150 150 :param repoid:
151 151 """
152 152
153 153 repo = get_repo_or_error(repoid)
154 154
155 155 try:
156 156 ScmModel().pull_changes(repo.repo_name,
157 157 self.rhodecode_user.username)
158 158 return 'Pulled from `%s`' % repo.repo_name
159 159 except Exception:
160 160 log.error(traceback.format_exc())
161 161 raise JSONRPCError(
162 162 'Unable to pull changes from `%s`' % repo.repo_name
163 163 )
164 164
165 165 @HasPermissionAllDecorator('hg.admin')
166 166 def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
167 167 """
168 168 Dispatch rescan repositories action. If remove_obsolete is set
169 169 than also delete repos that are in database but not in the filesystem.
170 170 aka "clean zombies"
171 171
172 172 :param apiuser:
173 173 :param remove_obsolete:
174 174 """
175 175
176 176 try:
177 177 rm_obsolete = Optional.extract(remove_obsolete)
178 178 added, removed = repo2db_mapper(ScmModel().repo_scan(),
179 179 remove_obsolete=rm_obsolete)
180 180 return {'added': added, 'removed': removed}
181 181 except Exception:
182 182 log.error(traceback.format_exc())
183 183 raise JSONRPCError(
184 184 'Error occurred during rescan repositories action'
185 185 )
186 186
187 187 @HasPermissionAllDecorator('hg.admin')
188 188 def lock(self, apiuser, repoid, userid, locked):
189 189 """
190 190 Set locking state on particular repository by given user
191 191
192 192 :param apiuser:
193 193 :param repoid:
194 194 :param userid:
195 195 :param locked:
196 196 """
197 197 repo = get_repo_or_error(repoid)
198 198 user = get_user_or_error(userid)
199 199 locked = bool(locked)
200 200 try:
201 201 if locked:
202 202 Repository.lock(repo, user.user_id)
203 203 else:
204 204 Repository.unlock(repo)
205 205
206 206 return ('User `%s` set lock state for repo `%s` to `%s`'
207 207 % (user.username, repo.repo_name, locked))
208 208 except Exception:
209 209 log.error(traceback.format_exc())
210 210 raise JSONRPCError(
211 211 'Error occurred locking repository `%s`' % repo.repo_name
212 212 )
213 213
214 214 @HasPermissionAllDecorator('hg.admin')
215 215 def get_user(self, apiuser, userid):
216 216 """"
217 217 Get a user by username
218 218
219 219 :param apiuser:
220 220 :param userid:
221 221 """
222 222
223 223 user = get_user_or_error(userid)
224 224 data = user.get_api_data()
225 225 data['permissions'] = AuthUser(user_id=user.user_id).permissions
226 226 return data
227 227
228 228 @HasPermissionAllDecorator('hg.admin')
229 229 def get_users(self, apiuser):
230 230 """"
231 231 Get all users
232 232
233 233 :param apiuser:
234 234 """
235 235
236 236 result = []
237 237 for user in UserModel().get_all():
238 238 result.append(user.get_api_data())
239 239 return result
240 240
241 241 @HasPermissionAllDecorator('hg.admin')
242 242 def create_user(self, apiuser, username, email, password,
243 243 firstname=Optional(None), lastname=Optional(None),
244 244 active=Optional(True), admin=Optional(False),
245 245 ldap_dn=Optional(None)):
246 246 """
247 247 Create new user
248 248
249 249 :param apiuser:
250 250 :param username:
251 251 :param email:
252 252 :param password:
253 253 :param firstname:
254 254 :param lastname:
255 255 :param active:
256 256 :param admin:
257 257 :param ldap_dn:
258 258 """
259 259
260 260 if UserModel().get_by_username(username):
261 261 raise JSONRPCError("user `%s` already exist" % username)
262 262
263 263 if UserModel().get_by_email(email, case_insensitive=True):
264 264 raise JSONRPCError("email `%s` already exist" % email)
265 265
266 266 if Optional.extract(ldap_dn):
267 267 # generate temporary password if ldap_dn
268 268 password = PasswordGenerator().gen_password(length=8)
269 269
270 270 try:
271 271 user = UserModel().create_or_update(
272 272 username=Optional.extract(username),
273 273 password=Optional.extract(password),
274 274 email=Optional.extract(email),
275 275 firstname=Optional.extract(firstname),
276 276 lastname=Optional.extract(lastname),
277 277 active=Optional.extract(active),
278 278 admin=Optional.extract(admin),
279 279 ldap_dn=Optional.extract(ldap_dn)
280 280 )
281 281 Session().commit()
282 282 return dict(
283 283 msg='created new user `%s`' % username,
284 284 user=user.get_api_data()
285 285 )
286 286 except Exception:
287 287 log.error(traceback.format_exc())
288 288 raise JSONRPCError('failed to create user `%s`' % username)
289 289
290 290 @HasPermissionAllDecorator('hg.admin')
291 291 def update_user(self, apiuser, userid, username=Optional(None),
292 292 email=Optional(None), firstname=Optional(None),
293 293 lastname=Optional(None), active=Optional(None),
294 294 admin=Optional(None), ldap_dn=Optional(None),
295 295 password=Optional(None)):
296 296 """
297 297 Updates given user
298 298
299 299 :param apiuser:
300 300 :param userid:
301 301 :param username:
302 302 :param email:
303 303 :param firstname:
304 304 :param lastname:
305 305 :param active:
306 306 :param admin:
307 307 :param ldap_dn:
308 308 :param password:
309 309 """
310 310
311 311 user = get_user_or_error(userid)
312 312
313 313 # call function and store only updated arguments
314 314 updates = {}
315 315
316 316 def store_update(attr, name):
317 317 if not isinstance(attr, Optional):
318 318 updates[name] = attr
319 319
320 320 try:
321 321
322 322 store_update(username, 'username')
323 323 store_update(password, 'password')
324 324 store_update(email, 'email')
325 325 store_update(firstname, 'name')
326 326 store_update(lastname, 'lastname')
327 327 store_update(active, 'active')
328 328 store_update(admin, 'admin')
329 329 store_update(ldap_dn, 'ldap_dn')
330 330
331 331 user = UserModel().update_user(user, **updates)
332 332 Session().commit()
333 333 return dict(
334 334 msg='updated user ID:%s %s' % (user.user_id, user.username),
335 335 user=user.get_api_data()
336 336 )
337 337 except Exception:
338 338 log.error(traceback.format_exc())
339 339 raise JSONRPCError('failed to update user `%s`' % userid)
340 340
341 341 @HasPermissionAllDecorator('hg.admin')
342 342 def delete_user(self, apiuser, userid):
343 343 """"
344 344 Deletes an user
345 345
346 346 :param apiuser:
347 347 :param userid:
348 348 """
349 349 user = get_user_or_error(userid)
350 350
351 351 try:
352 352 UserModel().delete(userid)
353 353 Session().commit()
354 354 return dict(
355 355 msg='deleted user ID:%s %s' % (user.user_id, user.username),
356 356 user=None
357 357 )
358 358 except Exception:
359 359 log.error(traceback.format_exc())
360 360 raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
361 361 user.username))
362 362
363 363 @HasPermissionAllDecorator('hg.admin')
364 364 def get_users_group(self, apiuser, usersgroupid):
365 365 """"
366 366 Get users group by name or id
367 367
368 368 :param apiuser:
369 369 :param usersgroupid:
370 370 """
371 371 users_group = get_users_group_or_error(usersgroupid)
372 372
373 373 data = users_group.get_api_data()
374 374
375 375 members = []
376 376 for user in users_group.members:
377 377 user = user.user
378 378 members.append(user.get_api_data())
379 379 data['members'] = members
380 380 return data
381 381
382 382 @HasPermissionAllDecorator('hg.admin')
383 383 def get_users_groups(self, apiuser):
384 384 """"
385 385 Get all users groups
386 386
387 387 :param apiuser:
388 388 """
389 389
390 390 result = []
391 391 for users_group in UsersGroupModel().get_all():
392 392 result.append(users_group.get_api_data())
393 393 return result
394 394
395 395 @HasPermissionAllDecorator('hg.admin')
396 396 def create_users_group(self, apiuser, group_name, active=Optional(True)):
397 397 """
398 398 Creates an new usergroup
399 399
400 400 :param apiuser:
401 401 :param group_name:
402 402 :param active:
403 403 """
404 404
405 405 if UsersGroupModel().get_by_name(group_name):
406 406 raise JSONRPCError("users group `%s` already exist" % group_name)
407 407
408 408 try:
409 409 active = Optional.extract(active)
410 410 ug = UsersGroupModel().create(name=group_name, active=active)
411 411 Session().commit()
412 412 return dict(
413 413 msg='created new users group `%s`' % group_name,
414 414 users_group=ug.get_api_data()
415 415 )
416 416 except Exception:
417 417 log.error(traceback.format_exc())
418 418 raise JSONRPCError('failed to create group `%s`' % group_name)
419 419
420 420 @HasPermissionAllDecorator('hg.admin')
421 421 def add_user_to_users_group(self, apiuser, usersgroupid, userid):
422 422 """"
423 423 Add a user to a users group
424 424
425 425 :param apiuser:
426 426 :param usersgroupid:
427 427 :param userid:
428 428 """
429 429 user = get_user_or_error(userid)
430 430 users_group = get_users_group_or_error(usersgroupid)
431 431
432 432 try:
433 433 ugm = UsersGroupModel().add_user_to_group(users_group, user)
434 434 success = True if ugm != True else False
435 435 msg = 'added member `%s` to users group `%s`' % (
436 436 user.username, users_group.users_group_name
437 437 )
438 438 msg = msg if success else 'User is already in that group'
439 439 Session().commit()
440 440
441 441 return dict(
442 442 success=success,
443 443 msg=msg
444 444 )
445 445 except Exception:
446 446 log.error(traceback.format_exc())
447 447 raise JSONRPCError(
448 448 'failed to add member to users group `%s`' % (
449 449 users_group.users_group_name
450 450 )
451 451 )
452 452
453 453 @HasPermissionAllDecorator('hg.admin')
454 454 def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
455 455 """
456 456 Remove user from a group
457 457
458 458 :param apiuser:
459 459 :param usersgroupid:
460 460 :param userid:
461 461 """
462 462 user = get_user_or_error(userid)
463 463 users_group = get_users_group_or_error(usersgroupid)
464 464
465 465 try:
466 466 success = UsersGroupModel().remove_user_from_group(users_group,
467 467 user)
468 468 msg = 'removed member `%s` from users group `%s`' % (
469 469 user.username, users_group.users_group_name
470 470 )
471 471 msg = msg if success else "User wasn't in group"
472 472 Session().commit()
473 473 return dict(success=success, msg=msg)
474 474 except Exception:
475 475 log.error(traceback.format_exc())
476 476 raise JSONRPCError(
477 477 'failed to remove member from users group `%s`' % (
478 478 users_group.users_group_name
479 479 )
480 480 )
481 481
482 482 @HasPermissionAnyDecorator('hg.admin')
483 483 def get_repo(self, apiuser, repoid):
484 484 """"
485 485 Get repository by name
486 486
487 487 :param apiuser:
488 488 :param repoid:
489 489 """
490 490 repo = get_repo_or_error(repoid)
491 491
492 492 members = []
493 493 for user in repo.repo_to_perm:
494 494 perm = user.permission.permission_name
495 495 user = user.user
496 496 user_data = user.get_api_data()
497 497 user_data['type'] = "user"
498 498 user_data['permission'] = perm
499 499 members.append(user_data)
500 500
501 501 for users_group in repo.users_group_to_perm:
502 502 perm = users_group.permission.permission_name
503 503 users_group = users_group.users_group
504 504 users_group_data = users_group.get_api_data()
505 505 users_group_data['type'] = "users_group"
506 506 users_group_data['permission'] = perm
507 507 members.append(users_group_data)
508 508
509 509 data = repo.get_api_data()
510 510 data['members'] = members
511 511 return data
512 512
513 513 @HasPermissionAnyDecorator('hg.admin')
514 514 def get_repos(self, apiuser):
515 515 """"
516 516 Get all repositories
517 517
518 518 :param apiuser:
519 519 """
520 520
521 521 result = []
522 522 for repo in RepoModel().get_all():
523 523 result.append(repo.get_api_data())
524 524 return result
525 525
526 526 @HasPermissionAnyDecorator('hg.admin')
527 527 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
528 528 ret_type='all'):
529 529 """
530 530 returns a list of nodes and it's children
531 531 for a given path at given revision. It's possible to specify ret_type
532 532 to show only files or dirs
533 533
534 534 :param apiuser:
535 535 :param repoid: name or id of repository
536 536 :param revision: revision for which listing should be done
537 537 :param root_path: path from which start displaying
538 538 :param ret_type: return type 'all|files|dirs' nodes
539 539 """
540 540 repo = get_repo_or_error(repoid)
541 541 try:
542 542 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
543 543 flat=False)
544 544 _map = {
545 545 'all': _d + _f,
546 546 'files': _f,
547 547 'dirs': _d,
548 548 }
549 549 return _map[ret_type]
550 550 except KeyError:
551 551 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
552 552 except Exception:
553 553 log.error(traceback.format_exc())
554 554 raise JSONRPCError(
555 555 'failed to get repo: `%s` nodes' % repo.repo_name
556 556 )
557 557
558 558 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
559 559 def create_repo(self, apiuser, repo_name, owner, repo_type,
560 560 description=Optional(''), private=Optional(False),
561 561 clone_uri=Optional(None), landing_rev=Optional('tip')):
562 562 """
563 563 Create repository, if clone_url is given it makes a remote clone
564 564 if repo_name is withina group name the groups will be created
565 565 automatically if they aren't present
566 566
567 567 :param apiuser:
568 568 :param repo_name:
569 569 :param onwer:
570 570 :param repo_type:
571 571 :param description:
572 572 :param private:
573 573 :param clone_uri:
574 574 :param landing_rev:
575 575 """
576 576 owner = get_user_or_error(owner)
577 577
578 578 if RepoModel().get_by_repo_name(repo_name):
579 579 raise JSONRPCError("repo `%s` already exist" % repo_name)
580 580
581 581 private = Optional.extract(private)
582 582 clone_uri = Optional.extract(clone_uri)
583 583 description = Optional.extract(description)
584 584 landing_rev = Optional.extract(landing_rev)
585 585
586 586 try:
587 587 # create structure of groups and return the last group
588 588 group = map_groups(repo_name)
589 589
590 590 repo = RepoModel().create_repo(
591 591 repo_name=repo_name,
592 592 repo_type=repo_type,
593 593 description=description,
594 594 owner=owner,
595 595 private=private,
596 596 clone_uri=clone_uri,
597 597 repos_group=group,
598 598 landing_rev=landing_rev,
599 599 )
600 600
601 601 Session().commit()
602 602
603 603 return dict(
604 604 msg="Created new repository `%s`" % (repo.repo_name),
605 605 repo=repo.get_api_data()
606 606 )
607 607
608 608 except Exception:
609 609 log.error(traceback.format_exc())
610 610 raise JSONRPCError('failed to create repository `%s`' % repo_name)
611 611
612 612 @HasPermissionAnyDecorator('hg.admin')
613 613 def fork_repo(self, apiuser, repoid, fork_name, owner,
614 614 description=Optional(''), copy_permissions=Optional(False),
615 615 private=Optional(False), landing_rev=Optional('tip')):
616 616 repo = get_repo_or_error(repoid)
617 617 repo_name = repo.repo_name
618 618 owner = get_user_or_error(owner)
619 619
620 620 _repo = RepoModel().get_by_repo_name(fork_name)
621 621 if _repo:
622 622 type_ = 'fork' if _repo.fork else 'repo'
623 623 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
624 624
625 625 try:
626 626 # create structure of groups and return the last group
627 627 group = map_groups(fork_name)
628 628
629 629 form_data = dict(
630 630 repo_name=fork_name,
631 631 repo_name_full=fork_name,
632 632 repo_group=group,
633 633 repo_type=repo.repo_type,
634 634 description=Optional.extract(description),
635 635 private=Optional.extract(private),
636 636 copy_permissions=Optional.extract(copy_permissions),
637 637 landing_rev=Optional.extract(landing_rev),
638 638 update_after_clone=False,
639 639 fork_parent_id=repo.repo_id,
640 640 )
641 641 RepoModel().create_fork(form_data, cur_user=owner)
642 642 return dict(
643 643 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
644 644 fork_name),
645 645 success=True # cannot return the repo data here since fork
646 646 # cann be done async
647 647 )
648 648 except Exception:
649 649 log.error(traceback.format_exc())
650 650 raise JSONRPCError(
651 651 'failed to fork repository `%s` as `%s`' % (repo_name,
652 652 fork_name)
653 653 )
654 654
655 655 @HasPermissionAnyDecorator('hg.admin')
656 656 def delete_repo(self, apiuser, repoid):
657 657 """
658 658 Deletes a given repository
659 659
660 660 :param apiuser:
661 661 :param repoid:
662 662 """
663 663 repo = get_repo_or_error(repoid)
664 664
665 665 try:
666 666 RepoModel().delete(repo)
667 667 Session().commit()
668 668 return dict(
669 669 msg='Deleted repository `%s`' % repo.repo_name,
670 670 success=True
671 671 )
672 672 except Exception:
673 673 log.error(traceback.format_exc())
674 674 raise JSONRPCError(
675 675 'failed to delete repository `%s`' % repo.repo_name
676 676 )
677 677
678 678 @HasPermissionAnyDecorator('hg.admin')
679 679 def grant_user_permission(self, apiuser, repoid, userid, perm):
680 680 """
681 681 Grant permission for user on given repository, or update existing one
682 682 if found
683 683
684 684 :param repoid:
685 685 :param userid:
686 686 :param perm:
687 687 """
688 688 repo = get_repo_or_error(repoid)
689 689 user = get_user_or_error(userid)
690 690 perm = get_perm_or_error(perm)
691 691
692 692 try:
693 693
694 694 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
695 695
696 696 Session().commit()
697 697 return dict(
698 698 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
699 699 perm.permission_name, user.username, repo.repo_name
700 700 ),
701 701 success=True
702 702 )
703 703 except Exception:
704 704 log.error(traceback.format_exc())
705 705 raise JSONRPCError(
706 706 'failed to edit permission for user: `%s` in repo: `%s`' % (
707 707 userid, repoid
708 708 )
709 709 )
710 710
711 711 @HasPermissionAnyDecorator('hg.admin')
712 712 def revoke_user_permission(self, apiuser, repoid, userid):
713 713 """
714 714 Revoke permission for user on given repository
715 715
716 716 :param apiuser:
717 717 :param repoid:
718 718 :param userid:
719 719 """
720 720
721 721 repo = get_repo_or_error(repoid)
722 722 user = get_user_or_error(userid)
723 723 try:
724 724
725 725 RepoModel().revoke_user_permission(repo=repo, user=user)
726 726
727 727 Session().commit()
728 728 return dict(
729 729 msg='Revoked perm for user: `%s` in repo: `%s`' % (
730 730 user.username, repo.repo_name
731 731 ),
732 732 success=True
733 733 )
734 734 except Exception:
735 735 log.error(traceback.format_exc())
736 736 raise JSONRPCError(
737 737 'failed to edit permission for user: `%s` in repo: `%s`' % (
738 738 userid, repoid
739 739 )
740 740 )
741 741
742 742 @HasPermissionAnyDecorator('hg.admin')
743 743 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
744 744 perm):
745 745 """
746 746 Grant permission for users group on given repository, or update
747 747 existing one if found
748 748
749 749 :param apiuser:
750 750 :param repoid:
751 751 :param usersgroupid:
752 752 :param perm:
753 753 """
754 754 repo = get_repo_or_error(repoid)
755 755 perm = get_perm_or_error(perm)
756 756 users_group = get_users_group_or_error(usersgroupid)
757 757
758 758 try:
759 759 RepoModel().grant_users_group_permission(repo=repo,
760 760 group_name=users_group,
761 761 perm=perm)
762 762
763 763 Session().commit()
764 764 return dict(
765 765 msg='Granted perm: `%s` for users group: `%s` in '
766 766 'repo: `%s`' % (
767 767 perm.permission_name, users_group.users_group_name,
768 768 repo.repo_name
769 769 ),
770 770 success=True
771 771 )
772 772 except Exception:
773 print traceback.format_exc()
774 773 log.error(traceback.format_exc())
775 774 raise JSONRPCError(
776 775 'failed to edit permission for users group: `%s` in '
777 776 'repo: `%s`' % (
778 777 usersgroupid, repo.repo_name
779 778 )
780 779 )
781 780
782 781 @HasPermissionAnyDecorator('hg.admin')
783 782 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
784 783 """
785 784 Revoke permission for users group on given repository
786 785
787 786 :param apiuser:
788 787 :param repoid:
789 788 :param usersgroupid:
790 789 """
791 790 repo = get_repo_or_error(repoid)
792 791 users_group = get_users_group_or_error(usersgroupid)
793 792
794 793 try:
795 794 RepoModel().revoke_users_group_permission(repo=repo,
796 795 group_name=users_group)
797 796
798 797 Session().commit()
799 798 return dict(
800 799 msg='Revoked perm for users group: `%s` in repo: `%s`' % (
801 800 users_group.users_group_name, repo.repo_name
802 801 ),
803 802 success=True
804 803 )
805 804 except Exception:
806 805 log.error(traceback.format_exc())
807 806 raise JSONRPCError(
808 807 'failed to edit permission for users group: `%s` in '
809 808 'repo: `%s`' % (
810 809 users_group.users_group_name, repo.repo_name
811 810 )
812 811 )
@@ -1,1812 +1,1811 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 logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
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
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode, remove_suffix
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 try:
122 122 id_ = int(id_)
123 123 except (TypeError, ValueError):
124 124 raise HTTPNotFound
125 125
126 126 res = cls.query().get(id_)
127 127 if not res:
128 128 raise HTTPNotFound
129 129 return res
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session().delete(obj)
139 139
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == unicode
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name == 'ldap_active':
171 171 v = str2bool(v)
172 172 return v
173 173
174 174 @app_settings_value.setter
175 175 def app_settings_value(self, val):
176 176 """
177 177 Setter that will always make sure we use unicode in app_settings_value
178 178
179 179 :param val:
180 180 """
181 181 self._app_settings_value = safe_unicode(val)
182 182
183 183 def __unicode__(self):
184 184 return u"<%s('%s:%s')>" % (
185 185 self.__class__.__name__,
186 186 self.app_settings_name, self.app_settings_value
187 187 )
188 188
189 189 @classmethod
190 190 def get_by_name(cls, key):
191 191 return cls.query()\
192 192 .filter(cls.app_settings_name == key).scalar()
193 193
194 194 @classmethod
195 195 def get_by_name_or_create(cls, key):
196 196 res = cls.get_by_name(key)
197 197 if not res:
198 198 res = cls(key)
199 199 return res
200 200
201 201 @classmethod
202 202 def get_app_settings(cls, cache=False):
203 203
204 204 ret = cls.query()
205 205
206 206 if cache:
207 207 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
208 208
209 209 if not ret:
210 210 raise Exception('Could not get application settings !')
211 211 settings = {}
212 212 for each in ret:
213 213 settings['rhodecode_' + each.app_settings_name] = \
214 214 each.app_settings_value
215 215
216 216 return settings
217 217
218 218 @classmethod
219 219 def get_ldap_settings(cls, cache=False):
220 220 ret = cls.query()\
221 221 .filter(cls.app_settings_name.startswith('ldap_')).all()
222 222 fd = {}
223 223 for row in ret:
224 224 fd.update({row.app_settings_name: row.app_settings_value})
225 225
226 226 return fd
227 227
228 228
229 229 class RhodeCodeUi(Base, BaseModel):
230 230 __tablename__ = 'rhodecode_ui'
231 231 __table_args__ = (
232 232 UniqueConstraint('ui_key'),
233 233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
234 234 'mysql_charset': 'utf8'}
235 235 )
236 236
237 237 HOOK_UPDATE = 'changegroup.update'
238 238 HOOK_REPO_SIZE = 'changegroup.repo_size'
239 239 HOOK_PUSH = 'changegroup.push_logger'
240 240 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
241 241 HOOK_PULL = 'outgoing.pull_logger'
242 242 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
243 243
244 244 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 245 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 246 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 247 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 248 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
249 249
250 250 @classmethod
251 251 def get_by_key(cls, key):
252 252 return cls.query().filter(cls.ui_key == key).scalar()
253 253
254 254 @classmethod
255 255 def get_builtin_hooks(cls):
256 256 q = cls.query()
257 257 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
258 258 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
259 259 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
260 260 return q.all()
261 261
262 262 @classmethod
263 263 def get_custom_hooks(cls):
264 264 q = cls.query()
265 265 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
266 266 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
267 267 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
268 268 q = q.filter(cls.ui_section == 'hooks')
269 269 return q.all()
270 270
271 271 @classmethod
272 272 def get_repos_location(cls):
273 273 return cls.get_by_key('/').ui_value
274 274
275 275 @classmethod
276 276 def create_or_update_hook(cls, key, val):
277 277 new_ui = cls.get_by_key(key) or cls()
278 278 new_ui.ui_section = 'hooks'
279 279 new_ui.ui_active = True
280 280 new_ui.ui_key = key
281 281 new_ui.ui_value = val
282 282
283 283 Session().add(new_ui)
284 284
285 285 def __repr__(self):
286 286 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
287 287 self.ui_value)
288 288
289 289
290 290 class User(Base, BaseModel):
291 291 __tablename__ = 'users'
292 292 __table_args__ = (
293 293 UniqueConstraint('username'), UniqueConstraint('email'),
294 294 Index('u_username_idx', 'username'),
295 295 Index('u_email_idx', 'email'),
296 296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
297 297 'mysql_charset': 'utf8'}
298 298 )
299 299 DEFAULT_USER = 'default'
300 300 DEFAULT_PERMISSIONS = [
301 301 'hg.register.manual_activate', 'hg.create.repository',
302 302 'hg.fork.repository', 'repository.read'
303 303 ]
304 304 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
305 305 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 306 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 307 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
308 308 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
309 309 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
310 310 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 311 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 312 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
313 313 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
314 314 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
315 315 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
316 316
317 317 user_log = relationship('UserLog', cascade='all')
318 318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
319 319
320 320 repositories = relationship('Repository')
321 321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
322 322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
323 323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
324 324
325 325 group_member = relationship('UsersGroupMember', cascade='all')
326 326
327 327 notifications = relationship('UserNotification', cascade='all')
328 328 # notifications assigned to this user
329 329 user_created_notifications = relationship('Notification', cascade='all')
330 330 # comments created by this user
331 331 user_comments = relationship('ChangesetComment', cascade='all')
332 332 #extra emails for this user
333 333 user_emails = relationship('UserEmailMap', cascade='all')
334 334
335 335 @hybrid_property
336 336 def email(self):
337 337 return self._email
338 338
339 339 @email.setter
340 340 def email(self, val):
341 341 self._email = val.lower() if val else None
342 342
343 343 @property
344 344 def firstname(self):
345 345 # alias for future
346 346 return self.name
347 347
348 348 @property
349 349 def emails(self):
350 350 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
351 351 return [self.email] + [x.email for x in other]
352 352
353 353 @property
354 354 def username_and_name(self):
355 355 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
356 356
357 357 @property
358 358 def full_name(self):
359 359 return '%s %s' % (self.firstname, self.lastname)
360 360
361 361 @property
362 362 def full_name_or_username(self):
363 363 return ('%s %s' % (self.firstname, self.lastname)
364 364 if (self.firstname and self.lastname) else self.username)
365 365
366 366 @property
367 367 def full_contact(self):
368 368 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
369 369
370 370 @property
371 371 def short_contact(self):
372 372 return '%s %s' % (self.firstname, self.lastname)
373 373
374 374 @property
375 375 def is_admin(self):
376 376 return self.admin
377 377
378 378 def __unicode__(self):
379 379 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
380 380 self.user_id, self.username)
381 381
382 382 @classmethod
383 383 def get_by_username(cls, username, case_insensitive=False, cache=False):
384 384 if case_insensitive:
385 385 q = cls.query().filter(cls.username.ilike(username))
386 386 else:
387 387 q = cls.query().filter(cls.username == username)
388 388
389 389 if cache:
390 390 q = q.options(FromCache(
391 391 "sql_cache_short",
392 392 "get_user_%s" % _hash_key(username)
393 393 )
394 394 )
395 395 return q.scalar()
396 396
397 397 @classmethod
398 398 def get_by_api_key(cls, api_key, cache=False):
399 399 q = cls.query().filter(cls.api_key == api_key)
400 400
401 401 if cache:
402 402 q = q.options(FromCache("sql_cache_short",
403 403 "get_api_key_%s" % api_key))
404 404 return q.scalar()
405 405
406 406 @classmethod
407 407 def get_by_email(cls, email, case_insensitive=False, cache=False):
408 408 if case_insensitive:
409 409 q = cls.query().filter(cls.email.ilike(email))
410 410 else:
411 411 q = cls.query().filter(cls.email == email)
412 412
413 413 if cache:
414 414 q = q.options(FromCache("sql_cache_short",
415 415 "get_email_key_%s" % email))
416 416
417 417 ret = q.scalar()
418 418 if ret is None:
419 419 q = UserEmailMap.query()
420 420 # try fetching in alternate email map
421 421 if case_insensitive:
422 422 q = q.filter(UserEmailMap.email.ilike(email))
423 423 else:
424 424 q = q.filter(UserEmailMap.email == email)
425 425 q = q.options(joinedload(UserEmailMap.user))
426 426 if cache:
427 427 q = q.options(FromCache("sql_cache_short",
428 428 "get_email_map_key_%s" % email))
429 429 ret = getattr(q.scalar(), 'user', None)
430 430
431 431 return ret
432 432
433 433 def update_lastlogin(self):
434 434 """Update user lastlogin"""
435 435 self.last_login = datetime.datetime.now()
436 436 Session().add(self)
437 437 log.debug('updated user %s lastlogin' % self.username)
438 438
439 439 def get_api_data(self):
440 440 """
441 441 Common function for generating user related data for API
442 442 """
443 443 user = self
444 444 data = dict(
445 445 user_id=user.user_id,
446 446 username=user.username,
447 447 firstname=user.name,
448 448 lastname=user.lastname,
449 449 email=user.email,
450 450 emails=user.emails,
451 451 api_key=user.api_key,
452 452 active=user.active,
453 453 admin=user.admin,
454 454 ldap_dn=user.ldap_dn,
455 455 last_login=user.last_login,
456 456 )
457 457 return data
458 458
459 459 def __json__(self):
460 460 data = dict(
461 461 full_name=self.full_name,
462 462 full_name_or_username=self.full_name_or_username,
463 463 short_contact=self.short_contact,
464 464 full_contact=self.full_contact
465 465 )
466 466 data.update(self.get_api_data())
467 467 return data
468 468
469 469
470 470 class UserEmailMap(Base, BaseModel):
471 471 __tablename__ = 'user_email_map'
472 472 __table_args__ = (
473 473 Index('uem_email_idx', 'email'),
474 474 UniqueConstraint('email'),
475 475 {'extend_existing': True, 'mysql_engine': 'InnoDB',
476 476 'mysql_charset': 'utf8'}
477 477 )
478 478 __mapper_args__ = {}
479 479
480 480 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 481 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
482 482 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
483 483 user = relationship('User', lazy='joined')
484 484
485 485 @validates('_email')
486 486 def validate_email(self, key, email):
487 487 # check if this email is not main one
488 488 main_email = Session().query(User).filter(User.email == email).scalar()
489 489 if main_email is not None:
490 490 raise AttributeError('email %s is present is user table' % email)
491 491 return email
492 492
493 493 @hybrid_property
494 494 def email(self):
495 495 return self._email
496 496
497 497 @email.setter
498 498 def email(self, val):
499 499 self._email = val.lower() if val else None
500 500
501 501
502 502 class UserLog(Base, BaseModel):
503 503 __tablename__ = 'user_logs'
504 504 __table_args__ = (
505 505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 506 'mysql_charset': 'utf8'},
507 507 )
508 508 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
510 510 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
511 511 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
512 512 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
513 513 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
514 514 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
515 515
516 516 @property
517 517 def action_as_day(self):
518 518 return datetime.date(*self.action_date.timetuple()[:3])
519 519
520 520 user = relationship('User')
521 521 repository = relationship('Repository', cascade='')
522 522
523 523
524 524 class UsersGroup(Base, BaseModel):
525 525 __tablename__ = 'users_groups'
526 526 __table_args__ = (
527 527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
528 528 'mysql_charset': 'utf8'},
529 529 )
530 530
531 531 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
532 532 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
533 533 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
534 534 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
535 535
536 536 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
537 537 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
538 538 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
539 539
540 540 def __unicode__(self):
541 541 return u'<userGroup(%s)>' % (self.users_group_name)
542 542
543 543 @classmethod
544 544 def get_by_group_name(cls, group_name, cache=False,
545 545 case_insensitive=False):
546 546 if case_insensitive:
547 547 q = cls.query().filter(cls.users_group_name.ilike(group_name))
548 548 else:
549 549 q = cls.query().filter(cls.users_group_name == group_name)
550 550 if cache:
551 551 q = q.options(FromCache(
552 552 "sql_cache_short",
553 553 "get_user_%s" % _hash_key(group_name)
554 554 )
555 555 )
556 556 return q.scalar()
557 557
558 558 @classmethod
559 559 def get(cls, users_group_id, cache=False):
560 560 users_group = cls.query()
561 561 if cache:
562 562 users_group = users_group.options(FromCache("sql_cache_short",
563 563 "get_users_group_%s" % users_group_id))
564 564 return users_group.get(users_group_id)
565 565
566 566 def get_api_data(self):
567 567 users_group = self
568 568
569 569 data = dict(
570 570 users_group_id=users_group.users_group_id,
571 571 group_name=users_group.users_group_name,
572 572 active=users_group.users_group_active,
573 573 )
574 574
575 575 return data
576 576
577 577
578 578 class UsersGroupMember(Base, BaseModel):
579 579 __tablename__ = 'users_groups_members'
580 580 __table_args__ = (
581 581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
582 582 'mysql_charset': 'utf8'},
583 583 )
584 584
585 585 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
586 586 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
587 587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
588 588
589 589 user = relationship('User', lazy='joined')
590 590 users_group = relationship('UsersGroup')
591 591
592 592 def __init__(self, gr_id='', u_id=''):
593 593 self.users_group_id = gr_id
594 594 self.user_id = u_id
595 595
596 596
597 597 class Repository(Base, BaseModel):
598 598 __tablename__ = 'repositories'
599 599 __table_args__ = (
600 600 UniqueConstraint('repo_name'),
601 601 Index('r_repo_name_idx', 'repo_name'),
602 602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
603 603 'mysql_charset': 'utf8'},
604 604 )
605 605
606 606 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
607 607 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
608 608 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
609 609 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
610 610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
611 611 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
612 612 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
613 613 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
614 614 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
615 615 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
616 616 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
617 617 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
618 618 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
619 619 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
620 620
621 621 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
622 622 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
623 623
624 624 user = relationship('User')
625 625 fork = relationship('Repository', remote_side=repo_id)
626 626 group = relationship('RepoGroup')
627 627 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
628 628 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
629 629 stats = relationship('Statistics', cascade='all', uselist=False)
630 630
631 631 followers = relationship('UserFollowing',
632 632 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
633 633 cascade='all')
634 634
635 635 logs = relationship('UserLog')
636 636 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
637 637
638 638 pull_requests_org = relationship('PullRequest',
639 639 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
640 640 cascade="all, delete, delete-orphan")
641 641
642 642 pull_requests_other = relationship('PullRequest',
643 643 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
644 644 cascade="all, delete, delete-orphan")
645 645
646 646 def __unicode__(self):
647 647 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
648 648 self.repo_name)
649 649
650 650 @hybrid_property
651 651 def locked(self):
652 652 # always should return [user_id, timelocked]
653 653 if self._locked:
654 654 _lock_info = self._locked.split(':')
655 655 return int(_lock_info[0]), _lock_info[1]
656 656 return [None, None]
657 657
658 658 @locked.setter
659 659 def locked(self, val):
660 660 if val and isinstance(val, (list, tuple)):
661 661 self._locked = ':'.join(map(str, val))
662 662 else:
663 663 self._locked = None
664 664
665 665 @classmethod
666 666 def url_sep(cls):
667 667 return URL_SEP
668 668
669 669 @classmethod
670 670 def get_by_repo_name(cls, repo_name):
671 671 q = Session().query(cls).filter(cls.repo_name == repo_name)
672 672 q = q.options(joinedload(Repository.fork))\
673 673 .options(joinedload(Repository.user))\
674 674 .options(joinedload(Repository.group))
675 675 return q.scalar()
676 676
677 677 @classmethod
678 678 def get_by_full_path(cls, repo_full_path):
679 679 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
680 680 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
681 681
682 682 @classmethod
683 683 def get_repo_forks(cls, repo_id):
684 684 return cls.query().filter(Repository.fork_id == repo_id)
685 685
686 686 @classmethod
687 687 def base_path(cls):
688 688 """
689 689 Returns base path when all repos are stored
690 690
691 691 :param cls:
692 692 """
693 693 q = Session().query(RhodeCodeUi)\
694 694 .filter(RhodeCodeUi.ui_key == cls.url_sep())
695 695 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
696 696 return q.one().ui_value
697 697
698 698 @property
699 699 def forks(self):
700 700 """
701 701 Return forks of this repo
702 702 """
703 703 return Repository.get_repo_forks(self.repo_id)
704 704
705 705 @property
706 706 def parent(self):
707 707 """
708 708 Returns fork parent
709 709 """
710 710 return self.fork
711 711
712 712 @property
713 713 def just_name(self):
714 714 return self.repo_name.split(Repository.url_sep())[-1]
715 715
716 716 @property
717 717 def groups_with_parents(self):
718 718 groups = []
719 719 if self.group is None:
720 720 return groups
721 721
722 722 cur_gr = self.group
723 723 groups.insert(0, cur_gr)
724 724 while 1:
725 725 gr = getattr(cur_gr, 'parent_group', None)
726 726 cur_gr = cur_gr.parent_group
727 727 if gr is None:
728 728 break
729 729 groups.insert(0, gr)
730 730
731 731 return groups
732 732
733 733 @property
734 734 def groups_and_repo(self):
735 735 return self.groups_with_parents, self.just_name
736 736
737 737 @LazyProperty
738 738 def repo_path(self):
739 739 """
740 740 Returns base full path for that repository means where it actually
741 741 exists on a filesystem
742 742 """
743 743 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
744 744 Repository.url_sep())
745 745 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
746 746 return q.one().ui_value
747 747
748 748 @property
749 749 def repo_full_path(self):
750 750 p = [self.repo_path]
751 751 # we need to split the name by / since this is how we store the
752 752 # names in the database, but that eventually needs to be converted
753 753 # into a valid system path
754 754 p += self.repo_name.split(Repository.url_sep())
755 755 return os.path.join(*p)
756 756
757 757 @property
758 758 def cache_keys(self):
759 759 """
760 760 Returns associated cache keys for that repo
761 761 """
762 762 return CacheInvalidation.query()\
763 763 .filter(CacheInvalidation.cache_args == self.repo_name)\
764 764 .order_by(CacheInvalidation.cache_key)\
765 765 .all()
766 766
767 767 def get_new_name(self, repo_name):
768 768 """
769 769 returns new full repository name based on assigned group and new new
770 770
771 771 :param group_name:
772 772 """
773 773 path_prefix = self.group.full_path_splitted if self.group else []
774 774 return Repository.url_sep().join(path_prefix + [repo_name])
775 775
776 776 @property
777 777 def _ui(self):
778 778 """
779 779 Creates an db based ui object for this repository
780 780 """
781 781 from rhodecode.lib.utils import make_ui
782 782 return make_ui('db', clear_session=False)
783 783
784 784 @classmethod
785 785 def inject_ui(cls, repo, extras={}):
786 786 from rhodecode.lib.vcs.backends.hg import MercurialRepository
787 787 from rhodecode.lib.vcs.backends.git import GitRepository
788 788 required = (MercurialRepository, GitRepository)
789 789 if not isinstance(repo, required):
790 790 raise Exception('repo must be instance of %s' % required)
791 791
792 792 # inject ui extra param to log this action via push logger
793 793 for k, v in extras.items():
794 794 repo._repo.ui.setconfig('rhodecode_extras', k, v)
795 795
796 796 @classmethod
797 797 def is_valid(cls, repo_name):
798 798 """
799 799 returns True if given repo name is a valid filesystem repository
800 800
801 801 :param cls:
802 802 :param repo_name:
803 803 """
804 804 from rhodecode.lib.utils import is_valid_repo
805 805
806 806 return is_valid_repo(repo_name, cls.base_path())
807 807
808 808 def get_api_data(self):
809 809 """
810 810 Common function for generating repo api data
811 811
812 812 """
813 813 repo = self
814 814 data = dict(
815 815 repo_id=repo.repo_id,
816 816 repo_name=repo.repo_name,
817 817 repo_type=repo.repo_type,
818 818 clone_uri=repo.clone_uri,
819 819 private=repo.private,
820 820 created_on=repo.created_on,
821 821 description=repo.description,
822 822 landing_rev=repo.landing_rev,
823 823 owner=repo.user.username,
824 824 fork_of=repo.fork.repo_name if repo.fork else None
825 825 )
826 826
827 827 return data
828 828
829 829 @classmethod
830 830 def lock(cls, repo, user_id):
831 831 repo.locked = [user_id, time.time()]
832 832 Session().add(repo)
833 833 Session().commit()
834 834
835 835 @classmethod
836 836 def unlock(cls, repo):
837 837 repo.locked = None
838 838 Session().add(repo)
839 839 Session().commit()
840 840
841 841 @property
842 842 def last_db_change(self):
843 843 return self.updated_on
844 844
845 845 #==========================================================================
846 846 # SCM PROPERTIES
847 847 #==========================================================================
848 848
849 849 def get_changeset(self, rev=None):
850 850 return get_changeset_safe(self.scm_instance, rev)
851 851
852 852 def get_landing_changeset(self):
853 853 """
854 854 Returns landing changeset, or if that doesn't exist returns the tip
855 855 """
856 856 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
857 857 return cs
858 858
859 859 def update_last_change(self, last_change=None):
860 860 if last_change is None:
861 861 last_change = datetime.datetime.now()
862 862 if self.updated_on is None or self.updated_on != last_change:
863 863 log.debug('updated repo %s with new date %s' % (self, last_change))
864 864 self.updated_on = last_change
865 865 Session().add(self)
866 866 Session().commit()
867 867
868 868 @property
869 869 def tip(self):
870 870 return self.get_changeset('tip')
871 871
872 872 @property
873 873 def author(self):
874 874 return self.tip.author
875 875
876 876 @property
877 877 def last_change(self):
878 878 return self.scm_instance.last_change
879 879
880 880 def get_comments(self, revisions=None):
881 881 """
882 882 Returns comments for this repository grouped by revisions
883 883
884 884 :param revisions: filter query by revisions only
885 885 """
886 886 cmts = ChangesetComment.query()\
887 887 .filter(ChangesetComment.repo == self)
888 888 if revisions:
889 889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
890 890 grouped = defaultdict(list)
891 891 for cmt in cmts.all():
892 892 grouped[cmt.revision].append(cmt)
893 893 return grouped
894 894
895 895 def statuses(self, revisions=None):
896 896 """
897 897 Returns statuses for this repository
898 898
899 899 :param revisions: list of revisions to get statuses for
900 900 :type revisions: list
901 901 """
902 902
903 903 statuses = ChangesetStatus.query()\
904 904 .filter(ChangesetStatus.repo == self)\
905 905 .filter(ChangesetStatus.version == 0)
906 906 if revisions:
907 907 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
908 908 grouped = {}
909 909
910 910 #maybe we have open new pullrequest without a status ?
911 911 stat = ChangesetStatus.STATUS_UNDER_REVIEW
912 912 status_lbl = ChangesetStatus.get_status_lbl(stat)
913 913 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
914 914 for rev in pr.revisions:
915 915 pr_id = pr.pull_request_id
916 916 pr_repo = pr.other_repo.repo_name
917 917 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
918 918
919 919 for stat in statuses.all():
920 920 pr_id = pr_repo = None
921 921 if stat.pull_request:
922 922 pr_id = stat.pull_request.pull_request_id
923 923 pr_repo = stat.pull_request.other_repo.repo_name
924 924 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
925 925 pr_id, pr_repo]
926 926 return grouped
927 927
928 928 #==========================================================================
929 929 # SCM CACHE INSTANCE
930 930 #==========================================================================
931 931
932 932 @property
933 933 def invalidate(self):
934 934 return CacheInvalidation.invalidate(self.repo_name)
935 935
936 936 def set_invalidate(self):
937 937 """
938 938 set a cache for invalidation for this instance
939 939 """
940 940 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
941 941
942 942 @LazyProperty
943 943 def scm_instance(self):
944 944 return self.scm_instance_cached()
945 945 return self.__get_instance()
946 946
947 947 def scm_instance_cached(self, cache_map=None):
948 948 @cache_region('long_term')
949 949 def _c(repo_name):
950 950 return self.__get_instance()
951 951 rn = self.repo_name
952 952 log.debug('Getting cached instance of repo')
953 953
954 954 if cache_map:
955 955 # get using prefilled cache_map
956 956 invalidate_repo = cache_map[self.repo_name]
957 957 if invalidate_repo:
958 958 invalidate_repo = (None if invalidate_repo.cache_active
959 959 else invalidate_repo)
960 960 else:
961 961 # get from invalidate
962 962 invalidate_repo = self.invalidate
963 963
964 964 if invalidate_repo is not None:
965 965 region_invalidate(_c, None, rn)
966 966 # update our cache
967 967 CacheInvalidation.set_valid(invalidate_repo.cache_key)
968 968 return _c(rn)
969 969
970 970 def __get_instance(self):
971 971 repo_full_path = self.repo_full_path
972 972 try:
973 973 alias = get_scm(repo_full_path)[0]
974 974 log.debug('Creating instance of %s repository' % alias)
975 975 backend = get_backend(alias)
976 976 except VCSError:
977 977 log.error(traceback.format_exc())
978 978 log.error('Perhaps this repository is in db and not in '
979 979 'filesystem run rescan repositories with '
980 980 '"destroy old data " option from admin panel')
981 981 return
982 982
983 983 if alias == 'hg':
984 984
985 985 repo = backend(safe_str(repo_full_path), create=False,
986 986 baseui=self._ui)
987 987 # skip hidden web repository
988 988 if repo._get_hidden():
989 989 return
990 990 else:
991 991 repo = backend(repo_full_path, create=False)
992 992
993 993 return repo
994 994
995 995
996 996 class RepoGroup(Base, BaseModel):
997 997 __tablename__ = 'groups'
998 998 __table_args__ = (
999 999 UniqueConstraint('group_name', 'group_parent_id'),
1000 1000 CheckConstraint('group_id != group_parent_id'),
1001 1001 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1002 1002 'mysql_charset': 'utf8'},
1003 1003 )
1004 1004 __mapper_args__ = {'order_by': 'group_name'}
1005 1005
1006 1006 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1007 1007 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1008 1008 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1009 1009 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1010 1010 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1011 1011
1012 1012 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1013 1013 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1014 1014
1015 1015 parent_group = relationship('RepoGroup', remote_side=group_id)
1016 1016
1017 1017 def __init__(self, group_name='', parent_group=None):
1018 1018 self.group_name = group_name
1019 1019 self.parent_group = parent_group
1020 1020
1021 1021 def __unicode__(self):
1022 1022 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1023 1023 self.group_name)
1024 1024
1025 1025 @classmethod
1026 1026 def groups_choices(cls, check_perms=False):
1027 1027 from webhelpers.html import literal as _literal
1028 1028 from rhodecode.model.scm import ScmModel
1029 1029 groups = cls.query().all()
1030 1030 if check_perms:
1031 1031 #filter group user have access to, it's done
1032 1032 #magically inside ScmModel based on current user
1033 1033 groups = ScmModel().get_repos_groups(groups)
1034 1034 repo_groups = [('', '')]
1035 1035 sep = ' &raquo; '
1036 1036 _name = lambda k: _literal(sep.join(k))
1037 1037
1038 1038 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1039 1039 for x in groups])
1040 1040
1041 1041 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1042 1042 return repo_groups
1043 1043
1044 1044 @classmethod
1045 1045 def url_sep(cls):
1046 1046 return URL_SEP
1047 1047
1048 1048 @classmethod
1049 1049 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1050 1050 if case_insensitive:
1051 1051 gr = cls.query()\
1052 1052 .filter(cls.group_name.ilike(group_name))
1053 1053 else:
1054 1054 gr = cls.query()\
1055 1055 .filter(cls.group_name == group_name)
1056 1056 if cache:
1057 1057 gr = gr.options(FromCache(
1058 1058 "sql_cache_short",
1059 1059 "get_group_%s" % _hash_key(group_name)
1060 1060 )
1061 1061 )
1062 1062 return gr.scalar()
1063 1063
1064 1064 @property
1065 1065 def parents(self):
1066 1066 parents_recursion_limit = 5
1067 1067 groups = []
1068 1068 if self.parent_group is None:
1069 1069 return groups
1070 1070 cur_gr = self.parent_group
1071 1071 groups.insert(0, cur_gr)
1072 1072 cnt = 0
1073 1073 while 1:
1074 1074 cnt += 1
1075 1075 gr = getattr(cur_gr, 'parent_group', None)
1076 1076 cur_gr = cur_gr.parent_group
1077 1077 if gr is None:
1078 1078 break
1079 1079 if cnt == parents_recursion_limit:
1080 1080 # this will prevent accidental infinit loops
1081 1081 log.error('group nested more than %s' %
1082 1082 parents_recursion_limit)
1083 1083 break
1084 1084
1085 1085 groups.insert(0, gr)
1086 1086 return groups
1087 1087
1088 1088 @property
1089 1089 def children(self):
1090 1090 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1091 1091
1092 1092 @property
1093 1093 def name(self):
1094 1094 return self.group_name.split(RepoGroup.url_sep())[-1]
1095 1095
1096 1096 @property
1097 1097 def full_path(self):
1098 1098 return self.group_name
1099 1099
1100 1100 @property
1101 1101 def full_path_splitted(self):
1102 1102 return self.group_name.split(RepoGroup.url_sep())
1103 1103
1104 1104 @property
1105 1105 def repositories(self):
1106 1106 return Repository.query()\
1107 1107 .filter(Repository.group == self)\
1108 1108 .order_by(Repository.repo_name)
1109 1109
1110 1110 @property
1111 1111 def repositories_recursive_count(self):
1112 1112 cnt = self.repositories.count()
1113 1113
1114 1114 def children_count(group):
1115 1115 cnt = 0
1116 1116 for child in group.children:
1117 1117 cnt += child.repositories.count()
1118 1118 cnt += children_count(child)
1119 1119 return cnt
1120 1120
1121 1121 return cnt + children_count(self)
1122 1122
1123 1123 def recursive_groups_and_repos(self):
1124 1124 """
1125 1125 Recursive return all groups, with repositories in those groups
1126 1126 """
1127 1127 all_ = []
1128 1128
1129 1129 def _get_members(root_gr):
1130 1130 for r in root_gr.repositories:
1131 1131 all_.append(r)
1132 1132 childs = root_gr.children.all()
1133 1133 if childs:
1134 1134 for gr in childs:
1135 1135 all_.append(gr)
1136 1136 _get_members(gr)
1137 1137
1138 1138 _get_members(self)
1139 1139 return [self] + all_
1140 1140
1141 1141 def get_new_name(self, group_name):
1142 1142 """
1143 1143 returns new full group name based on parent and new name
1144 1144
1145 1145 :param group_name:
1146 1146 """
1147 1147 path_prefix = (self.parent_group.full_path_splitted if
1148 1148 self.parent_group else [])
1149 1149 return RepoGroup.url_sep().join(path_prefix + [group_name])
1150 1150
1151 1151
1152 1152 class Permission(Base, BaseModel):
1153 1153 __tablename__ = 'permissions'
1154 1154 __table_args__ = (
1155 1155 Index('p_perm_name_idx', 'permission_name'),
1156 1156 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1157 1157 'mysql_charset': 'utf8'},
1158 1158 )
1159 1159 PERMS = [
1160 1160 ('repository.none', _('Repository no access')),
1161 1161 ('repository.read', _('Repository read access')),
1162 1162 ('repository.write', _('Repository write access')),
1163 1163 ('repository.admin', _('Repository admin access')),
1164 1164
1165 1165 ('group.none', _('Repositories Group no access')),
1166 1166 ('group.read', _('Repositories Group read access')),
1167 1167 ('group.write', _('Repositories Group write access')),
1168 1168 ('group.admin', _('Repositories Group admin access')),
1169 1169
1170 1170 ('hg.admin', _('RhodeCode Administrator')),
1171 1171 ('hg.create.none', _('Repository creation disabled')),
1172 1172 ('hg.create.repository', _('Repository creation enabled')),
1173 1173 ('hg.fork.none', _('Repository forking disabled')),
1174 1174 ('hg.fork.repository', _('Repository forking enabled')),
1175 1175 ('hg.register.none', _('Register disabled')),
1176 1176 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1177 1177 'with manual activation')),
1178 1178
1179 1179 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1180 1180 'with auto activation')),
1181 1181 ]
1182 1182
1183 1183 # defines which permissions are more important higher the more important
1184 1184 PERM_WEIGHTS = {
1185 1185 'repository.none': 0,
1186 1186 'repository.read': 1,
1187 1187 'repository.write': 3,
1188 1188 'repository.admin': 4,
1189 1189
1190 1190 'group.none': 0,
1191 1191 'group.read': 1,
1192 1192 'group.write': 3,
1193 1193 'group.admin': 4,
1194 1194
1195 1195 'hg.fork.none': 0,
1196 1196 'hg.fork.repository': 1,
1197 1197 'hg.create.none': 0,
1198 1198 'hg.create.repository':1
1199 1199 }
1200 1200
1201 1201 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1202 1202 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1203 1203 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1204 1204
1205 1205 def __unicode__(self):
1206 1206 return u"<%s('%s:%s')>" % (
1207 1207 self.__class__.__name__, self.permission_id, self.permission_name
1208 1208 )
1209 1209
1210 1210 @classmethod
1211 1211 def get_by_key(cls, key):
1212 1212 return cls.query().filter(cls.permission_name == key).scalar()
1213 1213
1214 1214 @classmethod
1215 1215 def get_default_perms(cls, default_user_id):
1216 1216 q = Session().query(UserRepoToPerm, Repository, cls)\
1217 1217 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1218 1218 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1219 1219 .filter(UserRepoToPerm.user_id == default_user_id)
1220 1220
1221 1221 return q.all()
1222 1222
1223 1223 @classmethod
1224 1224 def get_default_group_perms(cls, default_user_id):
1225 1225 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1226 1226 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1227 1227 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1228 1228 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1229 1229
1230 1230 return q.all()
1231 1231
1232 1232
1233 1233 class UserRepoToPerm(Base, BaseModel):
1234 1234 __tablename__ = 'repo_to_perm'
1235 1235 __table_args__ = (
1236 1236 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1237 1237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1238 1238 'mysql_charset': 'utf8'}
1239 1239 )
1240 1240 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1241 1241 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1242 1242 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1243 1243 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1244 1244
1245 1245 user = relationship('User')
1246 1246 repository = relationship('Repository')
1247 1247 permission = relationship('Permission')
1248 1248
1249 1249 @classmethod
1250 1250 def create(cls, user, repository, permission):
1251 1251 n = cls()
1252 1252 n.user = user
1253 1253 n.repository = repository
1254 1254 n.permission = permission
1255 1255 Session().add(n)
1256 1256 return n
1257 1257
1258 1258 def __unicode__(self):
1259 1259 return u'<user:%s => %s >' % (self.user, self.repository)
1260 1260
1261 1261
1262 1262 class UserToPerm(Base, BaseModel):
1263 1263 __tablename__ = 'user_to_perm'
1264 1264 __table_args__ = (
1265 1265 UniqueConstraint('user_id', 'permission_id'),
1266 1266 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1267 1267 'mysql_charset': 'utf8'}
1268 1268 )
1269 1269 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1270 1270 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1271 1271 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1272 1272
1273 1273 user = relationship('User')
1274 1274 permission = relationship('Permission', lazy='joined')
1275 1275
1276 1276
1277 1277 class UsersGroupRepoToPerm(Base, BaseModel):
1278 1278 __tablename__ = 'users_group_repo_to_perm'
1279 1279 __table_args__ = (
1280 1280 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1281 1281 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1282 1282 'mysql_charset': 'utf8'}
1283 1283 )
1284 1284 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1285 1285 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1286 1286 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1287 1287 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1288 1288
1289 1289 users_group = relationship('UsersGroup')
1290 1290 permission = relationship('Permission')
1291 1291 repository = relationship('Repository')
1292 1292
1293 1293 @classmethod
1294 1294 def create(cls, users_group, repository, permission):
1295 1295 n = cls()
1296 1296 n.users_group = users_group
1297 1297 n.repository = repository
1298 1298 n.permission = permission
1299 1299 Session().add(n)
1300 1300 return n
1301 1301
1302 1302 def __unicode__(self):
1303 1303 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1304 1304
1305 1305
1306 1306 class UsersGroupToPerm(Base, BaseModel):
1307 1307 __tablename__ = 'users_group_to_perm'
1308 1308 __table_args__ = (
1309 1309 UniqueConstraint('users_group_id', 'permission_id',),
1310 1310 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1311 1311 'mysql_charset': 'utf8'}
1312 1312 )
1313 1313 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1314 1314 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1315 1315 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1316 1316
1317 1317 users_group = relationship('UsersGroup')
1318 1318 permission = relationship('Permission')
1319 1319
1320 1320
1321 1321 class UserRepoGroupToPerm(Base, BaseModel):
1322 1322 __tablename__ = 'user_repo_group_to_perm'
1323 1323 __table_args__ = (
1324 1324 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1325 1325 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1326 1326 'mysql_charset': 'utf8'}
1327 1327 )
1328 1328
1329 1329 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1330 1330 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1331 1331 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1332 1332 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1333 1333
1334 1334 user = relationship('User')
1335 1335 group = relationship('RepoGroup')
1336 1336 permission = relationship('Permission')
1337 1337
1338 1338
1339 1339 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1340 1340 __tablename__ = 'users_group_repo_group_to_perm'
1341 1341 __table_args__ = (
1342 1342 UniqueConstraint('users_group_id', 'group_id'),
1343 1343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1344 1344 'mysql_charset': 'utf8'}
1345 1345 )
1346 1346
1347 1347 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)
1348 1348 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1349 1349 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1350 1350 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1351 1351
1352 1352 users_group = relationship('UsersGroup')
1353 1353 permission = relationship('Permission')
1354 1354 group = relationship('RepoGroup')
1355 1355
1356 1356
1357 1357 class Statistics(Base, BaseModel):
1358 1358 __tablename__ = 'statistics'
1359 1359 __table_args__ = (
1360 1360 UniqueConstraint('repository_id'),
1361 1361 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1362 1362 'mysql_charset': 'utf8'}
1363 1363 )
1364 1364 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1365 1365 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1366 1366 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1367 1367 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1368 1368 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1369 1369 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1370 1370
1371 1371 repository = relationship('Repository', single_parent=True)
1372 1372
1373 1373
1374 1374 class UserFollowing(Base, BaseModel):
1375 1375 __tablename__ = 'user_followings'
1376 1376 __table_args__ = (
1377 1377 UniqueConstraint('user_id', 'follows_repository_id'),
1378 1378 UniqueConstraint('user_id', 'follows_user_id'),
1379 1379 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1380 1380 'mysql_charset': 'utf8'}
1381 1381 )
1382 1382
1383 1383 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1384 1384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1385 1385 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1386 1386 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1387 1387 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1388 1388
1389 1389 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1390 1390
1391 1391 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1392 1392 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1393 1393
1394 1394 @classmethod
1395 1395 def get_repo_followers(cls, repo_id):
1396 1396 return cls.query().filter(cls.follows_repo_id == repo_id)
1397 1397
1398 1398
1399 1399 class CacheInvalidation(Base, BaseModel):
1400 1400 __tablename__ = 'cache_invalidation'
1401 1401 __table_args__ = (
1402 1402 UniqueConstraint('cache_key'),
1403 1403 Index('key_idx', 'cache_key'),
1404 1404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1405 1405 'mysql_charset': 'utf8'},
1406 1406 )
1407 1407 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1408 1408 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1409 1409 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1410 1410 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1411 1411
1412 1412 def __init__(self, cache_key, cache_args=''):
1413 1413 self.cache_key = cache_key
1414 1414 self.cache_args = cache_args
1415 1415 self.cache_active = False
1416 1416
1417 1417 def __unicode__(self):
1418 1418 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1419 1419 self.cache_id, self.cache_key)
1420 1420
1421 1421 @property
1422 1422 def prefix(self):
1423 1423 _split = self.cache_key.split(self.cache_args, 1)
1424 1424 if _split and len(_split) == 2:
1425 1425 return _split[0]
1426 1426 return ''
1427 1427
1428 1428 @classmethod
1429 1429 def clear_cache(cls):
1430 1430 cls.query().delete()
1431 1431
1432 1432 @classmethod
1433 1433 def _get_key(cls, key):
1434 1434 """
1435 1435 Wrapper for generating a key, together with a prefix
1436 1436
1437 1437 :param key:
1438 1438 """
1439 1439 import rhodecode
1440 1440 prefix = ''
1441 1441 org_key = key
1442 1442 iid = rhodecode.CONFIG.get('instance_id')
1443 1443 if iid:
1444 1444 prefix = iid
1445 1445
1446 1446 return "%s%s" % (prefix, key), prefix, org_key
1447 1447
1448 1448 @classmethod
1449 1449 def get_by_key(cls, key):
1450 1450 return cls.query().filter(cls.cache_key == key).scalar()
1451 1451
1452 1452 @classmethod
1453 1453 def get_by_repo_name(cls, repo_name):
1454 1454 return cls.query().filter(cls.cache_args == repo_name).all()
1455 1455
1456 1456 @classmethod
1457 1457 def _get_or_create_key(cls, key, repo_name, commit=True):
1458 1458 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1459 1459 if not inv_obj:
1460 1460 try:
1461 1461 inv_obj = CacheInvalidation(key, repo_name)
1462 1462 Session().add(inv_obj)
1463 1463 if commit:
1464 1464 Session().commit()
1465 1465 except Exception:
1466 1466 log.error(traceback.format_exc())
1467 1467 Session().rollback()
1468 1468 return inv_obj
1469 1469
1470 1470 @classmethod
1471 1471 def invalidate(cls, key):
1472 1472 """
1473 1473 Returns Invalidation object if this given key should be invalidated
1474 1474 None otherwise. `cache_active = False` means that this cache
1475 1475 state is not valid and needs to be invalidated
1476 1476
1477 1477 :param key:
1478 1478 """
1479 1479 repo_name = key
1480 1480 repo_name = remove_suffix(repo_name, '_README')
1481 1481 repo_name = remove_suffix(repo_name, '_RSS')
1482 1482 repo_name = remove_suffix(repo_name, '_ATOM')
1483 1483
1484 1484 # adds instance prefix
1485 1485 key, _prefix, _org_key = cls._get_key(key)
1486 1486 inv = cls._get_or_create_key(key, repo_name)
1487 1487
1488 1488 if inv and inv.cache_active is False:
1489 1489 return inv
1490 1490
1491 1491 @classmethod
1492 1492 def set_invalidate(cls, key=None, repo_name=None):
1493 1493 """
1494 1494 Mark this Cache key for invalidation, either by key or whole
1495 1495 cache sets based on repo_name
1496 1496
1497 1497 :param key:
1498 1498 """
1499 1499 if key:
1500 1500 key, _prefix, _org_key = cls._get_key(key)
1501 1501 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1502 1502 elif repo_name:
1503 1503 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1504 1504
1505 1505 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1506 1506 % (len(inv_objs), key, repo_name))
1507 1507 try:
1508 1508 for inv_obj in inv_objs:
1509 print inv_obj
1510 1509 inv_obj.cache_active = False
1511 1510 Session().add(inv_obj)
1512 1511 Session().commit()
1513 1512 except Exception:
1514 1513 log.error(traceback.format_exc())
1515 1514 Session().rollback()
1516 1515
1517 1516 @classmethod
1518 1517 def set_valid(cls, key):
1519 1518 """
1520 1519 Mark this cache key as active and currently cached
1521 1520
1522 1521 :param key:
1523 1522 """
1524 1523 inv_obj = cls.get_by_key(key)
1525 1524 inv_obj.cache_active = True
1526 1525 Session().add(inv_obj)
1527 1526 Session().commit()
1528 1527
1529 1528 @classmethod
1530 1529 def get_cache_map(cls):
1531 1530
1532 1531 class cachemapdict(dict):
1533 1532
1534 1533 def __init__(self, *args, **kwargs):
1535 1534 fixkey = kwargs.get('fixkey')
1536 1535 if fixkey:
1537 1536 del kwargs['fixkey']
1538 1537 self.fixkey = fixkey
1539 1538 super(cachemapdict, self).__init__(*args, **kwargs)
1540 1539
1541 1540 def __getattr__(self, name):
1542 1541 key = name
1543 1542 if self.fixkey:
1544 1543 key, _prefix, _org_key = cls._get_key(key)
1545 1544 if key in self.__dict__:
1546 1545 return self.__dict__[key]
1547 1546 else:
1548 1547 return self[key]
1549 1548
1550 1549 def __getitem__(self, key):
1551 1550 if self.fixkey:
1552 1551 key, _prefix, _org_key = cls._get_key(key)
1553 1552 try:
1554 1553 return super(cachemapdict, self).__getitem__(key)
1555 1554 except KeyError:
1556 1555 return
1557 1556
1558 1557 cache_map = cachemapdict(fixkey=True)
1559 1558 for obj in cls.query().all():
1560 1559 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1561 1560 return cache_map
1562 1561
1563 1562
1564 1563 class ChangesetComment(Base, BaseModel):
1565 1564 __tablename__ = 'changeset_comments'
1566 1565 __table_args__ = (
1567 1566 Index('cc_revision_idx', 'revision'),
1568 1567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1569 1568 'mysql_charset': 'utf8'},
1570 1569 )
1571 1570 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1572 1571 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1573 1572 revision = Column('revision', String(40), nullable=True)
1574 1573 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1575 1574 line_no = Column('line_no', Unicode(10), nullable=True)
1576 1575 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1577 1576 f_path = Column('f_path', Unicode(1000), nullable=True)
1578 1577 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1579 1578 text = Column('text', UnicodeText(25000), nullable=False)
1580 1579 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1581 1580 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1582 1581
1583 1582 author = relationship('User', lazy='joined')
1584 1583 repo = relationship('Repository')
1585 1584 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1586 1585 pull_request = relationship('PullRequest', lazy='joined')
1587 1586
1588 1587 @classmethod
1589 1588 def get_users(cls, revision=None, pull_request_id=None):
1590 1589 """
1591 1590 Returns user associated with this ChangesetComment. ie those
1592 1591 who actually commented
1593 1592
1594 1593 :param cls:
1595 1594 :param revision:
1596 1595 """
1597 1596 q = Session().query(User)\
1598 1597 .join(ChangesetComment.author)
1599 1598 if revision:
1600 1599 q = q.filter(cls.revision == revision)
1601 1600 elif pull_request_id:
1602 1601 q = q.filter(cls.pull_request_id == pull_request_id)
1603 1602 return q.all()
1604 1603
1605 1604
1606 1605 class ChangesetStatus(Base, BaseModel):
1607 1606 __tablename__ = 'changeset_statuses'
1608 1607 __table_args__ = (
1609 1608 Index('cs_revision_idx', 'revision'),
1610 1609 Index('cs_version_idx', 'version'),
1611 1610 UniqueConstraint('repo_id', 'revision', 'version'),
1612 1611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1613 1612 'mysql_charset': 'utf8'}
1614 1613 )
1615 1614 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1616 1615 STATUS_APPROVED = 'approved'
1617 1616 STATUS_REJECTED = 'rejected'
1618 1617 STATUS_UNDER_REVIEW = 'under_review'
1619 1618
1620 1619 STATUSES = [
1621 1620 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1622 1621 (STATUS_APPROVED, _("Approved")),
1623 1622 (STATUS_REJECTED, _("Rejected")),
1624 1623 (STATUS_UNDER_REVIEW, _("Under Review")),
1625 1624 ]
1626 1625
1627 1626 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1628 1627 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1629 1628 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1630 1629 revision = Column('revision', String(40), nullable=False)
1631 1630 status = Column('status', String(128), nullable=False, default=DEFAULT)
1632 1631 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1633 1632 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1634 1633 version = Column('version', Integer(), nullable=False, default=0)
1635 1634 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1636 1635
1637 1636 author = relationship('User', lazy='joined')
1638 1637 repo = relationship('Repository')
1639 1638 comment = relationship('ChangesetComment', lazy='joined')
1640 1639 pull_request = relationship('PullRequest', lazy='joined')
1641 1640
1642 1641 def __unicode__(self):
1643 1642 return u"<%s('%s:%s')>" % (
1644 1643 self.__class__.__name__,
1645 1644 self.status, self.author
1646 1645 )
1647 1646
1648 1647 @classmethod
1649 1648 def get_status_lbl(cls, value):
1650 1649 return dict(cls.STATUSES).get(value)
1651 1650
1652 1651 @property
1653 1652 def status_lbl(self):
1654 1653 return ChangesetStatus.get_status_lbl(self.status)
1655 1654
1656 1655
1657 1656 class PullRequest(Base, BaseModel):
1658 1657 __tablename__ = 'pull_requests'
1659 1658 __table_args__ = (
1660 1659 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1661 1660 'mysql_charset': 'utf8'},
1662 1661 )
1663 1662
1664 1663 STATUS_NEW = u'new'
1665 1664 STATUS_OPEN = u'open'
1666 1665 STATUS_CLOSED = u'closed'
1667 1666
1668 1667 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1669 1668 title = Column('title', Unicode(256), nullable=True)
1670 1669 description = Column('description', UnicodeText(10240), nullable=True)
1671 1670 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1672 1671 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1673 1672 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1674 1673 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1675 1674 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1676 1675 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1677 1676 org_ref = Column('org_ref', Unicode(256), nullable=False)
1678 1677 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1679 1678 other_ref = Column('other_ref', Unicode(256), nullable=False)
1680 1679
1681 1680 @hybrid_property
1682 1681 def revisions(self):
1683 1682 return self._revisions.split(':')
1684 1683
1685 1684 @revisions.setter
1686 1685 def revisions(self, val):
1687 1686 self._revisions = ':'.join(val)
1688 1687
1689 1688 author = relationship('User', lazy='joined')
1690 1689 reviewers = relationship('PullRequestReviewers',
1691 1690 cascade="all, delete, delete-orphan")
1692 1691 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1693 1692 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1694 1693 statuses = relationship('ChangesetStatus')
1695 1694 comments = relationship('ChangesetComment',
1696 1695 cascade="all, delete, delete-orphan")
1697 1696
1698 1697 def is_closed(self):
1699 1698 return self.status == self.STATUS_CLOSED
1700 1699
1701 1700 def __json__(self):
1702 1701 return dict(
1703 1702 revisions=self.revisions
1704 1703 )
1705 1704
1706 1705
1707 1706 class PullRequestReviewers(Base, BaseModel):
1708 1707 __tablename__ = 'pull_request_reviewers'
1709 1708 __table_args__ = (
1710 1709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1711 1710 'mysql_charset': 'utf8'},
1712 1711 )
1713 1712
1714 1713 def __init__(self, user=None, pull_request=None):
1715 1714 self.user = user
1716 1715 self.pull_request = pull_request
1717 1716
1718 1717 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1719 1718 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1720 1719 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1721 1720
1722 1721 user = relationship('User')
1723 1722 pull_request = relationship('PullRequest')
1724 1723
1725 1724
1726 1725 class Notification(Base, BaseModel):
1727 1726 __tablename__ = 'notifications'
1728 1727 __table_args__ = (
1729 1728 Index('notification_type_idx', 'type'),
1730 1729 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1731 1730 'mysql_charset': 'utf8'},
1732 1731 )
1733 1732
1734 1733 TYPE_CHANGESET_COMMENT = u'cs_comment'
1735 1734 TYPE_MESSAGE = u'message'
1736 1735 TYPE_MENTION = u'mention'
1737 1736 TYPE_REGISTRATION = u'registration'
1738 1737 TYPE_PULL_REQUEST = u'pull_request'
1739 1738 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1740 1739
1741 1740 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1742 1741 subject = Column('subject', Unicode(512), nullable=True)
1743 1742 body = Column('body', UnicodeText(50000), nullable=True)
1744 1743 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1745 1744 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1746 1745 type_ = Column('type', Unicode(256))
1747 1746
1748 1747 created_by_user = relationship('User')
1749 1748 notifications_to_users = relationship('UserNotification', lazy='joined',
1750 1749 cascade="all, delete, delete-orphan")
1751 1750
1752 1751 @property
1753 1752 def recipients(self):
1754 1753 return [x.user for x in UserNotification.query()\
1755 1754 .filter(UserNotification.notification == self)\
1756 1755 .order_by(UserNotification.user_id.asc()).all()]
1757 1756
1758 1757 @classmethod
1759 1758 def create(cls, created_by, subject, body, recipients, type_=None):
1760 1759 if type_ is None:
1761 1760 type_ = Notification.TYPE_MESSAGE
1762 1761
1763 1762 notification = cls()
1764 1763 notification.created_by_user = created_by
1765 1764 notification.subject = subject
1766 1765 notification.body = body
1767 1766 notification.type_ = type_
1768 1767 notification.created_on = datetime.datetime.now()
1769 1768
1770 1769 for u in recipients:
1771 1770 assoc = UserNotification()
1772 1771 assoc.notification = notification
1773 1772 u.notifications.append(assoc)
1774 1773 Session().add(notification)
1775 1774 return notification
1776 1775
1777 1776 @property
1778 1777 def description(self):
1779 1778 from rhodecode.model.notification import NotificationModel
1780 1779 return NotificationModel().make_description(self)
1781 1780
1782 1781
1783 1782 class UserNotification(Base, BaseModel):
1784 1783 __tablename__ = 'user_to_notification'
1785 1784 __table_args__ = (
1786 1785 UniqueConstraint('user_id', 'notification_id'),
1787 1786 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1788 1787 'mysql_charset': 'utf8'}
1789 1788 )
1790 1789 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1791 1790 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1792 1791 read = Column('read', Boolean, default=False)
1793 1792 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1794 1793
1795 1794 user = relationship('User', lazy="joined")
1796 1795 notification = relationship('Notification', lazy="joined",
1797 1796 order_by=lambda: Notification.created_on.desc(),)
1798 1797
1799 1798 def mark_as_read(self):
1800 1799 self.read = True
1801 1800 Session().add(self)
1802 1801
1803 1802
1804 1803 class DbMigrateVersion(Base, BaseModel):
1805 1804 __tablename__ = 'db_migrate_version'
1806 1805 __table_args__ = (
1807 1806 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1808 1807 'mysql_charset': 'utf8'},
1809 1808 )
1810 1809 repository_id = Column('repository_id', String(250), primary_key=True)
1811 1810 repository_path = Column('repository_path', Text)
1812 1811 version = Column('version', Integer)
@@ -1,88 +1,88 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import RhodeCodeSetting
3 3 from nose.plugins.skip import SkipTest
4 4
5 5 skip_ldap_test = False
6 6 try:
7 7 import ldap
8 8 except ImportError:
9 9 # means that python-ldap is not installed
10 10 skip_ldap_test = True
11 11 pass
12 12
13
13 14 class TestLdapSettingsController(TestController):
14 15
15 16 def test_index(self):
16 17 self.log_user()
17 18 response = self.app.get(url(controller='admin/ldap_settings',
18 19 action='index'))
19 20 self.assertTrue('LDAP administration' in response.body)
20 21
21 22 def test_ldap_save_settings(self):
22 23 self.log_user()
23 24 if skip_ldap_test:
24 25 raise SkipTest('skipping due to missing ldap lib')
25 26
26 27 test_url = url(controller='admin/ldap_settings',
27 28 action='ldap_settings')
28 29
29 30 response = self.app.post(url=test_url,
30 31 params={'ldap_host' : u'dc.example.com',
31 32 'ldap_port' : '999',
32 33 'ldap_tls_kind' : 'PLAIN',
33 34 'ldap_tls_reqcert' : 'NEVER',
34 35 'ldap_dn_user':'test_user',
35 36 'ldap_dn_pass':'test_pass',
36 37 'ldap_base_dn':'test_base_dn',
37 38 'ldap_filter':'test_filter',
38 39 'ldap_search_scope':'BASE',
39 40 'ldap_attr_login':'test_attr_login',
40 41 'ldap_attr_firstname':'ima',
41 42 'ldap_attr_lastname':'tester',
42 43 'ldap_attr_email':'test@example.com' })
43 44
44 45 new_settings = RhodeCodeSetting.get_ldap_settings()
45 print new_settings
46 46 self.assertEqual(new_settings['ldap_host'], u'dc.example.com',
47 47 'fail db write compare')
48 48
49 49 self.checkSessionFlash(response,
50 50 'Ldap settings updated successfully')
51 51
52 52 def test_ldap_error_form(self):
53 53 self.log_user()
54 54 if skip_ldap_test:
55 55 raise SkipTest('skipping due to missing ldap lib')
56 56
57 57 test_url = url(controller='admin/ldap_settings',
58 58 action='ldap_settings')
59 59
60 60 response = self.app.post(url=test_url,
61 61 params={'ldap_host' : '',
62 62 'ldap_port' : 'i-should-be-number',
63 63 'ldap_tls_kind' : 'PLAIN',
64 64 'ldap_tls_reqcert' : 'NEVER',
65 65 'ldap_dn_user':'',
66 66 'ldap_dn_pass':'',
67 67 'ldap_base_dn':'',
68 68 'ldap_filter':'',
69 69 'ldap_search_scope':'BASE',
70 70 'ldap_attr_login':'', # <----- missing required input
71 71 'ldap_attr_firstname':'',
72 72 'ldap_attr_lastname':'',
73 73 'ldap_attr_email':'' })
74 74
75 75 self.assertTrue("""<span class="error-message">The LDAP Login"""
76 76 """ attribute of the CN must be specified""" in
77 77 response.body)
78 78
79 79
80 80
81 81 self.assertTrue("""<span class="error-message">Please """
82 82 """enter a number</span>""" in response.body)
83 83
84 84 def test_ldap_login(self):
85 85 pass
86 86
87 87 def test_ldap_login_incorrect(self):
88 88 pass
General Comments 0
You need to be logged in to leave comments. Login now