##// END OF EJS Templates
fixed issue with renaming repos group together with changing parents with multiple nested trees...
marcink -
r3472:702da441 default
parent child Browse files
Show More
@@ -1,1986 +1,1996 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, remove_prefix
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 in ["ldap_active",
171 171 "default_repo_enable_statistics",
172 172 "default_repo_enable_locking",
173 173 "default_repo_private",
174 174 "default_repo_enable_downloads"]:
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_unicode(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193 @classmethod
194 194 def get_by_name(cls, key):
195 195 return cls.query()\
196 196 .filter(cls.app_settings_name == key).scalar()
197 197
198 198 @classmethod
199 199 def get_by_name_or_create(cls, key):
200 200 res = cls.get_by_name(key)
201 201 if not res:
202 202 res = cls(key)
203 203 return res
204 204
205 205 @classmethod
206 206 def get_app_settings(cls, cache=False):
207 207
208 208 ret = cls.query()
209 209
210 210 if cache:
211 211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 212
213 213 if not ret:
214 214 raise Exception('Could not get application settings !')
215 215 settings = {}
216 216 for each in ret:
217 217 settings['rhodecode_' + each.app_settings_name] = \
218 218 each.app_settings_value
219 219
220 220 return settings
221 221
222 222 @classmethod
223 223 def get_ldap_settings(cls, cache=False):
224 224 ret = cls.query()\
225 225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 226 fd = {}
227 227 for row in ret:
228 228 fd.update({row.app_settings_name: row.app_settings_value})
229 229
230 230 return fd
231 231
232 232 @classmethod
233 233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 234 ret = cls.query()\
235 235 .filter(cls.app_settings_name.startswith('default_')).all()
236 236 fd = {}
237 237 for row in ret:
238 238 key = row.app_settings_name
239 239 if strip_prefix:
240 240 key = remove_prefix(key, prefix='default_')
241 241 fd.update({key: row.app_settings_value})
242 242
243 243 return fd
244 244
245 245
246 246 class RhodeCodeUi(Base, BaseModel):
247 247 __tablename__ = 'rhodecode_ui'
248 248 __table_args__ = (
249 249 UniqueConstraint('ui_key'),
250 250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 251 'mysql_charset': 'utf8'}
252 252 )
253 253
254 254 HOOK_UPDATE = 'changegroup.update'
255 255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 256 HOOK_PUSH = 'changegroup.push_logger'
257 257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 258 HOOK_PULL = 'outgoing.pull_logger'
259 259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 260
261 261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 266
267 267 @classmethod
268 268 def get_by_key(cls, key):
269 269 return cls.query().filter(cls.ui_key == key).scalar()
270 270
271 271 @classmethod
272 272 def get_builtin_hooks(cls):
273 273 q = cls.query()
274 274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 277 return q.all()
278 278
279 279 @classmethod
280 280 def get_custom_hooks(cls):
281 281 q = cls.query()
282 282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 285 q = q.filter(cls.ui_section == 'hooks')
286 286 return q.all()
287 287
288 288 @classmethod
289 289 def get_repos_location(cls):
290 290 return cls.get_by_key('/').ui_value
291 291
292 292 @classmethod
293 293 def create_or_update_hook(cls, key, val):
294 294 new_ui = cls.get_by_key(key) or cls()
295 295 new_ui.ui_section = 'hooks'
296 296 new_ui.ui_active = True
297 297 new_ui.ui_key = key
298 298 new_ui.ui_value = val
299 299
300 300 Session().add(new_ui)
301 301
302 302 def __repr__(self):
303 303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 304 self.ui_value)
305 305
306 306
307 307 class User(Base, BaseModel):
308 308 __tablename__ = 'users'
309 309 __table_args__ = (
310 310 UniqueConstraint('username'), UniqueConstraint('email'),
311 311 Index('u_username_idx', 'username'),
312 312 Index('u_email_idx', 'email'),
313 313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 314 'mysql_charset': 'utf8'}
315 315 )
316 316 DEFAULT_USER = 'default'
317 317 DEFAULT_PERMISSIONS = [
318 318 'hg.register.manual_activate', 'hg.create.repository',
319 319 'hg.fork.repository', 'repository.read', 'group.read'
320 320 ]
321 321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 333
334 334 user_log = relationship('UserLog')
335 335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 336
337 337 repositories = relationship('Repository')
338 338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 340
341 341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 343
344 344 group_member = relationship('UsersGroupMember', cascade='all')
345 345
346 346 notifications = relationship('UserNotification', cascade='all')
347 347 # notifications assigned to this user
348 348 user_created_notifications = relationship('Notification', cascade='all')
349 349 # comments created by this user
350 350 user_comments = relationship('ChangesetComment', cascade='all')
351 351 #extra emails for this user
352 352 user_emails = relationship('UserEmailMap', cascade='all')
353 353
354 354 @hybrid_property
355 355 def email(self):
356 356 return self._email
357 357
358 358 @email.setter
359 359 def email(self, val):
360 360 self._email = val.lower() if val else None
361 361
362 362 @property
363 363 def firstname(self):
364 364 # alias for future
365 365 return self.name
366 366
367 367 @property
368 368 def emails(self):
369 369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 370 return [self.email] + [x.email for x in other]
371 371
372 372 @property
373 373 def ip_addresses(self):
374 374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 375 return [x.ip_addr for x in ret]
376 376
377 377 @property
378 378 def username_and_name(self):
379 379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 380
381 381 @property
382 382 def full_name(self):
383 383 return '%s %s' % (self.firstname, self.lastname)
384 384
385 385 @property
386 386 def full_name_or_username(self):
387 387 return ('%s %s' % (self.firstname, self.lastname)
388 388 if (self.firstname and self.lastname) else self.username)
389 389
390 390 @property
391 391 def full_contact(self):
392 392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 393
394 394 @property
395 395 def short_contact(self):
396 396 return '%s %s' % (self.firstname, self.lastname)
397 397
398 398 @property
399 399 def is_admin(self):
400 400 return self.admin
401 401
402 402 @property
403 403 def AuthUser(self):
404 404 """
405 405 Returns instance of AuthUser for this user
406 406 """
407 407 from rhodecode.lib.auth import AuthUser
408 408 return AuthUser(user_id=self.user_id, api_key=self.api_key,
409 409 username=self.username)
410 410
411 411 def __unicode__(self):
412 412 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
413 413 self.user_id, self.username)
414 414
415 415 @classmethod
416 416 def get_by_username(cls, username, case_insensitive=False, cache=False):
417 417 if case_insensitive:
418 418 q = cls.query().filter(cls.username.ilike(username))
419 419 else:
420 420 q = cls.query().filter(cls.username == username)
421 421
422 422 if cache:
423 423 q = q.options(FromCache(
424 424 "sql_cache_short",
425 425 "get_user_%s" % _hash_key(username)
426 426 )
427 427 )
428 428 return q.scalar()
429 429
430 430 @classmethod
431 431 def get_by_api_key(cls, api_key, cache=False):
432 432 q = cls.query().filter(cls.api_key == api_key)
433 433
434 434 if cache:
435 435 q = q.options(FromCache("sql_cache_short",
436 436 "get_api_key_%s" % api_key))
437 437 return q.scalar()
438 438
439 439 @classmethod
440 440 def get_by_email(cls, email, case_insensitive=False, cache=False):
441 441 if case_insensitive:
442 442 q = cls.query().filter(cls.email.ilike(email))
443 443 else:
444 444 q = cls.query().filter(cls.email == email)
445 445
446 446 if cache:
447 447 q = q.options(FromCache("sql_cache_short",
448 448 "get_email_key_%s" % email))
449 449
450 450 ret = q.scalar()
451 451 if ret is None:
452 452 q = UserEmailMap.query()
453 453 # try fetching in alternate email map
454 454 if case_insensitive:
455 455 q = q.filter(UserEmailMap.email.ilike(email))
456 456 else:
457 457 q = q.filter(UserEmailMap.email == email)
458 458 q = q.options(joinedload(UserEmailMap.user))
459 459 if cache:
460 460 q = q.options(FromCache("sql_cache_short",
461 461 "get_email_map_key_%s" % email))
462 462 ret = getattr(q.scalar(), 'user', None)
463 463
464 464 return ret
465 465
466 466 @classmethod
467 467 def get_from_cs_author(cls, author):
468 468 """
469 469 Tries to get User objects out of commit author string
470 470
471 471 :param author:
472 472 """
473 473 from rhodecode.lib.helpers import email, author_name
474 474 # Valid email in the attribute passed, see if they're in the system
475 475 _email = email(author)
476 476 if _email:
477 477 user = cls.get_by_email(_email, case_insensitive=True)
478 478 if user:
479 479 return user
480 480 # Maybe we can match by username?
481 481 _author = author_name(author)
482 482 user = cls.get_by_username(_author, case_insensitive=True)
483 483 if user:
484 484 return user
485 485
486 486 def update_lastlogin(self):
487 487 """Update user lastlogin"""
488 488 self.last_login = datetime.datetime.now()
489 489 Session().add(self)
490 490 log.debug('updated user %s lastlogin' % self.username)
491 491
492 492 def get_api_data(self):
493 493 """
494 494 Common function for generating user related data for API
495 495 """
496 496 user = self
497 497 data = dict(
498 498 user_id=user.user_id,
499 499 username=user.username,
500 500 firstname=user.name,
501 501 lastname=user.lastname,
502 502 email=user.email,
503 503 emails=user.emails,
504 504 api_key=user.api_key,
505 505 active=user.active,
506 506 admin=user.admin,
507 507 ldap_dn=user.ldap_dn,
508 508 last_login=user.last_login,
509 509 ip_addresses=user.ip_addresses
510 510 )
511 511 return data
512 512
513 513 def __json__(self):
514 514 data = dict(
515 515 full_name=self.full_name,
516 516 full_name_or_username=self.full_name_or_username,
517 517 short_contact=self.short_contact,
518 518 full_contact=self.full_contact
519 519 )
520 520 data.update(self.get_api_data())
521 521 return data
522 522
523 523
524 524 class UserEmailMap(Base, BaseModel):
525 525 __tablename__ = 'user_email_map'
526 526 __table_args__ = (
527 527 Index('uem_email_idx', 'email'),
528 528 UniqueConstraint('email'),
529 529 {'extend_existing': True, 'mysql_engine': 'InnoDB',
530 530 'mysql_charset': 'utf8'}
531 531 )
532 532 __mapper_args__ = {}
533 533
534 534 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
535 535 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
536 536 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
537 537 user = relationship('User', lazy='joined')
538 538
539 539 @validates('_email')
540 540 def validate_email(self, key, email):
541 541 # check if this email is not main one
542 542 main_email = Session().query(User).filter(User.email == email).scalar()
543 543 if main_email is not None:
544 544 raise AttributeError('email %s is present is user table' % email)
545 545 return email
546 546
547 547 @hybrid_property
548 548 def email(self):
549 549 return self._email
550 550
551 551 @email.setter
552 552 def email(self, val):
553 553 self._email = val.lower() if val else None
554 554
555 555
556 556 class UserIpMap(Base, BaseModel):
557 557 __tablename__ = 'user_ip_map'
558 558 __table_args__ = (
559 559 UniqueConstraint('user_id', 'ip_addr'),
560 560 {'extend_existing': True, 'mysql_engine': 'InnoDB',
561 561 'mysql_charset': 'utf8'}
562 562 )
563 563 __mapper_args__ = {}
564 564
565 565 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
566 566 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
567 567 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
568 568 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
569 569 user = relationship('User', lazy='joined')
570 570
571 571 @classmethod
572 572 def _get_ip_range(cls, ip_addr):
573 573 from rhodecode.lib import ipaddr
574 574 net = ipaddr.IPNetwork(address=ip_addr)
575 575 return [str(net.network), str(net.broadcast)]
576 576
577 577 def __json__(self):
578 578 return dict(
579 579 ip_addr=self.ip_addr,
580 580 ip_range=self._get_ip_range(self.ip_addr)
581 581 )
582 582
583 583
584 584 class UserLog(Base, BaseModel):
585 585 __tablename__ = 'user_logs'
586 586 __table_args__ = (
587 587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
588 588 'mysql_charset': 'utf8'},
589 589 )
590 590 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
591 591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
592 592 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
593 593 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
594 594 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
595 595 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 596 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 597 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
598 598
599 599 @property
600 600 def action_as_day(self):
601 601 return datetime.date(*self.action_date.timetuple()[:3])
602 602
603 603 user = relationship('User')
604 604 repository = relationship('Repository', cascade='')
605 605
606 606
607 607 class UsersGroup(Base, BaseModel):
608 608 __tablename__ = 'users_groups'
609 609 __table_args__ = (
610 610 {'extend_existing': True, 'mysql_engine': 'InnoDB',
611 611 'mysql_charset': 'utf8'},
612 612 )
613 613
614 614 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
615 615 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
616 616 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
617 617 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
618 618
619 619 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
620 620 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
621 621 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
622 622
623 623 def __unicode__(self):
624 624 return u'<userGroup(%s)>' % (self.users_group_name)
625 625
626 626 @classmethod
627 627 def get_by_group_name(cls, group_name, cache=False,
628 628 case_insensitive=False):
629 629 if case_insensitive:
630 630 q = cls.query().filter(cls.users_group_name.ilike(group_name))
631 631 else:
632 632 q = cls.query().filter(cls.users_group_name == group_name)
633 633 if cache:
634 634 q = q.options(FromCache(
635 635 "sql_cache_short",
636 636 "get_user_%s" % _hash_key(group_name)
637 637 )
638 638 )
639 639 return q.scalar()
640 640
641 641 @classmethod
642 642 def get(cls, users_group_id, cache=False):
643 643 users_group = cls.query()
644 644 if cache:
645 645 users_group = users_group.options(FromCache("sql_cache_short",
646 646 "get_users_group_%s" % users_group_id))
647 647 return users_group.get(users_group_id)
648 648
649 649 def get_api_data(self):
650 650 users_group = self
651 651
652 652 data = dict(
653 653 users_group_id=users_group.users_group_id,
654 654 group_name=users_group.users_group_name,
655 655 active=users_group.users_group_active,
656 656 )
657 657
658 658 return data
659 659
660 660
661 661 class UsersGroupMember(Base, BaseModel):
662 662 __tablename__ = 'users_groups_members'
663 663 __table_args__ = (
664 664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
665 665 'mysql_charset': 'utf8'},
666 666 )
667 667
668 668 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
669 669 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
670 670 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
671 671
672 672 user = relationship('User', lazy='joined')
673 673 users_group = relationship('UsersGroup')
674 674
675 675 def __init__(self, gr_id='', u_id=''):
676 676 self.users_group_id = gr_id
677 677 self.user_id = u_id
678 678
679 679
680 680 class Repository(Base, BaseModel):
681 681 __tablename__ = 'repositories'
682 682 __table_args__ = (
683 683 UniqueConstraint('repo_name'),
684 684 Index('r_repo_name_idx', 'repo_name'),
685 685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
686 686 'mysql_charset': 'utf8'},
687 687 )
688 688
689 689 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
690 690 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
691 691 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
692 692 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
693 693 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
694 694 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
695 695 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
696 696 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
697 697 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
698 698 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
699 699 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
700 700 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
701 701 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
702 702 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
703 703 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
704 704
705 705 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
706 706 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
707 707
708 708 user = relationship('User')
709 709 fork = relationship('Repository', remote_side=repo_id)
710 710 group = relationship('RepoGroup')
711 711 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
712 712 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
713 713 stats = relationship('Statistics', cascade='all', uselist=False)
714 714
715 715 followers = relationship('UserFollowing',
716 716 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
717 717 cascade='all')
718 718
719 719 logs = relationship('UserLog')
720 720 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
721 721
722 722 pull_requests_org = relationship('PullRequest',
723 723 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
724 724 cascade="all, delete, delete-orphan")
725 725
726 726 pull_requests_other = relationship('PullRequest',
727 727 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
728 728 cascade="all, delete, delete-orphan")
729 729
730 730 def __unicode__(self):
731 731 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
732 732 self.repo_name)
733 733
734 734 @hybrid_property
735 735 def locked(self):
736 736 # always should return [user_id, timelocked]
737 737 if self._locked:
738 738 _lock_info = self._locked.split(':')
739 739 return int(_lock_info[0]), _lock_info[1]
740 740 return [None, None]
741 741
742 742 @locked.setter
743 743 def locked(self, val):
744 744 if val and isinstance(val, (list, tuple)):
745 745 self._locked = ':'.join(map(str, val))
746 746 else:
747 747 self._locked = None
748 748
749 749 @hybrid_property
750 750 def changeset_cache(self):
751 751 from rhodecode.lib.vcs.backends.base import EmptyChangeset
752 752 dummy = EmptyChangeset().__json__()
753 753 if not self._changeset_cache:
754 754 return dummy
755 755 try:
756 756 return json.loads(self._changeset_cache)
757 757 except TypeError:
758 758 return dummy
759 759
760 760 @changeset_cache.setter
761 761 def changeset_cache(self, val):
762 762 try:
763 763 self._changeset_cache = json.dumps(val)
764 764 except:
765 765 log.error(traceback.format_exc())
766 766
767 767 @classmethod
768 768 def url_sep(cls):
769 769 return URL_SEP
770 770
771 771 @classmethod
772 772 def normalize_repo_name(cls, repo_name):
773 773 """
774 774 Normalizes os specific repo_name to the format internally stored inside
775 775 dabatabase using URL_SEP
776 776
777 777 :param cls:
778 778 :param repo_name:
779 779 """
780 780 return cls.url_sep().join(repo_name.split(os.sep))
781 781
782 782 @classmethod
783 783 def get_by_repo_name(cls, repo_name):
784 784 q = Session().query(cls).filter(cls.repo_name == repo_name)
785 785 q = q.options(joinedload(Repository.fork))\
786 786 .options(joinedload(Repository.user))\
787 787 .options(joinedload(Repository.group))
788 788 return q.scalar()
789 789
790 790 @classmethod
791 791 def get_by_full_path(cls, repo_full_path):
792 792 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
793 793 repo_name = cls.normalize_repo_name(repo_name)
794 794 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
795 795
796 796 @classmethod
797 797 def get_repo_forks(cls, repo_id):
798 798 return cls.query().filter(Repository.fork_id == repo_id)
799 799
800 800 @classmethod
801 801 def base_path(cls):
802 802 """
803 803 Returns base path when all repos are stored
804 804
805 805 :param cls:
806 806 """
807 807 q = Session().query(RhodeCodeUi)\
808 808 .filter(RhodeCodeUi.ui_key == cls.url_sep())
809 809 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
810 810 return q.one().ui_value
811 811
812 812 @property
813 813 def forks(self):
814 814 """
815 815 Return forks of this repo
816 816 """
817 817 return Repository.get_repo_forks(self.repo_id)
818 818
819 819 @property
820 820 def parent(self):
821 821 """
822 822 Returns fork parent
823 823 """
824 824 return self.fork
825 825
826 826 @property
827 827 def just_name(self):
828 828 return self.repo_name.split(Repository.url_sep())[-1]
829 829
830 830 @property
831 831 def groups_with_parents(self):
832 832 groups = []
833 833 if self.group is None:
834 834 return groups
835 835
836 836 cur_gr = self.group
837 837 groups.insert(0, cur_gr)
838 838 while 1:
839 839 gr = getattr(cur_gr, 'parent_group', None)
840 840 cur_gr = cur_gr.parent_group
841 841 if gr is None:
842 842 break
843 843 groups.insert(0, gr)
844 844
845 845 return groups
846 846
847 847 @property
848 848 def groups_and_repo(self):
849 849 return self.groups_with_parents, self.just_name
850 850
851 851 @LazyProperty
852 852 def repo_path(self):
853 853 """
854 854 Returns base full path for that repository means where it actually
855 855 exists on a filesystem
856 856 """
857 857 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
858 858 Repository.url_sep())
859 859 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
860 860 return q.one().ui_value
861 861
862 862 @property
863 863 def repo_full_path(self):
864 864 p = [self.repo_path]
865 865 # we need to split the name by / since this is how we store the
866 866 # names in the database, but that eventually needs to be converted
867 867 # into a valid system path
868 868 p += self.repo_name.split(Repository.url_sep())
869 869 return os.path.join(*p)
870 870
871 871 @property
872 872 def cache_keys(self):
873 873 """
874 874 Returns associated cache keys for that repo
875 875 """
876 876 return CacheInvalidation.query()\
877 877 .filter(CacheInvalidation.cache_args == self.repo_name)\
878 878 .order_by(CacheInvalidation.cache_key)\
879 879 .all()
880 880
881 881 def get_new_name(self, repo_name):
882 882 """
883 883 returns new full repository name based on assigned group and new new
884 884
885 885 :param group_name:
886 886 """
887 887 path_prefix = self.group.full_path_splitted if self.group else []
888 888 return Repository.url_sep().join(path_prefix + [repo_name])
889 889
890 890 @property
891 891 def _ui(self):
892 892 """
893 893 Creates an db based ui object for this repository
894 894 """
895 895 from rhodecode.lib.utils import make_ui
896 896 return make_ui('db', clear_session=False)
897 897
898 898 @classmethod
899 899 def inject_ui(cls, repo, extras={}):
900 900 from rhodecode.lib.vcs.backends.hg import MercurialRepository
901 901 from rhodecode.lib.vcs.backends.git import GitRepository
902 902 required = (MercurialRepository, GitRepository)
903 903 if not isinstance(repo, required):
904 904 raise Exception('repo must be instance of %s' % required)
905 905
906 906 # inject ui extra param to log this action via push logger
907 907 for k, v in extras.items():
908 908 repo._repo.ui.setconfig('rhodecode_extras', k, v)
909 909
910 910 @classmethod
911 911 def is_valid(cls, repo_name):
912 912 """
913 913 returns True if given repo name is a valid filesystem repository
914 914
915 915 :param cls:
916 916 :param repo_name:
917 917 """
918 918 from rhodecode.lib.utils import is_valid_repo
919 919
920 920 return is_valid_repo(repo_name, cls.base_path())
921 921
922 922 def get_api_data(self):
923 923 """
924 924 Common function for generating repo api data
925 925
926 926 """
927 927 repo = self
928 928 data = dict(
929 929 repo_id=repo.repo_id,
930 930 repo_name=repo.repo_name,
931 931 repo_type=repo.repo_type,
932 932 clone_uri=repo.clone_uri,
933 933 private=repo.private,
934 934 created_on=repo.created_on,
935 935 description=repo.description,
936 936 landing_rev=repo.landing_rev,
937 937 owner=repo.user.username,
938 938 fork_of=repo.fork.repo_name if repo.fork else None,
939 939 enable_statistics=repo.enable_statistics,
940 940 enable_locking=repo.enable_locking,
941 941 enable_downloads=repo.enable_downloads,
942 942 last_changeset=repo.changeset_cache
943 943 )
944 944
945 945 return data
946 946
947 947 @classmethod
948 948 def lock(cls, repo, user_id):
949 949 repo.locked = [user_id, time.time()]
950 950 Session().add(repo)
951 951 Session().commit()
952 952
953 953 @classmethod
954 954 def unlock(cls, repo):
955 955 repo.locked = None
956 956 Session().add(repo)
957 957 Session().commit()
958 958
959 959 @property
960 960 def last_db_change(self):
961 961 return self.updated_on
962 962
963 963 def clone_url(self, **override):
964 964 from pylons import url
965 965 from urlparse import urlparse
966 966 import urllib
967 967 parsed_url = urlparse(url('home', qualified=True))
968 968 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
969 969 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
970 970 args = {
971 971 'user': '',
972 972 'pass': '',
973 973 'scheme': parsed_url.scheme,
974 974 'netloc': parsed_url.netloc,
975 975 'prefix': decoded_path,
976 976 'path': self.repo_name
977 977 }
978 978
979 979 args.update(override)
980 980 return default_clone_uri % args
981 981
982 982 #==========================================================================
983 983 # SCM PROPERTIES
984 984 #==========================================================================
985 985
986 986 def get_changeset(self, rev=None):
987 987 return get_changeset_safe(self.scm_instance, rev)
988 988
989 989 def get_landing_changeset(self):
990 990 """
991 991 Returns landing changeset, or if that doesn't exist returns the tip
992 992 """
993 993 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
994 994 return cs
995 995
996 996 def update_changeset_cache(self, cs_cache=None):
997 997 """
998 998 Update cache of last changeset for repository, keys should be::
999 999
1000 1000 short_id
1001 1001 raw_id
1002 1002 revision
1003 1003 message
1004 1004 date
1005 1005 author
1006 1006
1007 1007 :param cs_cache:
1008 1008 """
1009 1009 from rhodecode.lib.vcs.backends.base import BaseChangeset
1010 1010 if cs_cache is None:
1011 1011 cs_cache = self.get_changeset()
1012 1012 if isinstance(cs_cache, BaseChangeset):
1013 1013 cs_cache = cs_cache.__json__()
1014 1014
1015 1015 if (cs_cache != self.changeset_cache
1016 1016 or not self.last_change
1017 1017 or not self.changeset_cache):
1018 1018 _default = datetime.datetime.fromtimestamp(0)
1019 1019 last_change = cs_cache.get('date') or self.last_change or _default
1020 1020 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1021 1021 self.updated_on = last_change
1022 1022 self.changeset_cache = cs_cache
1023 1023 Session().add(self)
1024 1024 Session().commit()
1025 1025 else:
1026 1026 log.debug('Skipping repo:%s already with latest changes' % self)
1027 1027
1028 1028 @property
1029 1029 def tip(self):
1030 1030 return self.get_changeset('tip')
1031 1031
1032 1032 @property
1033 1033 def author(self):
1034 1034 return self.tip.author
1035 1035
1036 1036 @property
1037 1037 def last_change(self):
1038 1038 return self.scm_instance.last_change
1039 1039
1040 1040 def get_comments(self, revisions=None):
1041 1041 """
1042 1042 Returns comments for this repository grouped by revisions
1043 1043
1044 1044 :param revisions: filter query by revisions only
1045 1045 """
1046 1046 cmts = ChangesetComment.query()\
1047 1047 .filter(ChangesetComment.repo == self)
1048 1048 if revisions:
1049 1049 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1050 1050 grouped = defaultdict(list)
1051 1051 for cmt in cmts.all():
1052 1052 grouped[cmt.revision].append(cmt)
1053 1053 return grouped
1054 1054
1055 1055 def statuses(self, revisions=None):
1056 1056 """
1057 1057 Returns statuses for this repository
1058 1058
1059 1059 :param revisions: list of revisions to get statuses for
1060 1060 :type revisions: list
1061 1061 """
1062 1062
1063 1063 statuses = ChangesetStatus.query()\
1064 1064 .filter(ChangesetStatus.repo == self)\
1065 1065 .filter(ChangesetStatus.version == 0)
1066 1066 if revisions:
1067 1067 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1068 1068 grouped = {}
1069 1069
1070 1070 #maybe we have open new pullrequest without a status ?
1071 1071 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1072 1072 status_lbl = ChangesetStatus.get_status_lbl(stat)
1073 1073 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1074 1074 for rev in pr.revisions:
1075 1075 pr_id = pr.pull_request_id
1076 1076 pr_repo = pr.other_repo.repo_name
1077 1077 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1078 1078
1079 1079 for stat in statuses.all():
1080 1080 pr_id = pr_repo = None
1081 1081 if stat.pull_request:
1082 1082 pr_id = stat.pull_request.pull_request_id
1083 1083 pr_repo = stat.pull_request.other_repo.repo_name
1084 1084 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1085 1085 pr_id, pr_repo]
1086 1086 return grouped
1087 1087
1088 1088 #==========================================================================
1089 1089 # SCM CACHE INSTANCE
1090 1090 #==========================================================================
1091 1091
1092 1092 @property
1093 1093 def invalidate(self):
1094 1094 return CacheInvalidation.invalidate(self.repo_name)
1095 1095
1096 1096 def set_invalidate(self):
1097 1097 """
1098 1098 set a cache for invalidation for this instance
1099 1099 """
1100 1100 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1101 1101
1102 1102 @LazyProperty
1103 1103 def scm_instance_no_cache(self):
1104 1104 return self.__get_instance()
1105 1105
1106 1106 @LazyProperty
1107 1107 def scm_instance(self):
1108 1108 import rhodecode
1109 1109 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1110 1110 if full_cache:
1111 1111 return self.scm_instance_cached()
1112 1112 return self.__get_instance()
1113 1113
1114 1114 def scm_instance_cached(self, cache_map=None):
1115 1115 @cache_region('long_term')
1116 1116 def _c(repo_name):
1117 1117 return self.__get_instance()
1118 1118 rn = self.repo_name
1119 1119 log.debug('Getting cached instance of repo')
1120 1120
1121 1121 if cache_map:
1122 1122 # get using prefilled cache_map
1123 1123 invalidate_repo = cache_map[self.repo_name]
1124 1124 if invalidate_repo:
1125 1125 invalidate_repo = (None if invalidate_repo.cache_active
1126 1126 else invalidate_repo)
1127 1127 else:
1128 1128 # get from invalidate
1129 1129 invalidate_repo = self.invalidate
1130 1130
1131 1131 if invalidate_repo is not None:
1132 1132 region_invalidate(_c, None, rn)
1133 1133 # update our cache
1134 1134 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1135 1135 return _c(rn)
1136 1136
1137 1137 def __get_instance(self):
1138 1138 repo_full_path = self.repo_full_path
1139 1139 try:
1140 1140 alias = get_scm(repo_full_path)[0]
1141 1141 log.debug('Creating instance of %s repository' % alias)
1142 1142 backend = get_backend(alias)
1143 1143 except VCSError:
1144 1144 log.error(traceback.format_exc())
1145 1145 log.error('Perhaps this repository is in db and not in '
1146 1146 'filesystem run rescan repositories with '
1147 1147 '"destroy old data " option from admin panel')
1148 1148 return
1149 1149
1150 1150 if alias == 'hg':
1151 1151
1152 1152 repo = backend(safe_str(repo_full_path), create=False,
1153 1153 baseui=self._ui)
1154 1154 # skip hidden web repository
1155 1155 if repo._get_hidden():
1156 1156 return
1157 1157 else:
1158 1158 repo = backend(repo_full_path, create=False)
1159 1159
1160 1160 return repo
1161 1161
1162 1162
1163 1163 class RepoGroup(Base, BaseModel):
1164 1164 __tablename__ = 'groups'
1165 1165 __table_args__ = (
1166 1166 UniqueConstraint('group_name', 'group_parent_id'),
1167 1167 CheckConstraint('group_id != group_parent_id'),
1168 1168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1169 1169 'mysql_charset': 'utf8'},
1170 1170 )
1171 1171 __mapper_args__ = {'order_by': 'group_name'}
1172 1172
1173 1173 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1174 1174 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1175 1175 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1176 1176 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1177 1177 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1178 1178
1179 1179 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1180 1180 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1181 1181
1182 1182 parent_group = relationship('RepoGroup', remote_side=group_id)
1183 1183
1184 1184 def __init__(self, group_name='', parent_group=None):
1185 1185 self.group_name = group_name
1186 1186 self.parent_group = parent_group
1187 1187
1188 1188 def __unicode__(self):
1189 1189 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1190 1190 self.group_name)
1191 1191
1192 1192 @classmethod
1193 1193 def groups_choices(cls, check_perms=False):
1194 1194 from webhelpers.html import literal as _literal
1195 1195 from rhodecode.model.scm import ScmModel
1196 1196 groups = cls.query().all()
1197 1197 if check_perms:
1198 1198 #filter group user have access to, it's done
1199 1199 #magically inside ScmModel based on current user
1200 1200 groups = ScmModel().get_repos_groups(groups)
1201 1201 repo_groups = [('', '')]
1202 1202 sep = ' &raquo; '
1203 1203 _name = lambda k: _literal(sep.join(k))
1204 1204
1205 1205 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1206 1206 for x in groups])
1207 1207
1208 1208 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1209 1209 return repo_groups
1210 1210
1211 1211 @classmethod
1212 1212 def url_sep(cls):
1213 1213 return URL_SEP
1214 1214
1215 1215 @classmethod
1216 1216 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1217 1217 if case_insensitive:
1218 1218 gr = cls.query()\
1219 1219 .filter(cls.group_name.ilike(group_name))
1220 1220 else:
1221 1221 gr = cls.query()\
1222 1222 .filter(cls.group_name == group_name)
1223 1223 if cache:
1224 1224 gr = gr.options(FromCache(
1225 1225 "sql_cache_short",
1226 1226 "get_group_%s" % _hash_key(group_name)
1227 1227 )
1228 1228 )
1229 1229 return gr.scalar()
1230 1230
1231 1231 @property
1232 1232 def parents(self):
1233 1233 parents_recursion_limit = 5
1234 1234 groups = []
1235 1235 if self.parent_group is None:
1236 1236 return groups
1237 1237 cur_gr = self.parent_group
1238 1238 groups.insert(0, cur_gr)
1239 1239 cnt = 0
1240 1240 while 1:
1241 1241 cnt += 1
1242 1242 gr = getattr(cur_gr, 'parent_group', None)
1243 1243 cur_gr = cur_gr.parent_group
1244 1244 if gr is None:
1245 1245 break
1246 1246 if cnt == parents_recursion_limit:
1247 1247 # this will prevent accidental infinit loops
1248 1248 log.error('group nested more than %s' %
1249 1249 parents_recursion_limit)
1250 1250 break
1251 1251
1252 1252 groups.insert(0, gr)
1253 1253 return groups
1254 1254
1255 1255 @property
1256 1256 def children(self):
1257 1257 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1258 1258
1259 1259 @property
1260 1260 def name(self):
1261 1261 return self.group_name.split(RepoGroup.url_sep())[-1]
1262 1262
1263 1263 @property
1264 1264 def full_path(self):
1265 1265 return self.group_name
1266 1266
1267 1267 @property
1268 1268 def full_path_splitted(self):
1269 1269 return self.group_name.split(RepoGroup.url_sep())
1270 1270
1271 1271 @property
1272 1272 def repositories(self):
1273 1273 return Repository.query()\
1274 1274 .filter(Repository.group == self)\
1275 1275 .order_by(Repository.repo_name)
1276 1276
1277 1277 @property
1278 1278 def repositories_recursive_count(self):
1279 1279 cnt = self.repositories.count()
1280 1280
1281 1281 def children_count(group):
1282 1282 cnt = 0
1283 1283 for child in group.children:
1284 1284 cnt += child.repositories.count()
1285 1285 cnt += children_count(child)
1286 1286 return cnt
1287 1287
1288 1288 return cnt + children_count(self)
1289 1289
1290 def recursive_groups_and_repos(self):
1291 """
1292 Recursive return all groups, with repositories in those groups
1293 """
1290 def _recursive_objects(self, include_repos=True):
1294 1291 all_ = []
1295 1292
1296 1293 def _get_members(root_gr):
1297 for r in root_gr.repositories:
1298 all_.append(r)
1294 if include_repos:
1295 for r in root_gr.repositories:
1296 all_.append(r)
1299 1297 childs = root_gr.children.all()
1300 1298 if childs:
1301 1299 for gr in childs:
1302 1300 all_.append(gr)
1303 1301 _get_members(gr)
1304 1302
1305 1303 _get_members(self)
1306 1304 return [self] + all_
1307 1305
1306 def recursive_groups_and_repos(self):
1307 """
1308 Recursive return all groups, with repositories in those groups
1309 """
1310 return self._recursive_objects()
1311
1312 def recursive_groups(self):
1313 """
1314 Returns all children groups for this group including children of children
1315 """
1316 return self._recursive_objects(include_repos=False)
1317
1308 1318 def get_new_name(self, group_name):
1309 1319 """
1310 1320 returns new full group name based on parent and new name
1311 1321
1312 1322 :param group_name:
1313 1323 """
1314 1324 path_prefix = (self.parent_group.full_path_splitted if
1315 1325 self.parent_group else [])
1316 1326 return RepoGroup.url_sep().join(path_prefix + [group_name])
1317 1327
1318 1328
1319 1329 class Permission(Base, BaseModel):
1320 1330 __tablename__ = 'permissions'
1321 1331 __table_args__ = (
1322 1332 Index('p_perm_name_idx', 'permission_name'),
1323 1333 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1324 1334 'mysql_charset': 'utf8'},
1325 1335 )
1326 1336 PERMS = [
1327 1337 ('repository.none', _('Repository no access')),
1328 1338 ('repository.read', _('Repository read access')),
1329 1339 ('repository.write', _('Repository write access')),
1330 1340 ('repository.admin', _('Repository admin access')),
1331 1341
1332 1342 ('group.none', _('Repositories Group no access')),
1333 1343 ('group.read', _('Repositories Group read access')),
1334 1344 ('group.write', _('Repositories Group write access')),
1335 1345 ('group.admin', _('Repositories Group admin access')),
1336 1346
1337 1347 ('hg.admin', _('RhodeCode Administrator')),
1338 1348 ('hg.create.none', _('Repository creation disabled')),
1339 1349 ('hg.create.repository', _('Repository creation enabled')),
1340 1350 ('hg.fork.none', _('Repository forking disabled')),
1341 1351 ('hg.fork.repository', _('Repository forking enabled')),
1342 1352 ('hg.register.none', _('Register disabled')),
1343 1353 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1344 1354 'with manual activation')),
1345 1355
1346 1356 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1347 1357 'with auto activation')),
1348 1358 ]
1349 1359
1350 1360 # defines which permissions are more important higher the more important
1351 1361 PERM_WEIGHTS = {
1352 1362 'repository.none': 0,
1353 1363 'repository.read': 1,
1354 1364 'repository.write': 3,
1355 1365 'repository.admin': 4,
1356 1366
1357 1367 'group.none': 0,
1358 1368 'group.read': 1,
1359 1369 'group.write': 3,
1360 1370 'group.admin': 4,
1361 1371
1362 1372 'hg.fork.none': 0,
1363 1373 'hg.fork.repository': 1,
1364 1374 'hg.create.none': 0,
1365 1375 'hg.create.repository':1
1366 1376 }
1367 1377
1368 1378 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1369 1379 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1370 1380 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1371 1381
1372 1382 def __unicode__(self):
1373 1383 return u"<%s('%s:%s')>" % (
1374 1384 self.__class__.__name__, self.permission_id, self.permission_name
1375 1385 )
1376 1386
1377 1387 @classmethod
1378 1388 def get_by_key(cls, key):
1379 1389 return cls.query().filter(cls.permission_name == key).scalar()
1380 1390
1381 1391 @classmethod
1382 1392 def get_default_perms(cls, default_user_id):
1383 1393 q = Session().query(UserRepoToPerm, Repository, cls)\
1384 1394 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1385 1395 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1386 1396 .filter(UserRepoToPerm.user_id == default_user_id)
1387 1397
1388 1398 return q.all()
1389 1399
1390 1400 @classmethod
1391 1401 def get_default_group_perms(cls, default_user_id):
1392 1402 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1393 1403 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1394 1404 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1395 1405 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1396 1406
1397 1407 return q.all()
1398 1408
1399 1409
1400 1410 class UserRepoToPerm(Base, BaseModel):
1401 1411 __tablename__ = 'repo_to_perm'
1402 1412 __table_args__ = (
1403 1413 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1404 1414 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1405 1415 'mysql_charset': 'utf8'}
1406 1416 )
1407 1417 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1408 1418 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1409 1419 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1410 1420 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1411 1421
1412 1422 user = relationship('User')
1413 1423 repository = relationship('Repository')
1414 1424 permission = relationship('Permission')
1415 1425
1416 1426 @classmethod
1417 1427 def create(cls, user, repository, permission):
1418 1428 n = cls()
1419 1429 n.user = user
1420 1430 n.repository = repository
1421 1431 n.permission = permission
1422 1432 Session().add(n)
1423 1433 return n
1424 1434
1425 1435 def __unicode__(self):
1426 1436 return u'<user:%s => %s >' % (self.user, self.repository)
1427 1437
1428 1438
1429 1439 class UserToPerm(Base, BaseModel):
1430 1440 __tablename__ = 'user_to_perm'
1431 1441 __table_args__ = (
1432 1442 UniqueConstraint('user_id', 'permission_id'),
1433 1443 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1434 1444 'mysql_charset': 'utf8'}
1435 1445 )
1436 1446 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1437 1447 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1438 1448 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1439 1449
1440 1450 user = relationship('User')
1441 1451 permission = relationship('Permission', lazy='joined')
1442 1452
1443 1453
1444 1454 class UsersGroupRepoToPerm(Base, BaseModel):
1445 1455 __tablename__ = 'users_group_repo_to_perm'
1446 1456 __table_args__ = (
1447 1457 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1448 1458 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1449 1459 'mysql_charset': 'utf8'}
1450 1460 )
1451 1461 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1452 1462 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1453 1463 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1454 1464 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1455 1465
1456 1466 users_group = relationship('UsersGroup')
1457 1467 permission = relationship('Permission')
1458 1468 repository = relationship('Repository')
1459 1469
1460 1470 @classmethod
1461 1471 def create(cls, users_group, repository, permission):
1462 1472 n = cls()
1463 1473 n.users_group = users_group
1464 1474 n.repository = repository
1465 1475 n.permission = permission
1466 1476 Session().add(n)
1467 1477 return n
1468 1478
1469 1479 def __unicode__(self):
1470 1480 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1471 1481
1472 1482
1473 1483 class UsersGroupToPerm(Base, BaseModel):
1474 1484 __tablename__ = 'users_group_to_perm'
1475 1485 __table_args__ = (
1476 1486 UniqueConstraint('users_group_id', 'permission_id',),
1477 1487 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1478 1488 'mysql_charset': 'utf8'}
1479 1489 )
1480 1490 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1481 1491 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1482 1492 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1483 1493
1484 1494 users_group = relationship('UsersGroup')
1485 1495 permission = relationship('Permission')
1486 1496
1487 1497
1488 1498 class UserRepoGroupToPerm(Base, BaseModel):
1489 1499 __tablename__ = 'user_repo_group_to_perm'
1490 1500 __table_args__ = (
1491 1501 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1492 1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 1503 'mysql_charset': 'utf8'}
1494 1504 )
1495 1505
1496 1506 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1497 1507 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1498 1508 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1499 1509 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1500 1510
1501 1511 user = relationship('User')
1502 1512 group = relationship('RepoGroup')
1503 1513 permission = relationship('Permission')
1504 1514
1505 1515
1506 1516 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1507 1517 __tablename__ = 'users_group_repo_group_to_perm'
1508 1518 __table_args__ = (
1509 1519 UniqueConstraint('users_group_id', 'group_id'),
1510 1520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1511 1521 'mysql_charset': 'utf8'}
1512 1522 )
1513 1523
1514 1524 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)
1515 1525 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1516 1526 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1517 1527 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1518 1528
1519 1529 users_group = relationship('UsersGroup')
1520 1530 permission = relationship('Permission')
1521 1531 group = relationship('RepoGroup')
1522 1532
1523 1533
1524 1534 class Statistics(Base, BaseModel):
1525 1535 __tablename__ = 'statistics'
1526 1536 __table_args__ = (
1527 1537 UniqueConstraint('repository_id'),
1528 1538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1529 1539 'mysql_charset': 'utf8'}
1530 1540 )
1531 1541 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1532 1542 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1533 1543 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1534 1544 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1535 1545 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1536 1546 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1537 1547
1538 1548 repository = relationship('Repository', single_parent=True)
1539 1549
1540 1550
1541 1551 class UserFollowing(Base, BaseModel):
1542 1552 __tablename__ = 'user_followings'
1543 1553 __table_args__ = (
1544 1554 UniqueConstraint('user_id', 'follows_repository_id'),
1545 1555 UniqueConstraint('user_id', 'follows_user_id'),
1546 1556 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1547 1557 'mysql_charset': 'utf8'}
1548 1558 )
1549 1559
1550 1560 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1551 1561 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1552 1562 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1553 1563 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1554 1564 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1555 1565
1556 1566 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1557 1567
1558 1568 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1559 1569 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1560 1570
1561 1571 @classmethod
1562 1572 def get_repo_followers(cls, repo_id):
1563 1573 return cls.query().filter(cls.follows_repo_id == repo_id)
1564 1574
1565 1575
1566 1576 class CacheInvalidation(Base, BaseModel):
1567 1577 __tablename__ = 'cache_invalidation'
1568 1578 __table_args__ = (
1569 1579 UniqueConstraint('cache_key'),
1570 1580 Index('key_idx', 'cache_key'),
1571 1581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1572 1582 'mysql_charset': 'utf8'},
1573 1583 )
1574 1584 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1575 1585 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1576 1586 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1577 1587 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1578 1588
1579 1589 def __init__(self, cache_key, cache_args=''):
1580 1590 self.cache_key = cache_key
1581 1591 self.cache_args = cache_args
1582 1592 self.cache_active = False
1583 1593
1584 1594 def __unicode__(self):
1585 1595 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1586 1596 self.cache_id, self.cache_key)
1587 1597
1588 1598 @property
1589 1599 def prefix(self):
1590 1600 _split = self.cache_key.split(self.cache_args, 1)
1591 1601 if _split and len(_split) == 2:
1592 1602 return _split[0]
1593 1603 return ''
1594 1604
1595 1605 @classmethod
1596 1606 def clear_cache(cls):
1597 1607 cls.query().delete()
1598 1608
1599 1609 @classmethod
1600 1610 def _get_key(cls, key):
1601 1611 """
1602 1612 Wrapper for generating a key, together with a prefix
1603 1613
1604 1614 :param key:
1605 1615 """
1606 1616 import rhodecode
1607 1617 prefix = ''
1608 1618 org_key = key
1609 1619 iid = rhodecode.CONFIG.get('instance_id')
1610 1620 if iid:
1611 1621 prefix = iid
1612 1622
1613 1623 return "%s%s" % (prefix, key), prefix, org_key
1614 1624
1615 1625 @classmethod
1616 1626 def get_by_key(cls, key):
1617 1627 return cls.query().filter(cls.cache_key == key).scalar()
1618 1628
1619 1629 @classmethod
1620 1630 def get_by_repo_name(cls, repo_name):
1621 1631 return cls.query().filter(cls.cache_args == repo_name).all()
1622 1632
1623 1633 @classmethod
1624 1634 def _get_or_create_key(cls, key, repo_name, commit=True):
1625 1635 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1626 1636 if not inv_obj:
1627 1637 try:
1628 1638 inv_obj = CacheInvalidation(key, repo_name)
1629 1639 Session().add(inv_obj)
1630 1640 if commit:
1631 1641 Session().commit()
1632 1642 except Exception:
1633 1643 log.error(traceback.format_exc())
1634 1644 Session().rollback()
1635 1645 return inv_obj
1636 1646
1637 1647 @classmethod
1638 1648 def invalidate(cls, key):
1639 1649 """
1640 1650 Returns Invalidation object if this given key should be invalidated
1641 1651 None otherwise. `cache_active = False` means that this cache
1642 1652 state is not valid and needs to be invalidated
1643 1653
1644 1654 :param key:
1645 1655 """
1646 1656 repo_name = key
1647 1657 repo_name = remove_suffix(repo_name, '_README')
1648 1658 repo_name = remove_suffix(repo_name, '_RSS')
1649 1659 repo_name = remove_suffix(repo_name, '_ATOM')
1650 1660
1651 1661 # adds instance prefix
1652 1662 key, _prefix, _org_key = cls._get_key(key)
1653 1663 inv = cls._get_or_create_key(key, repo_name)
1654 1664
1655 1665 if inv and inv.cache_active is False:
1656 1666 return inv
1657 1667
1658 1668 @classmethod
1659 1669 def set_invalidate(cls, key=None, repo_name=None):
1660 1670 """
1661 1671 Mark this Cache key for invalidation, either by key or whole
1662 1672 cache sets based on repo_name
1663 1673
1664 1674 :param key:
1665 1675 """
1666 1676 if key:
1667 1677 key, _prefix, _org_key = cls._get_key(key)
1668 1678 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1669 1679 elif repo_name:
1670 1680 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1671 1681
1672 1682 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1673 1683 % (len(inv_objs), key, repo_name))
1674 1684 try:
1675 1685 for inv_obj in inv_objs:
1676 1686 inv_obj.cache_active = False
1677 1687 Session().add(inv_obj)
1678 1688 Session().commit()
1679 1689 except Exception:
1680 1690 log.error(traceback.format_exc())
1681 1691 Session().rollback()
1682 1692
1683 1693 @classmethod
1684 1694 def set_valid(cls, key):
1685 1695 """
1686 1696 Mark this cache key as active and currently cached
1687 1697
1688 1698 :param key:
1689 1699 """
1690 1700 inv_obj = cls.get_by_key(key)
1691 1701 inv_obj.cache_active = True
1692 1702 Session().add(inv_obj)
1693 1703 Session().commit()
1694 1704
1695 1705 @classmethod
1696 1706 def get_cache_map(cls):
1697 1707
1698 1708 class cachemapdict(dict):
1699 1709
1700 1710 def __init__(self, *args, **kwargs):
1701 1711 fixkey = kwargs.get('fixkey')
1702 1712 if fixkey:
1703 1713 del kwargs['fixkey']
1704 1714 self.fixkey = fixkey
1705 1715 super(cachemapdict, self).__init__(*args, **kwargs)
1706 1716
1707 1717 def __getattr__(self, name):
1708 1718 key = name
1709 1719 if self.fixkey:
1710 1720 key, _prefix, _org_key = cls._get_key(key)
1711 1721 if key in self.__dict__:
1712 1722 return self.__dict__[key]
1713 1723 else:
1714 1724 return self[key]
1715 1725
1716 1726 def __getitem__(self, key):
1717 1727 if self.fixkey:
1718 1728 key, _prefix, _org_key = cls._get_key(key)
1719 1729 try:
1720 1730 return super(cachemapdict, self).__getitem__(key)
1721 1731 except KeyError:
1722 1732 return
1723 1733
1724 1734 cache_map = cachemapdict(fixkey=True)
1725 1735 for obj in cls.query().all():
1726 1736 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1727 1737 return cache_map
1728 1738
1729 1739
1730 1740 class ChangesetComment(Base, BaseModel):
1731 1741 __tablename__ = 'changeset_comments'
1732 1742 __table_args__ = (
1733 1743 Index('cc_revision_idx', 'revision'),
1734 1744 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1735 1745 'mysql_charset': 'utf8'},
1736 1746 )
1737 1747 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1738 1748 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1739 1749 revision = Column('revision', String(40), nullable=True)
1740 1750 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1741 1751 line_no = Column('line_no', Unicode(10), nullable=True)
1742 1752 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1743 1753 f_path = Column('f_path', Unicode(1000), nullable=True)
1744 1754 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1745 1755 text = Column('text', UnicodeText(25000), nullable=False)
1746 1756 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1747 1757 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1748 1758
1749 1759 author = relationship('User', lazy='joined')
1750 1760 repo = relationship('Repository')
1751 1761 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1752 1762 pull_request = relationship('PullRequest', lazy='joined')
1753 1763
1754 1764 @classmethod
1755 1765 def get_users(cls, revision=None, pull_request_id=None):
1756 1766 """
1757 1767 Returns user associated with this ChangesetComment. ie those
1758 1768 who actually commented
1759 1769
1760 1770 :param cls:
1761 1771 :param revision:
1762 1772 """
1763 1773 q = Session().query(User)\
1764 1774 .join(ChangesetComment.author)
1765 1775 if revision:
1766 1776 q = q.filter(cls.revision == revision)
1767 1777 elif pull_request_id:
1768 1778 q = q.filter(cls.pull_request_id == pull_request_id)
1769 1779 return q.all()
1770 1780
1771 1781
1772 1782 class ChangesetStatus(Base, BaseModel):
1773 1783 __tablename__ = 'changeset_statuses'
1774 1784 __table_args__ = (
1775 1785 Index('cs_revision_idx', 'revision'),
1776 1786 Index('cs_version_idx', 'version'),
1777 1787 UniqueConstraint('repo_id', 'revision', 'version'),
1778 1788 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1779 1789 'mysql_charset': 'utf8'}
1780 1790 )
1781 1791 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1782 1792 STATUS_APPROVED = 'approved'
1783 1793 STATUS_REJECTED = 'rejected'
1784 1794 STATUS_UNDER_REVIEW = 'under_review'
1785 1795
1786 1796 STATUSES = [
1787 1797 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1788 1798 (STATUS_APPROVED, _("Approved")),
1789 1799 (STATUS_REJECTED, _("Rejected")),
1790 1800 (STATUS_UNDER_REVIEW, _("Under Review")),
1791 1801 ]
1792 1802
1793 1803 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1794 1804 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1795 1805 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1796 1806 revision = Column('revision', String(40), nullable=False)
1797 1807 status = Column('status', String(128), nullable=False, default=DEFAULT)
1798 1808 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1799 1809 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1800 1810 version = Column('version', Integer(), nullable=False, default=0)
1801 1811 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1802 1812
1803 1813 author = relationship('User', lazy='joined')
1804 1814 repo = relationship('Repository')
1805 1815 comment = relationship('ChangesetComment', lazy='joined')
1806 1816 pull_request = relationship('PullRequest', lazy='joined')
1807 1817
1808 1818 def __unicode__(self):
1809 1819 return u"<%s('%s:%s')>" % (
1810 1820 self.__class__.__name__,
1811 1821 self.status, self.author
1812 1822 )
1813 1823
1814 1824 @classmethod
1815 1825 def get_status_lbl(cls, value):
1816 1826 return dict(cls.STATUSES).get(value)
1817 1827
1818 1828 @property
1819 1829 def status_lbl(self):
1820 1830 return ChangesetStatus.get_status_lbl(self.status)
1821 1831
1822 1832
1823 1833 class PullRequest(Base, BaseModel):
1824 1834 __tablename__ = 'pull_requests'
1825 1835 __table_args__ = (
1826 1836 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1827 1837 'mysql_charset': 'utf8'},
1828 1838 )
1829 1839
1830 1840 STATUS_NEW = u'new'
1831 1841 STATUS_OPEN = u'open'
1832 1842 STATUS_CLOSED = u'closed'
1833 1843
1834 1844 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1835 1845 title = Column('title', Unicode(256), nullable=True)
1836 1846 description = Column('description', UnicodeText(10240), nullable=True)
1837 1847 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1838 1848 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1839 1849 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1840 1850 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1841 1851 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1842 1852 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1843 1853 org_ref = Column('org_ref', Unicode(256), nullable=False)
1844 1854 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1845 1855 other_ref = Column('other_ref', Unicode(256), nullable=False)
1846 1856
1847 1857 @hybrid_property
1848 1858 def revisions(self):
1849 1859 return self._revisions.split(':')
1850 1860
1851 1861 @revisions.setter
1852 1862 def revisions(self, val):
1853 1863 self._revisions = ':'.join(val)
1854 1864
1855 1865 @property
1856 1866 def org_ref_parts(self):
1857 1867 return self.org_ref.split(':')
1858 1868
1859 1869 @property
1860 1870 def other_ref_parts(self):
1861 1871 return self.other_ref.split(':')
1862 1872
1863 1873 author = relationship('User', lazy='joined')
1864 1874 reviewers = relationship('PullRequestReviewers',
1865 1875 cascade="all, delete, delete-orphan")
1866 1876 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1867 1877 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1868 1878 statuses = relationship('ChangesetStatus')
1869 1879 comments = relationship('ChangesetComment',
1870 1880 cascade="all, delete, delete-orphan")
1871 1881
1872 1882 def is_closed(self):
1873 1883 return self.status == self.STATUS_CLOSED
1874 1884
1875 1885 def __json__(self):
1876 1886 return dict(
1877 1887 revisions=self.revisions
1878 1888 )
1879 1889
1880 1890
1881 1891 class PullRequestReviewers(Base, BaseModel):
1882 1892 __tablename__ = 'pull_request_reviewers'
1883 1893 __table_args__ = (
1884 1894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1885 1895 'mysql_charset': 'utf8'},
1886 1896 )
1887 1897
1888 1898 def __init__(self, user=None, pull_request=None):
1889 1899 self.user = user
1890 1900 self.pull_request = pull_request
1891 1901
1892 1902 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1893 1903 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1894 1904 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1895 1905
1896 1906 user = relationship('User')
1897 1907 pull_request = relationship('PullRequest')
1898 1908
1899 1909
1900 1910 class Notification(Base, BaseModel):
1901 1911 __tablename__ = 'notifications'
1902 1912 __table_args__ = (
1903 1913 Index('notification_type_idx', 'type'),
1904 1914 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1905 1915 'mysql_charset': 'utf8'},
1906 1916 )
1907 1917
1908 1918 TYPE_CHANGESET_COMMENT = u'cs_comment'
1909 1919 TYPE_MESSAGE = u'message'
1910 1920 TYPE_MENTION = u'mention'
1911 1921 TYPE_REGISTRATION = u'registration'
1912 1922 TYPE_PULL_REQUEST = u'pull_request'
1913 1923 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1914 1924
1915 1925 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1916 1926 subject = Column('subject', Unicode(512), nullable=True)
1917 1927 body = Column('body', UnicodeText(50000), nullable=True)
1918 1928 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1919 1929 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1920 1930 type_ = Column('type', Unicode(256))
1921 1931
1922 1932 created_by_user = relationship('User')
1923 1933 notifications_to_users = relationship('UserNotification', lazy='joined',
1924 1934 cascade="all, delete, delete-orphan")
1925 1935
1926 1936 @property
1927 1937 def recipients(self):
1928 1938 return [x.user for x in UserNotification.query()\
1929 1939 .filter(UserNotification.notification == self)\
1930 1940 .order_by(UserNotification.user_id.asc()).all()]
1931 1941
1932 1942 @classmethod
1933 1943 def create(cls, created_by, subject, body, recipients, type_=None):
1934 1944 if type_ is None:
1935 1945 type_ = Notification.TYPE_MESSAGE
1936 1946
1937 1947 notification = cls()
1938 1948 notification.created_by_user = created_by
1939 1949 notification.subject = subject
1940 1950 notification.body = body
1941 1951 notification.type_ = type_
1942 1952 notification.created_on = datetime.datetime.now()
1943 1953
1944 1954 for u in recipients:
1945 1955 assoc = UserNotification()
1946 1956 assoc.notification = notification
1947 1957 u.notifications.append(assoc)
1948 1958 Session().add(notification)
1949 1959 return notification
1950 1960
1951 1961 @property
1952 1962 def description(self):
1953 1963 from rhodecode.model.notification import NotificationModel
1954 1964 return NotificationModel().make_description(self)
1955 1965
1956 1966
1957 1967 class UserNotification(Base, BaseModel):
1958 1968 __tablename__ = 'user_to_notification'
1959 1969 __table_args__ = (
1960 1970 UniqueConstraint('user_id', 'notification_id'),
1961 1971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1962 1972 'mysql_charset': 'utf8'}
1963 1973 )
1964 1974 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1965 1975 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1966 1976 read = Column('read', Boolean, default=False)
1967 1977 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1968 1978
1969 1979 user = relationship('User', lazy="joined")
1970 1980 notification = relationship('Notification', lazy="joined",
1971 1981 order_by=lambda: Notification.created_on.desc(),)
1972 1982
1973 1983 def mark_as_read(self):
1974 1984 self.read = True
1975 1985 Session().add(self)
1976 1986
1977 1987
1978 1988 class DbMigrateVersion(Base, BaseModel):
1979 1989 __tablename__ = 'db_migrate_version'
1980 1990 __table_args__ = (
1981 1991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1982 1992 'mysql_charset': 'utf8'},
1983 1993 )
1984 1994 repository_id = Column('repository_id', String(250), primary_key=True)
1985 1995 repository_path = Column('repository_path', Text)
1986 1996 version = Column('version', Integer)
@@ -1,421 +1,431 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users groups model for RhodeCode
7 7
8 8 :created_on: Jan 25, 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 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 traceback
29 29 import shutil
30 30 import datetime
31 31
32 32 from rhodecode.lib.utils2 import LazyProperty
33 33
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
36 36 User, Permission, UsersGroupRepoGroupToPerm, UsersGroup, Repository
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class ReposGroupModel(BaseModel):
42 42
43 43 cls = RepoGroup
44 44
45 45 def __get_users_group(self, users_group):
46 46 return self._get_instance(UsersGroup, users_group,
47 47 callback=UsersGroup.get_by_group_name)
48 48
49 49 def _get_repos_group(self, repos_group):
50 50 return self._get_instance(RepoGroup, repos_group,
51 51 callback=RepoGroup.get_by_group_name)
52 52
53 53 @LazyProperty
54 54 def repos_path(self):
55 55 """
56 56 Get's the repositories root path from database
57 57 """
58 58
59 59 q = RhodeCodeUi.get_by_key('/')
60 60 return q.ui_value
61 61
62 62 def _create_default_perms(self, new_group):
63 63 # create default permission
64 64 repo_group_to_perm = UserRepoGroupToPerm()
65 65 default_perm = 'group.read'
66 66 for p in User.get_by_username('default').user_perms:
67 67 if p.permission.permission_name.startswith('group.'):
68 68 default_perm = p.permission.permission_name
69 69 break
70 70
71 71 repo_group_to_perm.permission_id = self.sa.query(Permission)\
72 72 .filter(Permission.permission_name == default_perm)\
73 73 .one().permission_id
74 74
75 75 repo_group_to_perm.group = new_group
76 76 repo_group_to_perm.user_id = User.get_by_username('default').user_id
77 77
78 78 self.sa.add(repo_group_to_perm)
79 79
80 80 def __create_group(self, group_name):
81 81 """
82 82 makes repositories group on filesystem
83 83
84 84 :param repo_name:
85 85 :param parent_id:
86 86 """
87 87
88 88 create_path = os.path.join(self.repos_path, group_name)
89 89 log.debug('creating new group in %s' % create_path)
90 90
91 91 if os.path.isdir(create_path):
92 92 raise Exception('That directory already exists !')
93 93
94 94 os.makedirs(create_path)
95 95
96 96 def __rename_group(self, old, new):
97 97 """
98 98 Renames a group on filesystem
99 99
100 100 :param group_name:
101 101 """
102 102
103 103 if old == new:
104 104 log.debug('skipping group rename')
105 105 return
106 106
107 107 log.debug('renaming repos group from %s to %s' % (old, new))
108 108
109 109 old_path = os.path.join(self.repos_path, old)
110 110 new_path = os.path.join(self.repos_path, new)
111 111
112 112 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
113 113
114 114 if os.path.isdir(new_path):
115 115 raise Exception('Was trying to rename to already '
116 116 'existing dir %s' % new_path)
117 117 shutil.move(old_path, new_path)
118 118
119 119 def __delete_group(self, group, force_delete=False):
120 120 """
121 121 Deletes a group from a filesystem
122 122
123 123 :param group: instance of group from database
124 124 :param force_delete: use shutil rmtree to remove all objects
125 125 """
126 126 paths = group.full_path.split(RepoGroup.url_sep())
127 127 paths = os.sep.join(paths)
128 128
129 129 rm_path = os.path.join(self.repos_path, paths)
130 130 log.info("Removing group %s" % (rm_path))
131 131 # delete only if that path really exists
132 132 if os.path.isdir(rm_path):
133 133 if force_delete:
134 134 shutil.rmtree(rm_path)
135 135 else:
136 136 #archive that group`
137 137 _now = datetime.datetime.now()
138 138 _ms = str(_now.microsecond).rjust(6, '0')
139 139 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
140 140 group.name)
141 141 shutil.move(rm_path, os.path.join(self.repos_path, _d))
142 142
143 143 def create(self, group_name, group_description, parent=None, just_db=False):
144 144 try:
145 145 new_repos_group = RepoGroup()
146 146 new_repos_group.group_description = group_description
147 147 new_repos_group.parent_group = self._get_repos_group(parent)
148 148 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
149 149
150 150 self.sa.add(new_repos_group)
151 151 self._create_default_perms(new_repos_group)
152 152
153 153 if not just_db:
154 154 # we need to flush here, in order to check if database won't
155 155 # throw any exceptions, create filesystem dirs at the very end
156 156 self.sa.flush()
157 157 self.__create_group(new_repos_group.group_name)
158 158
159 159 return new_repos_group
160 160 except:
161 161 log.error(traceback.format_exc())
162 162 raise
163 163
164 164 def _update_permissions(self, repos_group, perms_new=None,
165 165 perms_updates=None, recursive=False):
166 166 from rhodecode.model.repo import RepoModel
167 167 if not perms_new:
168 168 perms_new = []
169 169 if not perms_updates:
170 170 perms_updates = []
171 171
172 172 def _set_perm_user(obj, user, perm):
173 173 if isinstance(obj, RepoGroup):
174 174 ReposGroupModel().grant_user_permission(
175 175 repos_group=obj, user=user, perm=perm
176 176 )
177 177 elif isinstance(obj, Repository):
178 178 #we do this ONLY IF repository is non-private
179 179 if obj.private:
180 180 return
181 181
182 182 # we set group permission but we have to switch to repo
183 183 # permission
184 184 perm = perm.replace('group.', 'repository.')
185 185 RepoModel().grant_user_permission(
186 186 repo=obj, user=user, perm=perm
187 187 )
188 188
189 189 def _set_perm_group(obj, users_group, perm):
190 190 if isinstance(obj, RepoGroup):
191 191 ReposGroupModel().grant_users_group_permission(
192 192 repos_group=obj, group_name=users_group, perm=perm
193 193 )
194 194 elif isinstance(obj, Repository):
195 195 # we set group permission but we have to switch to repo
196 196 # permission
197 197 perm = perm.replace('group.', 'repository.')
198 198 RepoModel().grant_users_group_permission(
199 199 repo=obj, group_name=users_group, perm=perm
200 200 )
201 201 updates = []
202 202 log.debug('Now updating permissions for %s in recursive mode:%s'
203 203 % (repos_group, recursive))
204 204
205 205 for obj in repos_group.recursive_groups_and_repos():
206 206 #obj is an instance of a group or repositories in that group
207 207 if not recursive:
208 208 obj = repos_group
209 209
210 210 # update permissions
211 211 for member, perm, member_type in perms_updates:
212 212 ## set for user
213 213 if member_type == 'user':
214 214 # this updates also current one if found
215 215 _set_perm_user(obj, user=member, perm=perm)
216 216 ## set for users group
217 217 else:
218 218 _set_perm_group(obj, users_group=member, perm=perm)
219 219 # set new permissions
220 220 for member, perm, member_type in perms_new:
221 221 if member_type == 'user':
222 222 _set_perm_user(obj, user=member, perm=perm)
223 223 else:
224 224 _set_perm_group(obj, users_group=member, perm=perm)
225 225 updates.append(obj)
226 226 #if it's not recursive call
227 227 # break the loop and don't proceed with other changes
228 228 if not recursive:
229 229 break
230 230 return updates
231 231
232 232 def update(self, repos_group_id, form_data):
233 233
234 234 try:
235 235 repos_group = RepoGroup.get(repos_group_id)
236 236 recursive = form_data['recursive']
237 237 # iterate over all members(if in recursive mode) of this groups and
238 238 # set the permissions !
239 239 # this can be potentially heavy operation
240 240 self._update_permissions(repos_group, form_data['perms_new'],
241 241 form_data['perms_updates'], recursive)
242 242
243 243 old_path = repos_group.full_path
244 244
245 245 # change properties
246 246 repos_group.group_description = form_data['group_description']
247 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
248 247 repos_group.group_parent_id = form_data['group_parent_id']
249 248 repos_group.enable_locking = form_data['enable_locking']
249
250 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
250 251 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
251 252 new_path = repos_group.full_path
252
253 253 self.sa.add(repos_group)
254 254
255 # iterate over all members of this groups and set the locking !
255 # iterate over all members of this groups and do fixes
256 # set locking if given
257 # if obj is a repoGroup also fix the name of the group according
258 # to the parent
259 # if obj is a Repo fix it's name
256 260 # this can be potentially heavy operation
257 261 for obj in repos_group.recursive_groups_and_repos():
258 262 #set the value from it's parent
259 263 obj.enable_locking = repos_group.enable_locking
264 if isinstance(obj, RepoGroup):
265 new_name = obj.get_new_name(obj.name)
266 log.debug('Fixing group %s to new name %s' \
267 % (obj.group_name, new_name))
268 obj.group_name = new_name
269 elif isinstance(obj, Repository):
270 # we need to get all repositories from this new group and
271 # rename them accordingly to new group path
272 new_name = obj.get_new_name(obj.just_name)
273 log.debug('Fixing repo %s to new name %s' \
274 % (obj.repo_name, new_name))
275 obj.repo_name = new_name
260 276 self.sa.add(obj)
261 277
262 # we need to get all repositories from this new group and
263 # rename them accordingly to new group path
264 for r in repos_group.repositories:
265 r.repo_name = r.get_new_name(r.just_name)
266 self.sa.add(r)
267
268 278 self.__rename_group(old_path, new_path)
269 279
270 280 return repos_group
271 281 except:
272 282 log.error(traceback.format_exc())
273 283 raise
274 284
275 285 def delete(self, repos_group, force_delete=False):
276 286 repos_group = self._get_repos_group(repos_group)
277 287 try:
278 288 self.sa.delete(repos_group)
279 289 self.__delete_group(repos_group, force_delete)
280 290 except:
281 291 log.error('Error removing repos_group %s' % repos_group)
282 292 raise
283 293
284 294 def delete_permission(self, repos_group, obj, obj_type, recursive):
285 295 """
286 296 Revokes permission for repos_group for given obj(user or users_group),
287 297 obj_type can be user or users group
288 298
289 299 :param repos_group:
290 300 :param obj: user or users group id
291 301 :param obj_type: user or users group type
292 302 :param recursive: recurse to all children of group
293 303 """
294 304 from rhodecode.model.repo import RepoModel
295 305 repos_group = self._get_repos_group(repos_group)
296 306
297 307 for el in repos_group.recursive_groups_and_repos():
298 308 if not recursive:
299 309 # if we don't recurse set the permission on only the top level
300 310 # object
301 311 el = repos_group
302 312
303 313 if isinstance(el, RepoGroup):
304 314 if obj_type == 'user':
305 315 ReposGroupModel().revoke_user_permission(el, user=obj)
306 316 elif obj_type == 'users_group':
307 317 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
308 318 else:
309 319 raise Exception('undefined object type %s' % obj_type)
310 320 elif isinstance(el, Repository):
311 321 if obj_type == 'user':
312 322 RepoModel().revoke_user_permission(el, user=obj)
313 323 elif obj_type == 'users_group':
314 324 RepoModel().revoke_users_group_permission(el, group_name=obj)
315 325 else:
316 326 raise Exception('undefined object type %s' % obj_type)
317 327
318 328 #if it's not recursive call
319 329 # break the loop and don't proceed with other changes
320 330 if not recursive:
321 331 break
322 332
323 333 def grant_user_permission(self, repos_group, user, perm):
324 334 """
325 335 Grant permission for user on given repositories group, or update
326 336 existing one if found
327 337
328 338 :param repos_group: Instance of ReposGroup, repositories_group_id,
329 339 or repositories_group name
330 340 :param user: Instance of User, user_id or username
331 341 :param perm: Instance of Permission, or permission_name
332 342 """
333 343
334 344 repos_group = self._get_repos_group(repos_group)
335 345 user = self._get_user(user)
336 346 permission = self._get_perm(perm)
337 347
338 348 # check if we have that permission already
339 349 obj = self.sa.query(UserRepoGroupToPerm)\
340 350 .filter(UserRepoGroupToPerm.user == user)\
341 351 .filter(UserRepoGroupToPerm.group == repos_group)\
342 352 .scalar()
343 353 if obj is None:
344 354 # create new !
345 355 obj = UserRepoGroupToPerm()
346 356 obj.group = repos_group
347 357 obj.user = user
348 358 obj.permission = permission
349 359 self.sa.add(obj)
350 360 log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group))
351 361
352 362 def revoke_user_permission(self, repos_group, user):
353 363 """
354 364 Revoke permission for user on given repositories group
355 365
356 366 :param repos_group: Instance of ReposGroup, repositories_group_id,
357 367 or repositories_group name
358 368 :param user: Instance of User, user_id or username
359 369 """
360 370
361 371 repos_group = self._get_repos_group(repos_group)
362 372 user = self._get_user(user)
363 373
364 374 obj = self.sa.query(UserRepoGroupToPerm)\
365 375 .filter(UserRepoGroupToPerm.user == user)\
366 376 .filter(UserRepoGroupToPerm.group == repos_group)\
367 377 .scalar()
368 378 if obj:
369 379 self.sa.delete(obj)
370 380 log.debug('Revoked perm on %s on %s' % (repos_group, user))
371 381
372 382 def grant_users_group_permission(self, repos_group, group_name, perm):
373 383 """
374 384 Grant permission for users group on given repositories group, or update
375 385 existing one if found
376 386
377 387 :param repos_group: Instance of ReposGroup, repositories_group_id,
378 388 or repositories_group name
379 389 :param group_name: Instance of UserGroup, users_group_id,
380 390 or users group name
381 391 :param perm: Instance of Permission, or permission_name
382 392 """
383 393 repos_group = self._get_repos_group(repos_group)
384 394 group_name = self.__get_users_group(group_name)
385 395 permission = self._get_perm(perm)
386 396
387 397 # check if we have that permission already
388 398 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
389 399 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
390 400 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
391 401 .scalar()
392 402
393 403 if obj is None:
394 404 # create new
395 405 obj = UsersGroupRepoGroupToPerm()
396 406
397 407 obj.group = repos_group
398 408 obj.users_group = group_name
399 409 obj.permission = permission
400 410 self.sa.add(obj)
401 411 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group))
402 412
403 413 def revoke_users_group_permission(self, repos_group, group_name):
404 414 """
405 415 Revoke permission for users group on given repositories group
406 416
407 417 :param repos_group: Instance of ReposGroup, repositories_group_id,
408 418 or repositories_group name
409 419 :param group_name: Instance of UserGroup, users_group_id,
410 420 or users group name
411 421 """
412 422 repos_group = self._get_repos_group(repos_group)
413 423 group_name = self.__get_users_group(group_name)
414 424
415 425 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
416 426 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
417 427 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
418 428 .scalar()
419 429 if obj:
420 430 self.sa.delete(obj)
421 431 log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
@@ -1,184 +1,200 b''
1 1 """Pylons application test package
2 2
3 3 This package assumes the Pylons environment is already loaded, such as
4 4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 5 command.
6 6
7 7 This module initializes the application via ``websetup`` (`paster
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 10 import os
11 11 import time
12 12 import logging
13 13 import datetime
14 14 import hashlib
15 15 import tempfile
16 16 from os.path import join as jn
17 17
18 18 from unittest import TestCase
19 19 from tempfile import _RandomNameSequence
20 20
21 21 from paste.deploy import loadapp
22 22 from paste.script.appinstall import SetupCommand
23 23 from pylons import config, url
24 24 from routes.util import URLGenerator
25 25 from webtest import TestApp
26 26
27 27 from rhodecode import is_windows
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.db import User
30 30 from rhodecode.tests.nose_parametrized import parameterized
31 31
32 32 import pylons.test
33 33
34 34
35 35 os.environ['TZ'] = 'UTC'
36 36 if not is_windows:
37 37 time.tzset()
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41 __all__ = [
42 42 'parameterized', 'environ', 'url', 'get_new_dir', 'TestController',
43 43 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO',
44 44 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
45 45 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
46 46 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
47 47 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
48 48 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
49 49 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO',
50 'GIT_REMOTE_REPO', 'SCM_TESTS', '_get_repo_create_params'
50 'GIT_REMOTE_REPO', 'SCM_TESTS', '_get_repo_create_params',
51 '_get_group_create_params'
51 52 ]
52 53
53 54 # Invoke websetup with the current config file
54 55 # SetupCommand('setup-app').run([config_file])
55 56
56 57 ##RUNNING DESIRED TESTS
57 58 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
58 59 # nosetests --pdb --pdb-failures
59 60 # nosetests --with-coverage --cover-package=rhodecode.model.validators rhodecode.tests.test_validators
60 61 environ = {}
61 62
62 63 #SOME GLOBALS FOR TESTS
63 64
64 65 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
65 66 TEST_USER_ADMIN_LOGIN = 'test_admin'
66 67 TEST_USER_ADMIN_PASS = 'test12'
67 68 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
68 69
69 70 TEST_USER_REGULAR_LOGIN = 'test_regular'
70 71 TEST_USER_REGULAR_PASS = 'test12'
71 72 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
72 73
73 74 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
74 75 TEST_USER_REGULAR2_PASS = 'test12'
75 76 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
76 77
77 78 HG_REPO = 'vcs_test_hg'
78 79 GIT_REPO = 'vcs_test_git'
79 80
80 81 NEW_HG_REPO = 'vcs_test_hg_new'
81 82 NEW_GIT_REPO = 'vcs_test_git_new'
82 83
83 84 HG_FORK = 'vcs_test_hg_fork'
84 85 GIT_FORK = 'vcs_test_git_fork'
85 86
86 87 ## VCS
87 88 SCM_TESTS = ['hg', 'git']
88 89 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
89 90
90 91 GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
91 92
92 93 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
93 94 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
94 95 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
95 96
96 97
97 98 HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
98 99
99 100 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
100 101 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
101 102 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
102 103
103 104 TEST_DIR = tempfile.gettempdir()
104 105 TEST_REPO_PREFIX = 'vcs-test'
105 106
106 107 # cached repos if any !
107 108 # comment out to get some other repos from bb or github
108 109 GIT_REMOTE_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
109 110 HG_REMOTE_REPO = jn(TESTS_TMP_PATH, HG_REPO)
110 111
111 112
112 113 def get_new_dir(title):
113 114 """
114 115 Returns always new directory path.
115 116 """
116 117 from rhodecode.tests.vcs.utils import get_normalized_path
117 118 name = TEST_REPO_PREFIX
118 119 if title:
119 120 name = '-'.join((name, title))
120 121 hex = hashlib.sha1(str(time.time())).hexdigest()
121 122 name = '-'.join((name, hex))
122 123 path = os.path.join(TEST_DIR, name)
123 124 return get_normalized_path(path)
124 125
125 126
126 127 class TestController(TestCase):
127 128
128 129 def __init__(self, *args, **kwargs):
129 130 wsgiapp = pylons.test.pylonsapp
130 131 config = wsgiapp.config
131 132
132 133 self.app = TestApp(wsgiapp)
133 134 url._push_object(URLGenerator(config['routes.map'], environ))
134 135 self.Session = Session
135 136 self.index_location = config['app_conf']['index_dir']
136 137 TestCase.__init__(self, *args, **kwargs)
137 138
138 139 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
139 140 password=TEST_USER_ADMIN_PASS):
140 141 self._logged_username = username
141 142 response = self.app.post(url(controller='login', action='index'),
142 143 {'username': username,
143 144 'password': password})
144 145
145 146 if 'invalid user name' in response.body:
146 147 self.fail('could not login using %s %s' % (username, password))
147 148
148 149 self.assertEqual(response.status, '302 Found')
149 150 ses = response.session['rhodecode_user']
150 151 self.assertEqual(ses.get('username'), username)
151 152 response = response.follow()
152 153 self.assertEqual(ses.get('is_authenticated'), True)
153 154
154 155 return response.session['rhodecode_user']
155 156
156 157 def _get_logged_user(self):
157 158 return User.get_by_username(self._logged_username)
158 159
159 160 def checkSessionFlash(self, response, msg):
160 161 self.assertTrue('flash' in response.session)
161 162 if not msg in response.session['flash'][0][1]:
162 163 self.fail(
163 164 'msg `%s` not found in session flash: got `%s` instead' % (
164 165 msg, response.session['flash'])
165 166 )
166 167
167 168
168 169 ## HELPERS ##
169 170
170 171 def _get_repo_create_params(**custom):
171 172 defs = {
172 173 'repo_name': None,
173 174 'repo_type': 'hg',
174 175 'clone_uri': '',
175 176 'repo_group': '',
176 177 'repo_description': 'DESC',
177 178 'repo_private': False,
178 179 'repo_landing_rev': 'tip'
179 180 }
180 181 defs.update(custom)
181 182 if 'repo_name_full' not in custom:
182 183 defs.update({'repo_name_full': defs['repo_name']})
183 184
184 185 return defs
186
187
188 def _get_group_create_params(**custom):
189 defs = dict(
190 group_name=None,
191 group_description='DESC',
192 group_parent_id=None,
193 perms_updates=[],
194 perms_new=[],
195 enable_locking=False,
196 recursive=False
197 )
198 defs.update(custom)
199
200 return defs
@@ -1,165 +1,242 b''
1 1 import os
2 2 import unittest
3 3 from rhodecode.tests import *
4 4
5 5 from rhodecode.model.repos_group import ReposGroupModel
6 6 from rhodecode.model.repo import RepoModel
7 7 from rhodecode.model.db import RepoGroup, User
8 8 from rhodecode.model.meta import Session
9 9 from sqlalchemy.exc import IntegrityError
10 10
11 11
12 12 def _make_group(path, desc='desc', parent_id=None,
13 13 skip_if_exists=False):
14 14
15 15 gr = RepoGroup.get_by_group_name(path)
16 16 if gr and skip_if_exists:
17 17 return gr
18 18 if isinstance(parent_id, RepoGroup):
19 19 parent_id = parent_id.group_id
20 20 gr = ReposGroupModel().create(path, desc, parent_id)
21 21 return gr
22 22
23 23
24 def _update_group(id_, group_name, desc='desc', parent_id=None):
25 form_data = _get_group_create_params(group_name=group_name,
26 group_desc=desc,
27 group_parent_id=parent_id)
28 gr = ReposGroupModel().update(id_, form_data)
29 return gr
30
31
32 def _make_repo(name, **kwargs):
33 form_data = _get_repo_create_params(repo_name=name, **kwargs)
34 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
35 r = RepoModel().create(form_data, cur_user)
36 return r
37
38
39 def _update_repo(name, **kwargs):
40 form_data = _get_repo_create_params(**kwargs)
41 if not 'repo_name' in kwargs:
42 form_data['repo_name'] = name
43 if not 'perms_new' in kwargs:
44 form_data['perms_new'] = []
45 if not 'perms_updates' in kwargs:
46 form_data['perms_updates'] = []
47 r = RepoModel().update(name, **form_data)
48 return r
49
50
24 51 class TestReposGroups(unittest.TestCase):
25 52
26 53 def setUp(self):
27 54 self.g1 = _make_group('test1', skip_if_exists=True)
28 55 Session().commit()
29 56 self.g2 = _make_group('test2', skip_if_exists=True)
30 57 Session().commit()
31 58 self.g3 = _make_group('test3', skip_if_exists=True)
32 59 Session().commit()
33 60
34 61 def tearDown(self):
35 print 'out'
62 Session.remove()
36 63
37 64 def __check_path(self, *path):
38 65 """
39 66 Checks the path for existance !
40 67 """
41 68 path = [TESTS_TMP_PATH] + list(path)
42 69 path = os.path.join(*path)
43 70 return os.path.isdir(path)
44 71
45 72 def _check_folders(self):
46 73 print os.listdir(TESTS_TMP_PATH)
47 74
48 75 def __delete_group(self, id_):
49 76 ReposGroupModel().delete(id_)
50 77
51 def __update_group(self, id_, path, desc='desc', parent_id=None):
52 form_data = dict(
53 group_name=path,
54 group_description=desc,
55 group_parent_id=parent_id,
56 perms_updates=[],
57 perms_new=[],
58 enable_locking=False,
59 recursive=False
60 )
61 gr = ReposGroupModel().update(id_, form_data)
62 return gr
63
64 78 def test_create_group(self):
65 79 g = _make_group('newGroup')
80 Session().commit()
66 81 self.assertEqual(g.full_path, 'newGroup')
67 82
68 83 self.assertTrue(self.__check_path('newGroup'))
69 84
70 85 def test_create_same_name_group(self):
71 86 self.assertRaises(IntegrityError, lambda: _make_group('newGroup'))
72 87 Session().rollback()
73 88
74 89 def test_same_subgroup(self):
75 90 sg1 = _make_group('sub1', parent_id=self.g1.group_id)
91 Session().commit()
76 92 self.assertEqual(sg1.parent_group, self.g1)
77 93 self.assertEqual(sg1.full_path, 'test1/sub1')
78 94 self.assertTrue(self.__check_path('test1', 'sub1'))
79 95
80 96 ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
97 Session().commit()
81 98 self.assertEqual(ssg1.parent_group, sg1)
82 99 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
83 100 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
84 101
85 102 def test_remove_group(self):
86 103 sg1 = _make_group('deleteme')
104 Session().commit()
87 105 self.__delete_group(sg1.group_id)
88 106
89 107 self.assertEqual(RepoGroup.get(sg1.group_id), None)
90 108 self.assertFalse(self.__check_path('deteteme'))
91 109
92 110 sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
111 Session().commit()
93 112 self.__delete_group(sg1.group_id)
94 113
95 114 self.assertEqual(RepoGroup.get(sg1.group_id), None)
96 115 self.assertFalse(self.__check_path('test1', 'deteteme'))
97 116
98 117 def test_rename_single_group(self):
99 118 sg1 = _make_group('initial')
119 Session().commit()
100 120
101 new_sg1 = self.__update_group(sg1.group_id, 'after')
121 new_sg1 = _update_group(sg1.group_id, 'after')
102 122 self.assertTrue(self.__check_path('after'))
103 123 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
104 124
105 125 def test_update_group_parent(self):
106 126
107 127 sg1 = _make_group('initial', parent_id=self.g1.group_id)
128 Session().commit()
108 129
109 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
130 new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
110 131 self.assertTrue(self.__check_path('test1', 'after'))
111 132 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
112 133
113 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
134 new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
114 135 self.assertTrue(self.__check_path('test3', 'after'))
115 136 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
116 137
117 new_sg1 = self.__update_group(sg1.group_id, 'hello')
138 new_sg1 = _update_group(sg1.group_id, 'hello')
118 139 self.assertTrue(self.__check_path('hello'))
119 140
120 141 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
121 142
122 143 def test_subgrouping_with_repo(self):
123 144
124 145 g1 = _make_group('g1')
125 146 g2 = _make_group('g2')
126
147 Session().commit()
127 148 # create new repo
128 form_data = _get_repo_create_params(repo_name='john')
129 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
130 r = RepoModel().create(form_data, cur_user)
131
149 r = _make_repo('john')
150 Session().commit()
132 151 self.assertEqual(r.repo_name, 'john')
133
134 152 # put repo into group
135 form_data = form_data
136 form_data['repo_group'] = g1.group_id
137 form_data['perms_new'] = []
138 form_data['perms_updates'] = []
139 RepoModel().update(r.repo_name, **form_data)
153 r = _update_repo('john', repo_group=g1.group_id)
154 Session().commit()
140 155 self.assertEqual(r.repo_name, 'g1/john')
141 156
142 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
157 _update_group(g1.group_id, 'g1', parent_id=g2.group_id)
143 158 self.assertTrue(self.__check_path('g2', 'g1'))
144 159
145 160 # test repo
146 161 self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1',
147 162 r.just_name]))
148 163
149 164 def test_move_to_root(self):
150 165 g1 = _make_group('t11')
151 166 Session().commit()
152 167 g2 = _make_group('t22', parent_id=g1.group_id)
153 168 Session().commit()
154 169
155 170 self.assertEqual(g2.full_path, 't11/t22')
156 171 self.assertTrue(self.__check_path('t11', 't22'))
157 172
158 g2 = self.__update_group(g2.group_id, 'g22', parent_id=None)
173 g2 = _update_group(g2.group_id, 'g22', parent_id=None)
159 174 Session().commit()
160 175
161 176 self.assertEqual(g2.group_name, 'g22')
162 177 # we moved out group from t1 to '' so it's full path should be 'g2'
163 178 self.assertEqual(g2.full_path, 'g22')
164 179 self.assertFalse(self.__check_path('t11', 't22'))
165 180 self.assertTrue(self.__check_path('g22'))
181
182 def test_rename_top_level_group_in_nested_setup(self):
183 g1 = _make_group('L1')
184 Session().commit()
185 g2 = _make_group('L2', parent_id=g1.group_id)
186 Session().commit()
187 g3 = _make_group('L3', parent_id=g2.group_id)
188 Session().commit()
189
190 r = _make_repo('L1/L2/L3/L3_REPO', repo_group=g3.group_id)
191 Session().commit()
192
193 ##rename L1 all groups should be now changed
194 _update_group(g1.group_id, 'L1_NEW')
195 Session().commit()
196 self.assertEqual(g1.full_path, 'L1_NEW')
197 self.assertEqual(g2.full_path, 'L1_NEW/L2')
198 self.assertEqual(g3.full_path, 'L1_NEW/L2/L3')
199 self.assertEqual(r.repo_name, 'L1_NEW/L2/L3/L3_REPO')
200
201 def test_change_parent_of_top_level_group_in_nested_setup(self):
202 g1 = _make_group('R1')
203 Session().commit()
204 g2 = _make_group('R2', parent_id=g1.group_id)
205 Session().commit()
206 g3 = _make_group('R3', parent_id=g2.group_id)
207 Session().commit()
208
209 g4 = _make_group('R1_NEW')
210 Session().commit()
211
212 r = _make_repo('R1/R2/R3/R3_REPO', repo_group=g3.group_id)
213 Session().commit()
214 ##rename L1 all groups should be now changed
215 _update_group(g1.group_id, 'R1', parent_id=g4.group_id)
216 Session().commit()
217 self.assertEqual(g1.full_path, 'R1_NEW/R1')
218 self.assertEqual(g2.full_path, 'R1_NEW/R1/R2')
219 self.assertEqual(g3.full_path, 'R1_NEW/R1/R2/R3')
220 self.assertEqual(r.repo_name, 'R1_NEW/R1/R2/R3/R3_REPO')
221
222 def test_change_parent_of_top_level_group_in_nested_setup_with_rename(self):
223 g1 = _make_group('X1')
224 Session().commit()
225 g2 = _make_group('X2', parent_id=g1.group_id)
226 Session().commit()
227 g3 = _make_group('X3', parent_id=g2.group_id)
228 Session().commit()
229
230 g4 = _make_group('X1_NEW')
231 Session().commit()
232
233 r = _make_repo('X1/X2/X3/X3_REPO', repo_group=g3.group_id)
234 Session().commit()
235
236 ##rename L1 all groups should be now changed
237 _update_group(g1.group_id, 'X1_PRIM', parent_id=g4.group_id)
238 Session().commit()
239 self.assertEqual(g1.full_path, 'X1_NEW/X1_PRIM')
240 self.assertEqual(g2.full_path, 'X1_NEW/X1_PRIM/X2')
241 self.assertEqual(g3.full_path, 'X1_NEW/X1_PRIM/X2/X3')
242 self.assertEqual(r.repo_name, 'X1_NEW/X1_PRIM/X2/X3/X3_REPO')
General Comments 0
You need to be logged in to leave comments. Login now