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