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