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