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