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