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