##// END OF EJS Templates
caches: use optional identity cache options on get_by_repo_name and get_by_username...
marcink -
r255:a979e37d default
parent child Browse files
Show More
@@ -1,3437 +1,3462 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database Models for RhodeCode Enterprise
23 23 """
24 24
25 25 import os
26 26 import sys
27 27 import time
28 28 import hashlib
29 29 import logging
30 30 import datetime
31 31 import warnings
32 32 import ipaddress
33 33 import functools
34 34 import traceback
35 35 import collections
36 36
37 37
38 38 from sqlalchemy import *
39 39 from sqlalchemy.exc import IntegrityError
40 40 from sqlalchemy.ext.declarative import declared_attr
41 41 from sqlalchemy.ext.hybrid import hybrid_property
42 42 from sqlalchemy.orm import (
43 43 relationship, joinedload, class_mapper, validates, aliased)
44 44 from sqlalchemy.sql.expression import true
45 45 from beaker.cache import cache_region, region_invalidate
46 46 from webob.exc import HTTPNotFound
47 47 from zope.cachedescriptors.property import Lazy as LazyProperty
48 48
49 49 from pylons import url
50 50 from pylons.i18n.translation import lazy_ugettext as _
51 51
52 52 from rhodecode.lib.vcs import get_backend
53 53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 54 from rhodecode.lib.vcs.exceptions import VCSError
55 55 from rhodecode.lib.vcs.backends.base import (
56 56 EmptyCommit, Reference, MergeFailureReason)
57 57 from rhodecode.lib.utils2 import (
58 58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 60 from rhodecode.lib.ext_json import json
61 61 from rhodecode.lib.caching_query import FromCache
62 62 from rhodecode.lib.encrypt import AESCipher
63 63
64 64 from rhodecode.model.meta import Base, Session
65 65
66 66 URL_SEP = '/'
67 67 log = logging.getLogger(__name__)
68 68
69 69 # =============================================================================
70 70 # BASE CLASSES
71 71 # =============================================================================
72 72
73 73 # this is propagated from .ini file beaker.session.secret
74 74 # and initialized at environment.py
75 75 ENCRYPTION_KEY = None
76 76
77 77 # used to sort permissions by types, '#' used here is not allowed to be in
78 78 # usernames, and it's very early in sorted string.printable table.
79 79 PERMISSION_TYPE_SORT = {
80 80 'admin': '####',
81 81 'write': '###',
82 82 'read': '##',
83 83 'none': '#',
84 84 }
85 85
86 86
87 87 def display_sort(obj):
88 88 """
89 89 Sort function used to sort permissions in .permissions() function of
90 90 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 91 of all other resources
92 92 """
93 93
94 94 if obj.username == User.DEFAULT_USER:
95 95 return '#####'
96 96 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 97 return prefix + obj.username
98 98
99 99
100 100 def _hash_key(k):
101 101 return md5_safe(k)
102 102
103 103
104 104 class EncryptedTextValue(TypeDecorator):
105 105 """
106 106 Special column for encrypted long text data, use like::
107 107
108 108 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109 109
110 110 This column is intelligent so if value is in unencrypted form it return
111 111 unencrypted form, but on save it always encrypts
112 112 """
113 113 impl = Text
114 114
115 115 def process_bind_param(self, value, dialect):
116 116 if not value:
117 117 return value
118 118 if value.startswith('enc$aes$'):
119 119 # protect against double encrypting if someone manually starts
120 120 # doing
121 121 raise ValueError('value needs to be in unencrypted format, ie. '
122 122 'not starting with enc$aes$')
123 123 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
124 124
125 125 def process_result_value(self, value, dialect):
126 126 if not value:
127 127 return value
128 128
129 129 parts = value.split('$', 3)
130 130 if not len(parts) == 3:
131 131 # probably not encrypted values
132 132 return value
133 133 else:
134 134 if parts[0] != 'enc':
135 135 # parts ok but without our header ?
136 136 return value
137 137
138 138 # at that stage we know it's our encryption
139 139 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
140 140 return decrypted_data
141 141
142 142
143 143 class BaseModel(object):
144 144 """
145 145 Base Model for all classes
146 146 """
147 147
148 148 @classmethod
149 149 def _get_keys(cls):
150 150 """return column names for this model """
151 151 return class_mapper(cls).c.keys()
152 152
153 153 def get_dict(self):
154 154 """
155 155 return dict with keys and values corresponding
156 156 to this model data """
157 157
158 158 d = {}
159 159 for k in self._get_keys():
160 160 d[k] = getattr(self, k)
161 161
162 162 # also use __json__() if present to get additional fields
163 163 _json_attr = getattr(self, '__json__', None)
164 164 if _json_attr:
165 165 # update with attributes from __json__
166 166 if callable(_json_attr):
167 167 _json_attr = _json_attr()
168 168 for k, val in _json_attr.iteritems():
169 169 d[k] = val
170 170 return d
171 171
172 172 def get_appstruct(self):
173 173 """return list with keys and values tuples corresponding
174 174 to this model data """
175 175
176 176 l = []
177 177 for k in self._get_keys():
178 178 l.append((k, getattr(self, k),))
179 179 return l
180 180
181 181 def populate_obj(self, populate_dict):
182 182 """populate model with data from given populate_dict"""
183 183
184 184 for k in self._get_keys():
185 185 if k in populate_dict:
186 186 setattr(self, k, populate_dict[k])
187 187
188 188 @classmethod
189 189 def query(cls):
190 190 return Session().query(cls)
191 191
192 192 @classmethod
193 193 def get(cls, id_):
194 194 if id_:
195 195 return cls.query().get(id_)
196 196
197 197 @classmethod
198 198 def get_or_404(cls, id_):
199 199 try:
200 200 id_ = int(id_)
201 201 except (TypeError, ValueError):
202 202 raise HTTPNotFound
203 203
204 204 res = cls.query().get(id_)
205 205 if not res:
206 206 raise HTTPNotFound
207 207 return res
208 208
209 209 @classmethod
210 210 def getAll(cls):
211 211 # deprecated and left for backward compatibility
212 212 return cls.get_all()
213 213
214 214 @classmethod
215 215 def get_all(cls):
216 216 return cls.query().all()
217 217
218 218 @classmethod
219 219 def delete(cls, id_):
220 220 obj = cls.query().get(id_)
221 221 Session().delete(obj)
222 222
223 @classmethod
224 def identity_cache(cls, session, attr_name, value):
225 exist_in_session = []
226 for (item_cls, pkey), instance in session.identity_map.items():
227 if cls == item_cls and getattr(instance, attr_name) == value:
228 exist_in_session.append(instance)
229 if exist_in_session:
230 if len(exist_in_session) == 1:
231 return exist_in_session[0]
232 log.exception(
233 'multiple objects with attr %s and '
234 'value %s found with same name: %r',
235 attr_name, value, exist_in_session)
236
223 237 def __repr__(self):
224 238 if hasattr(self, '__unicode__'):
225 239 # python repr needs to return str
226 240 try:
227 241 return safe_str(self.__unicode__())
228 242 except UnicodeDecodeError:
229 243 pass
230 244 return '<DB:%s>' % (self.__class__.__name__)
231 245
232 246
233 247 class RhodeCodeSetting(Base, BaseModel):
234 248 __tablename__ = 'rhodecode_settings'
235 249 __table_args__ = (
236 250 UniqueConstraint('app_settings_name'),
237 251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
238 252 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
239 253 )
240 254
241 255 SETTINGS_TYPES = {
242 256 'str': safe_str,
243 257 'int': safe_int,
244 258 'unicode': safe_unicode,
245 259 'bool': str2bool,
246 260 'list': functools.partial(aslist, sep=',')
247 261 }
248 262 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
249 263 GLOBAL_CONF_KEY = 'app_settings'
250 264
251 265 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
252 266 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
253 267 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
254 268 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
255 269
256 270 def __init__(self, key='', val='', type='unicode'):
257 271 self.app_settings_name = key
258 272 self.app_settings_type = type
259 273 self.app_settings_value = val
260 274
261 275 @validates('_app_settings_value')
262 276 def validate_settings_value(self, key, val):
263 277 assert type(val) == unicode
264 278 return val
265 279
266 280 @hybrid_property
267 281 def app_settings_value(self):
268 282 v = self._app_settings_value
269 283 _type = self.app_settings_type
270 284 if _type:
271 285 _type = self.app_settings_type.split('.')[0]
272 286 # decode the encrypted value
273 287 if 'encrypted' in self.app_settings_type:
274 288 cipher = EncryptedTextValue()
275 289 v = safe_unicode(cipher.process_result_value(v, None))
276 290
277 291 converter = self.SETTINGS_TYPES.get(_type) or \
278 292 self.SETTINGS_TYPES['unicode']
279 293 return converter(v)
280 294
281 295 @app_settings_value.setter
282 296 def app_settings_value(self, val):
283 297 """
284 298 Setter that will always make sure we use unicode in app_settings_value
285 299
286 300 :param val:
287 301 """
288 302 val = safe_unicode(val)
289 303 # encode the encrypted value
290 304 if 'encrypted' in self.app_settings_type:
291 305 cipher = EncryptedTextValue()
292 306 val = safe_unicode(cipher.process_bind_param(val, None))
293 307 self._app_settings_value = val
294 308
295 309 @hybrid_property
296 310 def app_settings_type(self):
297 311 return self._app_settings_type
298 312
299 313 @app_settings_type.setter
300 314 def app_settings_type(self, val):
301 315 if val.split('.')[0] not in self.SETTINGS_TYPES:
302 316 raise Exception('type must be one of %s got %s'
303 317 % (self.SETTINGS_TYPES.keys(), val))
304 318 self._app_settings_type = val
305 319
306 320 def __unicode__(self):
307 321 return u"<%s('%s:%s[%s]')>" % (
308 322 self.__class__.__name__,
309 323 self.app_settings_name, self.app_settings_value,
310 324 self.app_settings_type
311 325 )
312 326
313 327
314 328 class RhodeCodeUi(Base, BaseModel):
315 329 __tablename__ = 'rhodecode_ui'
316 330 __table_args__ = (
317 331 UniqueConstraint('ui_key'),
318 332 {'extend_existing': True, 'mysql_engine': 'InnoDB',
319 333 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
320 334 )
321 335
322 336 HOOK_REPO_SIZE = 'changegroup.repo_size'
323 337 # HG
324 338 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
325 339 HOOK_PULL = 'outgoing.pull_logger'
326 340 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
327 341 HOOK_PUSH = 'changegroup.push_logger'
328 342
329 343 # TODO: johbo: Unify way how hooks are configured for git and hg,
330 344 # git part is currently hardcoded.
331 345
332 346 # SVN PATTERNS
333 347 SVN_BRANCH_ID = 'vcs_svn_branch'
334 348 SVN_TAG_ID = 'vcs_svn_tag'
335 349
336 350 ui_id = Column(
337 351 "ui_id", Integer(), nullable=False, unique=True, default=None,
338 352 primary_key=True)
339 353 ui_section = Column(
340 354 "ui_section", String(255), nullable=True, unique=None, default=None)
341 355 ui_key = Column(
342 356 "ui_key", String(255), nullable=True, unique=None, default=None)
343 357 ui_value = Column(
344 358 "ui_value", String(255), nullable=True, unique=None, default=None)
345 359 ui_active = Column(
346 360 "ui_active", Boolean(), nullable=True, unique=None, default=True)
347 361
348 362 def __repr__(self):
349 363 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
350 364 self.ui_key, self.ui_value)
351 365
352 366
353 367 class RepoRhodeCodeSetting(Base, BaseModel):
354 368 __tablename__ = 'repo_rhodecode_settings'
355 369 __table_args__ = (
356 370 UniqueConstraint(
357 371 'app_settings_name', 'repository_id',
358 372 name='uq_repo_rhodecode_setting_name_repo_id'),
359 373 {'extend_existing': True, 'mysql_engine': 'InnoDB',
360 374 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
361 375 )
362 376
363 377 repository_id = Column(
364 378 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
365 379 nullable=False)
366 380 app_settings_id = Column(
367 381 "app_settings_id", Integer(), nullable=False, unique=True,
368 382 default=None, primary_key=True)
369 383 app_settings_name = Column(
370 384 "app_settings_name", String(255), nullable=True, unique=None,
371 385 default=None)
372 386 _app_settings_value = Column(
373 387 "app_settings_value", String(4096), nullable=True, unique=None,
374 388 default=None)
375 389 _app_settings_type = Column(
376 390 "app_settings_type", String(255), nullable=True, unique=None,
377 391 default=None)
378 392
379 393 repository = relationship('Repository')
380 394
381 395 def __init__(self, repository_id, key='', val='', type='unicode'):
382 396 self.repository_id = repository_id
383 397 self.app_settings_name = key
384 398 self.app_settings_type = type
385 399 self.app_settings_value = val
386 400
387 401 @validates('_app_settings_value')
388 402 def validate_settings_value(self, key, val):
389 403 assert type(val) == unicode
390 404 return val
391 405
392 406 @hybrid_property
393 407 def app_settings_value(self):
394 408 v = self._app_settings_value
395 409 type_ = self.app_settings_type
396 410 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
397 411 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
398 412 return converter(v)
399 413
400 414 @app_settings_value.setter
401 415 def app_settings_value(self, val):
402 416 """
403 417 Setter that will always make sure we use unicode in app_settings_value
404 418
405 419 :param val:
406 420 """
407 421 self._app_settings_value = safe_unicode(val)
408 422
409 423 @hybrid_property
410 424 def app_settings_type(self):
411 425 return self._app_settings_type
412 426
413 427 @app_settings_type.setter
414 428 def app_settings_type(self, val):
415 429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
416 430 if val not in SETTINGS_TYPES:
417 431 raise Exception('type must be one of %s got %s'
418 432 % (SETTINGS_TYPES.keys(), val))
419 433 self._app_settings_type = val
420 434
421 435 def __unicode__(self):
422 436 return u"<%s('%s:%s:%s[%s]')>" % (
423 437 self.__class__.__name__, self.repository.repo_name,
424 438 self.app_settings_name, self.app_settings_value,
425 439 self.app_settings_type
426 440 )
427 441
428 442
429 443 class RepoRhodeCodeUi(Base, BaseModel):
430 444 __tablename__ = 'repo_rhodecode_ui'
431 445 __table_args__ = (
432 446 UniqueConstraint(
433 447 'repository_id', 'ui_section', 'ui_key',
434 448 name='uq_repo_rhodecode_ui_repository_id_section_key'),
435 449 {'extend_existing': True, 'mysql_engine': 'InnoDB',
436 450 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
437 451 )
438 452
439 453 repository_id = Column(
440 454 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
441 455 nullable=False)
442 456 ui_id = Column(
443 457 "ui_id", Integer(), nullable=False, unique=True, default=None,
444 458 primary_key=True)
445 459 ui_section = Column(
446 460 "ui_section", String(255), nullable=True, unique=None, default=None)
447 461 ui_key = Column(
448 462 "ui_key", String(255), nullable=True, unique=None, default=None)
449 463 ui_value = Column(
450 464 "ui_value", String(255), nullable=True, unique=None, default=None)
451 465 ui_active = Column(
452 466 "ui_active", Boolean(), nullable=True, unique=None, default=True)
453 467
454 468 repository = relationship('Repository')
455 469
456 470 def __repr__(self):
457 471 return '<%s[%s:%s]%s=>%s]>' % (
458 472 self.__class__.__name__, self.repository.repo_name,
459 473 self.ui_section, self.ui_key, self.ui_value)
460 474
461 475
462 476 class User(Base, BaseModel):
463 477 __tablename__ = 'users'
464 478 __table_args__ = (
465 479 UniqueConstraint('username'), UniqueConstraint('email'),
466 480 Index('u_username_idx', 'username'),
467 481 Index('u_email_idx', 'email'),
468 482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 484 )
471 485 DEFAULT_USER = 'default'
472 486 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
473 487 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
474 488
475 489 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
476 490 username = Column("username", String(255), nullable=True, unique=None, default=None)
477 491 password = Column("password", String(255), nullable=True, unique=None, default=None)
478 492 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
479 493 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
480 494 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
481 495 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
482 496 _email = Column("email", String(255), nullable=True, unique=None, default=None)
483 497 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
484 498 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
485 499 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
486 500 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
487 501 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
488 502 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
489 503 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
490 504
491 505 user_log = relationship('UserLog')
492 506 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
493 507
494 508 repositories = relationship('Repository')
495 509 repository_groups = relationship('RepoGroup')
496 510 user_groups = relationship('UserGroup')
497 511
498 512 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
499 513 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
500 514
501 515 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
502 516 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
503 517 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
504 518
505 519 group_member = relationship('UserGroupMember', cascade='all')
506 520
507 521 notifications = relationship('UserNotification', cascade='all')
508 522 # notifications assigned to this user
509 523 user_created_notifications = relationship('Notification', cascade='all')
510 524 # comments created by this user
511 525 user_comments = relationship('ChangesetComment', cascade='all')
512 526 # user profile extra info
513 527 user_emails = relationship('UserEmailMap', cascade='all')
514 528 user_ip_map = relationship('UserIpMap', cascade='all')
515 529 user_auth_tokens = relationship('UserApiKeys', cascade='all')
516 530 # gists
517 531 user_gists = relationship('Gist', cascade='all')
518 532 # user pull requests
519 533 user_pull_requests = relationship('PullRequest', cascade='all')
520 534 # external identities
521 535 extenal_identities = relationship(
522 536 'ExternalIdentity',
523 537 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
524 538 cascade='all')
525 539
526 540 def __unicode__(self):
527 541 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
528 542 self.user_id, self.username)
529 543
530 544 @hybrid_property
531 545 def email(self):
532 546 return self._email
533 547
534 548 @email.setter
535 549 def email(self, val):
536 550 self._email = val.lower() if val else None
537 551
538 552 @property
539 553 def firstname(self):
540 554 # alias for future
541 555 return self.name
542 556
543 557 @property
544 558 def emails(self):
545 559 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
546 560 return [self.email] + [x.email for x in other]
547 561
548 562 @property
549 563 def auth_tokens(self):
550 564 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
551 565
552 566 @property
553 567 def extra_auth_tokens(self):
554 568 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
555 569
556 570 @property
557 571 def feed_token(self):
558 572 feed_tokens = UserApiKeys.query()\
559 573 .filter(UserApiKeys.user == self)\
560 574 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
561 575 .all()
562 576 if feed_tokens:
563 577 return feed_tokens[0].api_key
564 578 else:
565 579 # use the main token so we don't end up with nothing...
566 580 return self.api_key
567 581
568 582 @classmethod
569 583 def extra_valid_auth_tokens(cls, user, role=None):
570 584 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
571 585 .filter(or_(UserApiKeys.expires == -1,
572 586 UserApiKeys.expires >= time.time()))
573 587 if role:
574 588 tokens = tokens.filter(or_(UserApiKeys.role == role,
575 589 UserApiKeys.role == UserApiKeys.ROLE_ALL))
576 590 return tokens.all()
577 591
578 592 @property
579 593 def ip_addresses(self):
580 594 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
581 595 return [x.ip_addr for x in ret]
582 596
583 597 @property
584 598 def username_and_name(self):
585 599 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
586 600
587 601 @property
588 602 def username_or_name_or_email(self):
589 603 full_name = self.full_name if self.full_name is not ' ' else None
590 604 return self.username or full_name or self.email
591 605
592 606 @property
593 607 def full_name(self):
594 608 return '%s %s' % (self.firstname, self.lastname)
595 609
596 610 @property
597 611 def full_name_or_username(self):
598 612 return ('%s %s' % (self.firstname, self.lastname)
599 613 if (self.firstname and self.lastname) else self.username)
600 614
601 615 @property
602 616 def full_contact(self):
603 617 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
604 618
605 619 @property
606 620 def short_contact(self):
607 621 return '%s %s' % (self.firstname, self.lastname)
608 622
609 623 @property
610 624 def is_admin(self):
611 625 return self.admin
612 626
613 627 @property
614 628 def AuthUser(self):
615 629 """
616 630 Returns instance of AuthUser for this user
617 631 """
618 632 from rhodecode.lib.auth import AuthUser
619 633 return AuthUser(user_id=self.user_id, api_key=self.api_key,
620 634 username=self.username)
621 635
622 636 @hybrid_property
623 637 def user_data(self):
624 638 if not self._user_data:
625 639 return {}
626 640
627 641 try:
628 642 return json.loads(self._user_data)
629 643 except TypeError:
630 644 return {}
631 645
632 646 @user_data.setter
633 647 def user_data(self, val):
634 648 if not isinstance(val, dict):
635 649 raise Exception('user_data must be dict, got %s' % type(val))
636 650 try:
637 651 self._user_data = json.dumps(val)
638 652 except Exception:
639 653 log.error(traceback.format_exc())
640 654
641 655 @classmethod
642 def get_by_username(cls, username, case_insensitive=False, cache=False):
656 def get_by_username(cls, username, case_insensitive=False,
657 cache=False, identity_cache=False):
658 session = Session()
659
643 660 if case_insensitive:
644 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
661 q = cls.query().filter(
662 func.lower(cls.username) == func.lower(username))
645 663 else:
646 664 q = cls.query().filter(cls.username == username)
647 665
648 666 if cache:
649 q = q.options(FromCache(
650 "sql_cache_short",
651 "get_user_%s" % _hash_key(username)))
667 if identity_cache:
668 val = cls.identity_cache(session, 'username', username)
669 if val:
670 return val
671 else:
672 q = q.options(
673 FromCache("sql_cache_short",
674 "get_user_by_name_%s" % _hash_key(username)))
675
652 676 return q.scalar()
653 677
654 678 @classmethod
655 679 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
656 680 q = cls.query().filter(cls.api_key == auth_token)
657 681
658 682 if cache:
659 683 q = q.options(FromCache("sql_cache_short",
660 684 "get_auth_token_%s" % auth_token))
661 685 res = q.scalar()
662 686
663 687 if fallback and not res:
664 688 #fallback to additional keys
665 689 _res = UserApiKeys.query()\
666 690 .filter(UserApiKeys.api_key == auth_token)\
667 691 .filter(or_(UserApiKeys.expires == -1,
668 692 UserApiKeys.expires >= time.time()))\
669 693 .first()
670 694 if _res:
671 695 res = _res.user
672 696 return res
673 697
674 698 @classmethod
675 699 def get_by_email(cls, email, case_insensitive=False, cache=False):
676 700
677 701 if case_insensitive:
678 702 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
679 703
680 704 else:
681 705 q = cls.query().filter(cls.email == email)
682 706
683 707 if cache:
684 708 q = q.options(FromCache("sql_cache_short",
685 709 "get_email_key_%s" % email))
686 710
687 711 ret = q.scalar()
688 712 if ret is None:
689 713 q = UserEmailMap.query()
690 714 # try fetching in alternate email map
691 715 if case_insensitive:
692 716 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
693 717 else:
694 718 q = q.filter(UserEmailMap.email == email)
695 719 q = q.options(joinedload(UserEmailMap.user))
696 720 if cache:
697 721 q = q.options(FromCache("sql_cache_short",
698 722 "get_email_map_key_%s" % email))
699 723 ret = getattr(q.scalar(), 'user', None)
700 724
701 725 return ret
702 726
703 727 @classmethod
704 728 def get_from_cs_author(cls, author):
705 729 """
706 730 Tries to get User objects out of commit author string
707 731
708 732 :param author:
709 733 """
710 734 from rhodecode.lib.helpers import email, author_name
711 735 # Valid email in the attribute passed, see if they're in the system
712 736 _email = email(author)
713 737 if _email:
714 738 user = cls.get_by_email(_email, case_insensitive=True)
715 739 if user:
716 740 return user
717 741 # Maybe we can match by username?
718 742 _author = author_name(author)
719 743 user = cls.get_by_username(_author, case_insensitive=True)
720 744 if user:
721 745 return user
722 746
723 747 def update_userdata(self, **kwargs):
724 748 usr = self
725 749 old = usr.user_data
726 750 old.update(**kwargs)
727 751 usr.user_data = old
728 752 Session().add(usr)
729 753 log.debug('updated userdata with ', kwargs)
730 754
731 755 def update_lastlogin(self):
732 756 """Update user lastlogin"""
733 757 self.last_login = datetime.datetime.now()
734 758 Session().add(self)
735 759 log.debug('updated user %s lastlogin', self.username)
736 760
737 761 def update_lastactivity(self):
738 762 """Update user lastactivity"""
739 763 usr = self
740 764 old = usr.user_data
741 765 old.update({'last_activity': time.time()})
742 766 usr.user_data = old
743 767 Session().add(usr)
744 768 log.debug('updated user %s lastactivity', usr.username)
745 769
746 770 def update_password(self, new_password, change_api_key=False):
747 771 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
748 772
749 773 self.password = get_crypt_password(new_password)
750 774 if change_api_key:
751 775 self.api_key = generate_auth_token(self.username)
752 776 Session().add(self)
753 777
754 778 @classmethod
755 779 def get_first_admin(cls):
756 780 user = User.query().filter(User.admin == True).first()
757 781 if user is None:
758 782 raise Exception('Missing administrative account!')
759 783 return user
760 784
761 785 @classmethod
762 786 def get_all_super_admins(cls):
763 787 """
764 788 Returns all admin accounts sorted by username
765 789 """
766 790 return User.query().filter(User.admin == true())\
767 791 .order_by(User.username.asc()).all()
768 792
769 793 @classmethod
770 794 def get_default_user(cls, cache=False):
771 795 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
772 796 if user is None:
773 797 raise Exception('Missing default account!')
774 798 return user
775 799
776 800 def _get_default_perms(self, user, suffix=''):
777 801 from rhodecode.model.permission import PermissionModel
778 802 return PermissionModel().get_default_perms(user.user_perms, suffix)
779 803
780 804 def get_default_perms(self, suffix=''):
781 805 return self._get_default_perms(self, suffix)
782 806
783 807 def get_api_data(self, include_secrets=False, details='full'):
784 808 """
785 809 Common function for generating user related data for API
786 810
787 811 :param include_secrets: By default secrets in the API data will be replaced
788 812 by a placeholder value to prevent exposing this data by accident. In case
789 813 this data shall be exposed, set this flag to ``True``.
790 814
791 815 :param details: details can be 'basic|full' basic gives only a subset of
792 816 the available user information that includes user_id, name and emails.
793 817 """
794 818 user = self
795 819 user_data = self.user_data
796 820 data = {
797 821 'user_id': user.user_id,
798 822 'username': user.username,
799 823 'firstname': user.name,
800 824 'lastname': user.lastname,
801 825 'email': user.email,
802 826 'emails': user.emails,
803 827 }
804 828 if details == 'basic':
805 829 return data
806 830
807 831 api_key_length = 40
808 832 api_key_replacement = '*' * api_key_length
809 833
810 834 extras = {
811 835 'api_key': api_key_replacement,
812 836 'api_keys': [api_key_replacement],
813 837 'active': user.active,
814 838 'admin': user.admin,
815 839 'extern_type': user.extern_type,
816 840 'extern_name': user.extern_name,
817 841 'last_login': user.last_login,
818 842 'ip_addresses': user.ip_addresses,
819 843 'language': user_data.get('language')
820 844 }
821 845 data.update(extras)
822 846
823 847 if include_secrets:
824 848 data['api_key'] = user.api_key
825 849 data['api_keys'] = user.auth_tokens
826 850 return data
827 851
828 852 def __json__(self):
829 853 data = {
830 854 'full_name': self.full_name,
831 855 'full_name_or_username': self.full_name_or_username,
832 856 'short_contact': self.short_contact,
833 857 'full_contact': self.full_contact,
834 858 }
835 859 data.update(self.get_api_data())
836 860 return data
837 861
838 862
839 863 class UserApiKeys(Base, BaseModel):
840 864 __tablename__ = 'user_api_keys'
841 865 __table_args__ = (
842 866 Index('uak_api_key_idx', 'api_key'),
843 867 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
844 868 UniqueConstraint('api_key'),
845 869 {'extend_existing': True, 'mysql_engine': 'InnoDB',
846 870 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
847 871 )
848 872 __mapper_args__ = {}
849 873
850 874 # ApiKey role
851 875 ROLE_ALL = 'token_role_all'
852 876 ROLE_HTTP = 'token_role_http'
853 877 ROLE_VCS = 'token_role_vcs'
854 878 ROLE_API = 'token_role_api'
855 879 ROLE_FEED = 'token_role_feed'
856 880 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
857 881
858 882 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 883 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
860 884 api_key = Column("api_key", String(255), nullable=False, unique=True)
861 885 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
862 886 expires = Column('expires', Float(53), nullable=False)
863 887 role = Column('role', String(255), nullable=True)
864 888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
865 889
866 890 user = relationship('User', lazy='joined')
867 891
868 892 @classmethod
869 893 def _get_role_name(cls, role):
870 894 return {
871 895 cls.ROLE_ALL: _('all'),
872 896 cls.ROLE_HTTP: _('http/web interface'),
873 897 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
874 898 cls.ROLE_API: _('api calls'),
875 899 cls.ROLE_FEED: _('feed access'),
876 900 }.get(role, role)
877 901
878 902 @property
879 903 def expired(self):
880 904 if self.expires == -1:
881 905 return False
882 906 return time.time() > self.expires
883 907
884 908 @property
885 909 def role_humanized(self):
886 910 return self._get_role_name(self.role)
887 911
888 912
889 913 class UserEmailMap(Base, BaseModel):
890 914 __tablename__ = 'user_email_map'
891 915 __table_args__ = (
892 916 Index('uem_email_idx', 'email'),
893 917 UniqueConstraint('email'),
894 918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
895 919 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
896 920 )
897 921 __mapper_args__ = {}
898 922
899 923 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
900 924 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
901 925 _email = Column("email", String(255), nullable=True, unique=False, default=None)
902 926 user = relationship('User', lazy='joined')
903 927
904 928 @validates('_email')
905 929 def validate_email(self, key, email):
906 930 # check if this email is not main one
907 931 main_email = Session().query(User).filter(User.email == email).scalar()
908 932 if main_email is not None:
909 933 raise AttributeError('email %s is present is user table' % email)
910 934 return email
911 935
912 936 @hybrid_property
913 937 def email(self):
914 938 return self._email
915 939
916 940 @email.setter
917 941 def email(self, val):
918 942 self._email = val.lower() if val else None
919 943
920 944
921 945 class UserIpMap(Base, BaseModel):
922 946 __tablename__ = 'user_ip_map'
923 947 __table_args__ = (
924 948 UniqueConstraint('user_id', 'ip_addr'),
925 949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
926 950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
927 951 )
928 952 __mapper_args__ = {}
929 953
930 954 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
931 955 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
932 956 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
933 957 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
934 958 description = Column("description", String(10000), nullable=True, unique=None, default=None)
935 959 user = relationship('User', lazy='joined')
936 960
937 961 @classmethod
938 962 def _get_ip_range(cls, ip_addr):
939 963 net = ipaddress.ip_network(ip_addr, strict=False)
940 964 return [str(net.network_address), str(net.broadcast_address)]
941 965
942 966 def __json__(self):
943 967 return {
944 968 'ip_addr': self.ip_addr,
945 969 'ip_range': self._get_ip_range(self.ip_addr),
946 970 }
947 971
948 972 def __unicode__(self):
949 973 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
950 974 self.user_id, self.ip_addr)
951 975
952 976 class UserLog(Base, BaseModel):
953 977 __tablename__ = 'user_logs'
954 978 __table_args__ = (
955 979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
956 980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
957 981 )
958 982 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
959 983 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
960 984 username = Column("username", String(255), nullable=True, unique=None, default=None)
961 985 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
962 986 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
963 987 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
964 988 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
965 989 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
966 990
967 991 def __unicode__(self):
968 992 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
969 993 self.repository_name,
970 994 self.action)
971 995
972 996 @property
973 997 def action_as_day(self):
974 998 return datetime.date(*self.action_date.timetuple()[:3])
975 999
976 1000 user = relationship('User')
977 1001 repository = relationship('Repository', cascade='')
978 1002
979 1003
980 1004 class UserGroup(Base, BaseModel):
981 1005 __tablename__ = 'users_groups'
982 1006 __table_args__ = (
983 1007 {'extend_existing': True, 'mysql_engine': 'InnoDB',
984 1008 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
985 1009 )
986 1010
987 1011 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
988 1012 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
989 1013 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
990 1014 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
991 1015 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
992 1016 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
993 1017 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
994 1018 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
995 1019
996 1020 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
997 1021 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
998 1022 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
999 1023 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1000 1024 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1001 1025 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1002 1026
1003 1027 user = relationship('User')
1004 1028
1005 1029 @hybrid_property
1006 1030 def group_data(self):
1007 1031 if not self._group_data:
1008 1032 return {}
1009 1033
1010 1034 try:
1011 1035 return json.loads(self._group_data)
1012 1036 except TypeError:
1013 1037 return {}
1014 1038
1015 1039 @group_data.setter
1016 1040 def group_data(self, val):
1017 1041 try:
1018 1042 self._group_data = json.dumps(val)
1019 1043 except Exception:
1020 1044 log.error(traceback.format_exc())
1021 1045
1022 1046 def __unicode__(self):
1023 1047 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1024 1048 self.users_group_id,
1025 1049 self.users_group_name)
1026 1050
1027 1051 @classmethod
1028 1052 def get_by_group_name(cls, group_name, cache=False,
1029 1053 case_insensitive=False):
1030 1054 if case_insensitive:
1031 1055 q = cls.query().filter(func.lower(cls.users_group_name) ==
1032 1056 func.lower(group_name))
1033 1057
1034 1058 else:
1035 1059 q = cls.query().filter(cls.users_group_name == group_name)
1036 1060 if cache:
1037 1061 q = q.options(FromCache(
1038 1062 "sql_cache_short",
1039 1063 "get_group_%s" % _hash_key(group_name)))
1040 1064 return q.scalar()
1041 1065
1042 1066 @classmethod
1043 1067 def get(cls, user_group_id, cache=False):
1044 1068 user_group = cls.query()
1045 1069 if cache:
1046 1070 user_group = user_group.options(FromCache("sql_cache_short",
1047 1071 "get_users_group_%s" % user_group_id))
1048 1072 return user_group.get(user_group_id)
1049 1073
1050 1074 def permissions(self, with_admins=True, with_owner=True):
1051 1075 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1052 1076 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1053 1077 joinedload(UserUserGroupToPerm.user),
1054 1078 joinedload(UserUserGroupToPerm.permission),)
1055 1079
1056 1080 # get owners and admins and permissions. We do a trick of re-writing
1057 1081 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1058 1082 # has a global reference and changing one object propagates to all
1059 1083 # others. This means if admin is also an owner admin_row that change
1060 1084 # would propagate to both objects
1061 1085 perm_rows = []
1062 1086 for _usr in q.all():
1063 1087 usr = AttributeDict(_usr.user.get_dict())
1064 1088 usr.permission = _usr.permission.permission_name
1065 1089 perm_rows.append(usr)
1066 1090
1067 1091 # filter the perm rows by 'default' first and then sort them by
1068 1092 # admin,write,read,none permissions sorted again alphabetically in
1069 1093 # each group
1070 1094 perm_rows = sorted(perm_rows, key=display_sort)
1071 1095
1072 1096 _admin_perm = 'usergroup.admin'
1073 1097 owner_row = []
1074 1098 if with_owner:
1075 1099 usr = AttributeDict(self.user.get_dict())
1076 1100 usr.owner_row = True
1077 1101 usr.permission = _admin_perm
1078 1102 owner_row.append(usr)
1079 1103
1080 1104 super_admin_rows = []
1081 1105 if with_admins:
1082 1106 for usr in User.get_all_super_admins():
1083 1107 # if this admin is also owner, don't double the record
1084 1108 if usr.user_id == owner_row[0].user_id:
1085 1109 owner_row[0].admin_row = True
1086 1110 else:
1087 1111 usr = AttributeDict(usr.get_dict())
1088 1112 usr.admin_row = True
1089 1113 usr.permission = _admin_perm
1090 1114 super_admin_rows.append(usr)
1091 1115
1092 1116 return super_admin_rows + owner_row + perm_rows
1093 1117
1094 1118 def permission_user_groups(self):
1095 1119 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1096 1120 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1097 1121 joinedload(UserGroupUserGroupToPerm.target_user_group),
1098 1122 joinedload(UserGroupUserGroupToPerm.permission),)
1099 1123
1100 1124 perm_rows = []
1101 1125 for _user_group in q.all():
1102 1126 usr = AttributeDict(_user_group.user_group.get_dict())
1103 1127 usr.permission = _user_group.permission.permission_name
1104 1128 perm_rows.append(usr)
1105 1129
1106 1130 return perm_rows
1107 1131
1108 1132 def _get_default_perms(self, user_group, suffix=''):
1109 1133 from rhodecode.model.permission import PermissionModel
1110 1134 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1111 1135
1112 1136 def get_default_perms(self, suffix=''):
1113 1137 return self._get_default_perms(self, suffix)
1114 1138
1115 1139 def get_api_data(self, with_group_members=True, include_secrets=False):
1116 1140 """
1117 1141 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1118 1142 basically forwarded.
1119 1143
1120 1144 """
1121 1145 user_group = self
1122 1146
1123 1147 data = {
1124 1148 'users_group_id': user_group.users_group_id,
1125 1149 'group_name': user_group.users_group_name,
1126 1150 'group_description': user_group.user_group_description,
1127 1151 'active': user_group.users_group_active,
1128 1152 'owner': user_group.user.username,
1129 1153 }
1130 1154 if with_group_members:
1131 1155 users = []
1132 1156 for user in user_group.members:
1133 1157 user = user.user
1134 1158 users.append(user.get_api_data(include_secrets=include_secrets))
1135 1159 data['users'] = users
1136 1160
1137 1161 return data
1138 1162
1139 1163
1140 1164 class UserGroupMember(Base, BaseModel):
1141 1165 __tablename__ = 'users_groups_members'
1142 1166 __table_args__ = (
1143 1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1144 1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1145 1169 )
1146 1170
1147 1171 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1148 1172 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1149 1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1150 1174
1151 1175 user = relationship('User', lazy='joined')
1152 1176 users_group = relationship('UserGroup')
1153 1177
1154 1178 def __init__(self, gr_id='', u_id=''):
1155 1179 self.users_group_id = gr_id
1156 1180 self.user_id = u_id
1157 1181
1158 1182
1159 1183 class RepositoryField(Base, BaseModel):
1160 1184 __tablename__ = 'repositories_fields'
1161 1185 __table_args__ = (
1162 1186 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1163 1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1164 1188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1165 1189 )
1166 1190 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1167 1191
1168 1192 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1169 1193 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1170 1194 field_key = Column("field_key", String(250))
1171 1195 field_label = Column("field_label", String(1024), nullable=False)
1172 1196 field_value = Column("field_value", String(10000), nullable=False)
1173 1197 field_desc = Column("field_desc", String(1024), nullable=False)
1174 1198 field_type = Column("field_type", String(255), nullable=False, unique=None)
1175 1199 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1176 1200
1177 1201 repository = relationship('Repository')
1178 1202
1179 1203 @property
1180 1204 def field_key_prefixed(self):
1181 1205 return 'ex_%s' % self.field_key
1182 1206
1183 1207 @classmethod
1184 1208 def un_prefix_key(cls, key):
1185 1209 if key.startswith(cls.PREFIX):
1186 1210 return key[len(cls.PREFIX):]
1187 1211 return key
1188 1212
1189 1213 @classmethod
1190 1214 def get_by_key_name(cls, key, repo):
1191 1215 row = cls.query()\
1192 1216 .filter(cls.repository == repo)\
1193 1217 .filter(cls.field_key == key).scalar()
1194 1218 return row
1195 1219
1196 1220
1197 1221 class Repository(Base, BaseModel):
1198 1222 __tablename__ = 'repositories'
1199 1223 __table_args__ = (
1200 1224 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1201 1225 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1202 1226 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1203 1227 )
1204 1228 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1205 1229 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1206 1230
1207 1231 STATE_CREATED = 'repo_state_created'
1208 1232 STATE_PENDING = 'repo_state_pending'
1209 1233 STATE_ERROR = 'repo_state_error'
1210 1234
1211 1235 LOCK_AUTOMATIC = 'lock_auto'
1212 1236 LOCK_API = 'lock_api'
1213 1237 LOCK_WEB = 'lock_web'
1214 1238 LOCK_PULL = 'lock_pull'
1215 1239
1216 1240 NAME_SEP = URL_SEP
1217 1241
1218 1242 repo_id = Column(
1219 1243 "repo_id", Integer(), nullable=False, unique=True, default=None,
1220 1244 primary_key=True)
1221 1245 _repo_name = Column(
1222 1246 "repo_name", Text(), nullable=False, default=None)
1223 1247 _repo_name_hash = Column(
1224 1248 "repo_name_hash", String(255), nullable=False, unique=True)
1225 1249 repo_state = Column("repo_state", String(255), nullable=True)
1226 1250
1227 1251 clone_uri = Column(
1228 1252 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1229 1253 default=None)
1230 1254 repo_type = Column(
1231 1255 "repo_type", String(255), nullable=False, unique=False, default=None)
1232 1256 user_id = Column(
1233 1257 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1234 1258 unique=False, default=None)
1235 1259 private = Column(
1236 1260 "private", Boolean(), nullable=True, unique=None, default=None)
1237 1261 enable_statistics = Column(
1238 1262 "statistics", Boolean(), nullable=True, unique=None, default=True)
1239 1263 enable_downloads = Column(
1240 1264 "downloads", Boolean(), nullable=True, unique=None, default=True)
1241 1265 description = Column(
1242 1266 "description", String(10000), nullable=True, unique=None, default=None)
1243 1267 created_on = Column(
1244 1268 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1245 1269 default=datetime.datetime.now)
1246 1270 updated_on = Column(
1247 1271 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1248 1272 default=datetime.datetime.now)
1249 1273 _landing_revision = Column(
1250 1274 "landing_revision", String(255), nullable=False, unique=False,
1251 1275 default=None)
1252 1276 enable_locking = Column(
1253 1277 "enable_locking", Boolean(), nullable=False, unique=None,
1254 1278 default=False)
1255 1279 _locked = Column(
1256 1280 "locked", String(255), nullable=True, unique=False, default=None)
1257 1281 _changeset_cache = Column(
1258 1282 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1259 1283
1260 1284 fork_id = Column(
1261 1285 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1262 1286 nullable=True, unique=False, default=None)
1263 1287 group_id = Column(
1264 1288 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1265 1289 unique=False, default=None)
1266 1290
1267 1291 user = relationship('User', lazy='joined')
1268 1292 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1269 1293 group = relationship('RepoGroup', lazy='joined')
1270 1294 repo_to_perm = relationship(
1271 1295 'UserRepoToPerm', cascade='all',
1272 1296 order_by='UserRepoToPerm.repo_to_perm_id')
1273 1297 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1274 1298 stats = relationship('Statistics', cascade='all', uselist=False)
1275 1299
1276 1300 followers = relationship(
1277 1301 'UserFollowing',
1278 1302 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1279 1303 cascade='all')
1280 1304 extra_fields = relationship(
1281 1305 'RepositoryField', cascade="all, delete, delete-orphan")
1282 1306 logs = relationship('UserLog')
1283 1307 comments = relationship(
1284 1308 'ChangesetComment', cascade="all, delete, delete-orphan")
1285 1309 pull_requests_source = relationship(
1286 1310 'PullRequest',
1287 1311 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1288 1312 cascade="all, delete, delete-orphan")
1289 1313 pull_requests_target = relationship(
1290 1314 'PullRequest',
1291 1315 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1292 1316 cascade="all, delete, delete-orphan")
1293 1317 ui = relationship('RepoRhodeCodeUi', cascade="all")
1294 1318 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1295 1319
1296 1320 def __unicode__(self):
1297 1321 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1298 1322 safe_unicode(self.repo_name))
1299 1323
1300 1324 @hybrid_property
1301 1325 def landing_rev(self):
1302 1326 # always should return [rev_type, rev]
1303 1327 if self._landing_revision:
1304 1328 _rev_info = self._landing_revision.split(':')
1305 1329 if len(_rev_info) < 2:
1306 1330 _rev_info.insert(0, 'rev')
1307 1331 return [_rev_info[0], _rev_info[1]]
1308 1332 return [None, None]
1309 1333
1310 1334 @landing_rev.setter
1311 1335 def landing_rev(self, val):
1312 1336 if ':' not in val:
1313 1337 raise ValueError('value must be delimited with `:` and consist '
1314 1338 'of <rev_type>:<rev>, got %s instead' % val)
1315 1339 self._landing_revision = val
1316 1340
1317 1341 @hybrid_property
1318 1342 def locked(self):
1319 1343 if self._locked:
1320 1344 user_id, timelocked, reason = self._locked.split(':')
1321 1345 lock_values = int(user_id), timelocked, reason
1322 1346 else:
1323 1347 lock_values = [None, None, None]
1324 1348 return lock_values
1325 1349
1326 1350 @locked.setter
1327 1351 def locked(self, val):
1328 1352 if val and isinstance(val, (list, tuple)):
1329 1353 self._locked = ':'.join(map(str, val))
1330 1354 else:
1331 1355 self._locked = None
1332 1356
1333 1357 @hybrid_property
1334 1358 def changeset_cache(self):
1335 1359 from rhodecode.lib.vcs.backends.base import EmptyCommit
1336 1360 dummy = EmptyCommit().__json__()
1337 1361 if not self._changeset_cache:
1338 1362 return dummy
1339 1363 try:
1340 1364 return json.loads(self._changeset_cache)
1341 1365 except TypeError:
1342 1366 return dummy
1343 1367 except Exception:
1344 1368 log.error(traceback.format_exc())
1345 1369 return dummy
1346 1370
1347 1371 @changeset_cache.setter
1348 1372 def changeset_cache(self, val):
1349 1373 try:
1350 1374 self._changeset_cache = json.dumps(val)
1351 1375 except Exception:
1352 1376 log.error(traceback.format_exc())
1353 1377
1354 1378 @hybrid_property
1355 1379 def repo_name(self):
1356 1380 return self._repo_name
1357 1381
1358 1382 @repo_name.setter
1359 1383 def repo_name(self, value):
1360 1384 self._repo_name = value
1361 1385 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1362 1386
1363 1387 @classmethod
1364 1388 def normalize_repo_name(cls, repo_name):
1365 1389 """
1366 1390 Normalizes os specific repo_name to the format internally stored inside
1367 dabatabase using URL_SEP
1391 database using URL_SEP
1368 1392
1369 1393 :param cls:
1370 1394 :param repo_name:
1371 1395 """
1372 1396 return cls.NAME_SEP.join(repo_name.split(os.sep))
1373 1397
1374 1398 @classmethod
1375 def get_by_repo_name(cls, repo_name):
1399 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1376 1400 session = Session()
1377 exist_in_session = []
1378 for (item_cls, pkey), instance in session.identity_map.items():
1379 if cls == item_cls and instance.repo_name == repo_name:
1380 exist_in_session.append(instance)
1381 if exist_in_session:
1382 if len(exist_in_session) == 1:
1383 return exist_in_session[0]
1384 log.exception(
1385 'multiple repos with same name: %r' % exist_in_session)
1386
1387 1401 q = session.query(cls).filter(cls.repo_name == repo_name)
1402
1403 if cache:
1404 if identity_cache:
1405 val = cls.identity_cache(session, 'repo_name', repo_name)
1406 if val:
1407 return val
1408 else:
1409 q = q.options(
1410 FromCache("sql_cache_short",
1411 "get_repo_by_name_%s" % _hash_key(repo_name)))
1412
1388 1413 return q.scalar()
1389 1414
1390 1415 @classmethod
1391 1416 def get_by_full_path(cls, repo_full_path):
1392 1417 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1393 1418 repo_name = cls.normalize_repo_name(repo_name)
1394 1419 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1395 1420
1396 1421 @classmethod
1397 1422 def get_repo_forks(cls, repo_id):
1398 1423 return cls.query().filter(Repository.fork_id == repo_id)
1399 1424
1400 1425 @classmethod
1401 1426 def base_path(cls):
1402 1427 """
1403 1428 Returns base path when all repos are stored
1404 1429
1405 1430 :param cls:
1406 1431 """
1407 1432 q = Session().query(RhodeCodeUi)\
1408 1433 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1409 1434 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1410 1435 return q.one().ui_value
1411 1436
1412 1437 @classmethod
1413 1438 def is_valid(cls, repo_name):
1414 1439 """
1415 1440 returns True if given repo name is a valid filesystem repository
1416 1441
1417 1442 :param cls:
1418 1443 :param repo_name:
1419 1444 """
1420 1445 from rhodecode.lib.utils import is_valid_repo
1421 1446
1422 1447 return is_valid_repo(repo_name, cls.base_path())
1423 1448
1424 1449 @classmethod
1425 1450 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1426 1451 case_insensitive=True):
1427 1452 q = Repository.query()
1428 1453
1429 1454 if not isinstance(user_id, Optional):
1430 1455 q = q.filter(Repository.user_id == user_id)
1431 1456
1432 1457 if not isinstance(group_id, Optional):
1433 1458 q = q.filter(Repository.group_id == group_id)
1434 1459
1435 1460 if case_insensitive:
1436 1461 q = q.order_by(func.lower(Repository.repo_name))
1437 1462 else:
1438 1463 q = q.order_by(Repository.repo_name)
1439 1464 return q.all()
1440 1465
1441 1466 @property
1442 1467 def forks(self):
1443 1468 """
1444 1469 Return forks of this repo
1445 1470 """
1446 1471 return Repository.get_repo_forks(self.repo_id)
1447 1472
1448 1473 @property
1449 1474 def parent(self):
1450 1475 """
1451 1476 Returns fork parent
1452 1477 """
1453 1478 return self.fork
1454 1479
1455 1480 @property
1456 1481 def just_name(self):
1457 1482 return self.repo_name.split(self.NAME_SEP)[-1]
1458 1483
1459 1484 @property
1460 1485 def groups_with_parents(self):
1461 1486 groups = []
1462 1487 if self.group is None:
1463 1488 return groups
1464 1489
1465 1490 cur_gr = self.group
1466 1491 groups.insert(0, cur_gr)
1467 1492 while 1:
1468 1493 gr = getattr(cur_gr, 'parent_group', None)
1469 1494 cur_gr = cur_gr.parent_group
1470 1495 if gr is None:
1471 1496 break
1472 1497 groups.insert(0, gr)
1473 1498
1474 1499 return groups
1475 1500
1476 1501 @property
1477 1502 def groups_and_repo(self):
1478 1503 return self.groups_with_parents, self
1479 1504
1480 1505 @LazyProperty
1481 1506 def repo_path(self):
1482 1507 """
1483 1508 Returns base full path for that repository means where it actually
1484 1509 exists on a filesystem
1485 1510 """
1486 1511 q = Session().query(RhodeCodeUi).filter(
1487 1512 RhodeCodeUi.ui_key == self.NAME_SEP)
1488 1513 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1489 1514 return q.one().ui_value
1490 1515
1491 1516 @property
1492 1517 def repo_full_path(self):
1493 1518 p = [self.repo_path]
1494 1519 # we need to split the name by / since this is how we store the
1495 1520 # names in the database, but that eventually needs to be converted
1496 1521 # into a valid system path
1497 1522 p += self.repo_name.split(self.NAME_SEP)
1498 1523 return os.path.join(*map(safe_unicode, p))
1499 1524
1500 1525 @property
1501 1526 def cache_keys(self):
1502 1527 """
1503 1528 Returns associated cache keys for that repo
1504 1529 """
1505 1530 return CacheKey.query()\
1506 1531 .filter(CacheKey.cache_args == self.repo_name)\
1507 1532 .order_by(CacheKey.cache_key)\
1508 1533 .all()
1509 1534
1510 1535 def get_new_name(self, repo_name):
1511 1536 """
1512 1537 returns new full repository name based on assigned group and new new
1513 1538
1514 1539 :param group_name:
1515 1540 """
1516 1541 path_prefix = self.group.full_path_splitted if self.group else []
1517 1542 return self.NAME_SEP.join(path_prefix + [repo_name])
1518 1543
1519 1544 @property
1520 1545 def _config(self):
1521 1546 """
1522 1547 Returns db based config object.
1523 1548 """
1524 1549 from rhodecode.lib.utils import make_db_config
1525 1550 return make_db_config(clear_session=False, repo=self)
1526 1551
1527 1552 def permissions(self, with_admins=True, with_owner=True):
1528 1553 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1529 1554 q = q.options(joinedload(UserRepoToPerm.repository),
1530 1555 joinedload(UserRepoToPerm.user),
1531 1556 joinedload(UserRepoToPerm.permission),)
1532 1557
1533 1558 # get owners and admins and permissions. We do a trick of re-writing
1534 1559 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1535 1560 # has a global reference and changing one object propagates to all
1536 1561 # others. This means if admin is also an owner admin_row that change
1537 1562 # would propagate to both objects
1538 1563 perm_rows = []
1539 1564 for _usr in q.all():
1540 1565 usr = AttributeDict(_usr.user.get_dict())
1541 1566 usr.permission = _usr.permission.permission_name
1542 1567 perm_rows.append(usr)
1543 1568
1544 1569 # filter the perm rows by 'default' first and then sort them by
1545 1570 # admin,write,read,none permissions sorted again alphabetically in
1546 1571 # each group
1547 1572 perm_rows = sorted(perm_rows, key=display_sort)
1548 1573
1549 1574 _admin_perm = 'repository.admin'
1550 1575 owner_row = []
1551 1576 if with_owner:
1552 1577 usr = AttributeDict(self.user.get_dict())
1553 1578 usr.owner_row = True
1554 1579 usr.permission = _admin_perm
1555 1580 owner_row.append(usr)
1556 1581
1557 1582 super_admin_rows = []
1558 1583 if with_admins:
1559 1584 for usr in User.get_all_super_admins():
1560 1585 # if this admin is also owner, don't double the record
1561 1586 if usr.user_id == owner_row[0].user_id:
1562 1587 owner_row[0].admin_row = True
1563 1588 else:
1564 1589 usr = AttributeDict(usr.get_dict())
1565 1590 usr.admin_row = True
1566 1591 usr.permission = _admin_perm
1567 1592 super_admin_rows.append(usr)
1568 1593
1569 1594 return super_admin_rows + owner_row + perm_rows
1570 1595
1571 1596 def permission_user_groups(self):
1572 1597 q = UserGroupRepoToPerm.query().filter(
1573 1598 UserGroupRepoToPerm.repository == self)
1574 1599 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1575 1600 joinedload(UserGroupRepoToPerm.users_group),
1576 1601 joinedload(UserGroupRepoToPerm.permission),)
1577 1602
1578 1603 perm_rows = []
1579 1604 for _user_group in q.all():
1580 1605 usr = AttributeDict(_user_group.users_group.get_dict())
1581 1606 usr.permission = _user_group.permission.permission_name
1582 1607 perm_rows.append(usr)
1583 1608
1584 1609 return perm_rows
1585 1610
1586 1611 def get_api_data(self, include_secrets=False):
1587 1612 """
1588 1613 Common function for generating repo api data
1589 1614
1590 1615 :param include_secrets: See :meth:`User.get_api_data`.
1591 1616
1592 1617 """
1593 1618 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1594 1619 # move this methods on models level.
1595 1620 from rhodecode.model.settings import SettingsModel
1596 1621
1597 1622 repo = self
1598 1623 _user_id, _time, _reason = self.locked
1599 1624
1600 1625 data = {
1601 1626 'repo_id': repo.repo_id,
1602 1627 'repo_name': repo.repo_name,
1603 1628 'repo_type': repo.repo_type,
1604 1629 'clone_uri': repo.clone_uri or '',
1605 1630 'private': repo.private,
1606 1631 'created_on': repo.created_on,
1607 1632 'description': repo.description,
1608 1633 'landing_rev': repo.landing_rev,
1609 1634 'owner': repo.user.username,
1610 1635 'fork_of': repo.fork.repo_name if repo.fork else None,
1611 1636 'enable_statistics': repo.enable_statistics,
1612 1637 'enable_locking': repo.enable_locking,
1613 1638 'enable_downloads': repo.enable_downloads,
1614 1639 'last_changeset': repo.changeset_cache,
1615 1640 'locked_by': User.get(_user_id).get_api_data(
1616 1641 include_secrets=include_secrets) if _user_id else None,
1617 1642 'locked_date': time_to_datetime(_time) if _time else None,
1618 1643 'lock_reason': _reason if _reason else None,
1619 1644 }
1620 1645
1621 1646 # TODO: mikhail: should be per-repo settings here
1622 1647 rc_config = SettingsModel().get_all_settings()
1623 1648 repository_fields = str2bool(
1624 1649 rc_config.get('rhodecode_repository_fields'))
1625 1650 if repository_fields:
1626 1651 for f in self.extra_fields:
1627 1652 data[f.field_key_prefixed] = f.field_value
1628 1653
1629 1654 return data
1630 1655
1631 1656 @classmethod
1632 1657 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1633 1658 if not lock_time:
1634 1659 lock_time = time.time()
1635 1660 if not lock_reason:
1636 1661 lock_reason = cls.LOCK_AUTOMATIC
1637 1662 repo.locked = [user_id, lock_time, lock_reason]
1638 1663 Session().add(repo)
1639 1664 Session().commit()
1640 1665
1641 1666 @classmethod
1642 1667 def unlock(cls, repo):
1643 1668 repo.locked = None
1644 1669 Session().add(repo)
1645 1670 Session().commit()
1646 1671
1647 1672 @classmethod
1648 1673 def getlock(cls, repo):
1649 1674 return repo.locked
1650 1675
1651 1676 def is_user_lock(self, user_id):
1652 1677 if self.lock[0]:
1653 1678 lock_user_id = safe_int(self.lock[0])
1654 1679 user_id = safe_int(user_id)
1655 1680 # both are ints, and they are equal
1656 1681 return all([lock_user_id, user_id]) and lock_user_id == user_id
1657 1682
1658 1683 return False
1659 1684
1660 1685 def get_locking_state(self, action, user_id, only_when_enabled=True):
1661 1686 """
1662 1687 Checks locking on this repository, if locking is enabled and lock is
1663 1688 present returns a tuple of make_lock, locked, locked_by.
1664 1689 make_lock can have 3 states None (do nothing) True, make lock
1665 1690 False release lock, This value is later propagated to hooks, which
1666 1691 do the locking. Think about this as signals passed to hooks what to do.
1667 1692
1668 1693 """
1669 1694 # TODO: johbo: This is part of the business logic and should be moved
1670 1695 # into the RepositoryModel.
1671 1696
1672 1697 if action not in ('push', 'pull'):
1673 1698 raise ValueError("Invalid action value: %s" % repr(action))
1674 1699
1675 1700 # defines if locked error should be thrown to user
1676 1701 currently_locked = False
1677 1702 # defines if new lock should be made, tri-state
1678 1703 make_lock = None
1679 1704 repo = self
1680 1705 user = User.get(user_id)
1681 1706
1682 1707 lock_info = repo.locked
1683 1708
1684 1709 if repo and (repo.enable_locking or not only_when_enabled):
1685 1710 if action == 'push':
1686 1711 # check if it's already locked !, if it is compare users
1687 1712 locked_by_user_id = lock_info[0]
1688 1713 if user.user_id == locked_by_user_id:
1689 1714 log.debug(
1690 1715 'Got `push` action from user %s, now unlocking', user)
1691 1716 # unlock if we have push from user who locked
1692 1717 make_lock = False
1693 1718 else:
1694 1719 # we're not the same user who locked, ban with
1695 1720 # code defined in settings (default is 423 HTTP Locked) !
1696 1721 log.debug('Repo %s is currently locked by %s', repo, user)
1697 1722 currently_locked = True
1698 1723 elif action == 'pull':
1699 1724 # [0] user [1] date
1700 1725 if lock_info[0] and lock_info[1]:
1701 1726 log.debug('Repo %s is currently locked by %s', repo, user)
1702 1727 currently_locked = True
1703 1728 else:
1704 1729 log.debug('Setting lock on repo %s by %s', repo, user)
1705 1730 make_lock = True
1706 1731
1707 1732 else:
1708 1733 log.debug('Repository %s do not have locking enabled', repo)
1709 1734
1710 1735 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1711 1736 make_lock, currently_locked, lock_info)
1712 1737
1713 1738 from rhodecode.lib.auth import HasRepoPermissionAny
1714 1739 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1715 1740 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1716 1741 # if we don't have at least write permission we cannot make a lock
1717 1742 log.debug('lock state reset back to FALSE due to lack '
1718 1743 'of at least read permission')
1719 1744 make_lock = False
1720 1745
1721 1746 return make_lock, currently_locked, lock_info
1722 1747
1723 1748 @property
1724 1749 def last_db_change(self):
1725 1750 return self.updated_on
1726 1751
1727 1752 @property
1728 1753 def clone_uri_hidden(self):
1729 1754 clone_uri = self.clone_uri
1730 1755 if clone_uri:
1731 1756 import urlobject
1732 1757 url_obj = urlobject.URLObject(self.clone_uri)
1733 1758 if url_obj.password:
1734 1759 clone_uri = url_obj.with_password('*****')
1735 1760 return clone_uri
1736 1761
1737 1762 def clone_url(self, **override):
1738 1763 qualified_home_url = url('home', qualified=True)
1739 1764
1740 1765 uri_tmpl = None
1741 1766 if 'with_id' in override:
1742 1767 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1743 1768 del override['with_id']
1744 1769
1745 1770 if 'uri_tmpl' in override:
1746 1771 uri_tmpl = override['uri_tmpl']
1747 1772 del override['uri_tmpl']
1748 1773
1749 1774 # we didn't override our tmpl from **overrides
1750 1775 if not uri_tmpl:
1751 1776 uri_tmpl = self.DEFAULT_CLONE_URI
1752 1777 try:
1753 1778 from pylons import tmpl_context as c
1754 1779 uri_tmpl = c.clone_uri_tmpl
1755 1780 except Exception:
1756 1781 # in any case if we call this outside of request context,
1757 1782 # ie, not having tmpl_context set up
1758 1783 pass
1759 1784
1760 1785 return get_clone_url(uri_tmpl=uri_tmpl,
1761 1786 qualifed_home_url=qualified_home_url,
1762 1787 repo_name=self.repo_name,
1763 1788 repo_id=self.repo_id, **override)
1764 1789
1765 1790 def set_state(self, state):
1766 1791 self.repo_state = state
1767 1792 Session().add(self)
1768 1793 #==========================================================================
1769 1794 # SCM PROPERTIES
1770 1795 #==========================================================================
1771 1796
1772 1797 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1773 1798 return get_commit_safe(
1774 1799 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1775 1800
1776 1801 def get_changeset(self, rev=None, pre_load=None):
1777 1802 warnings.warn("Use get_commit", DeprecationWarning)
1778 1803 commit_id = None
1779 1804 commit_idx = None
1780 1805 if isinstance(rev, basestring):
1781 1806 commit_id = rev
1782 1807 else:
1783 1808 commit_idx = rev
1784 1809 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1785 1810 pre_load=pre_load)
1786 1811
1787 1812 def get_landing_commit(self):
1788 1813 """
1789 1814 Returns landing commit, or if that doesn't exist returns the tip
1790 1815 """
1791 1816 _rev_type, _rev = self.landing_rev
1792 1817 commit = self.get_commit(_rev)
1793 1818 if isinstance(commit, EmptyCommit):
1794 1819 return self.get_commit()
1795 1820 return commit
1796 1821
1797 1822 def update_commit_cache(self, cs_cache=None, config=None):
1798 1823 """
1799 1824 Update cache of last changeset for repository, keys should be::
1800 1825
1801 1826 short_id
1802 1827 raw_id
1803 1828 revision
1804 1829 parents
1805 1830 message
1806 1831 date
1807 1832 author
1808 1833
1809 1834 :param cs_cache:
1810 1835 """
1811 1836 from rhodecode.lib.vcs.backends.base import BaseChangeset
1812 1837 if cs_cache is None:
1813 1838 # use no-cache version here
1814 1839 scm_repo = self.scm_instance(cache=False, config=config)
1815 1840 if scm_repo:
1816 1841 cs_cache = scm_repo.get_commit(
1817 1842 pre_load=["author", "date", "message", "parents"])
1818 1843 else:
1819 1844 cs_cache = EmptyCommit()
1820 1845
1821 1846 if isinstance(cs_cache, BaseChangeset):
1822 1847 cs_cache = cs_cache.__json__()
1823 1848
1824 1849 def is_outdated(new_cs_cache):
1825 1850 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1826 1851 return True
1827 1852 return False
1828 1853
1829 1854 # check if we have maybe already latest cached revision
1830 1855 if is_outdated(cs_cache) or not self.changeset_cache:
1831 1856 _default = datetime.datetime.fromtimestamp(0)
1832 1857 last_change = cs_cache.get('date') or _default
1833 1858 log.debug('updated repo %s with new cs cache %s',
1834 1859 self.repo_name, cs_cache)
1835 1860 self.updated_on = last_change
1836 1861 self.changeset_cache = cs_cache
1837 1862 Session().add(self)
1838 1863 Session().commit()
1839 1864 else:
1840 1865 log.debug('Skipping update_commit_cache for repo:`%s` '
1841 1866 'commit already with latest changes', self.repo_name)
1842 1867
1843 1868 @property
1844 1869 def tip(self):
1845 1870 return self.get_commit('tip')
1846 1871
1847 1872 @property
1848 1873 def author(self):
1849 1874 return self.tip.author
1850 1875
1851 1876 @property
1852 1877 def last_change(self):
1853 1878 return self.scm_instance().last_change
1854 1879
1855 1880 def get_comments(self, revisions=None):
1856 1881 """
1857 1882 Returns comments for this repository grouped by revisions
1858 1883
1859 1884 :param revisions: filter query by revisions only
1860 1885 """
1861 1886 cmts = ChangesetComment.query()\
1862 1887 .filter(ChangesetComment.repo == self)
1863 1888 if revisions:
1864 1889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1865 1890 grouped = collections.defaultdict(list)
1866 1891 for cmt in cmts.all():
1867 1892 grouped[cmt.revision].append(cmt)
1868 1893 return grouped
1869 1894
1870 1895 def statuses(self, revisions=None):
1871 1896 """
1872 1897 Returns statuses for this repository
1873 1898
1874 1899 :param revisions: list of revisions to get statuses for
1875 1900 """
1876 1901 statuses = ChangesetStatus.query()\
1877 1902 .filter(ChangesetStatus.repo == self)\
1878 1903 .filter(ChangesetStatus.version == 0)
1879 1904
1880 1905 if revisions:
1881 1906 # Try doing the filtering in chunks to avoid hitting limits
1882 1907 size = 500
1883 1908 status_results = []
1884 1909 for chunk in xrange(0, len(revisions), size):
1885 1910 status_results += statuses.filter(
1886 1911 ChangesetStatus.revision.in_(
1887 1912 revisions[chunk: chunk+size])
1888 1913 ).all()
1889 1914 else:
1890 1915 status_results = statuses.all()
1891 1916
1892 1917 grouped = {}
1893 1918
1894 1919 # maybe we have open new pullrequest without a status?
1895 1920 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1896 1921 status_lbl = ChangesetStatus.get_status_lbl(stat)
1897 1922 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1898 1923 for rev in pr.revisions:
1899 1924 pr_id = pr.pull_request_id
1900 1925 pr_repo = pr.target_repo.repo_name
1901 1926 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1902 1927
1903 1928 for stat in status_results:
1904 1929 pr_id = pr_repo = None
1905 1930 if stat.pull_request:
1906 1931 pr_id = stat.pull_request.pull_request_id
1907 1932 pr_repo = stat.pull_request.target_repo.repo_name
1908 1933 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1909 1934 pr_id, pr_repo]
1910 1935 return grouped
1911 1936
1912 1937 # ==========================================================================
1913 1938 # SCM CACHE INSTANCE
1914 1939 # ==========================================================================
1915 1940
1916 1941 def scm_instance(self, **kwargs):
1917 1942 import rhodecode
1918 1943
1919 1944 # Passing a config will not hit the cache currently only used
1920 1945 # for repo2dbmapper
1921 1946 config = kwargs.pop('config', None)
1922 1947 cache = kwargs.pop('cache', None)
1923 1948 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1924 1949 # if cache is NOT defined use default global, else we have a full
1925 1950 # control over cache behaviour
1926 1951 if cache is None and full_cache and not config:
1927 1952 return self._get_instance_cached()
1928 1953 return self._get_instance(cache=bool(cache), config=config)
1929 1954
1930 1955 def _get_instance_cached(self):
1931 1956 @cache_region('long_term')
1932 1957 def _get_repo(cache_key):
1933 1958 return self._get_instance()
1934 1959
1935 1960 invalidator_context = CacheKey.repo_context_cache(
1936 1961 _get_repo, self.repo_name, None)
1937 1962
1938 1963 with invalidator_context as context:
1939 1964 context.invalidate()
1940 1965 repo = context.compute()
1941 1966
1942 1967 return repo
1943 1968
1944 1969 def _get_instance(self, cache=True, config=None):
1945 1970 repo_full_path = self.repo_full_path
1946 1971 try:
1947 1972 vcs_alias = get_scm(repo_full_path)[0]
1948 1973 log.debug(
1949 1974 'Creating instance of %s repository from %s',
1950 1975 vcs_alias, repo_full_path)
1951 1976 backend = get_backend(vcs_alias)
1952 1977 except VCSError:
1953 1978 log.exception(
1954 1979 'Perhaps this repository is in db and not in '
1955 1980 'filesystem run rescan repositories with '
1956 1981 '"destroy old data" option from admin panel')
1957 1982 return
1958 1983
1959 1984 config = config or self._config
1960 1985 custom_wire = {
1961 1986 'cache': cache # controls the vcs.remote cache
1962 1987 }
1963 1988 repo = backend(
1964 1989 safe_str(repo_full_path), config=config, create=False,
1965 1990 with_wire=custom_wire)
1966 1991
1967 1992 return repo
1968 1993
1969 1994 def __json__(self):
1970 1995 return {'landing_rev': self.landing_rev}
1971 1996
1972 1997 def get_dict(self):
1973 1998
1974 1999 # Since we transformed `repo_name` to a hybrid property, we need to
1975 2000 # keep compatibility with the code which uses `repo_name` field.
1976 2001
1977 2002 result = super(Repository, self).get_dict()
1978 2003 result['repo_name'] = result.pop('_repo_name', None)
1979 2004 return result
1980 2005
1981 2006
1982 2007 class RepoGroup(Base, BaseModel):
1983 2008 __tablename__ = 'groups'
1984 2009 __table_args__ = (
1985 2010 UniqueConstraint('group_name', 'group_parent_id'),
1986 2011 CheckConstraint('group_id != group_parent_id'),
1987 2012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1988 2013 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1989 2014 )
1990 2015 __mapper_args__ = {'order_by': 'group_name'}
1991 2016
1992 2017 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
1993 2018
1994 2019 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1995 2020 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
1996 2021 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1997 2022 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
1998 2023 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1999 2024 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2000 2025 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2001 2026
2002 2027 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2003 2028 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2004 2029 parent_group = relationship('RepoGroup', remote_side=group_id)
2005 2030 user = relationship('User')
2006 2031
2007 2032 def __init__(self, group_name='', parent_group=None):
2008 2033 self.group_name = group_name
2009 2034 self.parent_group = parent_group
2010 2035
2011 2036 def __unicode__(self):
2012 2037 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2013 2038 self.group_name)
2014 2039
2015 2040 @classmethod
2016 2041 def _generate_choice(cls, repo_group):
2017 2042 from webhelpers.html import literal as _literal
2018 2043 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2019 2044 return repo_group.group_id, _name(repo_group.full_path_splitted)
2020 2045
2021 2046 @classmethod
2022 2047 def groups_choices(cls, groups=None, show_empty_group=True):
2023 2048 if not groups:
2024 2049 groups = cls.query().all()
2025 2050
2026 2051 repo_groups = []
2027 2052 if show_empty_group:
2028 2053 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2029 2054
2030 2055 repo_groups.extend([cls._generate_choice(x) for x in groups])
2031 2056
2032 2057 repo_groups = sorted(
2033 2058 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2034 2059 return repo_groups
2035 2060
2036 2061 @classmethod
2037 2062 def url_sep(cls):
2038 2063 return URL_SEP
2039 2064
2040 2065 @classmethod
2041 2066 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2042 2067 if case_insensitive:
2043 2068 gr = cls.query().filter(func.lower(cls.group_name)
2044 2069 == func.lower(group_name))
2045 2070 else:
2046 2071 gr = cls.query().filter(cls.group_name == group_name)
2047 2072 if cache:
2048 2073 gr = gr.options(FromCache(
2049 2074 "sql_cache_short",
2050 2075 "get_group_%s" % _hash_key(group_name)))
2051 2076 return gr.scalar()
2052 2077
2053 2078 @classmethod
2054 2079 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2055 2080 case_insensitive=True):
2056 2081 q = RepoGroup.query()
2057 2082
2058 2083 if not isinstance(user_id, Optional):
2059 2084 q = q.filter(RepoGroup.user_id == user_id)
2060 2085
2061 2086 if not isinstance(group_id, Optional):
2062 2087 q = q.filter(RepoGroup.group_parent_id == group_id)
2063 2088
2064 2089 if case_insensitive:
2065 2090 q = q.order_by(func.lower(RepoGroup.group_name))
2066 2091 else:
2067 2092 q = q.order_by(RepoGroup.group_name)
2068 2093 return q.all()
2069 2094
2070 2095 @property
2071 2096 def parents(self):
2072 2097 parents_recursion_limit = 10
2073 2098 groups = []
2074 2099 if self.parent_group is None:
2075 2100 return groups
2076 2101 cur_gr = self.parent_group
2077 2102 groups.insert(0, cur_gr)
2078 2103 cnt = 0
2079 2104 while 1:
2080 2105 cnt += 1
2081 2106 gr = getattr(cur_gr, 'parent_group', None)
2082 2107 cur_gr = cur_gr.parent_group
2083 2108 if gr is None:
2084 2109 break
2085 2110 if cnt == parents_recursion_limit:
2086 2111 # this will prevent accidental infinit loops
2087 2112 log.error(('more than %s parents found for group %s, stopping '
2088 2113 'recursive parent fetching' % (parents_recursion_limit, self)))
2089 2114 break
2090 2115
2091 2116 groups.insert(0, gr)
2092 2117 return groups
2093 2118
2094 2119 @property
2095 2120 def children(self):
2096 2121 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2097 2122
2098 2123 @property
2099 2124 def name(self):
2100 2125 return self.group_name.split(RepoGroup.url_sep())[-1]
2101 2126
2102 2127 @property
2103 2128 def full_path(self):
2104 2129 return self.group_name
2105 2130
2106 2131 @property
2107 2132 def full_path_splitted(self):
2108 2133 return self.group_name.split(RepoGroup.url_sep())
2109 2134
2110 2135 @property
2111 2136 def repositories(self):
2112 2137 return Repository.query()\
2113 2138 .filter(Repository.group == self)\
2114 2139 .order_by(Repository.repo_name)
2115 2140
2116 2141 @property
2117 2142 def repositories_recursive_count(self):
2118 2143 cnt = self.repositories.count()
2119 2144
2120 2145 def children_count(group):
2121 2146 cnt = 0
2122 2147 for child in group.children:
2123 2148 cnt += child.repositories.count()
2124 2149 cnt += children_count(child)
2125 2150 return cnt
2126 2151
2127 2152 return cnt + children_count(self)
2128 2153
2129 2154 def _recursive_objects(self, include_repos=True):
2130 2155 all_ = []
2131 2156
2132 2157 def _get_members(root_gr):
2133 2158 if include_repos:
2134 2159 for r in root_gr.repositories:
2135 2160 all_.append(r)
2136 2161 childs = root_gr.children.all()
2137 2162 if childs:
2138 2163 for gr in childs:
2139 2164 all_.append(gr)
2140 2165 _get_members(gr)
2141 2166
2142 2167 _get_members(self)
2143 2168 return [self] + all_
2144 2169
2145 2170 def recursive_groups_and_repos(self):
2146 2171 """
2147 2172 Recursive return all groups, with repositories in those groups
2148 2173 """
2149 2174 return self._recursive_objects()
2150 2175
2151 2176 def recursive_groups(self):
2152 2177 """
2153 2178 Returns all children groups for this group including children of children
2154 2179 """
2155 2180 return self._recursive_objects(include_repos=False)
2156 2181
2157 2182 def get_new_name(self, group_name):
2158 2183 """
2159 2184 returns new full group name based on parent and new name
2160 2185
2161 2186 :param group_name:
2162 2187 """
2163 2188 path_prefix = (self.parent_group.full_path_splitted if
2164 2189 self.parent_group else [])
2165 2190 return RepoGroup.url_sep().join(path_prefix + [group_name])
2166 2191
2167 2192 def permissions(self, with_admins=True, with_owner=True):
2168 2193 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2169 2194 q = q.options(joinedload(UserRepoGroupToPerm.group),
2170 2195 joinedload(UserRepoGroupToPerm.user),
2171 2196 joinedload(UserRepoGroupToPerm.permission),)
2172 2197
2173 2198 # get owners and admins and permissions. We do a trick of re-writing
2174 2199 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2175 2200 # has a global reference and changing one object propagates to all
2176 2201 # others. This means if admin is also an owner admin_row that change
2177 2202 # would propagate to both objects
2178 2203 perm_rows = []
2179 2204 for _usr in q.all():
2180 2205 usr = AttributeDict(_usr.user.get_dict())
2181 2206 usr.permission = _usr.permission.permission_name
2182 2207 perm_rows.append(usr)
2183 2208
2184 2209 # filter the perm rows by 'default' first and then sort them by
2185 2210 # admin,write,read,none permissions sorted again alphabetically in
2186 2211 # each group
2187 2212 perm_rows = sorted(perm_rows, key=display_sort)
2188 2213
2189 2214 _admin_perm = 'group.admin'
2190 2215 owner_row = []
2191 2216 if with_owner:
2192 2217 usr = AttributeDict(self.user.get_dict())
2193 2218 usr.owner_row = True
2194 2219 usr.permission = _admin_perm
2195 2220 owner_row.append(usr)
2196 2221
2197 2222 super_admin_rows = []
2198 2223 if with_admins:
2199 2224 for usr in User.get_all_super_admins():
2200 2225 # if this admin is also owner, don't double the record
2201 2226 if usr.user_id == owner_row[0].user_id:
2202 2227 owner_row[0].admin_row = True
2203 2228 else:
2204 2229 usr = AttributeDict(usr.get_dict())
2205 2230 usr.admin_row = True
2206 2231 usr.permission = _admin_perm
2207 2232 super_admin_rows.append(usr)
2208 2233
2209 2234 return super_admin_rows + owner_row + perm_rows
2210 2235
2211 2236 def permission_user_groups(self):
2212 2237 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2213 2238 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2214 2239 joinedload(UserGroupRepoGroupToPerm.users_group),
2215 2240 joinedload(UserGroupRepoGroupToPerm.permission),)
2216 2241
2217 2242 perm_rows = []
2218 2243 for _user_group in q.all():
2219 2244 usr = AttributeDict(_user_group.users_group.get_dict())
2220 2245 usr.permission = _user_group.permission.permission_name
2221 2246 perm_rows.append(usr)
2222 2247
2223 2248 return perm_rows
2224 2249
2225 2250 def get_api_data(self):
2226 2251 """
2227 2252 Common function for generating api data
2228 2253
2229 2254 """
2230 2255 group = self
2231 2256 data = {
2232 2257 'group_id': group.group_id,
2233 2258 'group_name': group.group_name,
2234 2259 'group_description': group.group_description,
2235 2260 'parent_group': group.parent_group.group_name if group.parent_group else None,
2236 2261 'repositories': [x.repo_name for x in group.repositories],
2237 2262 'owner': group.user.username,
2238 2263 }
2239 2264 return data
2240 2265
2241 2266
2242 2267 class Permission(Base, BaseModel):
2243 2268 __tablename__ = 'permissions'
2244 2269 __table_args__ = (
2245 2270 Index('p_perm_name_idx', 'permission_name'),
2246 2271 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2247 2272 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2248 2273 )
2249 2274 PERMS = [
2250 2275 ('hg.admin', _('RhodeCode Super Administrator')),
2251 2276
2252 2277 ('repository.none', _('Repository no access')),
2253 2278 ('repository.read', _('Repository read access')),
2254 2279 ('repository.write', _('Repository write access')),
2255 2280 ('repository.admin', _('Repository admin access')),
2256 2281
2257 2282 ('group.none', _('Repository group no access')),
2258 2283 ('group.read', _('Repository group read access')),
2259 2284 ('group.write', _('Repository group write access')),
2260 2285 ('group.admin', _('Repository group admin access')),
2261 2286
2262 2287 ('usergroup.none', _('User group no access')),
2263 2288 ('usergroup.read', _('User group read access')),
2264 2289 ('usergroup.write', _('User group write access')),
2265 2290 ('usergroup.admin', _('User group admin access')),
2266 2291
2267 2292 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2268 2293 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2269 2294
2270 2295 ('hg.usergroup.create.false', _('User Group creation disabled')),
2271 2296 ('hg.usergroup.create.true', _('User Group creation enabled')),
2272 2297
2273 2298 ('hg.create.none', _('Repository creation disabled')),
2274 2299 ('hg.create.repository', _('Repository creation enabled')),
2275 2300 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2276 2301 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2277 2302
2278 2303 ('hg.fork.none', _('Repository forking disabled')),
2279 2304 ('hg.fork.repository', _('Repository forking enabled')),
2280 2305
2281 2306 ('hg.register.none', _('Registration disabled')),
2282 2307 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2283 2308 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2284 2309
2285 2310 ('hg.extern_activate.manual', _('Manual activation of external account')),
2286 2311 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2287 2312
2288 2313 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2289 2314 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2290 2315 ]
2291 2316
2292 2317 # definition of system default permissions for DEFAULT user
2293 2318 DEFAULT_USER_PERMISSIONS = [
2294 2319 'repository.read',
2295 2320 'group.read',
2296 2321 'usergroup.read',
2297 2322 'hg.create.repository',
2298 2323 'hg.repogroup.create.false',
2299 2324 'hg.usergroup.create.false',
2300 2325 'hg.create.write_on_repogroup.true',
2301 2326 'hg.fork.repository',
2302 2327 'hg.register.manual_activate',
2303 2328 'hg.extern_activate.auto',
2304 2329 'hg.inherit_default_perms.true',
2305 2330 ]
2306 2331
2307 2332 # defines which permissions are more important higher the more important
2308 2333 # Weight defines which permissions are more important.
2309 2334 # The higher number the more important.
2310 2335 PERM_WEIGHTS = {
2311 2336 'repository.none': 0,
2312 2337 'repository.read': 1,
2313 2338 'repository.write': 3,
2314 2339 'repository.admin': 4,
2315 2340
2316 2341 'group.none': 0,
2317 2342 'group.read': 1,
2318 2343 'group.write': 3,
2319 2344 'group.admin': 4,
2320 2345
2321 2346 'usergroup.none': 0,
2322 2347 'usergroup.read': 1,
2323 2348 'usergroup.write': 3,
2324 2349 'usergroup.admin': 4,
2325 2350
2326 2351 'hg.repogroup.create.false': 0,
2327 2352 'hg.repogroup.create.true': 1,
2328 2353
2329 2354 'hg.usergroup.create.false': 0,
2330 2355 'hg.usergroup.create.true': 1,
2331 2356
2332 2357 'hg.fork.none': 0,
2333 2358 'hg.fork.repository': 1,
2334 2359 'hg.create.none': 0,
2335 2360 'hg.create.repository': 1
2336 2361 }
2337 2362
2338 2363 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2339 2364 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2340 2365 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2341 2366
2342 2367 def __unicode__(self):
2343 2368 return u"<%s('%s:%s')>" % (
2344 2369 self.__class__.__name__, self.permission_id, self.permission_name
2345 2370 )
2346 2371
2347 2372 @classmethod
2348 2373 def get_by_key(cls, key):
2349 2374 return cls.query().filter(cls.permission_name == key).scalar()
2350 2375
2351 2376 @classmethod
2352 2377 def get_default_repo_perms(cls, user_id, repo_id=None):
2353 2378 q = Session().query(UserRepoToPerm, Repository, Permission)\
2354 2379 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2355 2380 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2356 2381 .filter(UserRepoToPerm.user_id == user_id)
2357 2382 if repo_id:
2358 2383 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2359 2384 return q.all()
2360 2385
2361 2386 @classmethod
2362 2387 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2363 2388 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2364 2389 .join(
2365 2390 Permission,
2366 2391 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2367 2392 .join(
2368 2393 Repository,
2369 2394 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2370 2395 .join(
2371 2396 UserGroup,
2372 2397 UserGroupRepoToPerm.users_group_id ==
2373 2398 UserGroup.users_group_id)\
2374 2399 .join(
2375 2400 UserGroupMember,
2376 2401 UserGroupRepoToPerm.users_group_id ==
2377 2402 UserGroupMember.users_group_id)\
2378 2403 .filter(
2379 2404 UserGroupMember.user_id == user_id,
2380 2405 UserGroup.users_group_active == true())
2381 2406 if repo_id:
2382 2407 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2383 2408 return q.all()
2384 2409
2385 2410 @classmethod
2386 2411 def get_default_group_perms(cls, user_id, repo_group_id=None):
2387 2412 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2388 2413 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2389 2414 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2390 2415 .filter(UserRepoGroupToPerm.user_id == user_id)
2391 2416 if repo_group_id:
2392 2417 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2393 2418 return q.all()
2394 2419
2395 2420 @classmethod
2396 2421 def get_default_group_perms_from_user_group(
2397 2422 cls, user_id, repo_group_id=None):
2398 2423 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2399 2424 .join(
2400 2425 Permission,
2401 2426 UserGroupRepoGroupToPerm.permission_id ==
2402 2427 Permission.permission_id)\
2403 2428 .join(
2404 2429 RepoGroup,
2405 2430 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2406 2431 .join(
2407 2432 UserGroup,
2408 2433 UserGroupRepoGroupToPerm.users_group_id ==
2409 2434 UserGroup.users_group_id)\
2410 2435 .join(
2411 2436 UserGroupMember,
2412 2437 UserGroupRepoGroupToPerm.users_group_id ==
2413 2438 UserGroupMember.users_group_id)\
2414 2439 .filter(
2415 2440 UserGroupMember.user_id == user_id,
2416 2441 UserGroup.users_group_active == true())
2417 2442 if repo_group_id:
2418 2443 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2419 2444 return q.all()
2420 2445
2421 2446 @classmethod
2422 2447 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2423 2448 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2424 2449 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2425 2450 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2426 2451 .filter(UserUserGroupToPerm.user_id == user_id)
2427 2452 if user_group_id:
2428 2453 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2429 2454 return q.all()
2430 2455
2431 2456 @classmethod
2432 2457 def get_default_user_group_perms_from_user_group(
2433 2458 cls, user_id, user_group_id=None):
2434 2459 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2435 2460 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2436 2461 .join(
2437 2462 Permission,
2438 2463 UserGroupUserGroupToPerm.permission_id ==
2439 2464 Permission.permission_id)\
2440 2465 .join(
2441 2466 TargetUserGroup,
2442 2467 UserGroupUserGroupToPerm.target_user_group_id ==
2443 2468 TargetUserGroup.users_group_id)\
2444 2469 .join(
2445 2470 UserGroup,
2446 2471 UserGroupUserGroupToPerm.user_group_id ==
2447 2472 UserGroup.users_group_id)\
2448 2473 .join(
2449 2474 UserGroupMember,
2450 2475 UserGroupUserGroupToPerm.user_group_id ==
2451 2476 UserGroupMember.users_group_id)\
2452 2477 .filter(
2453 2478 UserGroupMember.user_id == user_id,
2454 2479 UserGroup.users_group_active == true())
2455 2480 if user_group_id:
2456 2481 q = q.filter(
2457 2482 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2458 2483
2459 2484 return q.all()
2460 2485
2461 2486
2462 2487 class UserRepoToPerm(Base, BaseModel):
2463 2488 __tablename__ = 'repo_to_perm'
2464 2489 __table_args__ = (
2465 2490 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2466 2491 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2467 2492 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2468 2493 )
2469 2494 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2470 2495 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2471 2496 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2472 2497 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2473 2498
2474 2499 user = relationship('User')
2475 2500 repository = relationship('Repository')
2476 2501 permission = relationship('Permission')
2477 2502
2478 2503 @classmethod
2479 2504 def create(cls, user, repository, permission):
2480 2505 n = cls()
2481 2506 n.user = user
2482 2507 n.repository = repository
2483 2508 n.permission = permission
2484 2509 Session().add(n)
2485 2510 return n
2486 2511
2487 2512 def __unicode__(self):
2488 2513 return u'<%s => %s >' % (self.user, self.repository)
2489 2514
2490 2515
2491 2516 class UserUserGroupToPerm(Base, BaseModel):
2492 2517 __tablename__ = 'user_user_group_to_perm'
2493 2518 __table_args__ = (
2494 2519 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2495 2520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2496 2521 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2497 2522 )
2498 2523 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2499 2524 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2500 2525 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2501 2526 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2502 2527
2503 2528 user = relationship('User')
2504 2529 user_group = relationship('UserGroup')
2505 2530 permission = relationship('Permission')
2506 2531
2507 2532 @classmethod
2508 2533 def create(cls, user, user_group, permission):
2509 2534 n = cls()
2510 2535 n.user = user
2511 2536 n.user_group = user_group
2512 2537 n.permission = permission
2513 2538 Session().add(n)
2514 2539 return n
2515 2540
2516 2541 def __unicode__(self):
2517 2542 return u'<%s => %s >' % (self.user, self.user_group)
2518 2543
2519 2544
2520 2545 class UserToPerm(Base, BaseModel):
2521 2546 __tablename__ = 'user_to_perm'
2522 2547 __table_args__ = (
2523 2548 UniqueConstraint('user_id', 'permission_id'),
2524 2549 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2525 2550 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2526 2551 )
2527 2552 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2528 2553 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2529 2554 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2530 2555
2531 2556 user = relationship('User')
2532 2557 permission = relationship('Permission', lazy='joined')
2533 2558
2534 2559 def __unicode__(self):
2535 2560 return u'<%s => %s >' % (self.user, self.permission)
2536 2561
2537 2562
2538 2563 class UserGroupRepoToPerm(Base, BaseModel):
2539 2564 __tablename__ = 'users_group_repo_to_perm'
2540 2565 __table_args__ = (
2541 2566 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2542 2567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2543 2568 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2544 2569 )
2545 2570 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2546 2571 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2547 2572 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2548 2573 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2549 2574
2550 2575 users_group = relationship('UserGroup')
2551 2576 permission = relationship('Permission')
2552 2577 repository = relationship('Repository')
2553 2578
2554 2579 @classmethod
2555 2580 def create(cls, users_group, repository, permission):
2556 2581 n = cls()
2557 2582 n.users_group = users_group
2558 2583 n.repository = repository
2559 2584 n.permission = permission
2560 2585 Session().add(n)
2561 2586 return n
2562 2587
2563 2588 def __unicode__(self):
2564 2589 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2565 2590
2566 2591
2567 2592 class UserGroupUserGroupToPerm(Base, BaseModel):
2568 2593 __tablename__ = 'user_group_user_group_to_perm'
2569 2594 __table_args__ = (
2570 2595 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2571 2596 CheckConstraint('target_user_group_id != user_group_id'),
2572 2597 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2573 2598 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2574 2599 )
2575 2600 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2576 2601 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2577 2602 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2578 2603 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2579 2604
2580 2605 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2581 2606 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2582 2607 permission = relationship('Permission')
2583 2608
2584 2609 @classmethod
2585 2610 def create(cls, target_user_group, user_group, permission):
2586 2611 n = cls()
2587 2612 n.target_user_group = target_user_group
2588 2613 n.user_group = user_group
2589 2614 n.permission = permission
2590 2615 Session().add(n)
2591 2616 return n
2592 2617
2593 2618 def __unicode__(self):
2594 2619 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2595 2620
2596 2621
2597 2622 class UserGroupToPerm(Base, BaseModel):
2598 2623 __tablename__ = 'users_group_to_perm'
2599 2624 __table_args__ = (
2600 2625 UniqueConstraint('users_group_id', 'permission_id',),
2601 2626 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2602 2627 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2603 2628 )
2604 2629 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2605 2630 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2606 2631 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2607 2632
2608 2633 users_group = relationship('UserGroup')
2609 2634 permission = relationship('Permission')
2610 2635
2611 2636
2612 2637 class UserRepoGroupToPerm(Base, BaseModel):
2613 2638 __tablename__ = 'user_repo_group_to_perm'
2614 2639 __table_args__ = (
2615 2640 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2616 2641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2617 2642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2618 2643 )
2619 2644
2620 2645 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2621 2646 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2622 2647 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2623 2648 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2624 2649
2625 2650 user = relationship('User')
2626 2651 group = relationship('RepoGroup')
2627 2652 permission = relationship('Permission')
2628 2653
2629 2654 @classmethod
2630 2655 def create(cls, user, repository_group, permission):
2631 2656 n = cls()
2632 2657 n.user = user
2633 2658 n.group = repository_group
2634 2659 n.permission = permission
2635 2660 Session().add(n)
2636 2661 return n
2637 2662
2638 2663
2639 2664 class UserGroupRepoGroupToPerm(Base, BaseModel):
2640 2665 __tablename__ = 'users_group_repo_group_to_perm'
2641 2666 __table_args__ = (
2642 2667 UniqueConstraint('users_group_id', 'group_id'),
2643 2668 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2644 2669 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2645 2670 )
2646 2671
2647 2672 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)
2648 2673 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2649 2674 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2650 2675 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2651 2676
2652 2677 users_group = relationship('UserGroup')
2653 2678 permission = relationship('Permission')
2654 2679 group = relationship('RepoGroup')
2655 2680
2656 2681 @classmethod
2657 2682 def create(cls, user_group, repository_group, permission):
2658 2683 n = cls()
2659 2684 n.users_group = user_group
2660 2685 n.group = repository_group
2661 2686 n.permission = permission
2662 2687 Session().add(n)
2663 2688 return n
2664 2689
2665 2690 def __unicode__(self):
2666 2691 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2667 2692
2668 2693
2669 2694 class Statistics(Base, BaseModel):
2670 2695 __tablename__ = 'statistics'
2671 2696 __table_args__ = (
2672 2697 UniqueConstraint('repository_id'),
2673 2698 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2674 2699 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2675 2700 )
2676 2701 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2677 2702 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2678 2703 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2679 2704 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2680 2705 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2681 2706 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2682 2707
2683 2708 repository = relationship('Repository', single_parent=True)
2684 2709
2685 2710
2686 2711 class UserFollowing(Base, BaseModel):
2687 2712 __tablename__ = 'user_followings'
2688 2713 __table_args__ = (
2689 2714 UniqueConstraint('user_id', 'follows_repository_id'),
2690 2715 UniqueConstraint('user_id', 'follows_user_id'),
2691 2716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2692 2717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2693 2718 )
2694 2719
2695 2720 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2696 2721 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2697 2722 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2698 2723 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2699 2724 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2700 2725
2701 2726 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2702 2727
2703 2728 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2704 2729 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2705 2730
2706 2731 @classmethod
2707 2732 def get_repo_followers(cls, repo_id):
2708 2733 return cls.query().filter(cls.follows_repo_id == repo_id)
2709 2734
2710 2735
2711 2736 class CacheKey(Base, BaseModel):
2712 2737 __tablename__ = 'cache_invalidation'
2713 2738 __table_args__ = (
2714 2739 UniqueConstraint('cache_key'),
2715 2740 Index('key_idx', 'cache_key'),
2716 2741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2717 2742 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2718 2743 )
2719 2744 CACHE_TYPE_ATOM = 'ATOM'
2720 2745 CACHE_TYPE_RSS = 'RSS'
2721 2746 CACHE_TYPE_README = 'README'
2722 2747
2723 2748 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2724 2749 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2725 2750 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2726 2751 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2727 2752
2728 2753 def __init__(self, cache_key, cache_args=''):
2729 2754 self.cache_key = cache_key
2730 2755 self.cache_args = cache_args
2731 2756 self.cache_active = False
2732 2757
2733 2758 def __unicode__(self):
2734 2759 return u"<%s('%s:%s[%s]')>" % (
2735 2760 self.__class__.__name__,
2736 2761 self.cache_id, self.cache_key, self.cache_active)
2737 2762
2738 2763 def _cache_key_partition(self):
2739 2764 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2740 2765 return prefix, repo_name, suffix
2741 2766
2742 2767 def get_prefix(self):
2743 2768 """
2744 2769 Try to extract prefix from existing cache key. The key could consist
2745 2770 of prefix, repo_name, suffix
2746 2771 """
2747 2772 # this returns prefix, repo_name, suffix
2748 2773 return self._cache_key_partition()[0]
2749 2774
2750 2775 def get_suffix(self):
2751 2776 """
2752 2777 get suffix that might have been used in _get_cache_key to
2753 2778 generate self.cache_key. Only used for informational purposes
2754 2779 in repo_edit.html.
2755 2780 """
2756 2781 # prefix, repo_name, suffix
2757 2782 return self._cache_key_partition()[2]
2758 2783
2759 2784 @classmethod
2760 2785 def delete_all_cache(cls):
2761 2786 """
2762 2787 Delete all cache keys from database.
2763 2788 Should only be run when all instances are down and all entries
2764 2789 thus stale.
2765 2790 """
2766 2791 cls.query().delete()
2767 2792 Session().commit()
2768 2793
2769 2794 @classmethod
2770 2795 def get_cache_key(cls, repo_name, cache_type):
2771 2796 """
2772 2797
2773 2798 Generate a cache key for this process of RhodeCode instance.
2774 2799 Prefix most likely will be process id or maybe explicitly set
2775 2800 instance_id from .ini file.
2776 2801 """
2777 2802 import rhodecode
2778 2803 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2779 2804
2780 2805 repo_as_unicode = safe_unicode(repo_name)
2781 2806 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2782 2807 if cache_type else repo_as_unicode
2783 2808
2784 2809 return u'{}{}'.format(prefix, key)
2785 2810
2786 2811 @classmethod
2787 2812 def set_invalidate(cls, repo_name, delete=False):
2788 2813 """
2789 2814 Mark all caches of a repo as invalid in the database.
2790 2815 """
2791 2816
2792 2817 try:
2793 2818 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2794 2819 if delete:
2795 2820 log.debug('cache objects deleted for repo %s',
2796 2821 safe_str(repo_name))
2797 2822 qry.delete()
2798 2823 else:
2799 2824 log.debug('cache objects marked as invalid for repo %s',
2800 2825 safe_str(repo_name))
2801 2826 qry.update({"cache_active": False})
2802 2827
2803 2828 Session().commit()
2804 2829 except Exception:
2805 2830 log.exception(
2806 2831 'Cache key invalidation failed for repository %s',
2807 2832 safe_str(repo_name))
2808 2833 Session().rollback()
2809 2834
2810 2835 @classmethod
2811 2836 def get_active_cache(cls, cache_key):
2812 2837 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2813 2838 if inv_obj:
2814 2839 return inv_obj
2815 2840 return None
2816 2841
2817 2842 @classmethod
2818 2843 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2819 2844 """
2820 2845 @cache_region('long_term')
2821 2846 def _heavy_calculation(cache_key):
2822 2847 return 'result'
2823 2848
2824 2849 cache_context = CacheKey.repo_context_cache(
2825 2850 _heavy_calculation, repo_name, cache_type)
2826 2851
2827 2852 with cache_context as context:
2828 2853 context.invalidate()
2829 2854 computed = context.compute()
2830 2855
2831 2856 assert computed == 'result'
2832 2857 """
2833 2858 from rhodecode.lib import caches
2834 2859 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2835 2860
2836 2861
2837 2862 class ChangesetComment(Base, BaseModel):
2838 2863 __tablename__ = 'changeset_comments'
2839 2864 __table_args__ = (
2840 2865 Index('cc_revision_idx', 'revision'),
2841 2866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2842 2867 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2843 2868 )
2844 2869
2845 2870 COMMENT_OUTDATED = u'comment_outdated'
2846 2871
2847 2872 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2848 2873 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2849 2874 revision = Column('revision', String(40), nullable=True)
2850 2875 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2851 2876 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2852 2877 line_no = Column('line_no', Unicode(10), nullable=True)
2853 2878 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2854 2879 f_path = Column('f_path', Unicode(1000), nullable=True)
2855 2880 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2856 2881 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2857 2882 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2858 2883 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2859 2884 renderer = Column('renderer', Unicode(64), nullable=True)
2860 2885 display_state = Column('display_state', Unicode(128), nullable=True)
2861 2886
2862 2887 author = relationship('User', lazy='joined')
2863 2888 repo = relationship('Repository')
2864 2889 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2865 2890 pull_request = relationship('PullRequest', lazy='joined')
2866 2891 pull_request_version = relationship('PullRequestVersion')
2867 2892
2868 2893 @classmethod
2869 2894 def get_users(cls, revision=None, pull_request_id=None):
2870 2895 """
2871 2896 Returns user associated with this ChangesetComment. ie those
2872 2897 who actually commented
2873 2898
2874 2899 :param cls:
2875 2900 :param revision:
2876 2901 """
2877 2902 q = Session().query(User)\
2878 2903 .join(ChangesetComment.author)
2879 2904 if revision:
2880 2905 q = q.filter(cls.revision == revision)
2881 2906 elif pull_request_id:
2882 2907 q = q.filter(cls.pull_request_id == pull_request_id)
2883 2908 return q.all()
2884 2909
2885 2910 def render(self, mentions=False):
2886 2911 from rhodecode.lib import helpers as h
2887 2912 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2888 2913
2889 2914 def __repr__(self):
2890 2915 if self.comment_id:
2891 2916 return '<DB:ChangesetComment #%s>' % self.comment_id
2892 2917 else:
2893 2918 return '<DB:ChangesetComment at %#x>' % id(self)
2894 2919
2895 2920
2896 2921 class ChangesetStatus(Base, BaseModel):
2897 2922 __tablename__ = 'changeset_statuses'
2898 2923 __table_args__ = (
2899 2924 Index('cs_revision_idx', 'revision'),
2900 2925 Index('cs_version_idx', 'version'),
2901 2926 UniqueConstraint('repo_id', 'revision', 'version'),
2902 2927 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2903 2928 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2904 2929 )
2905 2930 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2906 2931 STATUS_APPROVED = 'approved'
2907 2932 STATUS_REJECTED = 'rejected'
2908 2933 STATUS_UNDER_REVIEW = 'under_review'
2909 2934
2910 2935 STATUSES = [
2911 2936 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2912 2937 (STATUS_APPROVED, _("Approved")),
2913 2938 (STATUS_REJECTED, _("Rejected")),
2914 2939 (STATUS_UNDER_REVIEW, _("Under Review")),
2915 2940 ]
2916 2941
2917 2942 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2918 2943 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2919 2944 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2920 2945 revision = Column('revision', String(40), nullable=False)
2921 2946 status = Column('status', String(128), nullable=False, default=DEFAULT)
2922 2947 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2923 2948 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2924 2949 version = Column('version', Integer(), nullable=False, default=0)
2925 2950 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2926 2951
2927 2952 author = relationship('User', lazy='joined')
2928 2953 repo = relationship('Repository')
2929 2954 comment = relationship('ChangesetComment', lazy='joined')
2930 2955 pull_request = relationship('PullRequest', lazy='joined')
2931 2956
2932 2957 def __unicode__(self):
2933 2958 return u"<%s('%s[%s]:%s')>" % (
2934 2959 self.__class__.__name__,
2935 2960 self.status, self.version, self.author
2936 2961 )
2937 2962
2938 2963 @classmethod
2939 2964 def get_status_lbl(cls, value):
2940 2965 return dict(cls.STATUSES).get(value)
2941 2966
2942 2967 @property
2943 2968 def status_lbl(self):
2944 2969 return ChangesetStatus.get_status_lbl(self.status)
2945 2970
2946 2971
2947 2972 class _PullRequestBase(BaseModel):
2948 2973 """
2949 2974 Common attributes of pull request and version entries.
2950 2975 """
2951 2976
2952 2977 # .status values
2953 2978 STATUS_NEW = u'new'
2954 2979 STATUS_OPEN = u'open'
2955 2980 STATUS_CLOSED = u'closed'
2956 2981
2957 2982 title = Column('title', Unicode(255), nullable=True)
2958 2983 description = Column(
2959 2984 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2960 2985 nullable=True)
2961 2986 # new/open/closed status of pull request (not approve/reject/etc)
2962 2987 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2963 2988 created_on = Column(
2964 2989 'created_on', DateTime(timezone=False), nullable=False,
2965 2990 default=datetime.datetime.now)
2966 2991 updated_on = Column(
2967 2992 'updated_on', DateTime(timezone=False), nullable=False,
2968 2993 default=datetime.datetime.now)
2969 2994
2970 2995 @declared_attr
2971 2996 def user_id(cls):
2972 2997 return Column(
2973 2998 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
2974 2999 unique=None)
2975 3000
2976 3001 # 500 revisions max
2977 3002 _revisions = Column(
2978 3003 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
2979 3004
2980 3005 @declared_attr
2981 3006 def source_repo_id(cls):
2982 3007 # TODO: dan: rename column to source_repo_id
2983 3008 return Column(
2984 3009 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
2985 3010 nullable=False)
2986 3011
2987 3012 source_ref = Column('org_ref', Unicode(255), nullable=False)
2988 3013
2989 3014 @declared_attr
2990 3015 def target_repo_id(cls):
2991 3016 # TODO: dan: rename column to target_repo_id
2992 3017 return Column(
2993 3018 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
2994 3019 nullable=False)
2995 3020
2996 3021 target_ref = Column('other_ref', Unicode(255), nullable=False)
2997 3022
2998 3023 # TODO: dan: rename column to last_merge_source_rev
2999 3024 _last_merge_source_rev = Column(
3000 3025 'last_merge_org_rev', String(40), nullable=True)
3001 3026 # TODO: dan: rename column to last_merge_target_rev
3002 3027 _last_merge_target_rev = Column(
3003 3028 'last_merge_other_rev', String(40), nullable=True)
3004 3029 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3005 3030 merge_rev = Column('merge_rev', String(40), nullable=True)
3006 3031
3007 3032 @hybrid_property
3008 3033 def revisions(self):
3009 3034 return self._revisions.split(':') if self._revisions else []
3010 3035
3011 3036 @revisions.setter
3012 3037 def revisions(self, val):
3013 3038 self._revisions = ':'.join(val)
3014 3039
3015 3040 @declared_attr
3016 3041 def author(cls):
3017 3042 return relationship('User', lazy='joined')
3018 3043
3019 3044 @declared_attr
3020 3045 def source_repo(cls):
3021 3046 return relationship(
3022 3047 'Repository',
3023 3048 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3024 3049
3025 3050 @property
3026 3051 def source_ref_parts(self):
3027 3052 refs = self.source_ref.split(':')
3028 3053 return Reference(refs[0], refs[1], refs[2])
3029 3054
3030 3055 @declared_attr
3031 3056 def target_repo(cls):
3032 3057 return relationship(
3033 3058 'Repository',
3034 3059 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3035 3060
3036 3061 @property
3037 3062 def target_ref_parts(self):
3038 3063 refs = self.target_ref.split(':')
3039 3064 return Reference(refs[0], refs[1], refs[2])
3040 3065
3041 3066
3042 3067 class PullRequest(Base, _PullRequestBase):
3043 3068 __tablename__ = 'pull_requests'
3044 3069 __table_args__ = (
3045 3070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3046 3071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3047 3072 )
3048 3073
3049 3074 pull_request_id = Column(
3050 3075 'pull_request_id', Integer(), nullable=False, primary_key=True)
3051 3076
3052 3077 def __repr__(self):
3053 3078 if self.pull_request_id:
3054 3079 return '<DB:PullRequest #%s>' % self.pull_request_id
3055 3080 else:
3056 3081 return '<DB:PullRequest at %#x>' % id(self)
3057 3082
3058 3083 reviewers = relationship('PullRequestReviewers',
3059 3084 cascade="all, delete, delete-orphan")
3060 3085 statuses = relationship('ChangesetStatus')
3061 3086 comments = relationship('ChangesetComment',
3062 3087 cascade="all, delete, delete-orphan")
3063 3088 versions = relationship('PullRequestVersion',
3064 3089 cascade="all, delete, delete-orphan")
3065 3090
3066 3091 def is_closed(self):
3067 3092 return self.status == self.STATUS_CLOSED
3068 3093
3069 3094 def get_api_data(self):
3070 3095 from rhodecode.model.pull_request import PullRequestModel
3071 3096 pull_request = self
3072 3097 merge_status = PullRequestModel().merge_status(pull_request)
3073 3098 data = {
3074 3099 'pull_request_id': pull_request.pull_request_id,
3075 3100 'url': url('pullrequest_show',
3076 3101 repo_name=pull_request.target_repo.repo_name,
3077 3102 pull_request_id=pull_request.pull_request_id,
3078 3103 qualified=True),
3079 3104 'title': pull_request.title,
3080 3105 'description': pull_request.description,
3081 3106 'status': pull_request.status,
3082 3107 'created_on': pull_request.created_on,
3083 3108 'updated_on': pull_request.updated_on,
3084 3109 'commit_ids': pull_request.revisions,
3085 3110 'review_status': pull_request.calculated_review_status(),
3086 3111 'mergeable': {
3087 3112 'status': merge_status[0],
3088 3113 'message': unicode(merge_status[1]),
3089 3114 },
3090 3115 'source': {
3091 3116 'clone_url': pull_request.source_repo.clone_url(),
3092 3117 'repository': pull_request.source_repo.repo_name,
3093 3118 'reference': {
3094 3119 'name': pull_request.source_ref_parts.name,
3095 3120 'type': pull_request.source_ref_parts.type,
3096 3121 'commit_id': pull_request.source_ref_parts.commit_id,
3097 3122 },
3098 3123 },
3099 3124 'target': {
3100 3125 'clone_url': pull_request.target_repo.clone_url(),
3101 3126 'repository': pull_request.target_repo.repo_name,
3102 3127 'reference': {
3103 3128 'name': pull_request.target_ref_parts.name,
3104 3129 'type': pull_request.target_ref_parts.type,
3105 3130 'commit_id': pull_request.target_ref_parts.commit_id,
3106 3131 },
3107 3132 },
3108 3133 'author': pull_request.author.get_api_data(include_secrets=False,
3109 3134 details='basic'),
3110 3135 'reviewers': [
3111 3136 {
3112 3137 'user': reviewer.get_api_data(include_secrets=False,
3113 3138 details='basic'),
3114 3139 'review_status': st[0][1].status if st else 'not_reviewed',
3115 3140 }
3116 3141 for reviewer, st in pull_request.reviewers_statuses()
3117 3142 ]
3118 3143 }
3119 3144
3120 3145 return data
3121 3146
3122 3147 def __json__(self):
3123 3148 return {
3124 3149 'revisions': self.revisions,
3125 3150 }
3126 3151
3127 3152 def calculated_review_status(self):
3128 3153 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3129 3154 # because it's tricky on how to use ChangesetStatusModel from there
3130 3155 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3131 3156 from rhodecode.model.changeset_status import ChangesetStatusModel
3132 3157 return ChangesetStatusModel().calculated_review_status(self)
3133 3158
3134 3159 def reviewers_statuses(self):
3135 3160 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3136 3161 from rhodecode.model.changeset_status import ChangesetStatusModel
3137 3162 return ChangesetStatusModel().reviewers_statuses(self)
3138 3163
3139 3164
3140 3165 class PullRequestVersion(Base, _PullRequestBase):
3141 3166 __tablename__ = 'pull_request_versions'
3142 3167 __table_args__ = (
3143 3168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3144 3169 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3145 3170 )
3146 3171
3147 3172 pull_request_version_id = Column(
3148 3173 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3149 3174 pull_request_id = Column(
3150 3175 'pull_request_id', Integer(),
3151 3176 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3152 3177 pull_request = relationship('PullRequest')
3153 3178
3154 3179 def __repr__(self):
3155 3180 if self.pull_request_version_id:
3156 3181 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3157 3182 else:
3158 3183 return '<DB:PullRequestVersion at %#x>' % id(self)
3159 3184
3160 3185
3161 3186 class PullRequestReviewers(Base, BaseModel):
3162 3187 __tablename__ = 'pull_request_reviewers'
3163 3188 __table_args__ = (
3164 3189 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3165 3190 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3166 3191 )
3167 3192
3168 3193 def __init__(self, user=None, pull_request=None):
3169 3194 self.user = user
3170 3195 self.pull_request = pull_request
3171 3196
3172 3197 pull_requests_reviewers_id = Column(
3173 3198 'pull_requests_reviewers_id', Integer(), nullable=False,
3174 3199 primary_key=True)
3175 3200 pull_request_id = Column(
3176 3201 "pull_request_id", Integer(),
3177 3202 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3178 3203 user_id = Column(
3179 3204 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3180 3205
3181 3206 user = relationship('User')
3182 3207 pull_request = relationship('PullRequest')
3183 3208
3184 3209
3185 3210 class Notification(Base, BaseModel):
3186 3211 __tablename__ = 'notifications'
3187 3212 __table_args__ = (
3188 3213 Index('notification_type_idx', 'type'),
3189 3214 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3190 3215 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3191 3216 )
3192 3217
3193 3218 TYPE_CHANGESET_COMMENT = u'cs_comment'
3194 3219 TYPE_MESSAGE = u'message'
3195 3220 TYPE_MENTION = u'mention'
3196 3221 TYPE_REGISTRATION = u'registration'
3197 3222 TYPE_PULL_REQUEST = u'pull_request'
3198 3223 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3199 3224
3200 3225 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3201 3226 subject = Column('subject', Unicode(512), nullable=True)
3202 3227 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3203 3228 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3204 3229 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3205 3230 type_ = Column('type', Unicode(255))
3206 3231
3207 3232 created_by_user = relationship('User')
3208 3233 notifications_to_users = relationship('UserNotification', lazy='joined',
3209 3234 cascade="all, delete, delete-orphan")
3210 3235
3211 3236 @property
3212 3237 def recipients(self):
3213 3238 return [x.user for x in UserNotification.query()\
3214 3239 .filter(UserNotification.notification == self)\
3215 3240 .order_by(UserNotification.user_id.asc()).all()]
3216 3241
3217 3242 @classmethod
3218 3243 def create(cls, created_by, subject, body, recipients, type_=None):
3219 3244 if type_ is None:
3220 3245 type_ = Notification.TYPE_MESSAGE
3221 3246
3222 3247 notification = cls()
3223 3248 notification.created_by_user = created_by
3224 3249 notification.subject = subject
3225 3250 notification.body = body
3226 3251 notification.type_ = type_
3227 3252 notification.created_on = datetime.datetime.now()
3228 3253
3229 3254 for u in recipients:
3230 3255 assoc = UserNotification()
3231 3256 assoc.notification = notification
3232 3257
3233 3258 # if created_by is inside recipients mark his notification
3234 3259 # as read
3235 3260 if u.user_id == created_by.user_id:
3236 3261 assoc.read = True
3237 3262
3238 3263 u.notifications.append(assoc)
3239 3264 Session().add(notification)
3240 3265
3241 3266 return notification
3242 3267
3243 3268 @property
3244 3269 def description(self):
3245 3270 from rhodecode.model.notification import NotificationModel
3246 3271 return NotificationModel().make_description(self)
3247 3272
3248 3273
3249 3274 class UserNotification(Base, BaseModel):
3250 3275 __tablename__ = 'user_to_notification'
3251 3276 __table_args__ = (
3252 3277 UniqueConstraint('user_id', 'notification_id'),
3253 3278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3254 3279 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3255 3280 )
3256 3281 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3257 3282 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3258 3283 read = Column('read', Boolean, default=False)
3259 3284 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3260 3285
3261 3286 user = relationship('User', lazy="joined")
3262 3287 notification = relationship('Notification', lazy="joined",
3263 3288 order_by=lambda: Notification.created_on.desc(),)
3264 3289
3265 3290 def mark_as_read(self):
3266 3291 self.read = True
3267 3292 Session().add(self)
3268 3293
3269 3294
3270 3295 class Gist(Base, BaseModel):
3271 3296 __tablename__ = 'gists'
3272 3297 __table_args__ = (
3273 3298 Index('g_gist_access_id_idx', 'gist_access_id'),
3274 3299 Index('g_created_on_idx', 'created_on'),
3275 3300 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3276 3301 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3277 3302 )
3278 3303 GIST_PUBLIC = u'public'
3279 3304 GIST_PRIVATE = u'private'
3280 3305 DEFAULT_FILENAME = u'gistfile1.txt'
3281 3306
3282 3307 ACL_LEVEL_PUBLIC = u'acl_public'
3283 3308 ACL_LEVEL_PRIVATE = u'acl_private'
3284 3309
3285 3310 gist_id = Column('gist_id', Integer(), primary_key=True)
3286 3311 gist_access_id = Column('gist_access_id', Unicode(250))
3287 3312 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3288 3313 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3289 3314 gist_expires = Column('gist_expires', Float(53), nullable=False)
3290 3315 gist_type = Column('gist_type', Unicode(128), nullable=False)
3291 3316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3292 3317 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3293 3318 acl_level = Column('acl_level', Unicode(128), nullable=True)
3294 3319
3295 3320 owner = relationship('User')
3296 3321
3297 3322 def __repr__(self):
3298 3323 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3299 3324
3300 3325 @classmethod
3301 3326 def get_or_404(cls, id_):
3302 3327 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3303 3328 if not res:
3304 3329 raise HTTPNotFound
3305 3330 return res
3306 3331
3307 3332 @classmethod
3308 3333 def get_by_access_id(cls, gist_access_id):
3309 3334 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3310 3335
3311 3336 def gist_url(self):
3312 3337 import rhodecode
3313 3338 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3314 3339 if alias_url:
3315 3340 return alias_url.replace('{gistid}', self.gist_access_id)
3316 3341
3317 3342 return url('gist', gist_id=self.gist_access_id, qualified=True)
3318 3343
3319 3344 @classmethod
3320 3345 def base_path(cls):
3321 3346 """
3322 3347 Returns base path when all gists are stored
3323 3348
3324 3349 :param cls:
3325 3350 """
3326 3351 from rhodecode.model.gist import GIST_STORE_LOC
3327 3352 q = Session().query(RhodeCodeUi)\
3328 3353 .filter(RhodeCodeUi.ui_key == URL_SEP)
3329 3354 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3330 3355 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3331 3356
3332 3357 def get_api_data(self):
3333 3358 """
3334 3359 Common function for generating gist related data for API
3335 3360 """
3336 3361 gist = self
3337 3362 data = {
3338 3363 'gist_id': gist.gist_id,
3339 3364 'type': gist.gist_type,
3340 3365 'access_id': gist.gist_access_id,
3341 3366 'description': gist.gist_description,
3342 3367 'url': gist.gist_url(),
3343 3368 'expires': gist.gist_expires,
3344 3369 'created_on': gist.created_on,
3345 3370 'modified_at': gist.modified_at,
3346 3371 'content': None,
3347 3372 'acl_level': gist.acl_level,
3348 3373 }
3349 3374 return data
3350 3375
3351 3376 def __json__(self):
3352 3377 data = dict(
3353 3378 )
3354 3379 data.update(self.get_api_data())
3355 3380 return data
3356 3381 # SCM functions
3357 3382
3358 3383 def scm_instance(self, **kwargs):
3359 3384 from rhodecode.lib.vcs import get_repo
3360 3385 base_path = self.base_path()
3361 3386 return get_repo(os.path.join(*map(safe_str,
3362 3387 [base_path, self.gist_access_id])))
3363 3388
3364 3389
3365 3390 class DbMigrateVersion(Base, BaseModel):
3366 3391 __tablename__ = 'db_migrate_version'
3367 3392 __table_args__ = (
3368 3393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3369 3394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3370 3395 )
3371 3396 repository_id = Column('repository_id', String(250), primary_key=True)
3372 3397 repository_path = Column('repository_path', Text)
3373 3398 version = Column('version', Integer)
3374 3399
3375 3400
3376 3401 class ExternalIdentity(Base, BaseModel):
3377 3402 __tablename__ = 'external_identities'
3378 3403 __table_args__ = (
3379 3404 Index('local_user_id_idx', 'local_user_id'),
3380 3405 Index('external_id_idx', 'external_id'),
3381 3406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3382 3407 'mysql_charset': 'utf8'})
3383 3408
3384 3409 external_id = Column('external_id', Unicode(255), default=u'',
3385 3410 primary_key=True)
3386 3411 external_username = Column('external_username', Unicode(1024), default=u'')
3387 3412 local_user_id = Column('local_user_id', Integer(),
3388 3413 ForeignKey('users.user_id'), primary_key=True)
3389 3414 provider_name = Column('provider_name', Unicode(255), default=u'',
3390 3415 primary_key=True)
3391 3416 access_token = Column('access_token', String(1024), default=u'')
3392 3417 alt_token = Column('alt_token', String(1024), default=u'')
3393 3418 token_secret = Column('token_secret', String(1024), default=u'')
3394 3419
3395 3420 @classmethod
3396 3421 def by_external_id_and_provider(cls, external_id, provider_name,
3397 3422 local_user_id=None):
3398 3423 """
3399 3424 Returns ExternalIdentity instance based on search params
3400 3425
3401 3426 :param external_id:
3402 3427 :param provider_name:
3403 3428 :return: ExternalIdentity
3404 3429 """
3405 3430 query = cls.query()
3406 3431 query = query.filter(cls.external_id == external_id)
3407 3432 query = query.filter(cls.provider_name == provider_name)
3408 3433 if local_user_id:
3409 3434 query = query.filter(cls.local_user_id == local_user_id)
3410 3435 return query.first()
3411 3436
3412 3437 @classmethod
3413 3438 def user_by_external_id_and_provider(cls, external_id, provider_name):
3414 3439 """
3415 3440 Returns User instance based on search params
3416 3441
3417 3442 :param external_id:
3418 3443 :param provider_name:
3419 3444 :return: User
3420 3445 """
3421 3446 query = User.query()
3422 3447 query = query.filter(cls.external_id == external_id)
3423 3448 query = query.filter(cls.provider_name == provider_name)
3424 3449 query = query.filter(User.user_id == cls.local_user_id)
3425 3450 return query.first()
3426 3451
3427 3452 @classmethod
3428 3453 def by_local_user_id(cls, local_user_id):
3429 3454 """
3430 3455 Returns all tokens for user
3431 3456
3432 3457 :param local_user_id:
3433 3458 :return: ExternalIdentity
3434 3459 """
3435 3460 query = cls.query()
3436 3461 query = query.filter(cls.local_user_id == local_user_id)
3437 3462 return query
General Comments 0
You need to be logged in to leave comments. Login now