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