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