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