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