##// END OF EJS Templates
fixes for issue #731, update-repoinfo sometimes failed to update data when changesets...
marcink -
r3227:99ce5d09 beta
parent child Browse files
Show More
@@ -1,1972 +1,1976 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode, remove_suffix, remove_prefix
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 try:
122 122 id_ = int(id_)
123 123 except (TypeError, ValueError):
124 124 raise HTTPNotFound
125 125
126 126 res = cls.query().get(id_)
127 127 if not res:
128 128 raise HTTPNotFound
129 129 return res
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session().delete(obj)
139 139
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == unicode
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name in ["ldap_active",
171 171 "default_repo_enable_statistics",
172 172 "default_repo_enable_locking",
173 173 "default_repo_private",
174 174 "default_repo_enable_downloads"]:
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_unicode(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193 @classmethod
194 194 def get_by_name(cls, key):
195 195 return cls.query()\
196 196 .filter(cls.app_settings_name == key).scalar()
197 197
198 198 @classmethod
199 199 def get_by_name_or_create(cls, key):
200 200 res = cls.get_by_name(key)
201 201 if not res:
202 202 res = cls(key)
203 203 return res
204 204
205 205 @classmethod
206 206 def get_app_settings(cls, cache=False):
207 207
208 208 ret = cls.query()
209 209
210 210 if cache:
211 211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 212
213 213 if not ret:
214 214 raise Exception('Could not get application settings !')
215 215 settings = {}
216 216 for each in ret:
217 217 settings['rhodecode_' + each.app_settings_name] = \
218 218 each.app_settings_value
219 219
220 220 return settings
221 221
222 222 @classmethod
223 223 def get_ldap_settings(cls, cache=False):
224 224 ret = cls.query()\
225 225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 226 fd = {}
227 227 for row in ret:
228 228 fd.update({row.app_settings_name: row.app_settings_value})
229 229
230 230 return fd
231 231
232 232 @classmethod
233 233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 234 ret = cls.query()\
235 235 .filter(cls.app_settings_name.startswith('default_')).all()
236 236 fd = {}
237 237 for row in ret:
238 238 key = row.app_settings_name
239 239 if strip_prefix:
240 240 key = remove_prefix(key, prefix='default_')
241 241 fd.update({key: row.app_settings_value})
242 242
243 243 return fd
244 244
245 245
246 246 class RhodeCodeUi(Base, BaseModel):
247 247 __tablename__ = 'rhodecode_ui'
248 248 __table_args__ = (
249 249 UniqueConstraint('ui_key'),
250 250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 251 'mysql_charset': 'utf8'}
252 252 )
253 253
254 254 HOOK_UPDATE = 'changegroup.update'
255 255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 256 HOOK_PUSH = 'changegroup.push_logger'
257 257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 258 HOOK_PULL = 'outgoing.pull_logger'
259 259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 260
261 261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 266
267 267 @classmethod
268 268 def get_by_key(cls, key):
269 269 return cls.query().filter(cls.ui_key == key).scalar()
270 270
271 271 @classmethod
272 272 def get_builtin_hooks(cls):
273 273 q = cls.query()
274 274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 277 return q.all()
278 278
279 279 @classmethod
280 280 def get_custom_hooks(cls):
281 281 q = cls.query()
282 282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 285 q = q.filter(cls.ui_section == 'hooks')
286 286 return q.all()
287 287
288 288 @classmethod
289 289 def get_repos_location(cls):
290 290 return cls.get_by_key('/').ui_value
291 291
292 292 @classmethod
293 293 def create_or_update_hook(cls, key, val):
294 294 new_ui = cls.get_by_key(key) or cls()
295 295 new_ui.ui_section = 'hooks'
296 296 new_ui.ui_active = True
297 297 new_ui.ui_key = key
298 298 new_ui.ui_value = val
299 299
300 300 Session().add(new_ui)
301 301
302 302 def __repr__(self):
303 303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 304 self.ui_value)
305 305
306 306
307 307 class User(Base, BaseModel):
308 308 __tablename__ = 'users'
309 309 __table_args__ = (
310 310 UniqueConstraint('username'), UniqueConstraint('email'),
311 311 Index('u_username_idx', 'username'),
312 312 Index('u_email_idx', 'email'),
313 313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 314 'mysql_charset': 'utf8'}
315 315 )
316 316 DEFAULT_USER = 'default'
317 317 DEFAULT_PERMISSIONS = [
318 318 'hg.register.manual_activate', 'hg.create.repository',
319 319 'hg.fork.repository', 'repository.read', 'group.read'
320 320 ]
321 321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 333
334 334 user_log = relationship('UserLog')
335 335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 336
337 337 repositories = relationship('Repository')
338 338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 340
341 341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 343
344 344 group_member = relationship('UsersGroupMember', cascade='all')
345 345
346 346 notifications = relationship('UserNotification', cascade='all')
347 347 # notifications assigned to this user
348 348 user_created_notifications = relationship('Notification', cascade='all')
349 349 # comments created by this user
350 350 user_comments = relationship('ChangesetComment', cascade='all')
351 351 #extra emails for this user
352 352 user_emails = relationship('UserEmailMap', cascade='all')
353 353
354 354 @hybrid_property
355 355 def email(self):
356 356 return self._email
357 357
358 358 @email.setter
359 359 def email(self, val):
360 360 self._email = val.lower() if val else None
361 361
362 362 @property
363 363 def firstname(self):
364 364 # alias for future
365 365 return self.name
366 366
367 367 @property
368 368 def emails(self):
369 369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 370 return [self.email] + [x.email for x in other]
371 371
372 372 @property
373 373 def ip_addresses(self):
374 374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 375 return [x.ip_addr for x in ret]
376 376
377 377 @property
378 378 def username_and_name(self):
379 379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 380
381 381 @property
382 382 def full_name(self):
383 383 return '%s %s' % (self.firstname, self.lastname)
384 384
385 385 @property
386 386 def full_name_or_username(self):
387 387 return ('%s %s' % (self.firstname, self.lastname)
388 388 if (self.firstname and self.lastname) else self.username)
389 389
390 390 @property
391 391 def full_contact(self):
392 392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 393
394 394 @property
395 395 def short_contact(self):
396 396 return '%s %s' % (self.firstname, self.lastname)
397 397
398 398 @property
399 399 def is_admin(self):
400 400 return self.admin
401 401
402 402 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 if cs_cache != self.changeset_cache:
1006 if (cs_cache != self.changeset_cache
1007 or not self.last_change
1008 or not self.changeset_cache):
1007 1009 _default = datetime.datetime.fromtimestamp(0)
1008 1010 last_change = cs_cache.get('date') or self.last_change or _default
1009 1011 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1010 1012 self.updated_on = last_change
1011 1013 self.changeset_cache = cs_cache
1012 1014 Session().add(self)
1013 1015 Session().commit()
1016 else:
1017 log.debug('Skipping repo:%s already with latest changes' % self)
1014 1018
1015 1019 @property
1016 1020 def tip(self):
1017 1021 return self.get_changeset('tip')
1018 1022
1019 1023 @property
1020 1024 def author(self):
1021 1025 return self.tip.author
1022 1026
1023 1027 @property
1024 1028 def last_change(self):
1025 1029 return self.scm_instance.last_change
1026 1030
1027 1031 def get_comments(self, revisions=None):
1028 1032 """
1029 1033 Returns comments for this repository grouped by revisions
1030 1034
1031 1035 :param revisions: filter query by revisions only
1032 1036 """
1033 1037 cmts = ChangesetComment.query()\
1034 1038 .filter(ChangesetComment.repo == self)
1035 1039 if revisions:
1036 1040 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1037 1041 grouped = defaultdict(list)
1038 1042 for cmt in cmts.all():
1039 1043 grouped[cmt.revision].append(cmt)
1040 1044 return grouped
1041 1045
1042 1046 def statuses(self, revisions=None):
1043 1047 """
1044 1048 Returns statuses for this repository
1045 1049
1046 1050 :param revisions: list of revisions to get statuses for
1047 1051 :type revisions: list
1048 1052 """
1049 1053
1050 1054 statuses = ChangesetStatus.query()\
1051 1055 .filter(ChangesetStatus.repo == self)\
1052 1056 .filter(ChangesetStatus.version == 0)
1053 1057 if revisions:
1054 1058 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1055 1059 grouped = {}
1056 1060
1057 1061 #maybe we have open new pullrequest without a status ?
1058 1062 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1059 1063 status_lbl = ChangesetStatus.get_status_lbl(stat)
1060 1064 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1061 1065 for rev in pr.revisions:
1062 1066 pr_id = pr.pull_request_id
1063 1067 pr_repo = pr.other_repo.repo_name
1064 1068 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1065 1069
1066 1070 for stat in statuses.all():
1067 1071 pr_id = pr_repo = None
1068 1072 if stat.pull_request:
1069 1073 pr_id = stat.pull_request.pull_request_id
1070 1074 pr_repo = stat.pull_request.other_repo.repo_name
1071 1075 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1072 1076 pr_id, pr_repo]
1073 1077 return grouped
1074 1078
1075 1079 #==========================================================================
1076 1080 # SCM CACHE INSTANCE
1077 1081 #==========================================================================
1078 1082
1079 1083 @property
1080 1084 def invalidate(self):
1081 1085 return CacheInvalidation.invalidate(self.repo_name)
1082 1086
1083 1087 def set_invalidate(self):
1084 1088 """
1085 1089 set a cache for invalidation for this instance
1086 1090 """
1087 1091 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1088 1092
1089 1093 @LazyProperty
1090 1094 def scm_instance(self):
1091 1095 import rhodecode
1092 1096 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1093 1097 if full_cache:
1094 1098 return self.scm_instance_cached()
1095 1099 return self.__get_instance()
1096 1100
1097 1101 def scm_instance_cached(self, cache_map=None):
1098 1102 @cache_region('long_term')
1099 1103 def _c(repo_name):
1100 1104 return self.__get_instance()
1101 1105 rn = self.repo_name
1102 1106 log.debug('Getting cached instance of repo')
1103 1107
1104 1108 if cache_map:
1105 1109 # get using prefilled cache_map
1106 1110 invalidate_repo = cache_map[self.repo_name]
1107 1111 if invalidate_repo:
1108 1112 invalidate_repo = (None if invalidate_repo.cache_active
1109 1113 else invalidate_repo)
1110 1114 else:
1111 1115 # get from invalidate
1112 1116 invalidate_repo = self.invalidate
1113 1117
1114 1118 if invalidate_repo is not None:
1115 1119 region_invalidate(_c, None, rn)
1116 1120 # update our cache
1117 1121 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1118 1122 return _c(rn)
1119 1123
1120 1124 def __get_instance(self):
1121 1125 repo_full_path = self.repo_full_path
1122 1126 try:
1123 1127 alias = get_scm(repo_full_path)[0]
1124 1128 log.debug('Creating instance of %s repository' % alias)
1125 1129 backend = get_backend(alias)
1126 1130 except VCSError:
1127 1131 log.error(traceback.format_exc())
1128 1132 log.error('Perhaps this repository is in db and not in '
1129 1133 'filesystem run rescan repositories with '
1130 1134 '"destroy old data " option from admin panel')
1131 1135 return
1132 1136
1133 1137 if alias == 'hg':
1134 1138
1135 1139 repo = backend(safe_str(repo_full_path), create=False,
1136 1140 baseui=self._ui)
1137 1141 # skip hidden web repository
1138 1142 if repo._get_hidden():
1139 1143 return
1140 1144 else:
1141 1145 repo = backend(repo_full_path, create=False)
1142 1146
1143 1147 return repo
1144 1148
1145 1149
1146 1150 class RepoGroup(Base, BaseModel):
1147 1151 __tablename__ = 'groups'
1148 1152 __table_args__ = (
1149 1153 UniqueConstraint('group_name', 'group_parent_id'),
1150 1154 CheckConstraint('group_id != group_parent_id'),
1151 1155 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1152 1156 'mysql_charset': 'utf8'},
1153 1157 )
1154 1158 __mapper_args__ = {'order_by': 'group_name'}
1155 1159
1156 1160 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1157 1161 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1158 1162 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1159 1163 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1160 1164 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1161 1165
1162 1166 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1163 1167 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1164 1168
1165 1169 parent_group = relationship('RepoGroup', remote_side=group_id)
1166 1170
1167 1171 def __init__(self, group_name='', parent_group=None):
1168 1172 self.group_name = group_name
1169 1173 self.parent_group = parent_group
1170 1174
1171 1175 def __unicode__(self):
1172 1176 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1173 1177 self.group_name)
1174 1178
1175 1179 @classmethod
1176 1180 def groups_choices(cls, groups=None, check_perms=False, show_empty_group=True):
1177 1181 from webhelpers.html import literal as _literal
1178 1182 from rhodecode.model.scm import ScmModel
1179 1183 if not groups:
1180 1184 groups = cls.query().all()
1181 1185 if check_perms:
1182 1186 #filter group user have access to, it's done
1183 1187 #magically inside ScmModel based on current user
1184 1188 groups = ScmModel().get_repos_groups(groups)
1185 1189 repo_groups = []
1186 1190 if show_empty_group:
1187 1191 repo_groups = [('-1', '-- no parent --')]
1188 1192 sep = ' &raquo; '
1189 1193 _name = lambda k: _literal(sep.join(k))
1190 1194
1191 1195 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1192 1196 for x in groups])
1193 1197
1194 1198 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1195 1199 return repo_groups
1196 1200
1197 1201 @classmethod
1198 1202 def url_sep(cls):
1199 1203 return URL_SEP
1200 1204
1201 1205 @classmethod
1202 1206 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1203 1207 if case_insensitive:
1204 1208 gr = cls.query()\
1205 1209 .filter(cls.group_name.ilike(group_name))
1206 1210 else:
1207 1211 gr = cls.query()\
1208 1212 .filter(cls.group_name == group_name)
1209 1213 if cache:
1210 1214 gr = gr.options(FromCache(
1211 1215 "sql_cache_short",
1212 1216 "get_group_%s" % _hash_key(group_name)
1213 1217 )
1214 1218 )
1215 1219 return gr.scalar()
1216 1220
1217 1221 @property
1218 1222 def parents(self):
1219 1223 parents_recursion_limit = 5
1220 1224 groups = []
1221 1225 if self.parent_group is None:
1222 1226 return groups
1223 1227 cur_gr = self.parent_group
1224 1228 groups.insert(0, cur_gr)
1225 1229 cnt = 0
1226 1230 while 1:
1227 1231 cnt += 1
1228 1232 gr = getattr(cur_gr, 'parent_group', None)
1229 1233 cur_gr = cur_gr.parent_group
1230 1234 if gr is None:
1231 1235 break
1232 1236 if cnt == parents_recursion_limit:
1233 1237 # this will prevent accidental infinit loops
1234 1238 log.error('group nested more than %s' %
1235 1239 parents_recursion_limit)
1236 1240 break
1237 1241
1238 1242 groups.insert(0, gr)
1239 1243 return groups
1240 1244
1241 1245 @property
1242 1246 def children(self):
1243 1247 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1244 1248
1245 1249 @property
1246 1250 def name(self):
1247 1251 return self.group_name.split(RepoGroup.url_sep())[-1]
1248 1252
1249 1253 @property
1250 1254 def full_path(self):
1251 1255 return self.group_name
1252 1256
1253 1257 @property
1254 1258 def full_path_splitted(self):
1255 1259 return self.group_name.split(RepoGroup.url_sep())
1256 1260
1257 1261 @property
1258 1262 def repositories(self):
1259 1263 return Repository.query()\
1260 1264 .filter(Repository.group == self)\
1261 1265 .order_by(Repository.repo_name)
1262 1266
1263 1267 @property
1264 1268 def repositories_recursive_count(self):
1265 1269 cnt = self.repositories.count()
1266 1270
1267 1271 def children_count(group):
1268 1272 cnt = 0
1269 1273 for child in group.children:
1270 1274 cnt += child.repositories.count()
1271 1275 cnt += children_count(child)
1272 1276 return cnt
1273 1277
1274 1278 return cnt + children_count(self)
1275 1279
1276 1280 def recursive_groups_and_repos(self):
1277 1281 """
1278 1282 Recursive return all groups, with repositories in those groups
1279 1283 """
1280 1284 all_ = []
1281 1285
1282 1286 def _get_members(root_gr):
1283 1287 for r in root_gr.repositories:
1284 1288 all_.append(r)
1285 1289 childs = root_gr.children.all()
1286 1290 if childs:
1287 1291 for gr in childs:
1288 1292 all_.append(gr)
1289 1293 _get_members(gr)
1290 1294
1291 1295 _get_members(self)
1292 1296 return [self] + all_
1293 1297
1294 1298 def get_new_name(self, group_name):
1295 1299 """
1296 1300 returns new full group name based on parent and new name
1297 1301
1298 1302 :param group_name:
1299 1303 """
1300 1304 path_prefix = (self.parent_group.full_path_splitted if
1301 1305 self.parent_group else [])
1302 1306 return RepoGroup.url_sep().join(path_prefix + [group_name])
1303 1307
1304 1308
1305 1309 class Permission(Base, BaseModel):
1306 1310 __tablename__ = 'permissions'
1307 1311 __table_args__ = (
1308 1312 Index('p_perm_name_idx', 'permission_name'),
1309 1313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1310 1314 'mysql_charset': 'utf8'},
1311 1315 )
1312 1316 PERMS = [
1313 1317 ('repository.none', _('Repository no access')),
1314 1318 ('repository.read', _('Repository read access')),
1315 1319 ('repository.write', _('Repository write access')),
1316 1320 ('repository.admin', _('Repository admin access')),
1317 1321
1318 1322 ('group.none', _('Repositories Group no access')),
1319 1323 ('group.read', _('Repositories Group read access')),
1320 1324 ('group.write', _('Repositories Group write access')),
1321 1325 ('group.admin', _('Repositories Group admin access')),
1322 1326
1323 1327 ('hg.admin', _('RhodeCode Administrator')),
1324 1328 ('hg.create.none', _('Repository creation disabled')),
1325 1329 ('hg.create.repository', _('Repository creation enabled')),
1326 1330 ('hg.fork.none', _('Repository forking disabled')),
1327 1331 ('hg.fork.repository', _('Repository forking enabled')),
1328 1332 ('hg.register.none', _('Register disabled')),
1329 1333 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1330 1334 'with manual activation')),
1331 1335
1332 1336 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1333 1337 'with auto activation')),
1334 1338 ]
1335 1339
1336 1340 # defines which permissions are more important higher the more important
1337 1341 PERM_WEIGHTS = {
1338 1342 'repository.none': 0,
1339 1343 'repository.read': 1,
1340 1344 'repository.write': 3,
1341 1345 'repository.admin': 4,
1342 1346
1343 1347 'group.none': 0,
1344 1348 'group.read': 1,
1345 1349 'group.write': 3,
1346 1350 'group.admin': 4,
1347 1351
1348 1352 'hg.fork.none': 0,
1349 1353 'hg.fork.repository': 1,
1350 1354 'hg.create.none': 0,
1351 1355 'hg.create.repository':1
1352 1356 }
1353 1357
1354 1358 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1355 1359 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1356 1360 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1357 1361
1358 1362 def __unicode__(self):
1359 1363 return u"<%s('%s:%s')>" % (
1360 1364 self.__class__.__name__, self.permission_id, self.permission_name
1361 1365 )
1362 1366
1363 1367 @classmethod
1364 1368 def get_by_key(cls, key):
1365 1369 return cls.query().filter(cls.permission_name == key).scalar()
1366 1370
1367 1371 @classmethod
1368 1372 def get_default_perms(cls, default_user_id):
1369 1373 q = Session().query(UserRepoToPerm, Repository, cls)\
1370 1374 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1371 1375 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1372 1376 .filter(UserRepoToPerm.user_id == default_user_id)
1373 1377
1374 1378 return q.all()
1375 1379
1376 1380 @classmethod
1377 1381 def get_default_group_perms(cls, default_user_id):
1378 1382 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1379 1383 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1380 1384 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1381 1385 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1382 1386
1383 1387 return q.all()
1384 1388
1385 1389
1386 1390 class UserRepoToPerm(Base, BaseModel):
1387 1391 __tablename__ = 'repo_to_perm'
1388 1392 __table_args__ = (
1389 1393 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1390 1394 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1391 1395 'mysql_charset': 'utf8'}
1392 1396 )
1393 1397 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1394 1398 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1395 1399 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1396 1400 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1397 1401
1398 1402 user = relationship('User')
1399 1403 repository = relationship('Repository')
1400 1404 permission = relationship('Permission')
1401 1405
1402 1406 @classmethod
1403 1407 def create(cls, user, repository, permission):
1404 1408 n = cls()
1405 1409 n.user = user
1406 1410 n.repository = repository
1407 1411 n.permission = permission
1408 1412 Session().add(n)
1409 1413 return n
1410 1414
1411 1415 def __unicode__(self):
1412 1416 return u'<user:%s => %s >' % (self.user, self.repository)
1413 1417
1414 1418
1415 1419 class UserToPerm(Base, BaseModel):
1416 1420 __tablename__ = 'user_to_perm'
1417 1421 __table_args__ = (
1418 1422 UniqueConstraint('user_id', 'permission_id'),
1419 1423 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1420 1424 'mysql_charset': 'utf8'}
1421 1425 )
1422 1426 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1423 1427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1424 1428 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1425 1429
1426 1430 user = relationship('User')
1427 1431 permission = relationship('Permission', lazy='joined')
1428 1432
1429 1433
1430 1434 class UsersGroupRepoToPerm(Base, BaseModel):
1431 1435 __tablename__ = 'users_group_repo_to_perm'
1432 1436 __table_args__ = (
1433 1437 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1434 1438 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1435 1439 'mysql_charset': 'utf8'}
1436 1440 )
1437 1441 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1438 1442 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1439 1443 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1440 1444 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1441 1445
1442 1446 users_group = relationship('UsersGroup')
1443 1447 permission = relationship('Permission')
1444 1448 repository = relationship('Repository')
1445 1449
1446 1450 @classmethod
1447 1451 def create(cls, users_group, repository, permission):
1448 1452 n = cls()
1449 1453 n.users_group = users_group
1450 1454 n.repository = repository
1451 1455 n.permission = permission
1452 1456 Session().add(n)
1453 1457 return n
1454 1458
1455 1459 def __unicode__(self):
1456 1460 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1457 1461
1458 1462
1459 1463 class UsersGroupToPerm(Base, BaseModel):
1460 1464 __tablename__ = 'users_group_to_perm'
1461 1465 __table_args__ = (
1462 1466 UniqueConstraint('users_group_id', 'permission_id',),
1463 1467 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 1468 'mysql_charset': 'utf8'}
1465 1469 )
1466 1470 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1467 1471 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1468 1472 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1469 1473
1470 1474 users_group = relationship('UsersGroup')
1471 1475 permission = relationship('Permission')
1472 1476
1473 1477
1474 1478 class UserRepoGroupToPerm(Base, BaseModel):
1475 1479 __tablename__ = 'user_repo_group_to_perm'
1476 1480 __table_args__ = (
1477 1481 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1478 1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1479 1483 'mysql_charset': 'utf8'}
1480 1484 )
1481 1485
1482 1486 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1483 1487 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1484 1488 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1485 1489 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1486 1490
1487 1491 user = relationship('User')
1488 1492 group = relationship('RepoGroup')
1489 1493 permission = relationship('Permission')
1490 1494
1491 1495
1492 1496 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1493 1497 __tablename__ = 'users_group_repo_group_to_perm'
1494 1498 __table_args__ = (
1495 1499 UniqueConstraint('users_group_id', 'group_id'),
1496 1500 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1497 1501 'mysql_charset': 'utf8'}
1498 1502 )
1499 1503
1500 1504 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)
1501 1505 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1502 1506 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1503 1507 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1504 1508
1505 1509 users_group = relationship('UsersGroup')
1506 1510 permission = relationship('Permission')
1507 1511 group = relationship('RepoGroup')
1508 1512
1509 1513
1510 1514 class Statistics(Base, BaseModel):
1511 1515 __tablename__ = 'statistics'
1512 1516 __table_args__ = (
1513 1517 UniqueConstraint('repository_id'),
1514 1518 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1515 1519 'mysql_charset': 'utf8'}
1516 1520 )
1517 1521 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1518 1522 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1519 1523 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1520 1524 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1521 1525 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1522 1526 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1523 1527
1524 1528 repository = relationship('Repository', single_parent=True)
1525 1529
1526 1530
1527 1531 class UserFollowing(Base, BaseModel):
1528 1532 __tablename__ = 'user_followings'
1529 1533 __table_args__ = (
1530 1534 UniqueConstraint('user_id', 'follows_repository_id'),
1531 1535 UniqueConstraint('user_id', 'follows_user_id'),
1532 1536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1533 1537 'mysql_charset': 'utf8'}
1534 1538 )
1535 1539
1536 1540 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1537 1541 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1538 1542 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1539 1543 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1540 1544 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1541 1545
1542 1546 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1543 1547
1544 1548 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1545 1549 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1546 1550
1547 1551 @classmethod
1548 1552 def get_repo_followers(cls, repo_id):
1549 1553 return cls.query().filter(cls.follows_repo_id == repo_id)
1550 1554
1551 1555
1552 1556 class CacheInvalidation(Base, BaseModel):
1553 1557 __tablename__ = 'cache_invalidation'
1554 1558 __table_args__ = (
1555 1559 UniqueConstraint('cache_key'),
1556 1560 Index('key_idx', 'cache_key'),
1557 1561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1558 1562 'mysql_charset': 'utf8'},
1559 1563 )
1560 1564 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1561 1565 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1562 1566 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1563 1567 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1564 1568
1565 1569 def __init__(self, cache_key, cache_args=''):
1566 1570 self.cache_key = cache_key
1567 1571 self.cache_args = cache_args
1568 1572 self.cache_active = False
1569 1573
1570 1574 def __unicode__(self):
1571 1575 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1572 1576 self.cache_id, self.cache_key)
1573 1577
1574 1578 @property
1575 1579 def prefix(self):
1576 1580 _split = self.cache_key.split(self.cache_args, 1)
1577 1581 if _split and len(_split) == 2:
1578 1582 return _split[0]
1579 1583 return ''
1580 1584
1581 1585 @classmethod
1582 1586 def clear_cache(cls):
1583 1587 cls.query().delete()
1584 1588
1585 1589 @classmethod
1586 1590 def _get_key(cls, key):
1587 1591 """
1588 1592 Wrapper for generating a key, together with a prefix
1589 1593
1590 1594 :param key:
1591 1595 """
1592 1596 import rhodecode
1593 1597 prefix = ''
1594 1598 org_key = key
1595 1599 iid = rhodecode.CONFIG.get('instance_id')
1596 1600 if iid:
1597 1601 prefix = iid
1598 1602
1599 1603 return "%s%s" % (prefix, key), prefix, org_key
1600 1604
1601 1605 @classmethod
1602 1606 def get_by_key(cls, key):
1603 1607 return cls.query().filter(cls.cache_key == key).scalar()
1604 1608
1605 1609 @classmethod
1606 1610 def get_by_repo_name(cls, repo_name):
1607 1611 return cls.query().filter(cls.cache_args == repo_name).all()
1608 1612
1609 1613 @classmethod
1610 1614 def _get_or_create_key(cls, key, repo_name, commit=True):
1611 1615 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1612 1616 if not inv_obj:
1613 1617 try:
1614 1618 inv_obj = CacheInvalidation(key, repo_name)
1615 1619 Session().add(inv_obj)
1616 1620 if commit:
1617 1621 Session().commit()
1618 1622 except Exception:
1619 1623 log.error(traceback.format_exc())
1620 1624 Session().rollback()
1621 1625 return inv_obj
1622 1626
1623 1627 @classmethod
1624 1628 def invalidate(cls, key):
1625 1629 """
1626 1630 Returns Invalidation object if this given key should be invalidated
1627 1631 None otherwise. `cache_active = False` means that this cache
1628 1632 state is not valid and needs to be invalidated
1629 1633
1630 1634 :param key:
1631 1635 """
1632 1636 repo_name = key
1633 1637 repo_name = remove_suffix(repo_name, '_README')
1634 1638 repo_name = remove_suffix(repo_name, '_RSS')
1635 1639 repo_name = remove_suffix(repo_name, '_ATOM')
1636 1640
1637 1641 # adds instance prefix
1638 1642 key, _prefix, _org_key = cls._get_key(key)
1639 1643 inv = cls._get_or_create_key(key, repo_name)
1640 1644
1641 1645 if inv and inv.cache_active is False:
1642 1646 return inv
1643 1647
1644 1648 @classmethod
1645 1649 def set_invalidate(cls, key=None, repo_name=None):
1646 1650 """
1647 1651 Mark this Cache key for invalidation, either by key or whole
1648 1652 cache sets based on repo_name
1649 1653
1650 1654 :param key:
1651 1655 """
1652 1656 if key:
1653 1657 key, _prefix, _org_key = cls._get_key(key)
1654 1658 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1655 1659 elif repo_name:
1656 1660 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1657 1661
1658 1662 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1659 1663 % (len(inv_objs), key, repo_name))
1660 1664 try:
1661 1665 for inv_obj in inv_objs:
1662 1666 inv_obj.cache_active = False
1663 1667 Session().add(inv_obj)
1664 1668 Session().commit()
1665 1669 except Exception:
1666 1670 log.error(traceback.format_exc())
1667 1671 Session().rollback()
1668 1672
1669 1673 @classmethod
1670 1674 def set_valid(cls, key):
1671 1675 """
1672 1676 Mark this cache key as active and currently cached
1673 1677
1674 1678 :param key:
1675 1679 """
1676 1680 inv_obj = cls.get_by_key(key)
1677 1681 inv_obj.cache_active = True
1678 1682 Session().add(inv_obj)
1679 1683 Session().commit()
1680 1684
1681 1685 @classmethod
1682 1686 def get_cache_map(cls):
1683 1687
1684 1688 class cachemapdict(dict):
1685 1689
1686 1690 def __init__(self, *args, **kwargs):
1687 1691 fixkey = kwargs.get('fixkey')
1688 1692 if fixkey:
1689 1693 del kwargs['fixkey']
1690 1694 self.fixkey = fixkey
1691 1695 super(cachemapdict, self).__init__(*args, **kwargs)
1692 1696
1693 1697 def __getattr__(self, name):
1694 1698 key = name
1695 1699 if self.fixkey:
1696 1700 key, _prefix, _org_key = cls._get_key(key)
1697 1701 if key in self.__dict__:
1698 1702 return self.__dict__[key]
1699 1703 else:
1700 1704 return self[key]
1701 1705
1702 1706 def __getitem__(self, key):
1703 1707 if self.fixkey:
1704 1708 key, _prefix, _org_key = cls._get_key(key)
1705 1709 try:
1706 1710 return super(cachemapdict, self).__getitem__(key)
1707 1711 except KeyError:
1708 1712 return
1709 1713
1710 1714 cache_map = cachemapdict(fixkey=True)
1711 1715 for obj in cls.query().all():
1712 1716 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1713 1717 return cache_map
1714 1718
1715 1719
1716 1720 class ChangesetComment(Base, BaseModel):
1717 1721 __tablename__ = 'changeset_comments'
1718 1722 __table_args__ = (
1719 1723 Index('cc_revision_idx', 'revision'),
1720 1724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1721 1725 'mysql_charset': 'utf8'},
1722 1726 )
1723 1727 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1724 1728 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1725 1729 revision = Column('revision', String(40), nullable=True)
1726 1730 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1727 1731 line_no = Column('line_no', Unicode(10), nullable=True)
1728 1732 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1729 1733 f_path = Column('f_path', Unicode(1000), nullable=True)
1730 1734 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1731 1735 text = Column('text', UnicodeText(25000), nullable=False)
1732 1736 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1733 1737 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1734 1738
1735 1739 author = relationship('User', lazy='joined')
1736 1740 repo = relationship('Repository')
1737 1741 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1738 1742 pull_request = relationship('PullRequest', lazy='joined')
1739 1743
1740 1744 @classmethod
1741 1745 def get_users(cls, revision=None, pull_request_id=None):
1742 1746 """
1743 1747 Returns user associated with this ChangesetComment. ie those
1744 1748 who actually commented
1745 1749
1746 1750 :param cls:
1747 1751 :param revision:
1748 1752 """
1749 1753 q = Session().query(User)\
1750 1754 .join(ChangesetComment.author)
1751 1755 if revision:
1752 1756 q = q.filter(cls.revision == revision)
1753 1757 elif pull_request_id:
1754 1758 q = q.filter(cls.pull_request_id == pull_request_id)
1755 1759 return q.all()
1756 1760
1757 1761
1758 1762 class ChangesetStatus(Base, BaseModel):
1759 1763 __tablename__ = 'changeset_statuses'
1760 1764 __table_args__ = (
1761 1765 Index('cs_revision_idx', 'revision'),
1762 1766 Index('cs_version_idx', 'version'),
1763 1767 UniqueConstraint('repo_id', 'revision', 'version'),
1764 1768 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1765 1769 'mysql_charset': 'utf8'}
1766 1770 )
1767 1771 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1768 1772 STATUS_APPROVED = 'approved'
1769 1773 STATUS_REJECTED = 'rejected'
1770 1774 STATUS_UNDER_REVIEW = 'under_review'
1771 1775
1772 1776 STATUSES = [
1773 1777 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1774 1778 (STATUS_APPROVED, _("Approved")),
1775 1779 (STATUS_REJECTED, _("Rejected")),
1776 1780 (STATUS_UNDER_REVIEW, _("Under Review")),
1777 1781 ]
1778 1782
1779 1783 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1780 1784 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1781 1785 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1782 1786 revision = Column('revision', String(40), nullable=False)
1783 1787 status = Column('status', String(128), nullable=False, default=DEFAULT)
1784 1788 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1785 1789 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1786 1790 version = Column('version', Integer(), nullable=False, default=0)
1787 1791 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1788 1792
1789 1793 author = relationship('User', lazy='joined')
1790 1794 repo = relationship('Repository')
1791 1795 comment = relationship('ChangesetComment', lazy='joined')
1792 1796 pull_request = relationship('PullRequest', lazy='joined')
1793 1797
1794 1798 def __unicode__(self):
1795 1799 return u"<%s('%s:%s')>" % (
1796 1800 self.__class__.__name__,
1797 1801 self.status, self.author
1798 1802 )
1799 1803
1800 1804 @classmethod
1801 1805 def get_status_lbl(cls, value):
1802 1806 return dict(cls.STATUSES).get(value)
1803 1807
1804 1808 @property
1805 1809 def status_lbl(self):
1806 1810 return ChangesetStatus.get_status_lbl(self.status)
1807 1811
1808 1812
1809 1813 class PullRequest(Base, BaseModel):
1810 1814 __tablename__ = 'pull_requests'
1811 1815 __table_args__ = (
1812 1816 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1813 1817 'mysql_charset': 'utf8'},
1814 1818 )
1815 1819
1816 1820 STATUS_NEW = u'new'
1817 1821 STATUS_OPEN = u'open'
1818 1822 STATUS_CLOSED = u'closed'
1819 1823
1820 1824 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1821 1825 title = Column('title', Unicode(256), nullable=True)
1822 1826 description = Column('description', UnicodeText(10240), nullable=True)
1823 1827 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1824 1828 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1825 1829 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1826 1830 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1827 1831 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1828 1832 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1829 1833 org_ref = Column('org_ref', Unicode(256), nullable=False)
1830 1834 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1831 1835 other_ref = Column('other_ref', Unicode(256), nullable=False)
1832 1836
1833 1837 @hybrid_property
1834 1838 def revisions(self):
1835 1839 return self._revisions.split(':')
1836 1840
1837 1841 @revisions.setter
1838 1842 def revisions(self, val):
1839 1843 self._revisions = ':'.join(val)
1840 1844
1841 1845 @property
1842 1846 def org_ref_parts(self):
1843 1847 return self.org_ref.split(':')
1844 1848
1845 1849 @property
1846 1850 def other_ref_parts(self):
1847 1851 return self.other_ref.split(':')
1848 1852
1849 1853 author = relationship('User', lazy='joined')
1850 1854 reviewers = relationship('PullRequestReviewers',
1851 1855 cascade="all, delete, delete-orphan")
1852 1856 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1853 1857 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1854 1858 statuses = relationship('ChangesetStatus')
1855 1859 comments = relationship('ChangesetComment',
1856 1860 cascade="all, delete, delete-orphan")
1857 1861
1858 1862 def is_closed(self):
1859 1863 return self.status == self.STATUS_CLOSED
1860 1864
1861 1865 def __json__(self):
1862 1866 return dict(
1863 1867 revisions=self.revisions
1864 1868 )
1865 1869
1866 1870
1867 1871 class PullRequestReviewers(Base, BaseModel):
1868 1872 __tablename__ = 'pull_request_reviewers'
1869 1873 __table_args__ = (
1870 1874 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1871 1875 'mysql_charset': 'utf8'},
1872 1876 )
1873 1877
1874 1878 def __init__(self, user=None, pull_request=None):
1875 1879 self.user = user
1876 1880 self.pull_request = pull_request
1877 1881
1878 1882 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1879 1883 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1880 1884 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1881 1885
1882 1886 user = relationship('User')
1883 1887 pull_request = relationship('PullRequest')
1884 1888
1885 1889
1886 1890 class Notification(Base, BaseModel):
1887 1891 __tablename__ = 'notifications'
1888 1892 __table_args__ = (
1889 1893 Index('notification_type_idx', 'type'),
1890 1894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1891 1895 'mysql_charset': 'utf8'},
1892 1896 )
1893 1897
1894 1898 TYPE_CHANGESET_COMMENT = u'cs_comment'
1895 1899 TYPE_MESSAGE = u'message'
1896 1900 TYPE_MENTION = u'mention'
1897 1901 TYPE_REGISTRATION = u'registration'
1898 1902 TYPE_PULL_REQUEST = u'pull_request'
1899 1903 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1900 1904
1901 1905 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1902 1906 subject = Column('subject', Unicode(512), nullable=True)
1903 1907 body = Column('body', UnicodeText(50000), nullable=True)
1904 1908 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1905 1909 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1906 1910 type_ = Column('type', Unicode(256))
1907 1911
1908 1912 created_by_user = relationship('User')
1909 1913 notifications_to_users = relationship('UserNotification', lazy='joined',
1910 1914 cascade="all, delete, delete-orphan")
1911 1915
1912 1916 @property
1913 1917 def recipients(self):
1914 1918 return [x.user for x in UserNotification.query()\
1915 1919 .filter(UserNotification.notification == self)\
1916 1920 .order_by(UserNotification.user_id.asc()).all()]
1917 1921
1918 1922 @classmethod
1919 1923 def create(cls, created_by, subject, body, recipients, type_=None):
1920 1924 if type_ is None:
1921 1925 type_ = Notification.TYPE_MESSAGE
1922 1926
1923 1927 notification = cls()
1924 1928 notification.created_by_user = created_by
1925 1929 notification.subject = subject
1926 1930 notification.body = body
1927 1931 notification.type_ = type_
1928 1932 notification.created_on = datetime.datetime.now()
1929 1933
1930 1934 for u in recipients:
1931 1935 assoc = UserNotification()
1932 1936 assoc.notification = notification
1933 1937 u.notifications.append(assoc)
1934 1938 Session().add(notification)
1935 1939 return notification
1936 1940
1937 1941 @property
1938 1942 def description(self):
1939 1943 from rhodecode.model.notification import NotificationModel
1940 1944 return NotificationModel().make_description(self)
1941 1945
1942 1946
1943 1947 class UserNotification(Base, BaseModel):
1944 1948 __tablename__ = 'user_to_notification'
1945 1949 __table_args__ = (
1946 1950 UniqueConstraint('user_id', 'notification_id'),
1947 1951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1948 1952 'mysql_charset': 'utf8'}
1949 1953 )
1950 1954 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1951 1955 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1952 1956 read = Column('read', Boolean, default=False)
1953 1957 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1954 1958
1955 1959 user = relationship('User', lazy="joined")
1956 1960 notification = relationship('Notification', lazy="joined",
1957 1961 order_by=lambda: Notification.created_on.desc(),)
1958 1962
1959 1963 def mark_as_read(self):
1960 1964 self.read = True
1961 1965 Session().add(self)
1962 1966
1963 1967
1964 1968 class DbMigrateVersion(Base, BaseModel):
1965 1969 __tablename__ = 'db_migrate_version'
1966 1970 __table_args__ = (
1967 1971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1968 1972 'mysql_charset': 'utf8'},
1969 1973 )
1970 1974 repository_id = Column('repository_id', String(250), primary_key=True)
1971 1975 repository_path = Column('repository_path', Text)
1972 1976 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now