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