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