##// END OF EJS Templates
reviewers: add repo review rule models and expose default...
dan -
r821:618c046d default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (3640 lines changed) Show them Hide them
@@ -0,0 +1,3640 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Database Models for RhodeCode Enterprise
23 """
24
25 import re
26 import os
27 import sys
28 import time
29 import hashlib
30 import logging
31 import datetime
32 import warnings
33 import ipaddress
34 import functools
35 import traceback
36 import collections
37
38
39 from sqlalchemy import *
40 from sqlalchemy.exc import IntegrityError
41 from sqlalchemy.ext.declarative import declared_attr
42 from sqlalchemy.ext.hybrid import hybrid_property
43 from sqlalchemy.orm import (
44 relationship, joinedload, class_mapper, validates, aliased)
45 from sqlalchemy.sql.expression import true
46 from beaker.cache import cache_region, region_invalidate
47 from webob.exc import HTTPNotFound
48 from zope.cachedescriptors.property import Lazy as LazyProperty
49
50 from pylons import url
51 from pylons.i18n.translation import lazy_ugettext as _
52
53 from rhodecode.lib.vcs import get_backend, get_vcs_instance
54 from rhodecode.lib.vcs.utils.helpers import get_scm
55 from rhodecode.lib.vcs.exceptions import VCSError
56 from rhodecode.lib.vcs.backends.base import (
57 EmptyCommit, Reference, MergeFailureReason)
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re)
62 from rhodecode.lib.jsonalchemy import MutationObj, JsonType, JSONDict
63 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.encrypt import AESCipher
66
67 from rhodecode.model.meta import Base, Session
68
69 URL_SEP = '/'
70 log = logging.getLogger(__name__)
71
72 # =============================================================================
73 # BASE CLASSES
74 # =============================================================================
75
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # beaker.session.secret if first is not set.
78 # and initialized at environment.py
79 ENCRYPTION_KEY = None
80
81 # used to sort permissions by types, '#' used here is not allowed to be in
82 # usernames, and it's very early in sorted string.printable table.
83 PERMISSION_TYPE_SORT = {
84 'admin': '####',
85 'write': '###',
86 'read': '##',
87 'none': '#',
88 }
89
90
91 def display_sort(obj):
92 """
93 Sort function used to sort permissions in .permissions() function of
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 of all other resources
96 """
97
98 if obj.username == User.DEFAULT_USER:
99 return '#####'
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 return prefix + obj.username
102
103
104 def _hash_key(k):
105 return md5_safe(k)
106
107
108 class EncryptedTextValue(TypeDecorator):
109 """
110 Special column for encrypted long text data, use like::
111
112 value = Column("encrypted_value", EncryptedValue(), nullable=False)
113
114 This column is intelligent so if value is in unencrypted form it return
115 unencrypted form, but on save it always encrypts
116 """
117 impl = Text
118
119 def process_bind_param(self, value, dialect):
120 if not value:
121 return value
122 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
123 # protect against double encrypting if someone manually starts
124 # doing
125 raise ValueError('value needs to be in unencrypted format, ie. '
126 'not starting with enc$aes')
127 return 'enc$aes_hmac$%s' % AESCipher(
128 ENCRYPTION_KEY, hmac=True).encrypt(value)
129
130 def process_result_value(self, value, dialect):
131 import rhodecode
132
133 if not value:
134 return value
135
136 parts = value.split('$', 3)
137 if not len(parts) == 3:
138 # probably not encrypted values
139 return value
140 else:
141 if parts[0] != 'enc':
142 # parts ok but without our header ?
143 return value
144 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
145 'rhodecode.encrypted_values.strict') or True)
146 # at that stage we know it's our encryption
147 if parts[1] == 'aes':
148 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
149 elif parts[1] == 'aes_hmac':
150 decrypted_data = AESCipher(
151 ENCRYPTION_KEY, hmac=True,
152 strict_verification=enc_strict_mode).decrypt(parts[2])
153 else:
154 raise ValueError(
155 'Encryption type part is wrong, must be `aes` '
156 'or `aes_hmac`, got `%s` instead' % (parts[1]))
157 return decrypted_data
158
159
160 class BaseModel(object):
161 """
162 Base Model for all classes
163 """
164
165 @classmethod
166 def _get_keys(cls):
167 """return column names for this model """
168 return class_mapper(cls).c.keys()
169
170 def get_dict(self):
171 """
172 return dict with keys and values corresponding
173 to this model data """
174
175 d = {}
176 for k in self._get_keys():
177 d[k] = getattr(self, k)
178
179 # also use __json__() if present to get additional fields
180 _json_attr = getattr(self, '__json__', None)
181 if _json_attr:
182 # update with attributes from __json__
183 if callable(_json_attr):
184 _json_attr = _json_attr()
185 for k, val in _json_attr.iteritems():
186 d[k] = val
187 return d
188
189 def get_appstruct(self):
190 """return list with keys and values tuples corresponding
191 to this model data """
192
193 l = []
194 for k in self._get_keys():
195 l.append((k, getattr(self, k),))
196 return l
197
198 def populate_obj(self, populate_dict):
199 """populate model with data from given populate_dict"""
200
201 for k in self._get_keys():
202 if k in populate_dict:
203 setattr(self, k, populate_dict[k])
204
205 @classmethod
206 def query(cls):
207 return Session().query(cls)
208
209 @classmethod
210 def get(cls, id_):
211 if id_:
212 return cls.query().get(id_)
213
214 @classmethod
215 def get_or_404(cls, id_):
216 try:
217 id_ = int(id_)
218 except (TypeError, ValueError):
219 raise HTTPNotFound
220
221 res = cls.query().get(id_)
222 if not res:
223 raise HTTPNotFound
224 return res
225
226 @classmethod
227 def getAll(cls):
228 # deprecated and left for backward compatibility
229 return cls.get_all()
230
231 @classmethod
232 def get_all(cls):
233 return cls.query().all()
234
235 @classmethod
236 def delete(cls, id_):
237 obj = cls.query().get(id_)
238 Session().delete(obj)
239
240 @classmethod
241 def identity_cache(cls, session, attr_name, value):
242 exist_in_session = []
243 for (item_cls, pkey), instance in session.identity_map.items():
244 if cls == item_cls and getattr(instance, attr_name) == value:
245 exist_in_session.append(instance)
246 if exist_in_session:
247 if len(exist_in_session) == 1:
248 return exist_in_session[0]
249 log.exception(
250 'multiple objects with attr %s and '
251 'value %s found with same name: %r',
252 attr_name, value, exist_in_session)
253
254 def __repr__(self):
255 if hasattr(self, '__unicode__'):
256 # python repr needs to return str
257 try:
258 return safe_str(self.__unicode__())
259 except UnicodeDecodeError:
260 pass
261 return '<DB:%s>' % (self.__class__.__name__)
262
263
264 class RhodeCodeSetting(Base, BaseModel):
265 __tablename__ = 'rhodecode_settings'
266 __table_args__ = (
267 UniqueConstraint('app_settings_name'),
268 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 )
271
272 SETTINGS_TYPES = {
273 'str': safe_str,
274 'int': safe_int,
275 'unicode': safe_unicode,
276 'bool': str2bool,
277 'list': functools.partial(aslist, sep=',')
278 }
279 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 GLOBAL_CONF_KEY = 'app_settings'
281
282 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286
287 def __init__(self, key='', val='', type='unicode'):
288 self.app_settings_name = key
289 self.app_settings_type = type
290 self.app_settings_value = val
291
292 @validates('_app_settings_value')
293 def validate_settings_value(self, key, val):
294 assert type(val) == unicode
295 return val
296
297 @hybrid_property
298 def app_settings_value(self):
299 v = self._app_settings_value
300 _type = self.app_settings_type
301 if _type:
302 _type = self.app_settings_type.split('.')[0]
303 # decode the encrypted value
304 if 'encrypted' in self.app_settings_type:
305 cipher = EncryptedTextValue()
306 v = safe_unicode(cipher.process_result_value(v, None))
307
308 converter = self.SETTINGS_TYPES.get(_type) or \
309 self.SETTINGS_TYPES['unicode']
310 return converter(v)
311
312 @app_settings_value.setter
313 def app_settings_value(self, val):
314 """
315 Setter that will always make sure we use unicode in app_settings_value
316
317 :param val:
318 """
319 val = safe_unicode(val)
320 # encode the encrypted value
321 if 'encrypted' in self.app_settings_type:
322 cipher = EncryptedTextValue()
323 val = safe_unicode(cipher.process_bind_param(val, None))
324 self._app_settings_value = val
325
326 @hybrid_property
327 def app_settings_type(self):
328 return self._app_settings_type
329
330 @app_settings_type.setter
331 def app_settings_type(self, val):
332 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 raise Exception('type must be one of %s got %s'
334 % (self.SETTINGS_TYPES.keys(), val))
335 self._app_settings_type = val
336
337 def __unicode__(self):
338 return u"<%s('%s:%s[%s]')>" % (
339 self.__class__.__name__,
340 self.app_settings_name, self.app_settings_value,
341 self.app_settings_type
342 )
343
344
345 class RhodeCodeUi(Base, BaseModel):
346 __tablename__ = 'rhodecode_ui'
347 __table_args__ = (
348 UniqueConstraint('ui_key'),
349 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 )
352
353 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 # HG
355 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PUSH = 'changegroup.push_logger'
359
360 # TODO: johbo: Unify way how hooks are configured for git and hg,
361 # git part is currently hardcoded.
362
363 # SVN PATTERNS
364 SVN_BRANCH_ID = 'vcs_svn_branch'
365 SVN_TAG_ID = 'vcs_svn_tag'
366
367 ui_id = Column(
368 "ui_id", Integer(), nullable=False, unique=True, default=None,
369 primary_key=True)
370 ui_section = Column(
371 "ui_section", String(255), nullable=True, unique=None, default=None)
372 ui_key = Column(
373 "ui_key", String(255), nullable=True, unique=None, default=None)
374 ui_value = Column(
375 "ui_value", String(255), nullable=True, unique=None, default=None)
376 ui_active = Column(
377 "ui_active", Boolean(), nullable=True, unique=None, default=True)
378
379 def __repr__(self):
380 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
381 self.ui_key, self.ui_value)
382
383
384 class RepoRhodeCodeSetting(Base, BaseModel):
385 __tablename__ = 'repo_rhodecode_settings'
386 __table_args__ = (
387 UniqueConstraint(
388 'app_settings_name', 'repository_id',
389 name='uq_repo_rhodecode_setting_name_repo_id'),
390 {'extend_existing': True, 'mysql_engine': 'InnoDB',
391 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
392 )
393
394 repository_id = Column(
395 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
396 nullable=False)
397 app_settings_id = Column(
398 "app_settings_id", Integer(), nullable=False, unique=True,
399 default=None, primary_key=True)
400 app_settings_name = Column(
401 "app_settings_name", String(255), nullable=True, unique=None,
402 default=None)
403 _app_settings_value = Column(
404 "app_settings_value", String(4096), nullable=True, unique=None,
405 default=None)
406 _app_settings_type = Column(
407 "app_settings_type", String(255), nullable=True, unique=None,
408 default=None)
409
410 repository = relationship('Repository')
411
412 def __init__(self, repository_id, key='', val='', type='unicode'):
413 self.repository_id = repository_id
414 self.app_settings_name = key
415 self.app_settings_type = type
416 self.app_settings_value = val
417
418 @validates('_app_settings_value')
419 def validate_settings_value(self, key, val):
420 assert type(val) == unicode
421 return val
422
423 @hybrid_property
424 def app_settings_value(self):
425 v = self._app_settings_value
426 type_ = self.app_settings_type
427 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
428 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
429 return converter(v)
430
431 @app_settings_value.setter
432 def app_settings_value(self, val):
433 """
434 Setter that will always make sure we use unicode in app_settings_value
435
436 :param val:
437 """
438 self._app_settings_value = safe_unicode(val)
439
440 @hybrid_property
441 def app_settings_type(self):
442 return self._app_settings_type
443
444 @app_settings_type.setter
445 def app_settings_type(self, val):
446 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
447 if val not in SETTINGS_TYPES:
448 raise Exception('type must be one of %s got %s'
449 % (SETTINGS_TYPES.keys(), val))
450 self._app_settings_type = val
451
452 def __unicode__(self):
453 return u"<%s('%s:%s:%s[%s]')>" % (
454 self.__class__.__name__, self.repository.repo_name,
455 self.app_settings_name, self.app_settings_value,
456 self.app_settings_type
457 )
458
459
460 class RepoRhodeCodeUi(Base, BaseModel):
461 __tablename__ = 'repo_rhodecode_ui'
462 __table_args__ = (
463 UniqueConstraint(
464 'repository_id', 'ui_section', 'ui_key',
465 name='uq_repo_rhodecode_ui_repository_id_section_key'),
466 {'extend_existing': True, 'mysql_engine': 'InnoDB',
467 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
468 )
469
470 repository_id = Column(
471 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
472 nullable=False)
473 ui_id = Column(
474 "ui_id", Integer(), nullable=False, unique=True, default=None,
475 primary_key=True)
476 ui_section = Column(
477 "ui_section", String(255), nullable=True, unique=None, default=None)
478 ui_key = Column(
479 "ui_key", String(255), nullable=True, unique=None, default=None)
480 ui_value = Column(
481 "ui_value", String(255), nullable=True, unique=None, default=None)
482 ui_active = Column(
483 "ui_active", Boolean(), nullable=True, unique=None, default=True)
484
485 repository = relationship('Repository')
486
487 def __repr__(self):
488 return '<%s[%s:%s]%s=>%s]>' % (
489 self.__class__.__name__, self.repository.repo_name,
490 self.ui_section, self.ui_key, self.ui_value)
491
492
493 class User(Base, BaseModel):
494 __tablename__ = 'users'
495 __table_args__ = (
496 UniqueConstraint('username'), UniqueConstraint('email'),
497 Index('u_username_idx', 'username'),
498 Index('u_email_idx', 'email'),
499 {'extend_existing': True, 'mysql_engine': 'InnoDB',
500 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
501 )
502 DEFAULT_USER = 'default'
503 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
504 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
505
506 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
507 username = Column("username", String(255), nullable=True, unique=None, default=None)
508 password = Column("password", String(255), nullable=True, unique=None, default=None)
509 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
510 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
511 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
512 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
513 _email = Column("email", String(255), nullable=True, unique=None, default=None)
514 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
515 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
516 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
517 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
518 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
520 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
521
522 user_log = relationship('UserLog')
523 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
524
525 repositories = relationship('Repository')
526 repository_groups = relationship('RepoGroup')
527 user_groups = relationship('UserGroup')
528
529 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
530 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
531
532 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
533 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
534 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
535
536 group_member = relationship('UserGroupMember', cascade='all')
537
538 notifications = relationship('UserNotification', cascade='all')
539 # notifications assigned to this user
540 user_created_notifications = relationship('Notification', cascade='all')
541 # comments created by this user
542 user_comments = relationship('ChangesetComment', cascade='all')
543 # user profile extra info
544 user_emails = relationship('UserEmailMap', cascade='all')
545 user_ip_map = relationship('UserIpMap', cascade='all')
546 user_auth_tokens = relationship('UserApiKeys', cascade='all')
547 # gists
548 user_gists = relationship('Gist', cascade='all')
549 # user pull requests
550 user_pull_requests = relationship('PullRequest', cascade='all')
551 # external identities
552 extenal_identities = relationship(
553 'ExternalIdentity',
554 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
555 cascade='all')
556
557 def __unicode__(self):
558 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
559 self.user_id, self.username)
560
561 @hybrid_property
562 def email(self):
563 return self._email
564
565 @email.setter
566 def email(self, val):
567 self._email = val.lower() if val else None
568
569 @property
570 def firstname(self):
571 # alias for future
572 return self.name
573
574 @property
575 def emails(self):
576 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
577 return [self.email] + [x.email for x in other]
578
579 @property
580 def auth_tokens(self):
581 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
582
583 @property
584 def extra_auth_tokens(self):
585 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
586
587 @property
588 def feed_token(self):
589 feed_tokens = UserApiKeys.query()\
590 .filter(UserApiKeys.user == self)\
591 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
592 .all()
593 if feed_tokens:
594 return feed_tokens[0].api_key
595 else:
596 # use the main token so we don't end up with nothing...
597 return self.api_key
598
599 @classmethod
600 def extra_valid_auth_tokens(cls, user, role=None):
601 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
602 .filter(or_(UserApiKeys.expires == -1,
603 UserApiKeys.expires >= time.time()))
604 if role:
605 tokens = tokens.filter(or_(UserApiKeys.role == role,
606 UserApiKeys.role == UserApiKeys.ROLE_ALL))
607 return tokens.all()
608
609 @property
610 def ip_addresses(self):
611 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
612 return [x.ip_addr for x in ret]
613
614 @property
615 def username_and_name(self):
616 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
617
618 @property
619 def username_or_name_or_email(self):
620 full_name = self.full_name if self.full_name is not ' ' else None
621 return self.username or full_name or self.email
622
623 @property
624 def full_name(self):
625 return '%s %s' % (self.firstname, self.lastname)
626
627 @property
628 def full_name_or_username(self):
629 return ('%s %s' % (self.firstname, self.lastname)
630 if (self.firstname and self.lastname) else self.username)
631
632 @property
633 def full_contact(self):
634 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
635
636 @property
637 def short_contact(self):
638 return '%s %s' % (self.firstname, self.lastname)
639
640 @property
641 def is_admin(self):
642 return self.admin
643
644 @property
645 def AuthUser(self):
646 """
647 Returns instance of AuthUser for this user
648 """
649 from rhodecode.lib.auth import AuthUser
650 return AuthUser(user_id=self.user_id, api_key=self.api_key,
651 username=self.username)
652
653 @hybrid_property
654 def user_data(self):
655 if not self._user_data:
656 return {}
657
658 try:
659 return json.loads(self._user_data)
660 except TypeError:
661 return {}
662
663 @user_data.setter
664 def user_data(self, val):
665 if not isinstance(val, dict):
666 raise Exception('user_data must be dict, got %s' % type(val))
667 try:
668 self._user_data = json.dumps(val)
669 except Exception:
670 log.error(traceback.format_exc())
671
672 @classmethod
673 def get_by_username(cls, username, case_insensitive=False,
674 cache=False, identity_cache=False):
675 session = Session()
676
677 if case_insensitive:
678 q = cls.query().filter(
679 func.lower(cls.username) == func.lower(username))
680 else:
681 q = cls.query().filter(cls.username == username)
682
683 if cache:
684 if identity_cache:
685 val = cls.identity_cache(session, 'username', username)
686 if val:
687 return val
688 else:
689 q = q.options(
690 FromCache("sql_cache_short",
691 "get_user_by_name_%s" % _hash_key(username)))
692
693 return q.scalar()
694
695 @classmethod
696 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
697 q = cls.query().filter(cls.api_key == auth_token)
698
699 if cache:
700 q = q.options(FromCache("sql_cache_short",
701 "get_auth_token_%s" % auth_token))
702 res = q.scalar()
703
704 if fallback and not res:
705 #fallback to additional keys
706 _res = UserApiKeys.query()\
707 .filter(UserApiKeys.api_key == auth_token)\
708 .filter(or_(UserApiKeys.expires == -1,
709 UserApiKeys.expires >= time.time()))\
710 .first()
711 if _res:
712 res = _res.user
713 return res
714
715 @classmethod
716 def get_by_email(cls, email, case_insensitive=False, cache=False):
717
718 if case_insensitive:
719 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
720
721 else:
722 q = cls.query().filter(cls.email == email)
723
724 if cache:
725 q = q.options(FromCache("sql_cache_short",
726 "get_email_key_%s" % _hash_key(email)))
727
728 ret = q.scalar()
729 if ret is None:
730 q = UserEmailMap.query()
731 # try fetching in alternate email map
732 if case_insensitive:
733 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
734 else:
735 q = q.filter(UserEmailMap.email == email)
736 q = q.options(joinedload(UserEmailMap.user))
737 if cache:
738 q = q.options(FromCache("sql_cache_short",
739 "get_email_map_key_%s" % email))
740 ret = getattr(q.scalar(), 'user', None)
741
742 return ret
743
744 @classmethod
745 def get_from_cs_author(cls, author):
746 """
747 Tries to get User objects out of commit author string
748
749 :param author:
750 """
751 from rhodecode.lib.helpers import email, author_name
752 # Valid email in the attribute passed, see if they're in the system
753 _email = email(author)
754 if _email:
755 user = cls.get_by_email(_email, case_insensitive=True)
756 if user:
757 return user
758 # Maybe we can match by username?
759 _author = author_name(author)
760 user = cls.get_by_username(_author, case_insensitive=True)
761 if user:
762 return user
763
764 def update_userdata(self, **kwargs):
765 usr = self
766 old = usr.user_data
767 old.update(**kwargs)
768 usr.user_data = old
769 Session().add(usr)
770 log.debug('updated userdata with ', kwargs)
771
772 def update_lastlogin(self):
773 """Update user lastlogin"""
774 self.last_login = datetime.datetime.now()
775 Session().add(self)
776 log.debug('updated user %s lastlogin', self.username)
777
778 def update_lastactivity(self):
779 """Update user lastactivity"""
780 usr = self
781 old = usr.user_data
782 old.update({'last_activity': time.time()})
783 usr.user_data = old
784 Session().add(usr)
785 log.debug('updated user %s lastactivity', usr.username)
786
787 def update_password(self, new_password, change_api_key=False):
788 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
789
790 self.password = get_crypt_password(new_password)
791 if change_api_key:
792 self.api_key = generate_auth_token(self.username)
793 Session().add(self)
794
795 @classmethod
796 def get_first_super_admin(cls):
797 user = User.query().filter(User.admin == true()).first()
798 if user is None:
799 raise Exception('FATAL: Missing administrative account!')
800 return user
801
802 @classmethod
803 def get_all_super_admins(cls):
804 """
805 Returns all admin accounts sorted by username
806 """
807 return User.query().filter(User.admin == true())\
808 .order_by(User.username.asc()).all()
809
810 @classmethod
811 def get_default_user(cls, cache=False):
812 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
813 if user is None:
814 raise Exception('FATAL: Missing default account!')
815 return user
816
817 def _get_default_perms(self, user, suffix=''):
818 from rhodecode.model.permission import PermissionModel
819 return PermissionModel().get_default_perms(user.user_perms, suffix)
820
821 def get_default_perms(self, suffix=''):
822 return self._get_default_perms(self, suffix)
823
824 def get_api_data(self, include_secrets=False, details='full'):
825 """
826 Common function for generating user related data for API
827
828 :param include_secrets: By default secrets in the API data will be replaced
829 by a placeholder value to prevent exposing this data by accident. In case
830 this data shall be exposed, set this flag to ``True``.
831
832 :param details: details can be 'basic|full' basic gives only a subset of
833 the available user information that includes user_id, name and emails.
834 """
835 user = self
836 user_data = self.user_data
837 data = {
838 'user_id': user.user_id,
839 'username': user.username,
840 'firstname': user.name,
841 'lastname': user.lastname,
842 'email': user.email,
843 'emails': user.emails,
844 }
845 if details == 'basic':
846 return data
847
848 api_key_length = 40
849 api_key_replacement = '*' * api_key_length
850
851 extras = {
852 'api_key': api_key_replacement,
853 'api_keys': [api_key_replacement],
854 'active': user.active,
855 'admin': user.admin,
856 'extern_type': user.extern_type,
857 'extern_name': user.extern_name,
858 'last_login': user.last_login,
859 'ip_addresses': user.ip_addresses,
860 'language': user_data.get('language')
861 }
862 data.update(extras)
863
864 if include_secrets:
865 data['api_key'] = user.api_key
866 data['api_keys'] = user.auth_tokens
867 return data
868
869 def __json__(self):
870 data = {
871 'full_name': self.full_name,
872 'full_name_or_username': self.full_name_or_username,
873 'short_contact': self.short_contact,
874 'full_contact': self.full_contact,
875 }
876 data.update(self.get_api_data())
877 return data
878
879
880 class UserApiKeys(Base, BaseModel):
881 __tablename__ = 'user_api_keys'
882 __table_args__ = (
883 Index('uak_api_key_idx', 'api_key'),
884 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
885 UniqueConstraint('api_key'),
886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
888 )
889 __mapper_args__ = {}
890
891 # ApiKey role
892 ROLE_ALL = 'token_role_all'
893 ROLE_HTTP = 'token_role_http'
894 ROLE_VCS = 'token_role_vcs'
895 ROLE_API = 'token_role_api'
896 ROLE_FEED = 'token_role_feed'
897 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
898
899 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
900 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
901 api_key = Column("api_key", String(255), nullable=False, unique=True)
902 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
903 expires = Column('expires', Float(53), nullable=False)
904 role = Column('role', String(255), nullable=True)
905 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
906
907 user = relationship('User', lazy='joined')
908
909 @classmethod
910 def _get_role_name(cls, role):
911 return {
912 cls.ROLE_ALL: _('all'),
913 cls.ROLE_HTTP: _('http/web interface'),
914 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
915 cls.ROLE_API: _('api calls'),
916 cls.ROLE_FEED: _('feed access'),
917 }.get(role, role)
918
919 @property
920 def expired(self):
921 if self.expires == -1:
922 return False
923 return time.time() > self.expires
924
925 @property
926 def role_humanized(self):
927 return self._get_role_name(self.role)
928
929
930 class UserEmailMap(Base, BaseModel):
931 __tablename__ = 'user_email_map'
932 __table_args__ = (
933 Index('uem_email_idx', 'email'),
934 UniqueConstraint('email'),
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
937 )
938 __mapper_args__ = {}
939
940 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
942 _email = Column("email", String(255), nullable=True, unique=False, default=None)
943 user = relationship('User', lazy='joined')
944
945 @validates('_email')
946 def validate_email(self, key, email):
947 # check if this email is not main one
948 main_email = Session().query(User).filter(User.email == email).scalar()
949 if main_email is not None:
950 raise AttributeError('email %s is present is user table' % email)
951 return email
952
953 @hybrid_property
954 def email(self):
955 return self._email
956
957 @email.setter
958 def email(self, val):
959 self._email = val.lower() if val else None
960
961
962 class UserIpMap(Base, BaseModel):
963 __tablename__ = 'user_ip_map'
964 __table_args__ = (
965 UniqueConstraint('user_id', 'ip_addr'),
966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
967 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
968 )
969 __mapper_args__ = {}
970
971 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
972 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
973 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
974 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
975 description = Column("description", String(10000), nullable=True, unique=None, default=None)
976 user = relationship('User', lazy='joined')
977
978 @classmethod
979 def _get_ip_range(cls, ip_addr):
980 net = ipaddress.ip_network(ip_addr, strict=False)
981 return [str(net.network_address), str(net.broadcast_address)]
982
983 def __json__(self):
984 return {
985 'ip_addr': self.ip_addr,
986 'ip_range': self._get_ip_range(self.ip_addr),
987 }
988
989 def __unicode__(self):
990 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
991 self.user_id, self.ip_addr)
992
993 class UserLog(Base, BaseModel):
994 __tablename__ = 'user_logs'
995 __table_args__ = (
996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
998 )
999 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1000 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1001 username = Column("username", String(255), nullable=True, unique=None, default=None)
1002 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1003 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1004 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1005 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1006 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1007
1008 def __unicode__(self):
1009 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1010 self.repository_name,
1011 self.action)
1012
1013 @property
1014 def action_as_day(self):
1015 return datetime.date(*self.action_date.timetuple()[:3])
1016
1017 user = relationship('User')
1018 repository = relationship('Repository', cascade='')
1019
1020
1021 class UserGroup(Base, BaseModel):
1022 __tablename__ = 'users_groups'
1023 __table_args__ = (
1024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1026 )
1027
1028 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1029 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1030 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1031 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1032 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1033 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1034 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1035 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1036
1037 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1038 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1039 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1040 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1041 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1042 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1043
1044 user = relationship('User')
1045
1046 @hybrid_property
1047 def group_data(self):
1048 if not self._group_data:
1049 return {}
1050
1051 try:
1052 return json.loads(self._group_data)
1053 except TypeError:
1054 return {}
1055
1056 @group_data.setter
1057 def group_data(self, val):
1058 try:
1059 self._group_data = json.dumps(val)
1060 except Exception:
1061 log.error(traceback.format_exc())
1062
1063 def __unicode__(self):
1064 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1065 self.users_group_id,
1066 self.users_group_name)
1067
1068 @classmethod
1069 def get_by_group_name(cls, group_name, cache=False,
1070 case_insensitive=False):
1071 if case_insensitive:
1072 q = cls.query().filter(func.lower(cls.users_group_name) ==
1073 func.lower(group_name))
1074
1075 else:
1076 q = cls.query().filter(cls.users_group_name == group_name)
1077 if cache:
1078 q = q.options(FromCache(
1079 "sql_cache_short",
1080 "get_group_%s" % _hash_key(group_name)))
1081 return q.scalar()
1082
1083 @classmethod
1084 def get(cls, user_group_id, cache=False):
1085 user_group = cls.query()
1086 if cache:
1087 user_group = user_group.options(FromCache("sql_cache_short",
1088 "get_users_group_%s" % user_group_id))
1089 return user_group.get(user_group_id)
1090
1091 def permissions(self, with_admins=True, with_owner=True):
1092 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1093 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1094 joinedload(UserUserGroupToPerm.user),
1095 joinedload(UserUserGroupToPerm.permission),)
1096
1097 # get owners and admins and permissions. We do a trick of re-writing
1098 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1099 # has a global reference and changing one object propagates to all
1100 # others. This means if admin is also an owner admin_row that change
1101 # would propagate to both objects
1102 perm_rows = []
1103 for _usr in q.all():
1104 usr = AttributeDict(_usr.user.get_dict())
1105 usr.permission = _usr.permission.permission_name
1106 perm_rows.append(usr)
1107
1108 # filter the perm rows by 'default' first and then sort them by
1109 # admin,write,read,none permissions sorted again alphabetically in
1110 # each group
1111 perm_rows = sorted(perm_rows, key=display_sort)
1112
1113 _admin_perm = 'usergroup.admin'
1114 owner_row = []
1115 if with_owner:
1116 usr = AttributeDict(self.user.get_dict())
1117 usr.owner_row = True
1118 usr.permission = _admin_perm
1119 owner_row.append(usr)
1120
1121 super_admin_rows = []
1122 if with_admins:
1123 for usr in User.get_all_super_admins():
1124 # if this admin is also owner, don't double the record
1125 if usr.user_id == owner_row[0].user_id:
1126 owner_row[0].admin_row = True
1127 else:
1128 usr = AttributeDict(usr.get_dict())
1129 usr.admin_row = True
1130 usr.permission = _admin_perm
1131 super_admin_rows.append(usr)
1132
1133 return super_admin_rows + owner_row + perm_rows
1134
1135 def permission_user_groups(self):
1136 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1137 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1138 joinedload(UserGroupUserGroupToPerm.target_user_group),
1139 joinedload(UserGroupUserGroupToPerm.permission),)
1140
1141 perm_rows = []
1142 for _user_group in q.all():
1143 usr = AttributeDict(_user_group.user_group.get_dict())
1144 usr.permission = _user_group.permission.permission_name
1145 perm_rows.append(usr)
1146
1147 return perm_rows
1148
1149 def _get_default_perms(self, user_group, suffix=''):
1150 from rhodecode.model.permission import PermissionModel
1151 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1152
1153 def get_default_perms(self, suffix=''):
1154 return self._get_default_perms(self, suffix)
1155
1156 def get_api_data(self, with_group_members=True, include_secrets=False):
1157 """
1158 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1159 basically forwarded.
1160
1161 """
1162 user_group = self
1163
1164 data = {
1165 'users_group_id': user_group.users_group_id,
1166 'group_name': user_group.users_group_name,
1167 'group_description': user_group.user_group_description,
1168 'active': user_group.users_group_active,
1169 'owner': user_group.user.username,
1170 }
1171 if with_group_members:
1172 users = []
1173 for user in user_group.members:
1174 user = user.user
1175 users.append(user.get_api_data(include_secrets=include_secrets))
1176 data['users'] = users
1177
1178 return data
1179
1180
1181 class UserGroupMember(Base, BaseModel):
1182 __tablename__ = 'users_groups_members'
1183 __table_args__ = (
1184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1186 )
1187
1188 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1189 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1190 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1191
1192 user = relationship('User', lazy='joined')
1193 users_group = relationship('UserGroup')
1194
1195 def __init__(self, gr_id='', u_id=''):
1196 self.users_group_id = gr_id
1197 self.user_id = u_id
1198
1199
1200 class RepositoryField(Base, BaseModel):
1201 __tablename__ = 'repositories_fields'
1202 __table_args__ = (
1203 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1205 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1206 )
1207 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1208
1209 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1210 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1211 field_key = Column("field_key", String(250))
1212 field_label = Column("field_label", String(1024), nullable=False)
1213 field_value = Column("field_value", String(10000), nullable=False)
1214 field_desc = Column("field_desc", String(1024), nullable=False)
1215 field_type = Column("field_type", String(255), nullable=False, unique=None)
1216 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1217
1218 repository = relationship('Repository')
1219
1220 @property
1221 def field_key_prefixed(self):
1222 return 'ex_%s' % self.field_key
1223
1224 @classmethod
1225 def un_prefix_key(cls, key):
1226 if key.startswith(cls.PREFIX):
1227 return key[len(cls.PREFIX):]
1228 return key
1229
1230 @classmethod
1231 def get_by_key_name(cls, key, repo):
1232 row = cls.query()\
1233 .filter(cls.repository == repo)\
1234 .filter(cls.field_key == key).scalar()
1235 return row
1236
1237
1238 class Repository(Base, BaseModel):
1239 __tablename__ = 'repositories'
1240 __table_args__ = (
1241 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1243 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1244 )
1245 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1246 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1247
1248 STATE_CREATED = 'repo_state_created'
1249 STATE_PENDING = 'repo_state_pending'
1250 STATE_ERROR = 'repo_state_error'
1251
1252 LOCK_AUTOMATIC = 'lock_auto'
1253 LOCK_API = 'lock_api'
1254 LOCK_WEB = 'lock_web'
1255 LOCK_PULL = 'lock_pull'
1256
1257 NAME_SEP = URL_SEP
1258
1259 repo_id = Column(
1260 "repo_id", Integer(), nullable=False, unique=True, default=None,
1261 primary_key=True)
1262 _repo_name = Column(
1263 "repo_name", Text(), nullable=False, default=None)
1264 _repo_name_hash = Column(
1265 "repo_name_hash", String(255), nullable=False, unique=True)
1266 repo_state = Column("repo_state", String(255), nullable=True)
1267
1268 clone_uri = Column(
1269 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1270 default=None)
1271 repo_type = Column(
1272 "repo_type", String(255), nullable=False, unique=False, default=None)
1273 user_id = Column(
1274 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1275 unique=False, default=None)
1276 private = Column(
1277 "private", Boolean(), nullable=True, unique=None, default=None)
1278 enable_statistics = Column(
1279 "statistics", Boolean(), nullable=True, unique=None, default=True)
1280 enable_downloads = Column(
1281 "downloads", Boolean(), nullable=True, unique=None, default=True)
1282 description = Column(
1283 "description", String(10000), nullable=True, unique=None, default=None)
1284 created_on = Column(
1285 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1286 default=datetime.datetime.now)
1287 updated_on = Column(
1288 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1289 default=datetime.datetime.now)
1290 _landing_revision = Column(
1291 "landing_revision", String(255), nullable=False, unique=False,
1292 default=None)
1293 enable_locking = Column(
1294 "enable_locking", Boolean(), nullable=False, unique=None,
1295 default=False)
1296 _locked = Column(
1297 "locked", String(255), nullable=True, unique=False, default=None)
1298 _changeset_cache = Column(
1299 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1300
1301 fork_id = Column(
1302 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1303 nullable=True, unique=False, default=None)
1304 group_id = Column(
1305 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1306 unique=False, default=None)
1307
1308 user = relationship('User', lazy='joined')
1309 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1310 group = relationship('RepoGroup', lazy='joined')
1311 repo_to_perm = relationship(
1312 'UserRepoToPerm', cascade='all',
1313 order_by='UserRepoToPerm.repo_to_perm_id')
1314 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1315 stats = relationship('Statistics', cascade='all', uselist=False)
1316
1317 followers = relationship(
1318 'UserFollowing',
1319 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1320 cascade='all')
1321 extra_fields = relationship(
1322 'RepositoryField', cascade="all, delete, delete-orphan")
1323 logs = relationship('UserLog')
1324 comments = relationship(
1325 'ChangesetComment', cascade="all, delete, delete-orphan")
1326 pull_requests_source = relationship(
1327 'PullRequest',
1328 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1329 cascade="all, delete, delete-orphan")
1330 pull_requests_target = relationship(
1331 'PullRequest',
1332 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1333 cascade="all, delete, delete-orphan")
1334 ui = relationship('RepoRhodeCodeUi', cascade="all")
1335 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1336 integrations = relationship('Integration',
1337 cascade="all, delete, delete-orphan")
1338
1339 def __unicode__(self):
1340 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1341 safe_unicode(self.repo_name))
1342
1343 @hybrid_property
1344 def landing_rev(self):
1345 # always should return [rev_type, rev]
1346 if self._landing_revision:
1347 _rev_info = self._landing_revision.split(':')
1348 if len(_rev_info) < 2:
1349 _rev_info.insert(0, 'rev')
1350 return [_rev_info[0], _rev_info[1]]
1351 return [None, None]
1352
1353 @landing_rev.setter
1354 def landing_rev(self, val):
1355 if ':' not in val:
1356 raise ValueError('value must be delimited with `:` and consist '
1357 'of <rev_type>:<rev>, got %s instead' % val)
1358 self._landing_revision = val
1359
1360 @hybrid_property
1361 def locked(self):
1362 if self._locked:
1363 user_id, timelocked, reason = self._locked.split(':')
1364 lock_values = int(user_id), timelocked, reason
1365 else:
1366 lock_values = [None, None, None]
1367 return lock_values
1368
1369 @locked.setter
1370 def locked(self, val):
1371 if val and isinstance(val, (list, tuple)):
1372 self._locked = ':'.join(map(str, val))
1373 else:
1374 self._locked = None
1375
1376 @hybrid_property
1377 def changeset_cache(self):
1378 from rhodecode.lib.vcs.backends.base import EmptyCommit
1379 dummy = EmptyCommit().__json__()
1380 if not self._changeset_cache:
1381 return dummy
1382 try:
1383 return json.loads(self._changeset_cache)
1384 except TypeError:
1385 return dummy
1386 except Exception:
1387 log.error(traceback.format_exc())
1388 return dummy
1389
1390 @changeset_cache.setter
1391 def changeset_cache(self, val):
1392 try:
1393 self._changeset_cache = json.dumps(val)
1394 except Exception:
1395 log.error(traceback.format_exc())
1396
1397 @hybrid_property
1398 def repo_name(self):
1399 return self._repo_name
1400
1401 @repo_name.setter
1402 def repo_name(self, value):
1403 self._repo_name = value
1404 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1405
1406 @classmethod
1407 def normalize_repo_name(cls, repo_name):
1408 """
1409 Normalizes os specific repo_name to the format internally stored inside
1410 database using URL_SEP
1411
1412 :param cls:
1413 :param repo_name:
1414 """
1415 return cls.NAME_SEP.join(repo_name.split(os.sep))
1416
1417 @classmethod
1418 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1419 session = Session()
1420 q = session.query(cls).filter(cls.repo_name == repo_name)
1421
1422 if cache:
1423 if identity_cache:
1424 val = cls.identity_cache(session, 'repo_name', repo_name)
1425 if val:
1426 return val
1427 else:
1428 q = q.options(
1429 FromCache("sql_cache_short",
1430 "get_repo_by_name_%s" % _hash_key(repo_name)))
1431
1432 return q.scalar()
1433
1434 @classmethod
1435 def get_by_full_path(cls, repo_full_path):
1436 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1437 repo_name = cls.normalize_repo_name(repo_name)
1438 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1439
1440 @classmethod
1441 def get_repo_forks(cls, repo_id):
1442 return cls.query().filter(Repository.fork_id == repo_id)
1443
1444 @classmethod
1445 def base_path(cls):
1446 """
1447 Returns base path when all repos are stored
1448
1449 :param cls:
1450 """
1451 q = Session().query(RhodeCodeUi)\
1452 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1453 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1454 return q.one().ui_value
1455
1456 @classmethod
1457 def is_valid(cls, repo_name):
1458 """
1459 returns True if given repo name is a valid filesystem repository
1460
1461 :param cls:
1462 :param repo_name:
1463 """
1464 from rhodecode.lib.utils import is_valid_repo
1465
1466 return is_valid_repo(repo_name, cls.base_path())
1467
1468 @classmethod
1469 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1470 case_insensitive=True):
1471 q = Repository.query()
1472
1473 if not isinstance(user_id, Optional):
1474 q = q.filter(Repository.user_id == user_id)
1475
1476 if not isinstance(group_id, Optional):
1477 q = q.filter(Repository.group_id == group_id)
1478
1479 if case_insensitive:
1480 q = q.order_by(func.lower(Repository.repo_name))
1481 else:
1482 q = q.order_by(Repository.repo_name)
1483 return q.all()
1484
1485 @property
1486 def forks(self):
1487 """
1488 Return forks of this repo
1489 """
1490 return Repository.get_repo_forks(self.repo_id)
1491
1492 @property
1493 def parent(self):
1494 """
1495 Returns fork parent
1496 """
1497 return self.fork
1498
1499 @property
1500 def just_name(self):
1501 return self.repo_name.split(self.NAME_SEP)[-1]
1502
1503 @property
1504 def groups_with_parents(self):
1505 groups = []
1506 if self.group is None:
1507 return groups
1508
1509 cur_gr = self.group
1510 groups.insert(0, cur_gr)
1511 while 1:
1512 gr = getattr(cur_gr, 'parent_group', None)
1513 cur_gr = cur_gr.parent_group
1514 if gr is None:
1515 break
1516 groups.insert(0, gr)
1517
1518 return groups
1519
1520 @property
1521 def groups_and_repo(self):
1522 return self.groups_with_parents, self
1523
1524 @LazyProperty
1525 def repo_path(self):
1526 """
1527 Returns base full path for that repository means where it actually
1528 exists on a filesystem
1529 """
1530 q = Session().query(RhodeCodeUi).filter(
1531 RhodeCodeUi.ui_key == self.NAME_SEP)
1532 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1533 return q.one().ui_value
1534
1535 @property
1536 def repo_full_path(self):
1537 p = [self.repo_path]
1538 # we need to split the name by / since this is how we store the
1539 # names in the database, but that eventually needs to be converted
1540 # into a valid system path
1541 p += self.repo_name.split(self.NAME_SEP)
1542 return os.path.join(*map(safe_unicode, p))
1543
1544 @property
1545 def cache_keys(self):
1546 """
1547 Returns associated cache keys for that repo
1548 """
1549 return CacheKey.query()\
1550 .filter(CacheKey.cache_args == self.repo_name)\
1551 .order_by(CacheKey.cache_key)\
1552 .all()
1553
1554 def get_new_name(self, repo_name):
1555 """
1556 returns new full repository name based on assigned group and new new
1557
1558 :param group_name:
1559 """
1560 path_prefix = self.group.full_path_splitted if self.group else []
1561 return self.NAME_SEP.join(path_prefix + [repo_name])
1562
1563 @property
1564 def _config(self):
1565 """
1566 Returns db based config object.
1567 """
1568 from rhodecode.lib.utils import make_db_config
1569 return make_db_config(clear_session=False, repo=self)
1570
1571 def permissions(self, with_admins=True, with_owner=True):
1572 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1573 q = q.options(joinedload(UserRepoToPerm.repository),
1574 joinedload(UserRepoToPerm.user),
1575 joinedload(UserRepoToPerm.permission),)
1576
1577 # get owners and admins and permissions. We do a trick of re-writing
1578 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1579 # has a global reference and changing one object propagates to all
1580 # others. This means if admin is also an owner admin_row that change
1581 # would propagate to both objects
1582 perm_rows = []
1583 for _usr in q.all():
1584 usr = AttributeDict(_usr.user.get_dict())
1585 usr.permission = _usr.permission.permission_name
1586 perm_rows.append(usr)
1587
1588 # filter the perm rows by 'default' first and then sort them by
1589 # admin,write,read,none permissions sorted again alphabetically in
1590 # each group
1591 perm_rows = sorted(perm_rows, key=display_sort)
1592
1593 _admin_perm = 'repository.admin'
1594 owner_row = []
1595 if with_owner:
1596 usr = AttributeDict(self.user.get_dict())
1597 usr.owner_row = True
1598 usr.permission = _admin_perm
1599 owner_row.append(usr)
1600
1601 super_admin_rows = []
1602 if with_admins:
1603 for usr in User.get_all_super_admins():
1604 # if this admin is also owner, don't double the record
1605 if usr.user_id == owner_row[0].user_id:
1606 owner_row[0].admin_row = True
1607 else:
1608 usr = AttributeDict(usr.get_dict())
1609 usr.admin_row = True
1610 usr.permission = _admin_perm
1611 super_admin_rows.append(usr)
1612
1613 return super_admin_rows + owner_row + perm_rows
1614
1615 def permission_user_groups(self):
1616 q = UserGroupRepoToPerm.query().filter(
1617 UserGroupRepoToPerm.repository == self)
1618 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1619 joinedload(UserGroupRepoToPerm.users_group),
1620 joinedload(UserGroupRepoToPerm.permission),)
1621
1622 perm_rows = []
1623 for _user_group in q.all():
1624 usr = AttributeDict(_user_group.users_group.get_dict())
1625 usr.permission = _user_group.permission.permission_name
1626 perm_rows.append(usr)
1627
1628 return perm_rows
1629
1630 def get_api_data(self, include_secrets=False):
1631 """
1632 Common function for generating repo api data
1633
1634 :param include_secrets: See :meth:`User.get_api_data`.
1635
1636 """
1637 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1638 # move this methods on models level.
1639 from rhodecode.model.settings import SettingsModel
1640
1641 repo = self
1642 _user_id, _time, _reason = self.locked
1643
1644 data = {
1645 'repo_id': repo.repo_id,
1646 'repo_name': repo.repo_name,
1647 'repo_type': repo.repo_type,
1648 'clone_uri': repo.clone_uri or '',
1649 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1650 'private': repo.private,
1651 'created_on': repo.created_on,
1652 'description': repo.description,
1653 'landing_rev': repo.landing_rev,
1654 'owner': repo.user.username,
1655 'fork_of': repo.fork.repo_name if repo.fork else None,
1656 'enable_statistics': repo.enable_statistics,
1657 'enable_locking': repo.enable_locking,
1658 'enable_downloads': repo.enable_downloads,
1659 'last_changeset': repo.changeset_cache,
1660 'locked_by': User.get(_user_id).get_api_data(
1661 include_secrets=include_secrets) if _user_id else None,
1662 'locked_date': time_to_datetime(_time) if _time else None,
1663 'lock_reason': _reason if _reason else None,
1664 }
1665
1666 # TODO: mikhail: should be per-repo settings here
1667 rc_config = SettingsModel().get_all_settings()
1668 repository_fields = str2bool(
1669 rc_config.get('rhodecode_repository_fields'))
1670 if repository_fields:
1671 for f in self.extra_fields:
1672 data[f.field_key_prefixed] = f.field_value
1673
1674 return data
1675
1676 @classmethod
1677 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1678 if not lock_time:
1679 lock_time = time.time()
1680 if not lock_reason:
1681 lock_reason = cls.LOCK_AUTOMATIC
1682 repo.locked = [user_id, lock_time, lock_reason]
1683 Session().add(repo)
1684 Session().commit()
1685
1686 @classmethod
1687 def unlock(cls, repo):
1688 repo.locked = None
1689 Session().add(repo)
1690 Session().commit()
1691
1692 @classmethod
1693 def getlock(cls, repo):
1694 return repo.locked
1695
1696 def is_user_lock(self, user_id):
1697 if self.lock[0]:
1698 lock_user_id = safe_int(self.lock[0])
1699 user_id = safe_int(user_id)
1700 # both are ints, and they are equal
1701 return all([lock_user_id, user_id]) and lock_user_id == user_id
1702
1703 return False
1704
1705 def get_locking_state(self, action, user_id, only_when_enabled=True):
1706 """
1707 Checks locking on this repository, if locking is enabled and lock is
1708 present returns a tuple of make_lock, locked, locked_by.
1709 make_lock can have 3 states None (do nothing) True, make lock
1710 False release lock, This value is later propagated to hooks, which
1711 do the locking. Think about this as signals passed to hooks what to do.
1712
1713 """
1714 # TODO: johbo: This is part of the business logic and should be moved
1715 # into the RepositoryModel.
1716
1717 if action not in ('push', 'pull'):
1718 raise ValueError("Invalid action value: %s" % repr(action))
1719
1720 # defines if locked error should be thrown to user
1721 currently_locked = False
1722 # defines if new lock should be made, tri-state
1723 make_lock = None
1724 repo = self
1725 user = User.get(user_id)
1726
1727 lock_info = repo.locked
1728
1729 if repo and (repo.enable_locking or not only_when_enabled):
1730 if action == 'push':
1731 # check if it's already locked !, if it is compare users
1732 locked_by_user_id = lock_info[0]
1733 if user.user_id == locked_by_user_id:
1734 log.debug(
1735 'Got `push` action from user %s, now unlocking', user)
1736 # unlock if we have push from user who locked
1737 make_lock = False
1738 else:
1739 # we're not the same user who locked, ban with
1740 # code defined in settings (default is 423 HTTP Locked) !
1741 log.debug('Repo %s is currently locked by %s', repo, user)
1742 currently_locked = True
1743 elif action == 'pull':
1744 # [0] user [1] date
1745 if lock_info[0] and lock_info[1]:
1746 log.debug('Repo %s is currently locked by %s', repo, user)
1747 currently_locked = True
1748 else:
1749 log.debug('Setting lock on repo %s by %s', repo, user)
1750 make_lock = True
1751
1752 else:
1753 log.debug('Repository %s do not have locking enabled', repo)
1754
1755 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1756 make_lock, currently_locked, lock_info)
1757
1758 from rhodecode.lib.auth import HasRepoPermissionAny
1759 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1760 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1761 # if we don't have at least write permission we cannot make a lock
1762 log.debug('lock state reset back to FALSE due to lack '
1763 'of at least read permission')
1764 make_lock = False
1765
1766 return make_lock, currently_locked, lock_info
1767
1768 @property
1769 def last_db_change(self):
1770 return self.updated_on
1771
1772 @property
1773 def clone_uri_hidden(self):
1774 clone_uri = self.clone_uri
1775 if clone_uri:
1776 import urlobject
1777 url_obj = urlobject.URLObject(clone_uri)
1778 if url_obj.password:
1779 clone_uri = url_obj.with_password('*****')
1780 return clone_uri
1781
1782 def clone_url(self, **override):
1783 qualified_home_url = url('home', qualified=True)
1784
1785 uri_tmpl = None
1786 if 'with_id' in override:
1787 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1788 del override['with_id']
1789
1790 if 'uri_tmpl' in override:
1791 uri_tmpl = override['uri_tmpl']
1792 del override['uri_tmpl']
1793
1794 # we didn't override our tmpl from **overrides
1795 if not uri_tmpl:
1796 uri_tmpl = self.DEFAULT_CLONE_URI
1797 try:
1798 from pylons import tmpl_context as c
1799 uri_tmpl = c.clone_uri_tmpl
1800 except Exception:
1801 # in any case if we call this outside of request context,
1802 # ie, not having tmpl_context set up
1803 pass
1804
1805 return get_clone_url(uri_tmpl=uri_tmpl,
1806 qualifed_home_url=qualified_home_url,
1807 repo_name=self.repo_name,
1808 repo_id=self.repo_id, **override)
1809
1810 def set_state(self, state):
1811 self.repo_state = state
1812 Session().add(self)
1813 #==========================================================================
1814 # SCM PROPERTIES
1815 #==========================================================================
1816
1817 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1818 return get_commit_safe(
1819 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1820
1821 def get_changeset(self, rev=None, pre_load=None):
1822 warnings.warn("Use get_commit", DeprecationWarning)
1823 commit_id = None
1824 commit_idx = None
1825 if isinstance(rev, basestring):
1826 commit_id = rev
1827 else:
1828 commit_idx = rev
1829 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1830 pre_load=pre_load)
1831
1832 def get_landing_commit(self):
1833 """
1834 Returns landing commit, or if that doesn't exist returns the tip
1835 """
1836 _rev_type, _rev = self.landing_rev
1837 commit = self.get_commit(_rev)
1838 if isinstance(commit, EmptyCommit):
1839 return self.get_commit()
1840 return commit
1841
1842 def update_commit_cache(self, cs_cache=None, config=None):
1843 """
1844 Update cache of last changeset for repository, keys should be::
1845
1846 short_id
1847 raw_id
1848 revision
1849 parents
1850 message
1851 date
1852 author
1853
1854 :param cs_cache:
1855 """
1856 from rhodecode.lib.vcs.backends.base import BaseChangeset
1857 if cs_cache is None:
1858 # use no-cache version here
1859 scm_repo = self.scm_instance(cache=False, config=config)
1860 if scm_repo:
1861 cs_cache = scm_repo.get_commit(
1862 pre_load=["author", "date", "message", "parents"])
1863 else:
1864 cs_cache = EmptyCommit()
1865
1866 if isinstance(cs_cache, BaseChangeset):
1867 cs_cache = cs_cache.__json__()
1868
1869 def is_outdated(new_cs_cache):
1870 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1871 new_cs_cache['revision'] != self.changeset_cache['revision']):
1872 return True
1873 return False
1874
1875 # check if we have maybe already latest cached revision
1876 if is_outdated(cs_cache) or not self.changeset_cache:
1877 _default = datetime.datetime.fromtimestamp(0)
1878 last_change = cs_cache.get('date') or _default
1879 log.debug('updated repo %s with new cs cache %s',
1880 self.repo_name, cs_cache)
1881 self.updated_on = last_change
1882 self.changeset_cache = cs_cache
1883 Session().add(self)
1884 Session().commit()
1885 else:
1886 log.debug('Skipping update_commit_cache for repo:`%s` '
1887 'commit already with latest changes', self.repo_name)
1888
1889 @property
1890 def tip(self):
1891 return self.get_commit('tip')
1892
1893 @property
1894 def author(self):
1895 return self.tip.author
1896
1897 @property
1898 def last_change(self):
1899 return self.scm_instance().last_change
1900
1901 def get_comments(self, revisions=None):
1902 """
1903 Returns comments for this repository grouped by revisions
1904
1905 :param revisions: filter query by revisions only
1906 """
1907 cmts = ChangesetComment.query()\
1908 .filter(ChangesetComment.repo == self)
1909 if revisions:
1910 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1911 grouped = collections.defaultdict(list)
1912 for cmt in cmts.all():
1913 grouped[cmt.revision].append(cmt)
1914 return grouped
1915
1916 def statuses(self, revisions=None):
1917 """
1918 Returns statuses for this repository
1919
1920 :param revisions: list of revisions to get statuses for
1921 """
1922 statuses = ChangesetStatus.query()\
1923 .filter(ChangesetStatus.repo == self)\
1924 .filter(ChangesetStatus.version == 0)
1925
1926 if revisions:
1927 # Try doing the filtering in chunks to avoid hitting limits
1928 size = 500
1929 status_results = []
1930 for chunk in xrange(0, len(revisions), size):
1931 status_results += statuses.filter(
1932 ChangesetStatus.revision.in_(
1933 revisions[chunk: chunk+size])
1934 ).all()
1935 else:
1936 status_results = statuses.all()
1937
1938 grouped = {}
1939
1940 # maybe we have open new pullrequest without a status?
1941 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1942 status_lbl = ChangesetStatus.get_status_lbl(stat)
1943 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1944 for rev in pr.revisions:
1945 pr_id = pr.pull_request_id
1946 pr_repo = pr.target_repo.repo_name
1947 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1948
1949 for stat in status_results:
1950 pr_id = pr_repo = None
1951 if stat.pull_request:
1952 pr_id = stat.pull_request.pull_request_id
1953 pr_repo = stat.pull_request.target_repo.repo_name
1954 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1955 pr_id, pr_repo]
1956 return grouped
1957
1958 # ==========================================================================
1959 # SCM CACHE INSTANCE
1960 # ==========================================================================
1961
1962 def scm_instance(self, **kwargs):
1963 import rhodecode
1964
1965 # Passing a config will not hit the cache currently only used
1966 # for repo2dbmapper
1967 config = kwargs.pop('config', None)
1968 cache = kwargs.pop('cache', None)
1969 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1970 # if cache is NOT defined use default global, else we have a full
1971 # control over cache behaviour
1972 if cache is None and full_cache and not config:
1973 return self._get_instance_cached()
1974 return self._get_instance(cache=bool(cache), config=config)
1975
1976 def _get_instance_cached(self):
1977 @cache_region('long_term')
1978 def _get_repo(cache_key):
1979 return self._get_instance()
1980
1981 invalidator_context = CacheKey.repo_context_cache(
1982 _get_repo, self.repo_name, None, thread_scoped=True)
1983
1984 with invalidator_context as context:
1985 context.invalidate()
1986 repo = context.compute()
1987
1988 return repo
1989
1990 def _get_instance(self, cache=True, config=None):
1991 config = config or self._config
1992 custom_wire = {
1993 'cache': cache # controls the vcs.remote cache
1994 }
1995
1996 repo = get_vcs_instance(
1997 repo_path=safe_str(self.repo_full_path),
1998 config=config,
1999 with_wire=custom_wire,
2000 create=False)
2001
2002 return repo
2003
2004 def __json__(self):
2005 return {'landing_rev': self.landing_rev}
2006
2007 def get_dict(self):
2008
2009 # Since we transformed `repo_name` to a hybrid property, we need to
2010 # keep compatibility with the code which uses `repo_name` field.
2011
2012 result = super(Repository, self).get_dict()
2013 result['repo_name'] = result.pop('_repo_name', None)
2014 return result
2015
2016
2017 class RepoGroup(Base, BaseModel):
2018 __tablename__ = 'groups'
2019 __table_args__ = (
2020 UniqueConstraint('group_name', 'group_parent_id'),
2021 CheckConstraint('group_id != group_parent_id'),
2022 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2023 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2024 )
2025 __mapper_args__ = {'order_by': 'group_name'}
2026
2027 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2028
2029 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2030 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2031 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2032 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2033 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2035 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2036
2037 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2038 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2039 parent_group = relationship('RepoGroup', remote_side=group_id)
2040 user = relationship('User')
2041 integrations = relationship('Integration',
2042 cascade="all, delete, delete-orphan")
2043
2044 def __init__(self, group_name='', parent_group=None):
2045 self.group_name = group_name
2046 self.parent_group = parent_group
2047
2048 def __unicode__(self):
2049 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2050 self.group_name)
2051
2052 @classmethod
2053 def _generate_choice(cls, repo_group):
2054 from webhelpers.html import literal as _literal
2055 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2056 return repo_group.group_id, _name(repo_group.full_path_splitted)
2057
2058 @classmethod
2059 def groups_choices(cls, groups=None, show_empty_group=True):
2060 if not groups:
2061 groups = cls.query().all()
2062
2063 repo_groups = []
2064 if show_empty_group:
2065 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2066
2067 repo_groups.extend([cls._generate_choice(x) for x in groups])
2068
2069 repo_groups = sorted(
2070 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2071 return repo_groups
2072
2073 @classmethod
2074 def url_sep(cls):
2075 return URL_SEP
2076
2077 @classmethod
2078 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2079 if case_insensitive:
2080 gr = cls.query().filter(func.lower(cls.group_name)
2081 == func.lower(group_name))
2082 else:
2083 gr = cls.query().filter(cls.group_name == group_name)
2084 if cache:
2085 gr = gr.options(FromCache(
2086 "sql_cache_short",
2087 "get_group_%s" % _hash_key(group_name)))
2088 return gr.scalar()
2089
2090 @classmethod
2091 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2092 case_insensitive=True):
2093 q = RepoGroup.query()
2094
2095 if not isinstance(user_id, Optional):
2096 q = q.filter(RepoGroup.user_id == user_id)
2097
2098 if not isinstance(group_id, Optional):
2099 q = q.filter(RepoGroup.group_parent_id == group_id)
2100
2101 if case_insensitive:
2102 q = q.order_by(func.lower(RepoGroup.group_name))
2103 else:
2104 q = q.order_by(RepoGroup.group_name)
2105 return q.all()
2106
2107 @property
2108 def parents(self):
2109 parents_recursion_limit = 10
2110 groups = []
2111 if self.parent_group is None:
2112 return groups
2113 cur_gr = self.parent_group
2114 groups.insert(0, cur_gr)
2115 cnt = 0
2116 while 1:
2117 cnt += 1
2118 gr = getattr(cur_gr, 'parent_group', None)
2119 cur_gr = cur_gr.parent_group
2120 if gr is None:
2121 break
2122 if cnt == parents_recursion_limit:
2123 # this will prevent accidental infinit loops
2124 log.error(('more than %s parents found for group %s, stopping '
2125 'recursive parent fetching' % (parents_recursion_limit, self)))
2126 break
2127
2128 groups.insert(0, gr)
2129 return groups
2130
2131 @property
2132 def children(self):
2133 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2134
2135 @property
2136 def name(self):
2137 return self.group_name.split(RepoGroup.url_sep())[-1]
2138
2139 @property
2140 def full_path(self):
2141 return self.group_name
2142
2143 @property
2144 def full_path_splitted(self):
2145 return self.group_name.split(RepoGroup.url_sep())
2146
2147 @property
2148 def repositories(self):
2149 return Repository.query()\
2150 .filter(Repository.group == self)\
2151 .order_by(Repository.repo_name)
2152
2153 @property
2154 def repositories_recursive_count(self):
2155 cnt = self.repositories.count()
2156
2157 def children_count(group):
2158 cnt = 0
2159 for child in group.children:
2160 cnt += child.repositories.count()
2161 cnt += children_count(child)
2162 return cnt
2163
2164 return cnt + children_count(self)
2165
2166 def _recursive_objects(self, include_repos=True):
2167 all_ = []
2168
2169 def _get_members(root_gr):
2170 if include_repos:
2171 for r in root_gr.repositories:
2172 all_.append(r)
2173 childs = root_gr.children.all()
2174 if childs:
2175 for gr in childs:
2176 all_.append(gr)
2177 _get_members(gr)
2178
2179 _get_members(self)
2180 return [self] + all_
2181
2182 def recursive_groups_and_repos(self):
2183 """
2184 Recursive return all groups, with repositories in those groups
2185 """
2186 return self._recursive_objects()
2187
2188 def recursive_groups(self):
2189 """
2190 Returns all children groups for this group including children of children
2191 """
2192 return self._recursive_objects(include_repos=False)
2193
2194 def get_new_name(self, group_name):
2195 """
2196 returns new full group name based on parent and new name
2197
2198 :param group_name:
2199 """
2200 path_prefix = (self.parent_group.full_path_splitted if
2201 self.parent_group else [])
2202 return RepoGroup.url_sep().join(path_prefix + [group_name])
2203
2204 def permissions(self, with_admins=True, with_owner=True):
2205 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2206 q = q.options(joinedload(UserRepoGroupToPerm.group),
2207 joinedload(UserRepoGroupToPerm.user),
2208 joinedload(UserRepoGroupToPerm.permission),)
2209
2210 # get owners and admins and permissions. We do a trick of re-writing
2211 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2212 # has a global reference and changing one object propagates to all
2213 # others. This means if admin is also an owner admin_row that change
2214 # would propagate to both objects
2215 perm_rows = []
2216 for _usr in q.all():
2217 usr = AttributeDict(_usr.user.get_dict())
2218 usr.permission = _usr.permission.permission_name
2219 perm_rows.append(usr)
2220
2221 # filter the perm rows by 'default' first and then sort them by
2222 # admin,write,read,none permissions sorted again alphabetically in
2223 # each group
2224 perm_rows = sorted(perm_rows, key=display_sort)
2225
2226 _admin_perm = 'group.admin'
2227 owner_row = []
2228 if with_owner:
2229 usr = AttributeDict(self.user.get_dict())
2230 usr.owner_row = True
2231 usr.permission = _admin_perm
2232 owner_row.append(usr)
2233
2234 super_admin_rows = []
2235 if with_admins:
2236 for usr in User.get_all_super_admins():
2237 # if this admin is also owner, don't double the record
2238 if usr.user_id == owner_row[0].user_id:
2239 owner_row[0].admin_row = True
2240 else:
2241 usr = AttributeDict(usr.get_dict())
2242 usr.admin_row = True
2243 usr.permission = _admin_perm
2244 super_admin_rows.append(usr)
2245
2246 return super_admin_rows + owner_row + perm_rows
2247
2248 def permission_user_groups(self):
2249 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2250 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2251 joinedload(UserGroupRepoGroupToPerm.users_group),
2252 joinedload(UserGroupRepoGroupToPerm.permission),)
2253
2254 perm_rows = []
2255 for _user_group in q.all():
2256 usr = AttributeDict(_user_group.users_group.get_dict())
2257 usr.permission = _user_group.permission.permission_name
2258 perm_rows.append(usr)
2259
2260 return perm_rows
2261
2262 def get_api_data(self):
2263 """
2264 Common function for generating api data
2265
2266 """
2267 group = self
2268 data = {
2269 'group_id': group.group_id,
2270 'group_name': group.group_name,
2271 'group_description': group.group_description,
2272 'parent_group': group.parent_group.group_name if group.parent_group else None,
2273 'repositories': [x.repo_name for x in group.repositories],
2274 'owner': group.user.username,
2275 }
2276 return data
2277
2278
2279 class Permission(Base, BaseModel):
2280 __tablename__ = 'permissions'
2281 __table_args__ = (
2282 Index('p_perm_name_idx', 'permission_name'),
2283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2284 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2285 )
2286 PERMS = [
2287 ('hg.admin', _('RhodeCode Super Administrator')),
2288
2289 ('repository.none', _('Repository no access')),
2290 ('repository.read', _('Repository read access')),
2291 ('repository.write', _('Repository write access')),
2292 ('repository.admin', _('Repository admin access')),
2293
2294 ('group.none', _('Repository group no access')),
2295 ('group.read', _('Repository group read access')),
2296 ('group.write', _('Repository group write access')),
2297 ('group.admin', _('Repository group admin access')),
2298
2299 ('usergroup.none', _('User group no access')),
2300 ('usergroup.read', _('User group read access')),
2301 ('usergroup.write', _('User group write access')),
2302 ('usergroup.admin', _('User group admin access')),
2303
2304 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2305 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2306
2307 ('hg.usergroup.create.false', _('User Group creation disabled')),
2308 ('hg.usergroup.create.true', _('User Group creation enabled')),
2309
2310 ('hg.create.none', _('Repository creation disabled')),
2311 ('hg.create.repository', _('Repository creation enabled')),
2312 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2313 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2314
2315 ('hg.fork.none', _('Repository forking disabled')),
2316 ('hg.fork.repository', _('Repository forking enabled')),
2317
2318 ('hg.register.none', _('Registration disabled')),
2319 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2320 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2321
2322 ('hg.extern_activate.manual', _('Manual activation of external account')),
2323 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2324
2325 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2326 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2327 ]
2328
2329 # definition of system default permissions for DEFAULT user
2330 DEFAULT_USER_PERMISSIONS = [
2331 'repository.read',
2332 'group.read',
2333 'usergroup.read',
2334 'hg.create.repository',
2335 'hg.repogroup.create.false',
2336 'hg.usergroup.create.false',
2337 'hg.create.write_on_repogroup.true',
2338 'hg.fork.repository',
2339 'hg.register.manual_activate',
2340 'hg.extern_activate.auto',
2341 'hg.inherit_default_perms.true',
2342 ]
2343
2344 # defines which permissions are more important higher the more important
2345 # Weight defines which permissions are more important.
2346 # The higher number the more important.
2347 PERM_WEIGHTS = {
2348 'repository.none': 0,
2349 'repository.read': 1,
2350 'repository.write': 3,
2351 'repository.admin': 4,
2352
2353 'group.none': 0,
2354 'group.read': 1,
2355 'group.write': 3,
2356 'group.admin': 4,
2357
2358 'usergroup.none': 0,
2359 'usergroup.read': 1,
2360 'usergroup.write': 3,
2361 'usergroup.admin': 4,
2362
2363 'hg.repogroup.create.false': 0,
2364 'hg.repogroup.create.true': 1,
2365
2366 'hg.usergroup.create.false': 0,
2367 'hg.usergroup.create.true': 1,
2368
2369 'hg.fork.none': 0,
2370 'hg.fork.repository': 1,
2371 'hg.create.none': 0,
2372 'hg.create.repository': 1
2373 }
2374
2375 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2376 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2377 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2378
2379 def __unicode__(self):
2380 return u"<%s('%s:%s')>" % (
2381 self.__class__.__name__, self.permission_id, self.permission_name
2382 )
2383
2384 @classmethod
2385 def get_by_key(cls, key):
2386 return cls.query().filter(cls.permission_name == key).scalar()
2387
2388 @classmethod
2389 def get_default_repo_perms(cls, user_id, repo_id=None):
2390 q = Session().query(UserRepoToPerm, Repository, Permission)\
2391 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2392 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2393 .filter(UserRepoToPerm.user_id == user_id)
2394 if repo_id:
2395 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2396 return q.all()
2397
2398 @classmethod
2399 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2400 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2401 .join(
2402 Permission,
2403 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2404 .join(
2405 Repository,
2406 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2407 .join(
2408 UserGroup,
2409 UserGroupRepoToPerm.users_group_id ==
2410 UserGroup.users_group_id)\
2411 .join(
2412 UserGroupMember,
2413 UserGroupRepoToPerm.users_group_id ==
2414 UserGroupMember.users_group_id)\
2415 .filter(
2416 UserGroupMember.user_id == user_id,
2417 UserGroup.users_group_active == true())
2418 if repo_id:
2419 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2420 return q.all()
2421
2422 @classmethod
2423 def get_default_group_perms(cls, user_id, repo_group_id=None):
2424 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2425 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2426 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2427 .filter(UserRepoGroupToPerm.user_id == user_id)
2428 if repo_group_id:
2429 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2430 return q.all()
2431
2432 @classmethod
2433 def get_default_group_perms_from_user_group(
2434 cls, user_id, repo_group_id=None):
2435 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2436 .join(
2437 Permission,
2438 UserGroupRepoGroupToPerm.permission_id ==
2439 Permission.permission_id)\
2440 .join(
2441 RepoGroup,
2442 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2443 .join(
2444 UserGroup,
2445 UserGroupRepoGroupToPerm.users_group_id ==
2446 UserGroup.users_group_id)\
2447 .join(
2448 UserGroupMember,
2449 UserGroupRepoGroupToPerm.users_group_id ==
2450 UserGroupMember.users_group_id)\
2451 .filter(
2452 UserGroupMember.user_id == user_id,
2453 UserGroup.users_group_active == true())
2454 if repo_group_id:
2455 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2456 return q.all()
2457
2458 @classmethod
2459 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2460 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2461 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2462 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2463 .filter(UserUserGroupToPerm.user_id == user_id)
2464 if user_group_id:
2465 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2466 return q.all()
2467
2468 @classmethod
2469 def get_default_user_group_perms_from_user_group(
2470 cls, user_id, user_group_id=None):
2471 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2472 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2473 .join(
2474 Permission,
2475 UserGroupUserGroupToPerm.permission_id ==
2476 Permission.permission_id)\
2477 .join(
2478 TargetUserGroup,
2479 UserGroupUserGroupToPerm.target_user_group_id ==
2480 TargetUserGroup.users_group_id)\
2481 .join(
2482 UserGroup,
2483 UserGroupUserGroupToPerm.user_group_id ==
2484 UserGroup.users_group_id)\
2485 .join(
2486 UserGroupMember,
2487 UserGroupUserGroupToPerm.user_group_id ==
2488 UserGroupMember.users_group_id)\
2489 .filter(
2490 UserGroupMember.user_id == user_id,
2491 UserGroup.users_group_active == true())
2492 if user_group_id:
2493 q = q.filter(
2494 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2495
2496 return q.all()
2497
2498
2499 class UserRepoToPerm(Base, BaseModel):
2500 __tablename__ = 'repo_to_perm'
2501 __table_args__ = (
2502 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2505 )
2506 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2507 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2509 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2510
2511 user = relationship('User')
2512 repository = relationship('Repository')
2513 permission = relationship('Permission')
2514
2515 @classmethod
2516 def create(cls, user, repository, permission):
2517 n = cls()
2518 n.user = user
2519 n.repository = repository
2520 n.permission = permission
2521 Session().add(n)
2522 return n
2523
2524 def __unicode__(self):
2525 return u'<%s => %s >' % (self.user, self.repository)
2526
2527
2528 class UserUserGroupToPerm(Base, BaseModel):
2529 __tablename__ = 'user_user_group_to_perm'
2530 __table_args__ = (
2531 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2533 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2534 )
2535 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2537 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2538 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2539
2540 user = relationship('User')
2541 user_group = relationship('UserGroup')
2542 permission = relationship('Permission')
2543
2544 @classmethod
2545 def create(cls, user, user_group, permission):
2546 n = cls()
2547 n.user = user
2548 n.user_group = user_group
2549 n.permission = permission
2550 Session().add(n)
2551 return n
2552
2553 def __unicode__(self):
2554 return u'<%s => %s >' % (self.user, self.user_group)
2555
2556
2557 class UserToPerm(Base, BaseModel):
2558 __tablename__ = 'user_to_perm'
2559 __table_args__ = (
2560 UniqueConstraint('user_id', 'permission_id'),
2561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2562 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2563 )
2564 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2567
2568 user = relationship('User')
2569 permission = relationship('Permission', lazy='joined')
2570
2571 def __unicode__(self):
2572 return u'<%s => %s >' % (self.user, self.permission)
2573
2574
2575 class UserGroupRepoToPerm(Base, BaseModel):
2576 __tablename__ = 'users_group_repo_to_perm'
2577 __table_args__ = (
2578 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2579 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2580 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2581 )
2582 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2583 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2584 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2585 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2586
2587 users_group = relationship('UserGroup')
2588 permission = relationship('Permission')
2589 repository = relationship('Repository')
2590
2591 @classmethod
2592 def create(cls, users_group, repository, permission):
2593 n = cls()
2594 n.users_group = users_group
2595 n.repository = repository
2596 n.permission = permission
2597 Session().add(n)
2598 return n
2599
2600 def __unicode__(self):
2601 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2602
2603
2604 class UserGroupUserGroupToPerm(Base, BaseModel):
2605 __tablename__ = 'user_group_user_group_to_perm'
2606 __table_args__ = (
2607 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2608 CheckConstraint('target_user_group_id != user_group_id'),
2609 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2610 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2611 )
2612 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)
2613 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2614 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2615 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2616
2617 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2618 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2619 permission = relationship('Permission')
2620
2621 @classmethod
2622 def create(cls, target_user_group, user_group, permission):
2623 n = cls()
2624 n.target_user_group = target_user_group
2625 n.user_group = user_group
2626 n.permission = permission
2627 Session().add(n)
2628 return n
2629
2630 def __unicode__(self):
2631 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2632
2633
2634 class UserGroupToPerm(Base, BaseModel):
2635 __tablename__ = 'users_group_to_perm'
2636 __table_args__ = (
2637 UniqueConstraint('users_group_id', 'permission_id',),
2638 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2639 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2640 )
2641 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2642 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2643 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2644
2645 users_group = relationship('UserGroup')
2646 permission = relationship('Permission')
2647
2648
2649 class UserRepoGroupToPerm(Base, BaseModel):
2650 __tablename__ = 'user_repo_group_to_perm'
2651 __table_args__ = (
2652 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2653 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2654 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2655 )
2656
2657 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2658 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2659 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2660 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2661
2662 user = relationship('User')
2663 group = relationship('RepoGroup')
2664 permission = relationship('Permission')
2665
2666 @classmethod
2667 def create(cls, user, repository_group, permission):
2668 n = cls()
2669 n.user = user
2670 n.group = repository_group
2671 n.permission = permission
2672 Session().add(n)
2673 return n
2674
2675
2676 class UserGroupRepoGroupToPerm(Base, BaseModel):
2677 __tablename__ = 'users_group_repo_group_to_perm'
2678 __table_args__ = (
2679 UniqueConstraint('users_group_id', 'group_id'),
2680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2681 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2682 )
2683
2684 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)
2685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2686 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2687 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2688
2689 users_group = relationship('UserGroup')
2690 permission = relationship('Permission')
2691 group = relationship('RepoGroup')
2692
2693 @classmethod
2694 def create(cls, user_group, repository_group, permission):
2695 n = cls()
2696 n.users_group = user_group
2697 n.group = repository_group
2698 n.permission = permission
2699 Session().add(n)
2700 return n
2701
2702 def __unicode__(self):
2703 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2704
2705
2706 class Statistics(Base, BaseModel):
2707 __tablename__ = 'statistics'
2708 __table_args__ = (
2709 UniqueConstraint('repository_id'),
2710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2711 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2712 )
2713 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2714 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2715 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2716 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2717 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2718 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2719
2720 repository = relationship('Repository', single_parent=True)
2721
2722
2723 class UserFollowing(Base, BaseModel):
2724 __tablename__ = 'user_followings'
2725 __table_args__ = (
2726 UniqueConstraint('user_id', 'follows_repository_id'),
2727 UniqueConstraint('user_id', 'follows_user_id'),
2728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2729 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2730 )
2731
2732 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2733 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2734 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2735 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2736 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2737
2738 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2739
2740 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2741 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2742
2743 @classmethod
2744 def get_repo_followers(cls, repo_id):
2745 return cls.query().filter(cls.follows_repo_id == repo_id)
2746
2747
2748 class CacheKey(Base, BaseModel):
2749 __tablename__ = 'cache_invalidation'
2750 __table_args__ = (
2751 UniqueConstraint('cache_key'),
2752 Index('key_idx', 'cache_key'),
2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2755 )
2756 CACHE_TYPE_ATOM = 'ATOM'
2757 CACHE_TYPE_RSS = 'RSS'
2758 CACHE_TYPE_README = 'README'
2759
2760 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2761 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2762 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2763 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2764
2765 def __init__(self, cache_key, cache_args=''):
2766 self.cache_key = cache_key
2767 self.cache_args = cache_args
2768 self.cache_active = False
2769
2770 def __unicode__(self):
2771 return u"<%s('%s:%s[%s]')>" % (
2772 self.__class__.__name__,
2773 self.cache_id, self.cache_key, self.cache_active)
2774
2775 def _cache_key_partition(self):
2776 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2777 return prefix, repo_name, suffix
2778
2779 def get_prefix(self):
2780 """
2781 Try to extract prefix from existing cache key. The key could consist
2782 of prefix, repo_name, suffix
2783 """
2784 # this returns prefix, repo_name, suffix
2785 return self._cache_key_partition()[0]
2786
2787 def get_suffix(self):
2788 """
2789 get suffix that might have been used in _get_cache_key to
2790 generate self.cache_key. Only used for informational purposes
2791 in repo_edit.html.
2792 """
2793 # prefix, repo_name, suffix
2794 return self._cache_key_partition()[2]
2795
2796 @classmethod
2797 def delete_all_cache(cls):
2798 """
2799 Delete all cache keys from database.
2800 Should only be run when all instances are down and all entries
2801 thus stale.
2802 """
2803 cls.query().delete()
2804 Session().commit()
2805
2806 @classmethod
2807 def get_cache_key(cls, repo_name, cache_type):
2808 """
2809
2810 Generate a cache key for this process of RhodeCode instance.
2811 Prefix most likely will be process id or maybe explicitly set
2812 instance_id from .ini file.
2813 """
2814 import rhodecode
2815 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2816
2817 repo_as_unicode = safe_unicode(repo_name)
2818 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2819 if cache_type else repo_as_unicode
2820
2821 return u'{}{}'.format(prefix, key)
2822
2823 @classmethod
2824 def set_invalidate(cls, repo_name, delete=False):
2825 """
2826 Mark all caches of a repo as invalid in the database.
2827 """
2828
2829 try:
2830 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2831 if delete:
2832 log.debug('cache objects deleted for repo %s',
2833 safe_str(repo_name))
2834 qry.delete()
2835 else:
2836 log.debug('cache objects marked as invalid for repo %s',
2837 safe_str(repo_name))
2838 qry.update({"cache_active": False})
2839
2840 Session().commit()
2841 except Exception:
2842 log.exception(
2843 'Cache key invalidation failed for repository %s',
2844 safe_str(repo_name))
2845 Session().rollback()
2846
2847 @classmethod
2848 def get_active_cache(cls, cache_key):
2849 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2850 if inv_obj:
2851 return inv_obj
2852 return None
2853
2854 @classmethod
2855 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2856 thread_scoped=False):
2857 """
2858 @cache_region('long_term')
2859 def _heavy_calculation(cache_key):
2860 return 'result'
2861
2862 cache_context = CacheKey.repo_context_cache(
2863 _heavy_calculation, repo_name, cache_type)
2864
2865 with cache_context as context:
2866 context.invalidate()
2867 computed = context.compute()
2868
2869 assert computed == 'result'
2870 """
2871 from rhodecode.lib import caches
2872 return caches.InvalidationContext(
2873 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2874
2875
2876 class ChangesetComment(Base, BaseModel):
2877 __tablename__ = 'changeset_comments'
2878 __table_args__ = (
2879 Index('cc_revision_idx', 'revision'),
2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2882 )
2883
2884 COMMENT_OUTDATED = u'comment_outdated'
2885
2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2888 revision = Column('revision', String(40), nullable=True)
2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2891 line_no = Column('line_no', Unicode(10), nullable=True)
2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2898 renderer = Column('renderer', Unicode(64), nullable=True)
2899 display_state = Column('display_state', Unicode(128), nullable=True)
2900
2901 author = relationship('User', lazy='joined')
2902 repo = relationship('Repository')
2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2904 pull_request = relationship('PullRequest', lazy='joined')
2905 pull_request_version = relationship('PullRequestVersion')
2906
2907 @classmethod
2908 def get_users(cls, revision=None, pull_request_id=None):
2909 """
2910 Returns user associated with this ChangesetComment. ie those
2911 who actually commented
2912
2913 :param cls:
2914 :param revision:
2915 """
2916 q = Session().query(User)\
2917 .join(ChangesetComment.author)
2918 if revision:
2919 q = q.filter(cls.revision == revision)
2920 elif pull_request_id:
2921 q = q.filter(cls.pull_request_id == pull_request_id)
2922 return q.all()
2923
2924 def render(self, mentions=False):
2925 from rhodecode.lib import helpers as h
2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2927
2928 def __repr__(self):
2929 if self.comment_id:
2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2931 else:
2932 return '<DB:ChangesetComment at %#x>' % id(self)
2933
2934
2935 class ChangesetStatus(Base, BaseModel):
2936 __tablename__ = 'changeset_statuses'
2937 __table_args__ = (
2938 Index('cs_revision_idx', 'revision'),
2939 Index('cs_version_idx', 'version'),
2940 UniqueConstraint('repo_id', 'revision', 'version'),
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2943 )
2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2945 STATUS_APPROVED = 'approved'
2946 STATUS_REJECTED = 'rejected'
2947 STATUS_UNDER_REVIEW = 'under_review'
2948
2949 STATUSES = [
2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2951 (STATUS_APPROVED, _("Approved")),
2952 (STATUS_REJECTED, _("Rejected")),
2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2954 ]
2955
2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2959 revision = Column('revision', String(40), nullable=False)
2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2963 version = Column('version', Integer(), nullable=False, default=0)
2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2965
2966 author = relationship('User', lazy='joined')
2967 repo = relationship('Repository')
2968 comment = relationship('ChangesetComment', lazy='joined')
2969 pull_request = relationship('PullRequest', lazy='joined')
2970
2971 def __unicode__(self):
2972 return u"<%s('%s[%s]:%s')>" % (
2973 self.__class__.__name__,
2974 self.status, self.version, self.author
2975 )
2976
2977 @classmethod
2978 def get_status_lbl(cls, value):
2979 return dict(cls.STATUSES).get(value)
2980
2981 @property
2982 def status_lbl(self):
2983 return ChangesetStatus.get_status_lbl(self.status)
2984
2985
2986 class _PullRequestBase(BaseModel):
2987 """
2988 Common attributes of pull request and version entries.
2989 """
2990
2991 # .status values
2992 STATUS_NEW = u'new'
2993 STATUS_OPEN = u'open'
2994 STATUS_CLOSED = u'closed'
2995
2996 title = Column('title', Unicode(255), nullable=True)
2997 description = Column(
2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2999 nullable=True)
3000 # new/open/closed status of pull request (not approve/reject/etc)
3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3002 created_on = Column(
3003 'created_on', DateTime(timezone=False), nullable=False,
3004 default=datetime.datetime.now)
3005 updated_on = Column(
3006 'updated_on', DateTime(timezone=False), nullable=False,
3007 default=datetime.datetime.now)
3008
3009 @declared_attr
3010 def user_id(cls):
3011 return Column(
3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3013 unique=None)
3014
3015 # 500 revisions max
3016 _revisions = Column(
3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3018
3019 @declared_attr
3020 def source_repo_id(cls):
3021 # TODO: dan: rename column to source_repo_id
3022 return Column(
3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3024 nullable=False)
3025
3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3027
3028 @declared_attr
3029 def target_repo_id(cls):
3030 # TODO: dan: rename column to target_repo_id
3031 return Column(
3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3033 nullable=False)
3034
3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3036
3037 # TODO: dan: rename column to last_merge_source_rev
3038 _last_merge_source_rev = Column(
3039 'last_merge_org_rev', String(40), nullable=True)
3040 # TODO: dan: rename column to last_merge_target_rev
3041 _last_merge_target_rev = Column(
3042 'last_merge_other_rev', String(40), nullable=True)
3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3045
3046 @hybrid_property
3047 def revisions(self):
3048 return self._revisions.split(':') if self._revisions else []
3049
3050 @revisions.setter
3051 def revisions(self, val):
3052 self._revisions = ':'.join(val)
3053
3054 @declared_attr
3055 def author(cls):
3056 return relationship('User', lazy='joined')
3057
3058 @declared_attr
3059 def source_repo(cls):
3060 return relationship(
3061 'Repository',
3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3063
3064 @property
3065 def source_ref_parts(self):
3066 refs = self.source_ref.split(':')
3067 return Reference(refs[0], refs[1], refs[2])
3068
3069 @declared_attr
3070 def target_repo(cls):
3071 return relationship(
3072 'Repository',
3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3074
3075 @property
3076 def target_ref_parts(self):
3077 refs = self.target_ref.split(':')
3078 return Reference(refs[0], refs[1], refs[2])
3079
3080
3081 class PullRequest(Base, _PullRequestBase):
3082 __tablename__ = 'pull_requests'
3083 __table_args__ = (
3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3086 )
3087
3088 pull_request_id = Column(
3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3090
3091 def __repr__(self):
3092 if self.pull_request_id:
3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3094 else:
3095 return '<DB:PullRequest at %#x>' % id(self)
3096
3097 reviewers = relationship('PullRequestReviewers',
3098 cascade="all, delete, delete-orphan")
3099 statuses = relationship('ChangesetStatus')
3100 comments = relationship('ChangesetComment',
3101 cascade="all, delete, delete-orphan")
3102 versions = relationship('PullRequestVersion',
3103 cascade="all, delete, delete-orphan")
3104
3105 def is_closed(self):
3106 return self.status == self.STATUS_CLOSED
3107
3108 def get_api_data(self):
3109 from rhodecode.model.pull_request import PullRequestModel
3110 pull_request = self
3111 merge_status = PullRequestModel().merge_status(pull_request)
3112 data = {
3113 'pull_request_id': pull_request.pull_request_id,
3114 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3115 pull_request_id=self.pull_request_id,
3116 qualified=True),
3117 'title': pull_request.title,
3118 'description': pull_request.description,
3119 'status': pull_request.status,
3120 'created_on': pull_request.created_on,
3121 'updated_on': pull_request.updated_on,
3122 'commit_ids': pull_request.revisions,
3123 'review_status': pull_request.calculated_review_status(),
3124 'mergeable': {
3125 'status': merge_status[0],
3126 'message': unicode(merge_status[1]),
3127 },
3128 'source': {
3129 'clone_url': pull_request.source_repo.clone_url(),
3130 'repository': pull_request.source_repo.repo_name,
3131 'reference': {
3132 'name': pull_request.source_ref_parts.name,
3133 'type': pull_request.source_ref_parts.type,
3134 'commit_id': pull_request.source_ref_parts.commit_id,
3135 },
3136 },
3137 'target': {
3138 'clone_url': pull_request.target_repo.clone_url(),
3139 'repository': pull_request.target_repo.repo_name,
3140 'reference': {
3141 'name': pull_request.target_ref_parts.name,
3142 'type': pull_request.target_ref_parts.type,
3143 'commit_id': pull_request.target_ref_parts.commit_id,
3144 },
3145 },
3146 'author': pull_request.author.get_api_data(include_secrets=False,
3147 details='basic'),
3148 'reviewers': [
3149 {
3150 'user': reviewer.get_api_data(include_secrets=False,
3151 details='basic'),
3152 'review_status': st[0][1].status if st else 'not_reviewed',
3153 }
3154 for reviewer, st in pull_request.reviewers_statuses()
3155 ]
3156 }
3157
3158 return data
3159
3160 def __json__(self):
3161 return {
3162 'revisions': self.revisions,
3163 }
3164
3165 def calculated_review_status(self):
3166 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3167 # because it's tricky on how to use ChangesetStatusModel from there
3168 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3169 from rhodecode.model.changeset_status import ChangesetStatusModel
3170 return ChangesetStatusModel().calculated_review_status(self)
3171
3172 def reviewers_statuses(self):
3173 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3174 from rhodecode.model.changeset_status import ChangesetStatusModel
3175 return ChangesetStatusModel().reviewers_statuses(self)
3176
3177
3178 class PullRequestVersion(Base, _PullRequestBase):
3179 __tablename__ = 'pull_request_versions'
3180 __table_args__ = (
3181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3183 )
3184
3185 pull_request_version_id = Column(
3186 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3187 pull_request_id = Column(
3188 'pull_request_id', Integer(),
3189 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3190 pull_request = relationship('PullRequest')
3191
3192 def __repr__(self):
3193 if self.pull_request_version_id:
3194 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3195 else:
3196 return '<DB:PullRequestVersion at %#x>' % id(self)
3197
3198
3199 class PullRequestReviewers(Base, BaseModel):
3200 __tablename__ = 'pull_request_reviewers'
3201 __table_args__ = (
3202 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3203 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3204 )
3205
3206 def __init__(self, user=None, pull_request=None):
3207 self.user = user
3208 self.pull_request = pull_request
3209
3210 pull_requests_reviewers_id = Column(
3211 'pull_requests_reviewers_id', Integer(), nullable=False,
3212 primary_key=True)
3213 pull_request_id = Column(
3214 "pull_request_id", Integer(),
3215 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3216 user_id = Column(
3217 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3218
3219 user = relationship('User')
3220 pull_request = relationship('PullRequest')
3221
3222
3223 class Notification(Base, BaseModel):
3224 __tablename__ = 'notifications'
3225 __table_args__ = (
3226 Index('notification_type_idx', 'type'),
3227 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3228 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3229 )
3230
3231 TYPE_CHANGESET_COMMENT = u'cs_comment'
3232 TYPE_MESSAGE = u'message'
3233 TYPE_MENTION = u'mention'
3234 TYPE_REGISTRATION = u'registration'
3235 TYPE_PULL_REQUEST = u'pull_request'
3236 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3237
3238 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3239 subject = Column('subject', Unicode(512), nullable=True)
3240 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3241 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3242 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3243 type_ = Column('type', Unicode(255))
3244
3245 created_by_user = relationship('User')
3246 notifications_to_users = relationship('UserNotification', lazy='joined',
3247 cascade="all, delete, delete-orphan")
3248
3249 @property
3250 def recipients(self):
3251 return [x.user for x in UserNotification.query()\
3252 .filter(UserNotification.notification == self)\
3253 .order_by(UserNotification.user_id.asc()).all()]
3254
3255 @classmethod
3256 def create(cls, created_by, subject, body, recipients, type_=None):
3257 if type_ is None:
3258 type_ = Notification.TYPE_MESSAGE
3259
3260 notification = cls()
3261 notification.created_by_user = created_by
3262 notification.subject = subject
3263 notification.body = body
3264 notification.type_ = type_
3265 notification.created_on = datetime.datetime.now()
3266
3267 for u in recipients:
3268 assoc = UserNotification()
3269 assoc.notification = notification
3270
3271 # if created_by is inside recipients mark his notification
3272 # as read
3273 if u.user_id == created_by.user_id:
3274 assoc.read = True
3275
3276 u.notifications.append(assoc)
3277 Session().add(notification)
3278
3279 return notification
3280
3281 @property
3282 def description(self):
3283 from rhodecode.model.notification import NotificationModel
3284 return NotificationModel().make_description(self)
3285
3286
3287 class UserNotification(Base, BaseModel):
3288 __tablename__ = 'user_to_notification'
3289 __table_args__ = (
3290 UniqueConstraint('user_id', 'notification_id'),
3291 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3292 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3293 )
3294 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3295 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3296 read = Column('read', Boolean, default=False)
3297 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3298
3299 user = relationship('User', lazy="joined")
3300 notification = relationship('Notification', lazy="joined",
3301 order_by=lambda: Notification.created_on.desc(),)
3302
3303 def mark_as_read(self):
3304 self.read = True
3305 Session().add(self)
3306
3307
3308 class Gist(Base, BaseModel):
3309 __tablename__ = 'gists'
3310 __table_args__ = (
3311 Index('g_gist_access_id_idx', 'gist_access_id'),
3312 Index('g_created_on_idx', 'created_on'),
3313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3314 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3315 )
3316 GIST_PUBLIC = u'public'
3317 GIST_PRIVATE = u'private'
3318 DEFAULT_FILENAME = u'gistfile1.txt'
3319
3320 ACL_LEVEL_PUBLIC = u'acl_public'
3321 ACL_LEVEL_PRIVATE = u'acl_private'
3322
3323 gist_id = Column('gist_id', Integer(), primary_key=True)
3324 gist_access_id = Column('gist_access_id', Unicode(250))
3325 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3326 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3327 gist_expires = Column('gist_expires', Float(53), nullable=False)
3328 gist_type = Column('gist_type', Unicode(128), nullable=False)
3329 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3330 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3331 acl_level = Column('acl_level', Unicode(128), nullable=True)
3332
3333 owner = relationship('User')
3334
3335 def __repr__(self):
3336 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3337
3338 @classmethod
3339 def get_or_404(cls, id_):
3340 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3341 if not res:
3342 raise HTTPNotFound
3343 return res
3344
3345 @classmethod
3346 def get_by_access_id(cls, gist_access_id):
3347 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3348
3349 def gist_url(self):
3350 import rhodecode
3351 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3352 if alias_url:
3353 return alias_url.replace('{gistid}', self.gist_access_id)
3354
3355 return url('gist', gist_id=self.gist_access_id, qualified=True)
3356
3357 @classmethod
3358 def base_path(cls):
3359 """
3360 Returns base path when all gists are stored
3361
3362 :param cls:
3363 """
3364 from rhodecode.model.gist import GIST_STORE_LOC
3365 q = Session().query(RhodeCodeUi)\
3366 .filter(RhodeCodeUi.ui_key == URL_SEP)
3367 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3368 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3369
3370 def get_api_data(self):
3371 """
3372 Common function for generating gist related data for API
3373 """
3374 gist = self
3375 data = {
3376 'gist_id': gist.gist_id,
3377 'type': gist.gist_type,
3378 'access_id': gist.gist_access_id,
3379 'description': gist.gist_description,
3380 'url': gist.gist_url(),
3381 'expires': gist.gist_expires,
3382 'created_on': gist.created_on,
3383 'modified_at': gist.modified_at,
3384 'content': None,
3385 'acl_level': gist.acl_level,
3386 }
3387 return data
3388
3389 def __json__(self):
3390 data = dict(
3391 )
3392 data.update(self.get_api_data())
3393 return data
3394 # SCM functions
3395
3396 def scm_instance(self, **kwargs):
3397 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3398 return get_vcs_instance(
3399 repo_path=safe_str(full_repo_path), create=False)
3400
3401
3402 class DbMigrateVersion(Base, BaseModel):
3403 __tablename__ = 'db_migrate_version'
3404 __table_args__ = (
3405 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3406 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3407 )
3408 repository_id = Column('repository_id', String(250), primary_key=True)
3409 repository_path = Column('repository_path', Text)
3410 version = Column('version', Integer)
3411
3412
3413 class ExternalIdentity(Base, BaseModel):
3414 __tablename__ = 'external_identities'
3415 __table_args__ = (
3416 Index('local_user_id_idx', 'local_user_id'),
3417 Index('external_id_idx', 'external_id'),
3418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3419 'mysql_charset': 'utf8'})
3420
3421 external_id = Column('external_id', Unicode(255), default=u'',
3422 primary_key=True)
3423 external_username = Column('external_username', Unicode(1024), default=u'')
3424 local_user_id = Column('local_user_id', Integer(),
3425 ForeignKey('users.user_id'), primary_key=True)
3426 provider_name = Column('provider_name', Unicode(255), default=u'',
3427 primary_key=True)
3428 access_token = Column('access_token', String(1024), default=u'')
3429 alt_token = Column('alt_token', String(1024), default=u'')
3430 token_secret = Column('token_secret', String(1024), default=u'')
3431
3432 @classmethod
3433 def by_external_id_and_provider(cls, external_id, provider_name,
3434 local_user_id=None):
3435 """
3436 Returns ExternalIdentity instance based on search params
3437
3438 :param external_id:
3439 :param provider_name:
3440 :return: ExternalIdentity
3441 """
3442 query = cls.query()
3443 query = query.filter(cls.external_id == external_id)
3444 query = query.filter(cls.provider_name == provider_name)
3445 if local_user_id:
3446 query = query.filter(cls.local_user_id == local_user_id)
3447 return query.first()
3448
3449 @classmethod
3450 def user_by_external_id_and_provider(cls, external_id, provider_name):
3451 """
3452 Returns User instance based on search params
3453
3454 :param external_id:
3455 :param provider_name:
3456 :return: User
3457 """
3458 query = User.query()
3459 query = query.filter(cls.external_id == external_id)
3460 query = query.filter(cls.provider_name == provider_name)
3461 query = query.filter(User.user_id == cls.local_user_id)
3462 return query.first()
3463
3464 @classmethod
3465 def by_local_user_id(cls, local_user_id):
3466 """
3467 Returns all tokens for user
3468
3469 :param local_user_id:
3470 :return: ExternalIdentity
3471 """
3472 query = cls.query()
3473 query = query.filter(cls.local_user_id == local_user_id)
3474 return query
3475
3476
3477 class Integration(Base, BaseModel):
3478 __tablename__ = 'integrations'
3479 __table_args__ = (
3480 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3481 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3482 )
3483
3484 integration_id = Column('integration_id', Integer(), primary_key=True)
3485 integration_type = Column('integration_type', String(255))
3486 enabled = Column('enabled', Boolean(), nullable=False)
3487 name = Column('name', String(255), nullable=False)
3488 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3489 default=False)
3490
3491 settings = Column(
3492 'settings_json', MutationObj.as_mutable(
3493 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3494 repo_id = Column(
3495 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3496 nullable=True, unique=None, default=None)
3497 repo = relationship('Repository', lazy='joined')
3498
3499 repo_group_id = Column(
3500 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3501 nullable=True, unique=None, default=None)
3502 repo_group = relationship('RepoGroup', lazy='joined')
3503
3504 @property
3505 def scope(self):
3506 if self.repo:
3507 return repr(self.repo)
3508 if self.repo_group:
3509 if self.child_repos_only:
3510 return repr(self.repo_group) + ' (child repos only)'
3511 else:
3512 return repr(self.repo_group) + ' (recursive)'
3513 if self.child_repos_only:
3514 return 'root_repos'
3515 return 'global'
3516
3517 def __repr__(self):
3518 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3519
3520
3521 class RepoReviewRuleUser(Base, BaseModel):
3522 __tablename__ = 'repo_review_rules_users'
3523 __table_args__ = (
3524 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3525 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3526 )
3527 repo_review_rule_user_id = Column(
3528 'repo_review_rule_user_id', Integer(), primary_key=True)
3529 repo_review_rule_id = Column("repo_review_rule_id",
3530 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3532 nullable=False)
3533 user = relationship('User')
3534
3535
3536 class RepoReviewRuleUserGroup(Base, BaseModel):
3537 __tablename__ = 'repo_review_rules_users_groups'
3538 __table_args__ = (
3539 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3540 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3541 )
3542 repo_review_rule_users_group_id = Column(
3543 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3544 repo_review_rule_id = Column("repo_review_rule_id",
3545 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3546 users_group_id = Column("users_group_id", Integer(),
3547 ForeignKey('users_groups.users_group_id'), nullable=False)
3548 users_group = relationship('UserGroup')
3549
3550
3551 class RepoReviewRule(Base, BaseModel):
3552 __tablename__ = 'repo_review_rules'
3553 __table_args__ = (
3554 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3555 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3556 )
3557
3558 repo_review_rule_id = Column(
3559 'repo_review_rule_id', Integer(), primary_key=True)
3560 repo_id = Column(
3561 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3562 repo = relationship('Repository', backref='review_rules')
3563
3564 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3565 default=u'*') # glob
3566 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3567 default=u'*') # glob
3568
3569 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3570 nullable=False, default=False)
3571 rule_users = relationship('RepoReviewRuleUser')
3572 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3573
3574 @hybrid_property
3575 def branch_pattern(self):
3576 return self._branch_pattern or '*'
3577
3578 def _validate_pattern(self, value):
3579 re.compile('^' + glob2re(value) + '$')
3580
3581 @branch_pattern.setter
3582 def branch_pattern(self, value):
3583 self._validate_glob(value)
3584 self._branch_pattern = value or '*'
3585
3586 @hybrid_property
3587 def file_pattern(self):
3588 return self._file_pattern or '*'
3589
3590 @file_pattern.setter
3591 def file_pattern(self, value):
3592 self._validate_glob(value)
3593 self._file_pattern = value or '*'
3594
3595 def matches(self, branch, files_changed):
3596 """
3597 Check if this review rule matches a branch/files in a pull request
3598
3599 :param branch: branch name for the commit
3600 :param files_changed: list of file paths changed in the pull request
3601 """
3602
3603 branch = branch or ''
3604 files_changed = files_changed or []
3605
3606 branch_matches = True
3607 if branch:
3608 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3609 branch_matches = bool(branch_regex.search(branch))
3610
3611 files_matches = True
3612 if self.file_pattern != '*':
3613 files_matches = False
3614 file_regex = re.compile(glob2re(self.file_pattern))
3615 for filename in files_changed:
3616 if file_regex.search(filename):
3617 files_matches = True
3618 break
3619
3620 return branch_matches and files_matches
3621
3622 @property
3623 def review_users(self):
3624 """ Returns the users which this rule applies to """
3625
3626 users = set()
3627 users |= set([
3628 rule_user.user for rule_user in self.rule_users
3629 if rule_user.user.active])
3630 users |= set(
3631 member.user
3632 for rule_user_group in self.rule_user_groups
3633 for member in rule_user_group.users_group.members
3634 if member.user.active
3635 )
3636 return users
3637
3638 def __repr__(self):
3639 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3640 self.repo_review_rule_id, self.repo)
@@ -0,0 +1,35 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
7 from sqlalchemy.orm.session import Session
8 from sqlalchemy.ext.declarative import declarative_base
9
10 from rhodecode.lib.dbmigrate.migrate import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12 from rhodecode.lib.utils2 import str2bool
13
14 from rhodecode.model.meta import Base
15 from rhodecode.model import meta
16 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
17
18 log = logging.getLogger(__name__)
19
20
21 def upgrade(migrate_engine):
22 """
23 Upgrade operations go here.
24 Don't create your own engine; bind migrate_engine to your metadata
25 """
26 _reset_base(migrate_engine)
27 from rhodecode.lib.dbmigrate.schema import db_4_4_0_2
28
29 db_4_4_0_2.RepoReviewRule.__table__.create()
30 db_4_4_0_2.RepoReviewRuleUser.__table__.create()
31 db_4_4_0_2.RepoReviewRuleUserGroup.__table__.create()
32
33 def downgrade(migrate_engine):
34 meta = MetaData()
35 meta.bind = migrate_engine
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 58 # defines current db version for migrations
54 __dbversion__ = 59 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'http://rhodecode.com'
58 __url__ = 'http://rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,1160 +1,1167 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
36
36
37 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
40
40
41 # Default requirements for URL parts
41 # Default requirements for URL parts
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
46 # repo names can have a slash in them, but they must not end with a slash
46 # repo names can have a slash in them, but they must not end with a slash
47 'repo_name': r'.*?[^/]',
47 'repo_name': r'.*?[^/]',
48 # file path eats up everything at the end
48 # file path eats up everything at the end
49 'f_path': r'.*',
49 'f_path': r'.*',
50 # reference types
50 # reference types
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 }
53 }
54
54
55
55
56 def add_route_requirements(route_path, requirements):
56 def add_route_requirements(route_path, requirements):
57 """
57 """
58 Adds regex requirements to pyramid routes using a mapping dict
58 Adds regex requirements to pyramid routes using a mapping dict
59
59
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 '/{action}/{id:\d+}'
61 '/{action}/{id:\d+}'
62
62
63 """
63 """
64 for key, regex in requirements.items():
64 for key, regex in requirements.items():
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 return route_path
66 return route_path
67
67
68
68
69 class JSRoutesMapper(Mapper):
69 class JSRoutesMapper(Mapper):
70 """
70 """
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 """
72 """
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 def __init__(self, *args, **kw):
75 def __init__(self, *args, **kw):
76 super(JSRoutesMapper, self).__init__(*args, **kw)
76 super(JSRoutesMapper, self).__init__(*args, **kw)
77 self._jsroutes = []
77 self._jsroutes = []
78
78
79 def connect(self, *args, **kw):
79 def connect(self, *args, **kw):
80 """
80 """
81 Wrapper for connect to take an extra argument jsroute=True
81 Wrapper for connect to take an extra argument jsroute=True
82
82
83 :param jsroute: boolean, if True will add the route to the pyroutes list
83 :param jsroute: boolean, if True will add the route to the pyroutes list
84 """
84 """
85 if kw.pop('jsroute', False):
85 if kw.pop('jsroute', False):
86 if not self._named_route_regex.match(args[0]):
86 if not self._named_route_regex.match(args[0]):
87 raise Exception('only named routes can be added to pyroutes')
87 raise Exception('only named routes can be added to pyroutes')
88 self._jsroutes.append(args[0])
88 self._jsroutes.append(args[0])
89
89
90 super(JSRoutesMapper, self).connect(*args, **kw)
90 super(JSRoutesMapper, self).connect(*args, **kw)
91
91
92 def _extract_route_information(self, route):
92 def _extract_route_information(self, route):
93 """
93 """
94 Convert a route into tuple(name, path, args), eg:
94 Convert a route into tuple(name, path, args), eg:
95 ('user_profile', '/profile/%(username)s', ['username'])
95 ('user_profile', '/profile/%(username)s', ['username'])
96 """
96 """
97 routepath = route.routepath
97 routepath = route.routepath
98 def replace(matchobj):
98 def replace(matchobj):
99 if matchobj.group(1):
99 if matchobj.group(1):
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 else:
101 else:
102 return "%%(%s)s" % matchobj.group(2)
102 return "%%(%s)s" % matchobj.group(2)
103
103
104 routepath = self._argument_prog.sub(replace, routepath)
104 routepath = self._argument_prog.sub(replace, routepath)
105 return (
105 return (
106 route.name,
106 route.name,
107 routepath,
107 routepath,
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 for arg in self._argument_prog.findall(route.routepath)]
109 for arg in self._argument_prog.findall(route.routepath)]
110 )
110 )
111
111
112 def jsroutes(self):
112 def jsroutes(self):
113 """
113 """
114 Return a list of pyroutes.js compatible routes
114 Return a list of pyroutes.js compatible routes
115 """
115 """
116 for route_name in self._jsroutes:
116 for route_name in self._jsroutes:
117 yield self._extract_route_information(self._routenames[route_name])
117 yield self._extract_route_information(self._routenames[route_name])
118
118
119
119
120 def make_map(config):
120 def make_map(config):
121 """Create, configure and return the routes Mapper"""
121 """Create, configure and return the routes Mapper"""
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 always_scan=config['debug'])
123 always_scan=config['debug'])
124 rmap.minimization = False
124 rmap.minimization = False
125 rmap.explicit = False
125 rmap.explicit = False
126
126
127 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.lib.utils2 import str2bool
128 from rhodecode.model import repo, repo_group
128 from rhodecode.model import repo, repo_group
129
129
130 def check_repo(environ, match_dict):
130 def check_repo(environ, match_dict):
131 """
131 """
132 check for valid repository for proper 404 handling
132 check for valid repository for proper 404 handling
133
133
134 :param environ:
134 :param environ:
135 :param match_dict:
135 :param match_dict:
136 """
136 """
137 repo_name = match_dict.get('repo_name')
137 repo_name = match_dict.get('repo_name')
138
138
139 if match_dict.get('f_path'):
139 if match_dict.get('f_path'):
140 # fix for multiple initial slashes that causes errors
140 # fix for multiple initial slashes that causes errors
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 repo_model = repo.RepoModel()
142 repo_model = repo.RepoModel()
143 by_name_match = repo_model.get_by_repo_name(repo_name)
143 by_name_match = repo_model.get_by_repo_name(repo_name)
144 # if we match quickly from database, short circuit the operation,
144 # if we match quickly from database, short circuit the operation,
145 # and validate repo based on the type.
145 # and validate repo based on the type.
146 if by_name_match:
146 if by_name_match:
147 return True
147 return True
148
148
149 by_id_match = repo_model.get_repo_by_id(repo_name)
149 by_id_match = repo_model.get_repo_by_id(repo_name)
150 if by_id_match:
150 if by_id_match:
151 repo_name = by_id_match.repo_name
151 repo_name = by_id_match.repo_name
152 match_dict['repo_name'] = repo_name
152 match_dict['repo_name'] = repo_name
153 return True
153 return True
154
154
155 return False
155 return False
156
156
157 def check_group(environ, match_dict):
157 def check_group(environ, match_dict):
158 """
158 """
159 check for valid repository group path for proper 404 handling
159 check for valid repository group path for proper 404 handling
160
160
161 :param environ:
161 :param environ:
162 :param match_dict:
162 :param match_dict:
163 """
163 """
164 repo_group_name = match_dict.get('group_name')
164 repo_group_name = match_dict.get('group_name')
165 repo_group_model = repo_group.RepoGroupModel()
165 repo_group_model = repo_group.RepoGroupModel()
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 if by_name_match:
167 if by_name_match:
168 return True
168 return True
169
169
170 return False
170 return False
171
171
172 def check_user_group(environ, match_dict):
172 def check_user_group(environ, match_dict):
173 """
173 """
174 check for valid user group for proper 404 handling
174 check for valid user group for proper 404 handling
175
175
176 :param environ:
176 :param environ:
177 :param match_dict:
177 :param match_dict:
178 """
178 """
179 return True
179 return True
180
180
181 def check_int(environ, match_dict):
181 def check_int(environ, match_dict):
182 return match_dict.get('id').isdigit()
182 return match_dict.get('id').isdigit()
183
183
184
184
185 #==========================================================================
185 #==========================================================================
186 # CUSTOM ROUTES HERE
186 # CUSTOM ROUTES HERE
187 #==========================================================================
187 #==========================================================================
188
188
189 # MAIN PAGE
189 # MAIN PAGE
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 action='goto_switcher_data')
192 action='goto_switcher_data')
193 rmap.connect('repo_list_data', '/_repos', controller='home',
193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 action='repo_list_data')
194 action='repo_list_data')
195
195
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 action='user_autocomplete_data', jsroute=True)
197 action='user_autocomplete_data', jsroute=True)
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 action='user_group_autocomplete_data')
199 action='user_group_autocomplete_data', jsroute=True)
200
200
201 rmap.connect(
201 rmap.connect(
202 'user_profile', '/_profiles/{username}', controller='users',
202 'user_profile', '/_profiles/{username}', controller='users',
203 action='user_profile')
203 action='user_profile')
204
204
205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
206 rmap.connect('rst_help',
206 rmap.connect('rst_help',
207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
208 _static=True)
208 _static=True)
209 rmap.connect('markdown_help',
209 rmap.connect('markdown_help',
210 'http://daringfireball.net/projects/markdown/syntax',
210 'http://daringfireball.net/projects/markdown/syntax',
211 _static=True)
211 _static=True)
212 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
212 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
213 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
213 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
214 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
214 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
215 # TODO: anderson - making this a static link since redirect won't play
215 # TODO: anderson - making this a static link since redirect won't play
216 # nice with POST requests
216 # nice with POST requests
217 rmap.connect('enterprise_license_convert_from_old',
217 rmap.connect('enterprise_license_convert_from_old',
218 'https://rhodecode.com/u/license-upgrade',
218 'https://rhodecode.com/u/license-upgrade',
219 _static=True)
219 _static=True)
220
220
221 routing_links.connect_redirection_links(rmap)
221 routing_links.connect_redirection_links(rmap)
222
222
223 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
223 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
224 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
224 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
225
225
226 # ADMIN REPOSITORY ROUTES
226 # ADMIN REPOSITORY ROUTES
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
228 controller='admin/repos') as m:
228 controller='admin/repos') as m:
229 m.connect('repos', '/repos',
229 m.connect('repos', '/repos',
230 action='create', conditions={'method': ['POST']})
230 action='create', conditions={'method': ['POST']})
231 m.connect('repos', '/repos',
231 m.connect('repos', '/repos',
232 action='index', conditions={'method': ['GET']})
232 action='index', conditions={'method': ['GET']})
233 m.connect('new_repo', '/create_repository', jsroute=True,
233 m.connect('new_repo', '/create_repository', jsroute=True,
234 action='create_repository', conditions={'method': ['GET']})
234 action='create_repository', conditions={'method': ['GET']})
235 m.connect('/repos/{repo_name}',
235 m.connect('/repos/{repo_name}',
236 action='update', conditions={'method': ['PUT'],
236 action='update', conditions={'method': ['PUT'],
237 'function': check_repo},
237 'function': check_repo},
238 requirements=URL_NAME_REQUIREMENTS)
238 requirements=URL_NAME_REQUIREMENTS)
239 m.connect('delete_repo', '/repos/{repo_name}',
239 m.connect('delete_repo', '/repos/{repo_name}',
240 action='delete', conditions={'method': ['DELETE']},
240 action='delete', conditions={'method': ['DELETE']},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242 m.connect('repo', '/repos/{repo_name}',
242 m.connect('repo', '/repos/{repo_name}',
243 action='show', conditions={'method': ['GET'],
243 action='show', conditions={'method': ['GET'],
244 'function': check_repo},
244 'function': check_repo},
245 requirements=URL_NAME_REQUIREMENTS)
245 requirements=URL_NAME_REQUIREMENTS)
246
246
247 # ADMIN REPOSITORY GROUPS ROUTES
247 # ADMIN REPOSITORY GROUPS ROUTES
248 with rmap.submapper(path_prefix=ADMIN_PREFIX,
248 with rmap.submapper(path_prefix=ADMIN_PREFIX,
249 controller='admin/repo_groups') as m:
249 controller='admin/repo_groups') as m:
250 m.connect('repo_groups', '/repo_groups',
250 m.connect('repo_groups', '/repo_groups',
251 action='create', conditions={'method': ['POST']})
251 action='create', conditions={'method': ['POST']})
252 m.connect('repo_groups', '/repo_groups',
252 m.connect('repo_groups', '/repo_groups',
253 action='index', conditions={'method': ['GET']})
253 action='index', conditions={'method': ['GET']})
254 m.connect('new_repo_group', '/repo_groups/new',
254 m.connect('new_repo_group', '/repo_groups/new',
255 action='new', conditions={'method': ['GET']})
255 action='new', conditions={'method': ['GET']})
256 m.connect('update_repo_group', '/repo_groups/{group_name}',
256 m.connect('update_repo_group', '/repo_groups/{group_name}',
257 action='update', conditions={'method': ['PUT'],
257 action='update', conditions={'method': ['PUT'],
258 'function': check_group},
258 'function': check_group},
259 requirements=URL_NAME_REQUIREMENTS)
259 requirements=URL_NAME_REQUIREMENTS)
260
260
261 # EXTRAS REPO GROUP ROUTES
261 # EXTRAS REPO GROUP ROUTES
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
263 action='edit',
263 action='edit',
264 conditions={'method': ['GET'], 'function': check_group},
264 conditions={'method': ['GET'], 'function': check_group},
265 requirements=URL_NAME_REQUIREMENTS)
265 requirements=URL_NAME_REQUIREMENTS)
266 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
266 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
267 action='edit',
267 action='edit',
268 conditions={'method': ['PUT'], 'function': check_group},
268 conditions={'method': ['PUT'], 'function': check_group},
269 requirements=URL_NAME_REQUIREMENTS)
269 requirements=URL_NAME_REQUIREMENTS)
270
270
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
272 action='edit_repo_group_advanced',
272 action='edit_repo_group_advanced',
273 conditions={'method': ['GET'], 'function': check_group},
273 conditions={'method': ['GET'], 'function': check_group},
274 requirements=URL_NAME_REQUIREMENTS)
274 requirements=URL_NAME_REQUIREMENTS)
275 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
275 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
276 action='edit_repo_group_advanced',
276 action='edit_repo_group_advanced',
277 conditions={'method': ['PUT'], 'function': check_group},
277 conditions={'method': ['PUT'], 'function': check_group},
278 requirements=URL_NAME_REQUIREMENTS)
278 requirements=URL_NAME_REQUIREMENTS)
279
279
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
281 action='edit_repo_group_perms',
281 action='edit_repo_group_perms',
282 conditions={'method': ['GET'], 'function': check_group},
282 conditions={'method': ['GET'], 'function': check_group},
283 requirements=URL_NAME_REQUIREMENTS)
283 requirements=URL_NAME_REQUIREMENTS)
284 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
284 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
285 action='update_perms',
285 action='update_perms',
286 conditions={'method': ['PUT'], 'function': check_group},
286 conditions={'method': ['PUT'], 'function': check_group},
287 requirements=URL_NAME_REQUIREMENTS)
287 requirements=URL_NAME_REQUIREMENTS)
288
288
289 m.connect('delete_repo_group', '/repo_groups/{group_name}',
289 m.connect('delete_repo_group', '/repo_groups/{group_name}',
290 action='delete', conditions={'method': ['DELETE'],
290 action='delete', conditions={'method': ['DELETE'],
291 'function': check_group},
291 'function': check_group},
292 requirements=URL_NAME_REQUIREMENTS)
292 requirements=URL_NAME_REQUIREMENTS)
293
293
294 # ADMIN USER ROUTES
294 # ADMIN USER ROUTES
295 with rmap.submapper(path_prefix=ADMIN_PREFIX,
295 with rmap.submapper(path_prefix=ADMIN_PREFIX,
296 controller='admin/users') as m:
296 controller='admin/users') as m:
297 m.connect('users', '/users',
297 m.connect('users', '/users',
298 action='create', conditions={'method': ['POST']})
298 action='create', conditions={'method': ['POST']})
299 m.connect('users', '/users',
299 m.connect('users', '/users',
300 action='index', conditions={'method': ['GET']})
300 action='index', conditions={'method': ['GET']})
301 m.connect('new_user', '/users/new',
301 m.connect('new_user', '/users/new',
302 action='new', conditions={'method': ['GET']})
302 action='new', conditions={'method': ['GET']})
303 m.connect('update_user', '/users/{user_id}',
303 m.connect('update_user', '/users/{user_id}',
304 action='update', conditions={'method': ['PUT']})
304 action='update', conditions={'method': ['PUT']})
305 m.connect('delete_user', '/users/{user_id}',
305 m.connect('delete_user', '/users/{user_id}',
306 action='delete', conditions={'method': ['DELETE']})
306 action='delete', conditions={'method': ['DELETE']})
307 m.connect('edit_user', '/users/{user_id}/edit',
307 m.connect('edit_user', '/users/{user_id}/edit',
308 action='edit', conditions={'method': ['GET']})
308 action='edit', conditions={'method': ['GET']})
309 m.connect('user', '/users/{user_id}',
309 m.connect('user', '/users/{user_id}',
310 action='show', conditions={'method': ['GET']})
310 action='show', conditions={'method': ['GET']})
311 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
311 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
312 action='reset_password', conditions={'method': ['POST']})
312 action='reset_password', conditions={'method': ['POST']})
313 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
313 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
314 action='create_personal_repo_group', conditions={'method': ['POST']})
314 action='create_personal_repo_group', conditions={'method': ['POST']})
315
315
316 # EXTRAS USER ROUTES
316 # EXTRAS USER ROUTES
317 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
317 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
318 action='edit_advanced', conditions={'method': ['GET']})
318 action='edit_advanced', conditions={'method': ['GET']})
319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
320 action='update_advanced', conditions={'method': ['PUT']})
320 action='update_advanced', conditions={'method': ['PUT']})
321
321
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 action='edit_auth_tokens', conditions={'method': ['GET']})
323 action='edit_auth_tokens', conditions={'method': ['GET']})
324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
325 action='add_auth_token', conditions={'method': ['PUT']})
325 action='add_auth_token', conditions={'method': ['PUT']})
326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
327 action='delete_auth_token', conditions={'method': ['DELETE']})
327 action='delete_auth_token', conditions={'method': ['DELETE']})
328
328
329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
330 action='edit_global_perms', conditions={'method': ['GET']})
330 action='edit_global_perms', conditions={'method': ['GET']})
331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
332 action='update_global_perms', conditions={'method': ['PUT']})
332 action='update_global_perms', conditions={'method': ['PUT']})
333
333
334 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
334 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
335 action='edit_perms_summary', conditions={'method': ['GET']})
335 action='edit_perms_summary', conditions={'method': ['GET']})
336
336
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
338 action='edit_emails', conditions={'method': ['GET']})
338 action='edit_emails', conditions={'method': ['GET']})
339 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
339 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
340 action='add_email', conditions={'method': ['PUT']})
340 action='add_email', conditions={'method': ['PUT']})
341 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
341 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
342 action='delete_email', conditions={'method': ['DELETE']})
342 action='delete_email', conditions={'method': ['DELETE']})
343
343
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
345 action='edit_ips', conditions={'method': ['GET']})
345 action='edit_ips', conditions={'method': ['GET']})
346 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
346 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
347 action='add_ip', conditions={'method': ['PUT']})
347 action='add_ip', conditions={'method': ['PUT']})
348 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
348 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
349 action='delete_ip', conditions={'method': ['DELETE']})
349 action='delete_ip', conditions={'method': ['DELETE']})
350
350
351 # ADMIN USER GROUPS REST ROUTES
351 # ADMIN USER GROUPS REST ROUTES
352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
353 controller='admin/user_groups') as m:
353 controller='admin/user_groups') as m:
354 m.connect('users_groups', '/user_groups',
354 m.connect('users_groups', '/user_groups',
355 action='create', conditions={'method': ['POST']})
355 action='create', conditions={'method': ['POST']})
356 m.connect('users_groups', '/user_groups',
356 m.connect('users_groups', '/user_groups',
357 action='index', conditions={'method': ['GET']})
357 action='index', conditions={'method': ['GET']})
358 m.connect('new_users_group', '/user_groups/new',
358 m.connect('new_users_group', '/user_groups/new',
359 action='new', conditions={'method': ['GET']})
359 action='new', conditions={'method': ['GET']})
360 m.connect('update_users_group', '/user_groups/{user_group_id}',
360 m.connect('update_users_group', '/user_groups/{user_group_id}',
361 action='update', conditions={'method': ['PUT']})
361 action='update', conditions={'method': ['PUT']})
362 m.connect('delete_users_group', '/user_groups/{user_group_id}',
362 m.connect('delete_users_group', '/user_groups/{user_group_id}',
363 action='delete', conditions={'method': ['DELETE']})
363 action='delete', conditions={'method': ['DELETE']})
364 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
364 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
365 action='edit', conditions={'method': ['GET']},
365 action='edit', conditions={'method': ['GET']},
366 function=check_user_group)
366 function=check_user_group)
367
367
368 # EXTRAS USER GROUP ROUTES
368 # EXTRAS USER GROUP ROUTES
369 m.connect('edit_user_group_global_perms',
369 m.connect('edit_user_group_global_perms',
370 '/user_groups/{user_group_id}/edit/global_permissions',
370 '/user_groups/{user_group_id}/edit/global_permissions',
371 action='edit_global_perms', conditions={'method': ['GET']})
371 action='edit_global_perms', conditions={'method': ['GET']})
372 m.connect('edit_user_group_global_perms',
372 m.connect('edit_user_group_global_perms',
373 '/user_groups/{user_group_id}/edit/global_permissions',
373 '/user_groups/{user_group_id}/edit/global_permissions',
374 action='update_global_perms', conditions={'method': ['PUT']})
374 action='update_global_perms', conditions={'method': ['PUT']})
375 m.connect('edit_user_group_perms_summary',
375 m.connect('edit_user_group_perms_summary',
376 '/user_groups/{user_group_id}/edit/permissions_summary',
376 '/user_groups/{user_group_id}/edit/permissions_summary',
377 action='edit_perms_summary', conditions={'method': ['GET']})
377 action='edit_perms_summary', conditions={'method': ['GET']})
378
378
379 m.connect('edit_user_group_perms',
379 m.connect('edit_user_group_perms',
380 '/user_groups/{user_group_id}/edit/permissions',
380 '/user_groups/{user_group_id}/edit/permissions',
381 action='edit_perms', conditions={'method': ['GET']})
381 action='edit_perms', conditions={'method': ['GET']})
382 m.connect('edit_user_group_perms',
382 m.connect('edit_user_group_perms',
383 '/user_groups/{user_group_id}/edit/permissions',
383 '/user_groups/{user_group_id}/edit/permissions',
384 action='update_perms', conditions={'method': ['PUT']})
384 action='update_perms', conditions={'method': ['PUT']})
385
385
386 m.connect('edit_user_group_advanced',
386 m.connect('edit_user_group_advanced',
387 '/user_groups/{user_group_id}/edit/advanced',
387 '/user_groups/{user_group_id}/edit/advanced',
388 action='edit_advanced', conditions={'method': ['GET']})
388 action='edit_advanced', conditions={'method': ['GET']})
389
389
390 m.connect('edit_user_group_members',
390 m.connect('edit_user_group_members',
391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
392 action='edit_members', conditions={'method': ['GET']})
392 action='edit_members', conditions={'method': ['GET']})
393
393
394 # ADMIN PERMISSIONS ROUTES
394 # ADMIN PERMISSIONS ROUTES
395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
396 controller='admin/permissions') as m:
396 controller='admin/permissions') as m:
397 m.connect('admin_permissions_application', '/permissions/application',
397 m.connect('admin_permissions_application', '/permissions/application',
398 action='permission_application_update', conditions={'method': ['POST']})
398 action='permission_application_update', conditions={'method': ['POST']})
399 m.connect('admin_permissions_application', '/permissions/application',
399 m.connect('admin_permissions_application', '/permissions/application',
400 action='permission_application', conditions={'method': ['GET']})
400 action='permission_application', conditions={'method': ['GET']})
401
401
402 m.connect('admin_permissions_global', '/permissions/global',
402 m.connect('admin_permissions_global', '/permissions/global',
403 action='permission_global_update', conditions={'method': ['POST']})
403 action='permission_global_update', conditions={'method': ['POST']})
404 m.connect('admin_permissions_global', '/permissions/global',
404 m.connect('admin_permissions_global', '/permissions/global',
405 action='permission_global', conditions={'method': ['GET']})
405 action='permission_global', conditions={'method': ['GET']})
406
406
407 m.connect('admin_permissions_object', '/permissions/object',
407 m.connect('admin_permissions_object', '/permissions/object',
408 action='permission_objects_update', conditions={'method': ['POST']})
408 action='permission_objects_update', conditions={'method': ['POST']})
409 m.connect('admin_permissions_object', '/permissions/object',
409 m.connect('admin_permissions_object', '/permissions/object',
410 action='permission_objects', conditions={'method': ['GET']})
410 action='permission_objects', conditions={'method': ['GET']})
411
411
412 m.connect('admin_permissions_ips', '/permissions/ips',
412 m.connect('admin_permissions_ips', '/permissions/ips',
413 action='permission_ips', conditions={'method': ['POST']})
413 action='permission_ips', conditions={'method': ['POST']})
414 m.connect('admin_permissions_ips', '/permissions/ips',
414 m.connect('admin_permissions_ips', '/permissions/ips',
415 action='permission_ips', conditions={'method': ['GET']})
415 action='permission_ips', conditions={'method': ['GET']})
416
416
417 m.connect('admin_permissions_overview', '/permissions/overview',
417 m.connect('admin_permissions_overview', '/permissions/overview',
418 action='permission_perms', conditions={'method': ['GET']})
418 action='permission_perms', conditions={'method': ['GET']})
419
419
420 # ADMIN DEFAULTS REST ROUTES
420 # ADMIN DEFAULTS REST ROUTES
421 with rmap.submapper(path_prefix=ADMIN_PREFIX,
421 with rmap.submapper(path_prefix=ADMIN_PREFIX,
422 controller='admin/defaults') as m:
422 controller='admin/defaults') as m:
423 m.connect('admin_defaults_repositories', '/defaults/repositories',
423 m.connect('admin_defaults_repositories', '/defaults/repositories',
424 action='update_repository_defaults', conditions={'method': ['POST']})
424 action='update_repository_defaults', conditions={'method': ['POST']})
425 m.connect('admin_defaults_repositories', '/defaults/repositories',
425 m.connect('admin_defaults_repositories', '/defaults/repositories',
426 action='index', conditions={'method': ['GET']})
426 action='index', conditions={'method': ['GET']})
427
427
428 # ADMIN DEBUG STYLE ROUTES
428 # ADMIN DEBUG STYLE ROUTES
429 if str2bool(config.get('debug_style')):
429 if str2bool(config.get('debug_style')):
430 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
430 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
431 controller='debug_style') as m:
431 controller='debug_style') as m:
432 m.connect('debug_style_home', '',
432 m.connect('debug_style_home', '',
433 action='index', conditions={'method': ['GET']})
433 action='index', conditions={'method': ['GET']})
434 m.connect('debug_style_template', '/t/{t_path}',
434 m.connect('debug_style_template', '/t/{t_path}',
435 action='template', conditions={'method': ['GET']})
435 action='template', conditions={'method': ['GET']})
436
436
437 # ADMIN SETTINGS ROUTES
437 # ADMIN SETTINGS ROUTES
438 with rmap.submapper(path_prefix=ADMIN_PREFIX,
438 with rmap.submapper(path_prefix=ADMIN_PREFIX,
439 controller='admin/settings') as m:
439 controller='admin/settings') as m:
440
440
441 # default
441 # default
442 m.connect('admin_settings', '/settings',
442 m.connect('admin_settings', '/settings',
443 action='settings_global_update',
443 action='settings_global_update',
444 conditions={'method': ['POST']})
444 conditions={'method': ['POST']})
445 m.connect('admin_settings', '/settings',
445 m.connect('admin_settings', '/settings',
446 action='settings_global', conditions={'method': ['GET']})
446 action='settings_global', conditions={'method': ['GET']})
447
447
448 m.connect('admin_settings_vcs', '/settings/vcs',
448 m.connect('admin_settings_vcs', '/settings/vcs',
449 action='settings_vcs_update',
449 action='settings_vcs_update',
450 conditions={'method': ['POST']})
450 conditions={'method': ['POST']})
451 m.connect('admin_settings_vcs', '/settings/vcs',
451 m.connect('admin_settings_vcs', '/settings/vcs',
452 action='settings_vcs',
452 action='settings_vcs',
453 conditions={'method': ['GET']})
453 conditions={'method': ['GET']})
454 m.connect('admin_settings_vcs', '/settings/vcs',
454 m.connect('admin_settings_vcs', '/settings/vcs',
455 action='delete_svn_pattern',
455 action='delete_svn_pattern',
456 conditions={'method': ['DELETE']})
456 conditions={'method': ['DELETE']})
457
457
458 m.connect('admin_settings_mapping', '/settings/mapping',
458 m.connect('admin_settings_mapping', '/settings/mapping',
459 action='settings_mapping_update',
459 action='settings_mapping_update',
460 conditions={'method': ['POST']})
460 conditions={'method': ['POST']})
461 m.connect('admin_settings_mapping', '/settings/mapping',
461 m.connect('admin_settings_mapping', '/settings/mapping',
462 action='settings_mapping', conditions={'method': ['GET']})
462 action='settings_mapping', conditions={'method': ['GET']})
463
463
464 m.connect('admin_settings_global', '/settings/global',
464 m.connect('admin_settings_global', '/settings/global',
465 action='settings_global_update',
465 action='settings_global_update',
466 conditions={'method': ['POST']})
466 conditions={'method': ['POST']})
467 m.connect('admin_settings_global', '/settings/global',
467 m.connect('admin_settings_global', '/settings/global',
468 action='settings_global', conditions={'method': ['GET']})
468 action='settings_global', conditions={'method': ['GET']})
469
469
470 m.connect('admin_settings_visual', '/settings/visual',
470 m.connect('admin_settings_visual', '/settings/visual',
471 action='settings_visual_update',
471 action='settings_visual_update',
472 conditions={'method': ['POST']})
472 conditions={'method': ['POST']})
473 m.connect('admin_settings_visual', '/settings/visual',
473 m.connect('admin_settings_visual', '/settings/visual',
474 action='settings_visual', conditions={'method': ['GET']})
474 action='settings_visual', conditions={'method': ['GET']})
475
475
476 m.connect('admin_settings_issuetracker',
476 m.connect('admin_settings_issuetracker',
477 '/settings/issue-tracker', action='settings_issuetracker',
477 '/settings/issue-tracker', action='settings_issuetracker',
478 conditions={'method': ['GET']})
478 conditions={'method': ['GET']})
479 m.connect('admin_settings_issuetracker_save',
479 m.connect('admin_settings_issuetracker_save',
480 '/settings/issue-tracker/save',
480 '/settings/issue-tracker/save',
481 action='settings_issuetracker_save',
481 action='settings_issuetracker_save',
482 conditions={'method': ['POST']})
482 conditions={'method': ['POST']})
483 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
483 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
484 action='settings_issuetracker_test',
484 action='settings_issuetracker_test',
485 conditions={'method': ['POST']})
485 conditions={'method': ['POST']})
486 m.connect('admin_issuetracker_delete',
486 m.connect('admin_issuetracker_delete',
487 '/settings/issue-tracker/delete',
487 '/settings/issue-tracker/delete',
488 action='settings_issuetracker_delete',
488 action='settings_issuetracker_delete',
489 conditions={'method': ['DELETE']})
489 conditions={'method': ['DELETE']})
490
490
491 m.connect('admin_settings_email', '/settings/email',
491 m.connect('admin_settings_email', '/settings/email',
492 action='settings_email_update',
492 action='settings_email_update',
493 conditions={'method': ['POST']})
493 conditions={'method': ['POST']})
494 m.connect('admin_settings_email', '/settings/email',
494 m.connect('admin_settings_email', '/settings/email',
495 action='settings_email', conditions={'method': ['GET']})
495 action='settings_email', conditions={'method': ['GET']})
496
496
497 m.connect('admin_settings_hooks', '/settings/hooks',
497 m.connect('admin_settings_hooks', '/settings/hooks',
498 action='settings_hooks_update',
498 action='settings_hooks_update',
499 conditions={'method': ['POST', 'DELETE']})
499 conditions={'method': ['POST', 'DELETE']})
500 m.connect('admin_settings_hooks', '/settings/hooks',
500 m.connect('admin_settings_hooks', '/settings/hooks',
501 action='settings_hooks', conditions={'method': ['GET']})
501 action='settings_hooks', conditions={'method': ['GET']})
502
502
503 m.connect('admin_settings_search', '/settings/search',
503 m.connect('admin_settings_search', '/settings/search',
504 action='settings_search', conditions={'method': ['GET']})
504 action='settings_search', conditions={'method': ['GET']})
505
505
506 m.connect('admin_settings_system', '/settings/system',
506 m.connect('admin_settings_system', '/settings/system',
507 action='settings_system', conditions={'method': ['GET']})
507 action='settings_system', conditions={'method': ['GET']})
508
508
509 m.connect('admin_settings_system_update', '/settings/system/updates',
509 m.connect('admin_settings_system_update', '/settings/system/updates',
510 action='settings_system_update', conditions={'method': ['GET']})
510 action='settings_system_update', conditions={'method': ['GET']})
511
511
512 m.connect('admin_settings_supervisor', '/settings/supervisor',
512 m.connect('admin_settings_supervisor', '/settings/supervisor',
513 action='settings_supervisor', conditions={'method': ['GET']})
513 action='settings_supervisor', conditions={'method': ['GET']})
514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
515 action='settings_supervisor_log', conditions={'method': ['GET']})
515 action='settings_supervisor_log', conditions={'method': ['GET']})
516
516
517 m.connect('admin_settings_labs', '/settings/labs',
517 m.connect('admin_settings_labs', '/settings/labs',
518 action='settings_labs_update',
518 action='settings_labs_update',
519 conditions={'method': ['POST']})
519 conditions={'method': ['POST']})
520 m.connect('admin_settings_labs', '/settings/labs',
520 m.connect('admin_settings_labs', '/settings/labs',
521 action='settings_labs', conditions={'method': ['GET']})
521 action='settings_labs', conditions={'method': ['GET']})
522
522
523 # ADMIN MY ACCOUNT
523 # ADMIN MY ACCOUNT
524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
525 controller='admin/my_account') as m:
525 controller='admin/my_account') as m:
526
526
527 m.connect('my_account', '/my_account',
527 m.connect('my_account', '/my_account',
528 action='my_account', conditions={'method': ['GET']})
528 action='my_account', conditions={'method': ['GET']})
529 m.connect('my_account_edit', '/my_account/edit',
529 m.connect('my_account_edit', '/my_account/edit',
530 action='my_account_edit', conditions={'method': ['GET']})
530 action='my_account_edit', conditions={'method': ['GET']})
531 m.connect('my_account', '/my_account',
531 m.connect('my_account', '/my_account',
532 action='my_account_update', conditions={'method': ['POST']})
532 action='my_account_update', conditions={'method': ['POST']})
533
533
534 m.connect('my_account_password', '/my_account/password',
534 m.connect('my_account_password', '/my_account/password',
535 action='my_account_password', conditions={'method': ['GET', 'POST']})
535 action='my_account_password', conditions={'method': ['GET', 'POST']})
536
536
537 m.connect('my_account_repos', '/my_account/repos',
537 m.connect('my_account_repos', '/my_account/repos',
538 action='my_account_repos', conditions={'method': ['GET']})
538 action='my_account_repos', conditions={'method': ['GET']})
539
539
540 m.connect('my_account_watched', '/my_account/watched',
540 m.connect('my_account_watched', '/my_account/watched',
541 action='my_account_watched', conditions={'method': ['GET']})
541 action='my_account_watched', conditions={'method': ['GET']})
542
542
543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
544 action='my_account_pullrequests', conditions={'method': ['GET']})
544 action='my_account_pullrequests', conditions={'method': ['GET']})
545
545
546 m.connect('my_account_perms', '/my_account/perms',
546 m.connect('my_account_perms', '/my_account/perms',
547 action='my_account_perms', conditions={'method': ['GET']})
547 action='my_account_perms', conditions={'method': ['GET']})
548
548
549 m.connect('my_account_emails', '/my_account/emails',
549 m.connect('my_account_emails', '/my_account/emails',
550 action='my_account_emails', conditions={'method': ['GET']})
550 action='my_account_emails', conditions={'method': ['GET']})
551 m.connect('my_account_emails', '/my_account/emails',
551 m.connect('my_account_emails', '/my_account/emails',
552 action='my_account_emails_add', conditions={'method': ['POST']})
552 action='my_account_emails_add', conditions={'method': ['POST']})
553 m.connect('my_account_emails', '/my_account/emails',
553 m.connect('my_account_emails', '/my_account/emails',
554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
555
555
556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
557 action='my_account_auth_tokens', conditions={'method': ['GET']})
557 action='my_account_auth_tokens', conditions={'method': ['GET']})
558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
562 m.connect('my_account_notifications', '/my_account/notifications',
562 m.connect('my_account_notifications', '/my_account/notifications',
563 action='my_notifications',
563 action='my_notifications',
564 conditions={'method': ['GET']})
564 conditions={'method': ['GET']})
565 m.connect('my_account_notifications_toggle_visibility',
565 m.connect('my_account_notifications_toggle_visibility',
566 '/my_account/toggle_visibility',
566 '/my_account/toggle_visibility',
567 action='my_notifications_toggle_visibility',
567 action='my_notifications_toggle_visibility',
568 conditions={'method': ['POST']})
568 conditions={'method': ['POST']})
569
569
570 # NOTIFICATION REST ROUTES
570 # NOTIFICATION REST ROUTES
571 with rmap.submapper(path_prefix=ADMIN_PREFIX,
571 with rmap.submapper(path_prefix=ADMIN_PREFIX,
572 controller='admin/notifications') as m:
572 controller='admin/notifications') as m:
573 m.connect('notifications', '/notifications',
573 m.connect('notifications', '/notifications',
574 action='index', conditions={'method': ['GET']})
574 action='index', conditions={'method': ['GET']})
575 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
575 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
576 action='mark_all_read', conditions={'method': ['POST']})
576 action='mark_all_read', conditions={'method': ['POST']})
577 m.connect('/notifications/{notification_id}',
577 m.connect('/notifications/{notification_id}',
578 action='update', conditions={'method': ['PUT']})
578 action='update', conditions={'method': ['PUT']})
579 m.connect('/notifications/{notification_id}',
579 m.connect('/notifications/{notification_id}',
580 action='delete', conditions={'method': ['DELETE']})
580 action='delete', conditions={'method': ['DELETE']})
581 m.connect('notification', '/notifications/{notification_id}',
581 m.connect('notification', '/notifications/{notification_id}',
582 action='show', conditions={'method': ['GET']})
582 action='show', conditions={'method': ['GET']})
583
583
584 # ADMIN GIST
584 # ADMIN GIST
585 with rmap.submapper(path_prefix=ADMIN_PREFIX,
585 with rmap.submapper(path_prefix=ADMIN_PREFIX,
586 controller='admin/gists') as m:
586 controller='admin/gists') as m:
587 m.connect('gists', '/gists',
587 m.connect('gists', '/gists',
588 action='create', conditions={'method': ['POST']})
588 action='create', conditions={'method': ['POST']})
589 m.connect('gists', '/gists', jsroute=True,
589 m.connect('gists', '/gists', jsroute=True,
590 action='index', conditions={'method': ['GET']})
590 action='index', conditions={'method': ['GET']})
591 m.connect('new_gist', '/gists/new', jsroute=True,
591 m.connect('new_gist', '/gists/new', jsroute=True,
592 action='new', conditions={'method': ['GET']})
592 action='new', conditions={'method': ['GET']})
593
593
594 m.connect('/gists/{gist_id}',
594 m.connect('/gists/{gist_id}',
595 action='delete', conditions={'method': ['DELETE']})
595 action='delete', conditions={'method': ['DELETE']})
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
597 action='edit_form', conditions={'method': ['GET']})
597 action='edit_form', conditions={'method': ['GET']})
598 m.connect('edit_gist', '/gists/{gist_id}/edit',
598 m.connect('edit_gist', '/gists/{gist_id}/edit',
599 action='edit', conditions={'method': ['POST']})
599 action='edit', conditions={'method': ['POST']})
600 m.connect(
600 m.connect(
601 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
601 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
602 action='check_revision', conditions={'method': ['GET']})
602 action='check_revision', conditions={'method': ['GET']})
603
603
604 m.connect('gist', '/gists/{gist_id}',
604 m.connect('gist', '/gists/{gist_id}',
605 action='show', conditions={'method': ['GET']})
605 action='show', conditions={'method': ['GET']})
606 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
606 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
607 revision='tip',
607 revision='tip',
608 action='show', conditions={'method': ['GET']})
608 action='show', conditions={'method': ['GET']})
609 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
609 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
610 revision='tip',
610 revision='tip',
611 action='show', conditions={'method': ['GET']})
611 action='show', conditions={'method': ['GET']})
612 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
612 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
613 revision='tip',
613 revision='tip',
614 action='show', conditions={'method': ['GET']},
614 action='show', conditions={'method': ['GET']},
615 requirements=URL_NAME_REQUIREMENTS)
615 requirements=URL_NAME_REQUIREMENTS)
616
616
617 # ADMIN MAIN PAGES
617 # ADMIN MAIN PAGES
618 with rmap.submapper(path_prefix=ADMIN_PREFIX,
618 with rmap.submapper(path_prefix=ADMIN_PREFIX,
619 controller='admin/admin') as m:
619 controller='admin/admin') as m:
620 m.connect('admin_home', '', action='index')
620 m.connect('admin_home', '', action='index')
621 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
621 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
622 action='add_repo')
622 action='add_repo')
623 m.connect(
623 m.connect(
624 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
624 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
625 action='pull_requests')
625 action='pull_requests')
626 m.connect(
626 m.connect(
627 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
627 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
628 action='pull_requests')
628 action='pull_requests')
629
629
630
630
631 # USER JOURNAL
631 # USER JOURNAL
632 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
632 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
633 controller='journal', action='index')
633 controller='journal', action='index')
634 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
634 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
635 controller='journal', action='journal_rss')
635 controller='journal', action='journal_rss')
636 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
636 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
637 controller='journal', action='journal_atom')
637 controller='journal', action='journal_atom')
638
638
639 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
639 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
640 controller='journal', action='public_journal')
640 controller='journal', action='public_journal')
641
641
642 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
642 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
643 controller='journal', action='public_journal_rss')
643 controller='journal', action='public_journal_rss')
644
644
645 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
645 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
646 controller='journal', action='public_journal_rss')
646 controller='journal', action='public_journal_rss')
647
647
648 rmap.connect('public_journal_atom',
648 rmap.connect('public_journal_atom',
649 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
649 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
650 action='public_journal_atom')
650 action='public_journal_atom')
651
651
652 rmap.connect('public_journal_atom_old',
652 rmap.connect('public_journal_atom_old',
653 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
653 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
654 action='public_journal_atom')
654 action='public_journal_atom')
655
655
656 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
656 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
657 controller='journal', action='toggle_following', jsroute=True,
657 controller='journal', action='toggle_following', jsroute=True,
658 conditions={'method': ['POST']})
658 conditions={'method': ['POST']})
659
659
660 # FULL TEXT SEARCH
660 # FULL TEXT SEARCH
661 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
661 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
662 controller='search')
662 controller='search')
663 rmap.connect('search_repo_home', '/{repo_name}/search',
663 rmap.connect('search_repo_home', '/{repo_name}/search',
664 controller='search',
664 controller='search',
665 action='index',
665 action='index',
666 conditions={'function': check_repo},
666 conditions={'function': check_repo},
667 requirements=URL_NAME_REQUIREMENTS)
667 requirements=URL_NAME_REQUIREMENTS)
668
668
669 # FEEDS
669 # FEEDS
670 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
670 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
671 controller='feed', action='rss',
671 controller='feed', action='rss',
672 conditions={'function': check_repo},
672 conditions={'function': check_repo},
673 requirements=URL_NAME_REQUIREMENTS)
673 requirements=URL_NAME_REQUIREMENTS)
674
674
675 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
675 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
676 controller='feed', action='atom',
676 controller='feed', action='atom',
677 conditions={'function': check_repo},
677 conditions={'function': check_repo},
678 requirements=URL_NAME_REQUIREMENTS)
678 requirements=URL_NAME_REQUIREMENTS)
679
679
680 #==========================================================================
680 #==========================================================================
681 # REPOSITORY ROUTES
681 # REPOSITORY ROUTES
682 #==========================================================================
682 #==========================================================================
683
683
684 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
684 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
685 controller='admin/repos', action='repo_creating',
685 controller='admin/repos', action='repo_creating',
686 requirements=URL_NAME_REQUIREMENTS)
686 requirements=URL_NAME_REQUIREMENTS)
687 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
687 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
688 controller='admin/repos', action='repo_check',
688 controller='admin/repos', action='repo_check',
689 requirements=URL_NAME_REQUIREMENTS)
689 requirements=URL_NAME_REQUIREMENTS)
690
690
691 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
691 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
692 controller='summary', action='repo_stats',
692 controller='summary', action='repo_stats',
693 conditions={'function': check_repo},
693 conditions={'function': check_repo},
694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
695
695
696 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
696 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
697 controller='summary', action='repo_refs_data', jsroute=True,
697 controller='summary', action='repo_refs_data', jsroute=True,
698 requirements=URL_NAME_REQUIREMENTS)
698 requirements=URL_NAME_REQUIREMENTS)
699 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
699 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
700 controller='summary', action='repo_refs_changelog_data',
700 controller='summary', action='repo_refs_changelog_data',
701 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
701 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
702 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
703 controller='summary', action='repo_default_reviewers_data',
704 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
702
705
703 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
706 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
704 controller='changeset', revision='tip', jsroute=True,
707 controller='changeset', revision='tip', jsroute=True,
705 conditions={'function': check_repo},
708 conditions={'function': check_repo},
706 requirements=URL_NAME_REQUIREMENTS)
709 requirements=URL_NAME_REQUIREMENTS)
707 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
710 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
708 controller='changeset', revision='tip', action='changeset_children',
711 controller='changeset', revision='tip', action='changeset_children',
709 conditions={'function': check_repo},
712 conditions={'function': check_repo},
710 requirements=URL_NAME_REQUIREMENTS)
713 requirements=URL_NAME_REQUIREMENTS)
711 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
714 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
712 controller='changeset', revision='tip', action='changeset_parents',
715 controller='changeset', revision='tip', action='changeset_parents',
713 conditions={'function': check_repo},
716 conditions={'function': check_repo},
714 requirements=URL_NAME_REQUIREMENTS)
717 requirements=URL_NAME_REQUIREMENTS)
715
718
716 # repo edit options
719 # repo edit options
717 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
720 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
718 controller='admin/repos', action='edit',
721 controller='admin/repos', action='edit',
719 conditions={'method': ['GET'], 'function': check_repo},
722 conditions={'method': ['GET'], 'function': check_repo},
720 requirements=URL_NAME_REQUIREMENTS)
723 requirements=URL_NAME_REQUIREMENTS)
721
724
722 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
725 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
723 jsroute=True,
726 jsroute=True,
724 controller='admin/repos', action='edit_permissions',
727 controller='admin/repos', action='edit_permissions',
725 conditions={'method': ['GET'], 'function': check_repo},
728 conditions={'method': ['GET'], 'function': check_repo},
726 requirements=URL_NAME_REQUIREMENTS)
729 requirements=URL_NAME_REQUIREMENTS)
727 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
730 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
728 controller='admin/repos', action='edit_permissions_update',
731 controller='admin/repos', action='edit_permissions_update',
729 conditions={'method': ['PUT'], 'function': check_repo},
732 conditions={'method': ['PUT'], 'function': check_repo},
730 requirements=URL_NAME_REQUIREMENTS)
733 requirements=URL_NAME_REQUIREMENTS)
731
734
732 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
735 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
733 controller='admin/repos', action='edit_fields',
736 controller='admin/repos', action='edit_fields',
734 conditions={'method': ['GET'], 'function': check_repo},
737 conditions={'method': ['GET'], 'function': check_repo},
735 requirements=URL_NAME_REQUIREMENTS)
738 requirements=URL_NAME_REQUIREMENTS)
736 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
739 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
737 controller='admin/repos', action='create_repo_field',
740 controller='admin/repos', action='create_repo_field',
738 conditions={'method': ['PUT'], 'function': check_repo},
741 conditions={'method': ['PUT'], 'function': check_repo},
739 requirements=URL_NAME_REQUIREMENTS)
742 requirements=URL_NAME_REQUIREMENTS)
740 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
743 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
741 controller='admin/repos', action='delete_repo_field',
744 controller='admin/repos', action='delete_repo_field',
742 conditions={'method': ['DELETE'], 'function': check_repo},
745 conditions={'method': ['DELETE'], 'function': check_repo},
743 requirements=URL_NAME_REQUIREMENTS)
746 requirements=URL_NAME_REQUIREMENTS)
744
747
745 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
748 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
746 controller='admin/repos', action='edit_advanced',
749 controller='admin/repos', action='edit_advanced',
747 conditions={'method': ['GET'], 'function': check_repo},
750 conditions={'method': ['GET'], 'function': check_repo},
748 requirements=URL_NAME_REQUIREMENTS)
751 requirements=URL_NAME_REQUIREMENTS)
749
752
750 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
753 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
751 controller='admin/repos', action='edit_advanced_locking',
754 controller='admin/repos', action='edit_advanced_locking',
752 conditions={'method': ['PUT'], 'function': check_repo},
755 conditions={'method': ['PUT'], 'function': check_repo},
753 requirements=URL_NAME_REQUIREMENTS)
756 requirements=URL_NAME_REQUIREMENTS)
754 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
757 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
755 controller='admin/repos', action='toggle_locking',
758 controller='admin/repos', action='toggle_locking',
756 conditions={'method': ['GET'], 'function': check_repo},
759 conditions={'method': ['GET'], 'function': check_repo},
757 requirements=URL_NAME_REQUIREMENTS)
760 requirements=URL_NAME_REQUIREMENTS)
758
761
759 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
762 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
760 controller='admin/repos', action='edit_advanced_journal',
763 controller='admin/repos', action='edit_advanced_journal',
761 conditions={'method': ['PUT'], 'function': check_repo},
764 conditions={'method': ['PUT'], 'function': check_repo},
762 requirements=URL_NAME_REQUIREMENTS)
765 requirements=URL_NAME_REQUIREMENTS)
763
766
764 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
767 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
765 controller='admin/repos', action='edit_advanced_fork',
768 controller='admin/repos', action='edit_advanced_fork',
766 conditions={'method': ['PUT'], 'function': check_repo},
769 conditions={'method': ['PUT'], 'function': check_repo},
767 requirements=URL_NAME_REQUIREMENTS)
770 requirements=URL_NAME_REQUIREMENTS)
768
771
769 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
770 controller='admin/repos', action='edit_caches_form',
773 controller='admin/repos', action='edit_caches_form',
771 conditions={'method': ['GET'], 'function': check_repo},
774 conditions={'method': ['GET'], 'function': check_repo},
772 requirements=URL_NAME_REQUIREMENTS)
775 requirements=URL_NAME_REQUIREMENTS)
773 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
776 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
774 controller='admin/repos', action='edit_caches',
777 controller='admin/repos', action='edit_caches',
775 conditions={'method': ['PUT'], 'function': check_repo},
778 conditions={'method': ['PUT'], 'function': check_repo},
776 requirements=URL_NAME_REQUIREMENTS)
779 requirements=URL_NAME_REQUIREMENTS)
777
780
778 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
779 controller='admin/repos', action='edit_remote_form',
782 controller='admin/repos', action='edit_remote_form',
780 conditions={'method': ['GET'], 'function': check_repo},
783 conditions={'method': ['GET'], 'function': check_repo},
781 requirements=URL_NAME_REQUIREMENTS)
784 requirements=URL_NAME_REQUIREMENTS)
782 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
785 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
783 controller='admin/repos', action='edit_remote',
786 controller='admin/repos', action='edit_remote',
784 conditions={'method': ['PUT'], 'function': check_repo},
787 conditions={'method': ['PUT'], 'function': check_repo},
785 requirements=URL_NAME_REQUIREMENTS)
788 requirements=URL_NAME_REQUIREMENTS)
786
789
787 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
788 controller='admin/repos', action='edit_statistics_form',
791 controller='admin/repos', action='edit_statistics_form',
789 conditions={'method': ['GET'], 'function': check_repo},
792 conditions={'method': ['GET'], 'function': check_repo},
790 requirements=URL_NAME_REQUIREMENTS)
793 requirements=URL_NAME_REQUIREMENTS)
791 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
794 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
792 controller='admin/repos', action='edit_statistics',
795 controller='admin/repos', action='edit_statistics',
793 conditions={'method': ['PUT'], 'function': check_repo},
796 conditions={'method': ['PUT'], 'function': check_repo},
794 requirements=URL_NAME_REQUIREMENTS)
797 requirements=URL_NAME_REQUIREMENTS)
795 rmap.connect('repo_settings_issuetracker',
798 rmap.connect('repo_settings_issuetracker',
796 '/{repo_name}/settings/issue-tracker',
799 '/{repo_name}/settings/issue-tracker',
797 controller='admin/repos', action='repo_issuetracker',
800 controller='admin/repos', action='repo_issuetracker',
798 conditions={'method': ['GET'], 'function': check_repo},
801 conditions={'method': ['GET'], 'function': check_repo},
799 requirements=URL_NAME_REQUIREMENTS)
802 requirements=URL_NAME_REQUIREMENTS)
800 rmap.connect('repo_issuetracker_test',
803 rmap.connect('repo_issuetracker_test',
801 '/{repo_name}/settings/issue-tracker/test',
804 '/{repo_name}/settings/issue-tracker/test',
802 controller='admin/repos', action='repo_issuetracker_test',
805 controller='admin/repos', action='repo_issuetracker_test',
803 conditions={'method': ['POST'], 'function': check_repo},
806 conditions={'method': ['POST'], 'function': check_repo},
804 requirements=URL_NAME_REQUIREMENTS)
807 requirements=URL_NAME_REQUIREMENTS)
805 rmap.connect('repo_issuetracker_delete',
808 rmap.connect('repo_issuetracker_delete',
806 '/{repo_name}/settings/issue-tracker/delete',
809 '/{repo_name}/settings/issue-tracker/delete',
807 controller='admin/repos', action='repo_issuetracker_delete',
810 controller='admin/repos', action='repo_issuetracker_delete',
808 conditions={'method': ['DELETE'], 'function': check_repo},
811 conditions={'method': ['DELETE'], 'function': check_repo},
809 requirements=URL_NAME_REQUIREMENTS)
812 requirements=URL_NAME_REQUIREMENTS)
810 rmap.connect('repo_issuetracker_save',
813 rmap.connect('repo_issuetracker_save',
811 '/{repo_name}/settings/issue-tracker/save',
814 '/{repo_name}/settings/issue-tracker/save',
812 controller='admin/repos', action='repo_issuetracker_save',
815 controller='admin/repos', action='repo_issuetracker_save',
813 conditions={'method': ['POST'], 'function': check_repo},
816 conditions={'method': ['POST'], 'function': check_repo},
814 requirements=URL_NAME_REQUIREMENTS)
817 requirements=URL_NAME_REQUIREMENTS)
815 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
816 controller='admin/repos', action='repo_settings_vcs_update',
819 controller='admin/repos', action='repo_settings_vcs_update',
817 conditions={'method': ['POST'], 'function': check_repo},
820 conditions={'method': ['POST'], 'function': check_repo},
818 requirements=URL_NAME_REQUIREMENTS)
821 requirements=URL_NAME_REQUIREMENTS)
819 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
820 controller='admin/repos', action='repo_settings_vcs',
823 controller='admin/repos', action='repo_settings_vcs',
821 conditions={'method': ['GET'], 'function': check_repo},
824 conditions={'method': ['GET'], 'function': check_repo},
822 requirements=URL_NAME_REQUIREMENTS)
825 requirements=URL_NAME_REQUIREMENTS)
823 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
826 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
824 controller='admin/repos', action='repo_delete_svn_pattern',
827 controller='admin/repos', action='repo_delete_svn_pattern',
825 conditions={'method': ['DELETE'], 'function': check_repo},
828 conditions={'method': ['DELETE'], 'function': check_repo},
826 requirements=URL_NAME_REQUIREMENTS)
829 requirements=URL_NAME_REQUIREMENTS)
830 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
831 controller='admin/repos', action='repo_settings_pullrequest',
832 conditions={'method': ['GET', 'POST'], 'function': check_repo},
833 requirements=URL_NAME_REQUIREMENTS)
827
834
828 # still working url for backward compat.
835 # still working url for backward compat.
829 rmap.connect('raw_changeset_home_depraced',
836 rmap.connect('raw_changeset_home_depraced',
830 '/{repo_name}/raw-changeset/{revision}',
837 '/{repo_name}/raw-changeset/{revision}',
831 controller='changeset', action='changeset_raw',
838 controller='changeset', action='changeset_raw',
832 revision='tip', conditions={'function': check_repo},
839 revision='tip', conditions={'function': check_repo},
833 requirements=URL_NAME_REQUIREMENTS)
840 requirements=URL_NAME_REQUIREMENTS)
834
841
835 # new URLs
842 # new URLs
836 rmap.connect('changeset_raw_home',
843 rmap.connect('changeset_raw_home',
837 '/{repo_name}/changeset-diff/{revision}',
844 '/{repo_name}/changeset-diff/{revision}',
838 controller='changeset', action='changeset_raw',
845 controller='changeset', action='changeset_raw',
839 revision='tip', conditions={'function': check_repo},
846 revision='tip', conditions={'function': check_repo},
840 requirements=URL_NAME_REQUIREMENTS)
847 requirements=URL_NAME_REQUIREMENTS)
841
848
842 rmap.connect('changeset_patch_home',
849 rmap.connect('changeset_patch_home',
843 '/{repo_name}/changeset-patch/{revision}',
850 '/{repo_name}/changeset-patch/{revision}',
844 controller='changeset', action='changeset_patch',
851 controller='changeset', action='changeset_patch',
845 revision='tip', conditions={'function': check_repo},
852 revision='tip', conditions={'function': check_repo},
846 requirements=URL_NAME_REQUIREMENTS)
853 requirements=URL_NAME_REQUIREMENTS)
847
854
848 rmap.connect('changeset_download_home',
855 rmap.connect('changeset_download_home',
849 '/{repo_name}/changeset-download/{revision}',
856 '/{repo_name}/changeset-download/{revision}',
850 controller='changeset', action='changeset_download',
857 controller='changeset', action='changeset_download',
851 revision='tip', conditions={'function': check_repo},
858 revision='tip', conditions={'function': check_repo},
852 requirements=URL_NAME_REQUIREMENTS)
859 requirements=URL_NAME_REQUIREMENTS)
853
860
854 rmap.connect('changeset_comment',
861 rmap.connect('changeset_comment',
855 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
862 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
856 controller='changeset', revision='tip', action='comment',
863 controller='changeset', revision='tip', action='comment',
857 conditions={'function': check_repo},
864 conditions={'function': check_repo},
858 requirements=URL_NAME_REQUIREMENTS)
865 requirements=URL_NAME_REQUIREMENTS)
859
866
860 rmap.connect('changeset_comment_preview',
867 rmap.connect('changeset_comment_preview',
861 '/{repo_name}/changeset/comment/preview', jsroute=True,
868 '/{repo_name}/changeset/comment/preview', jsroute=True,
862 controller='changeset', action='preview_comment',
869 controller='changeset', action='preview_comment',
863 conditions={'function': check_repo, 'method': ['POST']},
870 conditions={'function': check_repo, 'method': ['POST']},
864 requirements=URL_NAME_REQUIREMENTS)
871 requirements=URL_NAME_REQUIREMENTS)
865
872
866 rmap.connect('changeset_comment_delete',
873 rmap.connect('changeset_comment_delete',
867 '/{repo_name}/changeset/comment/{comment_id}/delete',
874 '/{repo_name}/changeset/comment/{comment_id}/delete',
868 controller='changeset', action='delete_comment',
875 controller='changeset', action='delete_comment',
869 conditions={'function': check_repo, 'method': ['DELETE']},
876 conditions={'function': check_repo, 'method': ['DELETE']},
870 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
877 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
871
878
872 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
879 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
873 controller='changeset', action='changeset_info',
880 controller='changeset', action='changeset_info',
874 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
875
882
876 rmap.connect('compare_home',
883 rmap.connect('compare_home',
877 '/{repo_name}/compare',
884 '/{repo_name}/compare',
878 controller='compare', action='index',
885 controller='compare', action='index',
879 conditions={'function': check_repo},
886 conditions={'function': check_repo},
880 requirements=URL_NAME_REQUIREMENTS)
887 requirements=URL_NAME_REQUIREMENTS)
881
888
882 rmap.connect('compare_url',
889 rmap.connect('compare_url',
883 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
890 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
884 controller='compare', action='compare',
891 controller='compare', action='compare',
885 conditions={'function': check_repo},
892 conditions={'function': check_repo},
886 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
887
894
888 rmap.connect('pullrequest_home',
895 rmap.connect('pullrequest_home',
889 '/{repo_name}/pull-request/new', controller='pullrequests',
896 '/{repo_name}/pull-request/new', controller='pullrequests',
890 action='index', conditions={'function': check_repo,
897 action='index', conditions={'function': check_repo,
891 'method': ['GET']},
898 'method': ['GET']},
892 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893
900
894 rmap.connect('pullrequest',
901 rmap.connect('pullrequest',
895 '/{repo_name}/pull-request/new', controller='pullrequests',
902 '/{repo_name}/pull-request/new', controller='pullrequests',
896 action='create', conditions={'function': check_repo,
903 action='create', conditions={'function': check_repo,
897 'method': ['POST']},
904 'method': ['POST']},
898 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
899
906
900 rmap.connect('pullrequest_repo_refs',
907 rmap.connect('pullrequest_repo_refs',
901 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
908 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
902 controller='pullrequests',
909 controller='pullrequests',
903 action='get_repo_refs',
910 action='get_repo_refs',
904 conditions={'function': check_repo, 'method': ['GET']},
911 conditions={'function': check_repo, 'method': ['GET']},
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
906
913
907 rmap.connect('pullrequest_repo_destinations',
914 rmap.connect('pullrequest_repo_destinations',
908 '/{repo_name}/pull-request/repo-destinations',
915 '/{repo_name}/pull-request/repo-destinations',
909 controller='pullrequests',
916 controller='pullrequests',
910 action='get_repo_destinations',
917 action='get_repo_destinations',
911 conditions={'function': check_repo, 'method': ['GET']},
918 conditions={'function': check_repo, 'method': ['GET']},
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
919 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
913
920
914 rmap.connect('pullrequest_show',
921 rmap.connect('pullrequest_show',
915 '/{repo_name}/pull-request/{pull_request_id}',
922 '/{repo_name}/pull-request/{pull_request_id}',
916 controller='pullrequests',
923 controller='pullrequests',
917 action='show', conditions={'function': check_repo,
924 action='show', conditions={'function': check_repo,
918 'method': ['GET']},
925 'method': ['GET']},
919 requirements=URL_NAME_REQUIREMENTS)
926 requirements=URL_NAME_REQUIREMENTS)
920
927
921 rmap.connect('pullrequest_update',
928 rmap.connect('pullrequest_update',
922 '/{repo_name}/pull-request/{pull_request_id}',
929 '/{repo_name}/pull-request/{pull_request_id}',
923 controller='pullrequests',
930 controller='pullrequests',
924 action='update', conditions={'function': check_repo,
931 action='update', conditions={'function': check_repo,
925 'method': ['PUT']},
932 'method': ['PUT']},
926 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
933 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
927
934
928 rmap.connect('pullrequest_merge',
935 rmap.connect('pullrequest_merge',
929 '/{repo_name}/pull-request/{pull_request_id}',
936 '/{repo_name}/pull-request/{pull_request_id}',
930 controller='pullrequests',
937 controller='pullrequests',
931 action='merge', conditions={'function': check_repo,
938 action='merge', conditions={'function': check_repo,
932 'method': ['POST']},
939 'method': ['POST']},
933 requirements=URL_NAME_REQUIREMENTS)
940 requirements=URL_NAME_REQUIREMENTS)
934
941
935 rmap.connect('pullrequest_delete',
942 rmap.connect('pullrequest_delete',
936 '/{repo_name}/pull-request/{pull_request_id}',
943 '/{repo_name}/pull-request/{pull_request_id}',
937 controller='pullrequests',
944 controller='pullrequests',
938 action='delete', conditions={'function': check_repo,
945 action='delete', conditions={'function': check_repo,
939 'method': ['DELETE']},
946 'method': ['DELETE']},
940 requirements=URL_NAME_REQUIREMENTS)
947 requirements=URL_NAME_REQUIREMENTS)
941
948
942 rmap.connect('pullrequest_show_all',
949 rmap.connect('pullrequest_show_all',
943 '/{repo_name}/pull-request',
950 '/{repo_name}/pull-request',
944 controller='pullrequests',
951 controller='pullrequests',
945 action='show_all', conditions={'function': check_repo,
952 action='show_all', conditions={'function': check_repo,
946 'method': ['GET']},
953 'method': ['GET']},
947 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
948
955
949 rmap.connect('pullrequest_comment',
956 rmap.connect('pullrequest_comment',
950 '/{repo_name}/pull-request-comment/{pull_request_id}',
957 '/{repo_name}/pull-request-comment/{pull_request_id}',
951 controller='pullrequests',
958 controller='pullrequests',
952 action='comment', conditions={'function': check_repo,
959 action='comment', conditions={'function': check_repo,
953 'method': ['POST']},
960 'method': ['POST']},
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
955
962
956 rmap.connect('pullrequest_comment_delete',
963 rmap.connect('pullrequest_comment_delete',
957 '/{repo_name}/pull-request-comment/{comment_id}/delete',
964 '/{repo_name}/pull-request-comment/{comment_id}/delete',
958 controller='pullrequests', action='delete_comment',
965 controller='pullrequests', action='delete_comment',
959 conditions={'function': check_repo, 'method': ['DELETE']},
966 conditions={'function': check_repo, 'method': ['DELETE']},
960 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
967 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961
968
962 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
969 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
963 controller='summary', conditions={'function': check_repo},
970 controller='summary', conditions={'function': check_repo},
964 requirements=URL_NAME_REQUIREMENTS)
971 requirements=URL_NAME_REQUIREMENTS)
965
972
966 rmap.connect('branches_home', '/{repo_name}/branches',
973 rmap.connect('branches_home', '/{repo_name}/branches',
967 controller='branches', conditions={'function': check_repo},
974 controller='branches', conditions={'function': check_repo},
968 requirements=URL_NAME_REQUIREMENTS)
975 requirements=URL_NAME_REQUIREMENTS)
969
976
970 rmap.connect('tags_home', '/{repo_name}/tags',
977 rmap.connect('tags_home', '/{repo_name}/tags',
971 controller='tags', conditions={'function': check_repo},
978 controller='tags', conditions={'function': check_repo},
972 requirements=URL_NAME_REQUIREMENTS)
979 requirements=URL_NAME_REQUIREMENTS)
973
980
974 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
981 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
975 controller='bookmarks', conditions={'function': check_repo},
982 controller='bookmarks', conditions={'function': check_repo},
976 requirements=URL_NAME_REQUIREMENTS)
983 requirements=URL_NAME_REQUIREMENTS)
977
984
978 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
985 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
979 controller='changelog', conditions={'function': check_repo},
986 controller='changelog', conditions={'function': check_repo},
980 requirements=URL_NAME_REQUIREMENTS)
987 requirements=URL_NAME_REQUIREMENTS)
981
988
982 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
989 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
983 controller='changelog', action='changelog_summary',
990 controller='changelog', action='changelog_summary',
984 conditions={'function': check_repo},
991 conditions={'function': check_repo},
985 requirements=URL_NAME_REQUIREMENTS)
992 requirements=URL_NAME_REQUIREMENTS)
986
993
987 rmap.connect('changelog_file_home',
994 rmap.connect('changelog_file_home',
988 '/{repo_name}/changelog/{revision}/{f_path}',
995 '/{repo_name}/changelog/{revision}/{f_path}',
989 controller='changelog', f_path=None,
996 controller='changelog', f_path=None,
990 conditions={'function': check_repo},
997 conditions={'function': check_repo},
991 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
998 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
992
999
993 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
1000 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
994 controller='changelog', action='changelog_details',
1001 controller='changelog', action='changelog_details',
995 conditions={'function': check_repo},
1002 conditions={'function': check_repo},
996 requirements=URL_NAME_REQUIREMENTS)
1003 requirements=URL_NAME_REQUIREMENTS)
997
1004
998 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1005 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
999 controller='files', revision='tip', f_path='',
1006 controller='files', revision='tip', f_path='',
1000 conditions={'function': check_repo},
1007 conditions={'function': check_repo},
1001 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1008 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1002
1009
1003 rmap.connect('files_home_simple_catchrev',
1010 rmap.connect('files_home_simple_catchrev',
1004 '/{repo_name}/files/{revision}',
1011 '/{repo_name}/files/{revision}',
1005 controller='files', revision='tip', f_path='',
1012 controller='files', revision='tip', f_path='',
1006 conditions={'function': check_repo},
1013 conditions={'function': check_repo},
1007 requirements=URL_NAME_REQUIREMENTS)
1014 requirements=URL_NAME_REQUIREMENTS)
1008
1015
1009 rmap.connect('files_home_simple_catchall',
1016 rmap.connect('files_home_simple_catchall',
1010 '/{repo_name}/files',
1017 '/{repo_name}/files',
1011 controller='files', revision='tip', f_path='',
1018 controller='files', revision='tip', f_path='',
1012 conditions={'function': check_repo},
1019 conditions={'function': check_repo},
1013 requirements=URL_NAME_REQUIREMENTS)
1020 requirements=URL_NAME_REQUIREMENTS)
1014
1021
1015 rmap.connect('files_history_home',
1022 rmap.connect('files_history_home',
1016 '/{repo_name}/history/{revision}/{f_path}',
1023 '/{repo_name}/history/{revision}/{f_path}',
1017 controller='files', action='history', revision='tip', f_path='',
1024 controller='files', action='history', revision='tip', f_path='',
1018 conditions={'function': check_repo},
1025 conditions={'function': check_repo},
1019 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1026 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1020
1027
1021 rmap.connect('files_authors_home',
1028 rmap.connect('files_authors_home',
1022 '/{repo_name}/authors/{revision}/{f_path}',
1029 '/{repo_name}/authors/{revision}/{f_path}',
1023 controller='files', action='authors', revision='tip', f_path='',
1030 controller='files', action='authors', revision='tip', f_path='',
1024 conditions={'function': check_repo},
1031 conditions={'function': check_repo},
1025 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1032 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1026
1033
1027 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1034 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1028 controller='files', action='diff', f_path='',
1035 controller='files', action='diff', f_path='',
1029 conditions={'function': check_repo},
1036 conditions={'function': check_repo},
1030 requirements=URL_NAME_REQUIREMENTS)
1037 requirements=URL_NAME_REQUIREMENTS)
1031
1038
1032 rmap.connect('files_diff_2way_home',
1039 rmap.connect('files_diff_2way_home',
1033 '/{repo_name}/diff-2way/{f_path}',
1040 '/{repo_name}/diff-2way/{f_path}',
1034 controller='files', action='diff_2way', f_path='',
1041 controller='files', action='diff_2way', f_path='',
1035 conditions={'function': check_repo},
1042 conditions={'function': check_repo},
1036 requirements=URL_NAME_REQUIREMENTS)
1043 requirements=URL_NAME_REQUIREMENTS)
1037
1044
1038 rmap.connect('files_rawfile_home',
1045 rmap.connect('files_rawfile_home',
1039 '/{repo_name}/rawfile/{revision}/{f_path}',
1046 '/{repo_name}/rawfile/{revision}/{f_path}',
1040 controller='files', action='rawfile', revision='tip',
1047 controller='files', action='rawfile', revision='tip',
1041 f_path='', conditions={'function': check_repo},
1048 f_path='', conditions={'function': check_repo},
1042 requirements=URL_NAME_REQUIREMENTS)
1049 requirements=URL_NAME_REQUIREMENTS)
1043
1050
1044 rmap.connect('files_raw_home',
1051 rmap.connect('files_raw_home',
1045 '/{repo_name}/raw/{revision}/{f_path}',
1052 '/{repo_name}/raw/{revision}/{f_path}',
1046 controller='files', action='raw', revision='tip', f_path='',
1053 controller='files', action='raw', revision='tip', f_path='',
1047 conditions={'function': check_repo},
1054 conditions={'function': check_repo},
1048 requirements=URL_NAME_REQUIREMENTS)
1055 requirements=URL_NAME_REQUIREMENTS)
1049
1056
1050 rmap.connect('files_render_home',
1057 rmap.connect('files_render_home',
1051 '/{repo_name}/render/{revision}/{f_path}',
1058 '/{repo_name}/render/{revision}/{f_path}',
1052 controller='files', action='index', revision='tip', f_path='',
1059 controller='files', action='index', revision='tip', f_path='',
1053 rendered=True, conditions={'function': check_repo},
1060 rendered=True, conditions={'function': check_repo},
1054 requirements=URL_NAME_REQUIREMENTS)
1061 requirements=URL_NAME_REQUIREMENTS)
1055
1062
1056 rmap.connect('files_annotate_home',
1063 rmap.connect('files_annotate_home',
1057 '/{repo_name}/annotate/{revision}/{f_path}',
1064 '/{repo_name}/annotate/{revision}/{f_path}',
1058 controller='files', action='index', revision='tip',
1065 controller='files', action='index', revision='tip',
1059 f_path='', annotate=True, conditions={'function': check_repo},
1066 f_path='', annotate=True, conditions={'function': check_repo},
1060 requirements=URL_NAME_REQUIREMENTS)
1067 requirements=URL_NAME_REQUIREMENTS)
1061
1068
1062 rmap.connect('files_edit',
1069 rmap.connect('files_edit',
1063 '/{repo_name}/edit/{revision}/{f_path}',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1064 controller='files', action='edit', revision='tip',
1071 controller='files', action='edit', revision='tip',
1065 f_path='',
1072 f_path='',
1066 conditions={'function': check_repo, 'method': ['POST']},
1073 conditions={'function': check_repo, 'method': ['POST']},
1067 requirements=URL_NAME_REQUIREMENTS)
1074 requirements=URL_NAME_REQUIREMENTS)
1068
1075
1069 rmap.connect('files_edit_home',
1076 rmap.connect('files_edit_home',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1077 '/{repo_name}/edit/{revision}/{f_path}',
1071 controller='files', action='edit_home', revision='tip',
1078 controller='files', action='edit_home', revision='tip',
1072 f_path='', conditions={'function': check_repo},
1079 f_path='', conditions={'function': check_repo},
1073 requirements=URL_NAME_REQUIREMENTS)
1080 requirements=URL_NAME_REQUIREMENTS)
1074
1081
1075 rmap.connect('files_add',
1082 rmap.connect('files_add',
1076 '/{repo_name}/add/{revision}/{f_path}',
1083 '/{repo_name}/add/{revision}/{f_path}',
1077 controller='files', action='add', revision='tip',
1084 controller='files', action='add', revision='tip',
1078 f_path='',
1085 f_path='',
1079 conditions={'function': check_repo, 'method': ['POST']},
1086 conditions={'function': check_repo, 'method': ['POST']},
1080 requirements=URL_NAME_REQUIREMENTS)
1087 requirements=URL_NAME_REQUIREMENTS)
1081
1088
1082 rmap.connect('files_add_home',
1089 rmap.connect('files_add_home',
1083 '/{repo_name}/add/{revision}/{f_path}',
1090 '/{repo_name}/add/{revision}/{f_path}',
1084 controller='files', action='add_home', revision='tip',
1091 controller='files', action='add_home', revision='tip',
1085 f_path='', conditions={'function': check_repo},
1092 f_path='', conditions={'function': check_repo},
1086 requirements=URL_NAME_REQUIREMENTS)
1093 requirements=URL_NAME_REQUIREMENTS)
1087
1094
1088 rmap.connect('files_delete',
1095 rmap.connect('files_delete',
1089 '/{repo_name}/delete/{revision}/{f_path}',
1096 '/{repo_name}/delete/{revision}/{f_path}',
1090 controller='files', action='delete', revision='tip',
1097 controller='files', action='delete', revision='tip',
1091 f_path='',
1098 f_path='',
1092 conditions={'function': check_repo, 'method': ['POST']},
1099 conditions={'function': check_repo, 'method': ['POST']},
1093 requirements=URL_NAME_REQUIREMENTS)
1100 requirements=URL_NAME_REQUIREMENTS)
1094
1101
1095 rmap.connect('files_delete_home',
1102 rmap.connect('files_delete_home',
1096 '/{repo_name}/delete/{revision}/{f_path}',
1103 '/{repo_name}/delete/{revision}/{f_path}',
1097 controller='files', action='delete_home', revision='tip',
1104 controller='files', action='delete_home', revision='tip',
1098 f_path='', conditions={'function': check_repo},
1105 f_path='', conditions={'function': check_repo},
1099 requirements=URL_NAME_REQUIREMENTS)
1106 requirements=URL_NAME_REQUIREMENTS)
1100
1107
1101 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1108 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1102 controller='files', action='archivefile',
1109 controller='files', action='archivefile',
1103 conditions={'function': check_repo},
1110 conditions={'function': check_repo},
1104 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1105
1112
1106 rmap.connect('files_nodelist_home',
1113 rmap.connect('files_nodelist_home',
1107 '/{repo_name}/nodelist/{revision}/{f_path}',
1114 '/{repo_name}/nodelist/{revision}/{f_path}',
1108 controller='files', action='nodelist',
1115 controller='files', action='nodelist',
1109 conditions={'function': check_repo},
1116 conditions={'function': check_repo},
1110 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111
1118
1112 rmap.connect('files_nodetree_full',
1119 rmap.connect('files_nodetree_full',
1113 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1120 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1114 controller='files', action='nodetree_full',
1121 controller='files', action='nodetree_full',
1115 conditions={'function': check_repo},
1122 conditions={'function': check_repo},
1116 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1117
1124
1118 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1125 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1119 controller='forks', action='fork_create',
1126 controller='forks', action='fork_create',
1120 conditions={'function': check_repo, 'method': ['POST']},
1127 conditions={'function': check_repo, 'method': ['POST']},
1121 requirements=URL_NAME_REQUIREMENTS)
1128 requirements=URL_NAME_REQUIREMENTS)
1122
1129
1123 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1130 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1124 controller='forks', action='fork',
1131 controller='forks', action='fork',
1125 conditions={'function': check_repo},
1132 conditions={'function': check_repo},
1126 requirements=URL_NAME_REQUIREMENTS)
1133 requirements=URL_NAME_REQUIREMENTS)
1127
1134
1128 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1135 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1129 controller='forks', action='forks',
1136 controller='forks', action='forks',
1130 conditions={'function': check_repo},
1137 conditions={'function': check_repo},
1131 requirements=URL_NAME_REQUIREMENTS)
1138 requirements=URL_NAME_REQUIREMENTS)
1132
1139
1133 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1140 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1134 controller='followers', action='followers',
1141 controller='followers', action='followers',
1135 conditions={'function': check_repo},
1142 conditions={'function': check_repo},
1136 requirements=URL_NAME_REQUIREMENTS)
1143 requirements=URL_NAME_REQUIREMENTS)
1137
1144
1138 # must be here for proper group/repo catching pattern
1145 # must be here for proper group/repo catching pattern
1139 _connect_with_slash(
1146 _connect_with_slash(
1140 rmap, 'repo_group_home', '/{group_name}',
1147 rmap, 'repo_group_home', '/{group_name}',
1141 controller='home', action='index_repo_group',
1148 controller='home', action='index_repo_group',
1142 conditions={'function': check_group},
1149 conditions={'function': check_group},
1143 requirements=URL_NAME_REQUIREMENTS)
1150 requirements=URL_NAME_REQUIREMENTS)
1144
1151
1145 # catch all, at the end
1152 # catch all, at the end
1146 _connect_with_slash(
1153 _connect_with_slash(
1147 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1154 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1148 controller='summary', action='index',
1155 controller='summary', action='index',
1149 conditions={'function': check_repo},
1156 conditions={'function': check_repo},
1150 requirements=URL_NAME_REQUIREMENTS)
1157 requirements=URL_NAME_REQUIREMENTS)
1151
1158
1152 return rmap
1159 return rmap
1153
1160
1154
1161
1155 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1162 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1156 """
1163 """
1157 Connect a route with an optional trailing slash in `path`.
1164 Connect a route with an optional trailing slash in `path`.
1158 """
1165 """
1159 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1166 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1160 mapper.connect(name, path, *args, **kwargs)
1167 mapper.connect(name, path, *args, **kwargs)
@@ -1,289 +1,288 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Home controller for RhodeCode Enterprise
22 Home controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import logging
25 import logging
26 import time
26 import time
27 import re
27 import re
28
28
29 from pylons import tmpl_context as c, request, url, config
29 from pylons import tmpl_context as c, request, url, config
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from sqlalchemy.sql import func
31 from sqlalchemy.sql import func
32
32
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
36 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.index import searcher_from_config
37 from rhodecode.lib.index import searcher_from_config
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.utils import jsonify
39 from rhodecode.lib.utils import jsonify
40 from rhodecode.lib.utils2 import safe_unicode, str2bool
40 from rhodecode.lib.utils2 import safe_unicode, str2bool
41 from rhodecode.model.db import Repository, RepoGroup
41 from rhodecode.model.db import Repository, RepoGroup
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.repo_group import RepoGroupModel
44 from rhodecode.model.scm import RepoList, RepoGroupList
44 from rhodecode.model.scm import RepoList, RepoGroupList
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class HomeController(BaseController):
50 class HomeController(BaseController):
51 def __before__(self):
51 def __before__(self):
52 super(HomeController, self).__before__()
52 super(HomeController, self).__before__()
53
53
54 def ping(self):
54 def ping(self):
55 """
55 """
56 Ping, doesn't require login, good for checking out the platform
56 Ping, doesn't require login, good for checking out the platform
57 """
57 """
58 instance_id = getattr(c, 'rhodecode_instanceid', '')
58 instance_id = getattr(c, 'rhodecode_instanceid', '')
59 return 'pong[%s] => %s' % (instance_id, self.ip_addr,)
59 return 'pong[%s] => %s' % (instance_id, self.ip_addr,)
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasPermissionAllDecorator('hg.admin')
62 @HasPermissionAllDecorator('hg.admin')
63 def error_test(self):
63 def error_test(self):
64 """
64 """
65 Test exception handling and emails on errors
65 Test exception handling and emails on errors
66 """
66 """
67 class TestException(Exception):
67 class TestException(Exception):
68 pass
68 pass
69
69
70 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
70 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
71 % (c.rhodecode_name, time.time()))
71 % (c.rhodecode_name, time.time()))
72 raise TestException(msg)
72 raise TestException(msg)
73
73
74 def _get_groups_and_repos(self, repo_group_id=None):
74 def _get_groups_and_repos(self, repo_group_id=None):
75 # repo groups groups
75 # repo groups groups
76 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
76 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
77 _perms = ['group.read', 'group.write', 'group.admin']
77 _perms = ['group.read', 'group.write', 'group.admin']
78 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
78 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
79 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
79 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
80 repo_group_list=repo_group_list_acl, admin=False)
80 repo_group_list=repo_group_list_acl, admin=False)
81
81
82 # repositories
82 # repositories
83 repo_list = Repository.get_all_repos(group_id=repo_group_id)
83 repo_list = Repository.get_all_repos(group_id=repo_group_id)
84 _perms = ['repository.read', 'repository.write', 'repository.admin']
84 _perms = ['repository.read', 'repository.write', 'repository.admin']
85 repo_list_acl = RepoList(repo_list, perm_set=_perms)
85 repo_list_acl = RepoList(repo_list, perm_set=_perms)
86 repo_data = RepoModel().get_repos_as_dict(
86 repo_data = RepoModel().get_repos_as_dict(
87 repo_list=repo_list_acl, admin=False)
87 repo_list=repo_list_acl, admin=False)
88
88
89 return repo_data, repo_group_data
89 return repo_data, repo_group_data
90
90
91 @LoginRequired()
91 @LoginRequired()
92 def index(self):
92 def index(self):
93 c.repo_group = None
93 c.repo_group = None
94
94
95 repo_data, repo_group_data = self._get_groups_and_repos()
95 repo_data, repo_group_data = self._get_groups_and_repos()
96 # json used to render the grids
96 # json used to render the grids
97 c.repos_data = json.dumps(repo_data)
97 c.repos_data = json.dumps(repo_data)
98 c.repo_groups_data = json.dumps(repo_group_data)
98 c.repo_groups_data = json.dumps(repo_group_data)
99
99
100 return render('/index.html')
100 return render('/index.html')
101
101
102 @LoginRequired()
102 @LoginRequired()
103 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
103 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
104 'group.admin')
104 'group.admin')
105 def index_repo_group(self, group_name):
105 def index_repo_group(self, group_name):
106 """GET /repo_group_name: Show a specific item"""
106 """GET /repo_group_name: Show a specific item"""
107 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
107 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
108 repo_data, repo_group_data = self._get_groups_and_repos(
108 repo_data, repo_group_data = self._get_groups_and_repos(
109 c.repo_group.group_id)
109 c.repo_group.group_id)
110
110
111 # json used to render the grids
111 # json used to render the grids
112 c.repos_data = json.dumps(repo_data)
112 c.repos_data = json.dumps(repo_data)
113 c.repo_groups_data = json.dumps(repo_group_data)
113 c.repo_groups_data = json.dumps(repo_group_data)
114
114
115 return render('index_repo_group.html')
115 return render('index_repo_group.html')
116
116
117 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
117 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
118 query = Repository.query()\
118 query = Repository.query()\
119 .order_by(func.length(Repository.repo_name))\
119 .order_by(func.length(Repository.repo_name))\
120 .order_by(Repository.repo_name)
120 .order_by(Repository.repo_name)
121
121
122 if repo_type:
122 if repo_type:
123 query = query.filter(Repository.repo_type == repo_type)
123 query = query.filter(Repository.repo_type == repo_type)
124
124
125 if name_contains:
125 if name_contains:
126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
127 query = query.filter(
127 query = query.filter(
128 Repository.repo_name.ilike(ilike_expression))
128 Repository.repo_name.ilike(ilike_expression))
129 query = query.limit(limit)
129 query = query.limit(limit)
130
130
131 all_repos = query.all()
131 all_repos = query.all()
132 repo_iter = self.scm_model.get_repos(all_repos)
132 repo_iter = self.scm_model.get_repos(all_repos)
133 return [
133 return [
134 {
134 {
135 'id': obj['name'],
135 'id': obj['name'],
136 'text': obj['name'],
136 'text': obj['name'],
137 'type': 'repo',
137 'type': 'repo',
138 'obj': obj['dbrepo'],
138 'obj': obj['dbrepo'],
139 'url': url('summary_home', repo_name=obj['name'])
139 'url': url('summary_home', repo_name=obj['name'])
140 }
140 }
141 for obj in repo_iter]
141 for obj in repo_iter]
142
142
143 def _get_repo_group_list(self, name_contains=None, limit=20):
143 def _get_repo_group_list(self, name_contains=None, limit=20):
144 query = RepoGroup.query()\
144 query = RepoGroup.query()\
145 .order_by(func.length(RepoGroup.group_name))\
145 .order_by(func.length(RepoGroup.group_name))\
146 .order_by(RepoGroup.group_name)
146 .order_by(RepoGroup.group_name)
147
147
148 if name_contains:
148 if name_contains:
149 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
149 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
150 query = query.filter(
150 query = query.filter(
151 RepoGroup.group_name.ilike(ilike_expression))
151 RepoGroup.group_name.ilike(ilike_expression))
152 query = query.limit(limit)
152 query = query.limit(limit)
153
153
154 all_groups = query.all()
154 all_groups = query.all()
155 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
155 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
156 return [
156 return [
157 {
157 {
158 'id': obj.group_name,
158 'id': obj.group_name,
159 'text': obj.group_name,
159 'text': obj.group_name,
160 'type': 'group',
160 'type': 'group',
161 'obj': {},
161 'obj': {},
162 'url': url('repo_group_home', group_name=obj.group_name)
162 'url': url('repo_group_home', group_name=obj.group_name)
163 }
163 }
164 for obj in repo_groups_iter]
164 for obj in repo_groups_iter]
165
165
166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
167 if not hash_starts_with or len(hash_starts_with) < 3:
167 if not hash_starts_with or len(hash_starts_with) < 3:
168 return []
168 return []
169
169
170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
171
171
172 if len(commit_hashes) != 1:
172 if len(commit_hashes) != 1:
173 return []
173 return []
174
174
175 commit_hash_prefix = commit_hashes[0]
175 commit_hash_prefix = commit_hashes[0]
176
176
177 auth_user = AuthUser(
177 auth_user = AuthUser(
178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
179 searcher = searcher_from_config(config)
179 searcher = searcher_from_config(config)
180 result = searcher.search(
180 result = searcher.search(
181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user)
181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user)
182
182
183 return [
183 return [
184 {
184 {
185 'id': entry['commit_id'],
185 'id': entry['commit_id'],
186 'text': entry['commit_id'],
186 'text': entry['commit_id'],
187 'type': 'commit',
187 'type': 'commit',
188 'obj': {'repo': entry['repository']},
188 'obj': {'repo': entry['repository']},
189 'url': url('changeset_home',
189 'url': url('changeset_home',
190 repo_name=entry['repository'], revision=entry['commit_id'])
190 repo_name=entry['repository'], revision=entry['commit_id'])
191 }
191 }
192 for entry in result['results']]
192 for entry in result['results']]
193
193
194 @LoginRequired()
194 @LoginRequired()
195 @XHRRequired()
195 @XHRRequired()
196 @jsonify
196 @jsonify
197 def goto_switcher_data(self):
197 def goto_switcher_data(self):
198 query = request.GET.get('query')
198 query = request.GET.get('query')
199 log.debug('generating goto switcher list, query %s', query)
199 log.debug('generating goto switcher list, query %s', query)
200
200
201 res = []
201 res = []
202 repo_groups = self._get_repo_group_list(query)
202 repo_groups = self._get_repo_group_list(query)
203 if repo_groups:
203 if repo_groups:
204 res.append({
204 res.append({
205 'text': _('Groups'),
205 'text': _('Groups'),
206 'children': repo_groups
206 'children': repo_groups
207 })
207 })
208
208
209 repos = self._get_repo_list(query)
209 repos = self._get_repo_list(query)
210 if repos:
210 if repos:
211 res.append({
211 res.append({
212 'text': _('Repositories'),
212 'text': _('Repositories'),
213 'children': repos
213 'children': repos
214 })
214 })
215
215
216 commits = self._get_hash_commit_list(query)
216 commits = self._get_hash_commit_list(query)
217 if commits:
217 if commits:
218 unique_repos = {}
218 unique_repos = {}
219 for commit in commits:
219 for commit in commits:
220 unique_repos.setdefault(commit['obj']['repo'], []
220 unique_repos.setdefault(commit['obj']['repo'], []
221 ).append(commit)
221 ).append(commit)
222
222
223 for repo in unique_repos:
223 for repo in unique_repos:
224 res.append({
224 res.append({
225 'text': _('Commits in %(repo)s') % {'repo': repo},
225 'text': _('Commits in %(repo)s') % {'repo': repo},
226 'children': unique_repos[repo]
226 'children': unique_repos[repo]
227 })
227 })
228
228
229 data = {
229 data = {
230 'more': False,
230 'more': False,
231 'results': res
231 'results': res
232 }
232 }
233 return data
233 return data
234
234
235 @LoginRequired()
235 @LoginRequired()
236 @XHRRequired()
236 @XHRRequired()
237 @jsonify
237 @jsonify
238 def repo_list_data(self):
238 def repo_list_data(self):
239 query = request.GET.get('query')
239 query = request.GET.get('query')
240 repo_type = request.GET.get('repo_type')
240 repo_type = request.GET.get('repo_type')
241 log.debug('generating repo list, query:%s', query)
241 log.debug('generating repo list, query:%s', query)
242
242
243 res = []
243 res = []
244 repos = self._get_repo_list(query, repo_type=repo_type)
244 repos = self._get_repo_list(query, repo_type=repo_type)
245 if repos:
245 if repos:
246 res.append({
246 res.append({
247 'text': _('Repositories'),
247 'text': _('Repositories'),
248 'children': repos
248 'children': repos
249 })
249 })
250
250
251 data = {
251 data = {
252 'more': False,
252 'more': False,
253 'results': res
253 'results': res
254 }
254 }
255 return data
255 return data
256
256
257 @LoginRequired()
257 @LoginRequired()
258 @XHRRequired()
258 @XHRRequired()
259 @jsonify
259 @jsonify
260 def user_autocomplete_data(self):
260 def user_autocomplete_data(self):
261 query = request.GET.get('query')
261 query = request.GET.get('query')
262 active = str2bool(request.GET.get('active') or True)
262 active = str2bool(request.GET.get('active') or True)
263
263
264 repo_model = RepoModel()
264 repo_model = RepoModel()
265 _users = repo_model.get_users(
265 _users = repo_model.get_users(
266 name_contains=query, only_active=active)
266 name_contains=query, only_active=active)
267
267
268 if request.GET.get('user_groups'):
268 if request.GET.get('user_groups'):
269 # extend with user groups
269 # extend with user groups
270 _user_groups = repo_model.get_user_groups(
270 _user_groups = repo_model.get_user_groups(
271 name_contains=query, only_active=active)
271 name_contains=query, only_active=active)
272 _users = _users + _user_groups
272 _users = _users + _user_groups
273
273
274 return {'suggestions': _users}
274 return {'suggestions': _users}
275
275
276 @LoginRequired()
276 @LoginRequired()
277 @XHRRequired()
277 @XHRRequired()
278 @jsonify
278 @jsonify
279 def user_group_autocomplete_data(self):
279 def user_group_autocomplete_data(self):
280 query = request.GET.get('query')
280 query = request.GET.get('query')
281 active = str2bool(request.GET.get('active') or True)
281 active = str2bool(request.GET.get('active') or True)
282
282
283 repo_model = RepoModel()
283 repo_model = RepoModel()
284 _user_groups = repo_model.get_user_groups(
284 _user_groups = repo_model.get_user_groups(
285 name_contains=query, only_active=active)
285 name_contains=query, only_active=active)
286 _user_groups = _user_groups
286 _user_groups = _user_groups
287
287
288 return {'suggestions': _user_groups}
288 return {'suggestions': _user_groups}
289
@@ -1,308 +1,318 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Summary controller for RhodeCode Enterprise
22 Summary controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import logging
25 import logging
26 from string import lower
26 from string import lower
27
27
28 from pylons import tmpl_context as c, request
28 from pylons import tmpl_context as c, request
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from beaker.cache import cache_region, region_invalidate
30 from beaker.cache import cache_region, region_invalidate
31
31
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
33 from rhodecode.controllers import utils
33 from rhodecode.controllers import utils
34 from rhodecode.controllers.changelog import _load_changelog_summary
34 from rhodecode.controllers.changelog import _load_changelog_summary
35 from rhodecode.lib import caches, helpers as h
35 from rhodecode.lib import caches, helpers as h
36 from rhodecode.lib.utils import jsonify
36 from rhodecode.lib.utils import jsonify
37 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, XHRRequired)
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, XHRRequired)
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.markup_renderer import MarkupRenderer
41 from rhodecode.lib.markup_renderer import MarkupRenderer
42 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.exceptions import (
44 from rhodecode.lib.vcs.exceptions import (
45 CommitError, EmptyRepositoryError, NodeDoesNotExistError)
45 CommitError, EmptyRepositoryError, NodeDoesNotExistError)
46 from rhodecode.model.db import Statistics, CacheKey, User
46 from rhodecode.model.db import Statistics, CacheKey, User
47 from rhodecode.model.repo import ReadmeFinder
47 from rhodecode.model.repo import ReadmeFinder
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class SummaryController(BaseRepoController):
53 class SummaryController(BaseRepoController):
54
54
55 def __before__(self):
55 def __before__(self):
56 super(SummaryController, self).__before__()
56 super(SummaryController, self).__before__()
57
57
58 def __get_readme_data(self, db_repo):
58 def __get_readme_data(self, db_repo):
59 repo_name = db_repo.repo_name
59 repo_name = db_repo.repo_name
60 log.debug('Looking for README file')
60 log.debug('Looking for README file')
61 default_renderer = c.visual.default_renderer
61 default_renderer = c.visual.default_renderer
62
62
63 @cache_region('long_term')
63 @cache_region('long_term')
64 def _generate_readme(cache_key):
64 def _generate_readme(cache_key):
65 readme_data = None
65 readme_data = None
66 readme_node = None
66 readme_node = None
67 readme_filename = None
67 readme_filename = None
68 commit = self._get_landing_commit_or_none(db_repo)
68 commit = self._get_landing_commit_or_none(db_repo)
69 if commit:
69 if commit:
70 log.debug("Searching for a README file.")
70 log.debug("Searching for a README file.")
71 readme_node = ReadmeFinder(default_renderer).search(commit)
71 readme_node = ReadmeFinder(default_renderer).search(commit)
72 if readme_node:
72 if readme_node:
73 readme_data = self._render_readme_or_none(commit, readme_node)
73 readme_data = self._render_readme_or_none(commit, readme_node)
74 readme_filename = readme_node.path
74 readme_filename = readme_node.path
75 return readme_data, readme_filename
75 return readme_data, readme_filename
76
76
77 invalidator_context = CacheKey.repo_context_cache(
77 invalidator_context = CacheKey.repo_context_cache(
78 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
78 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
79
79
80 with invalidator_context as context:
80 with invalidator_context as context:
81 context.invalidate()
81 context.invalidate()
82 computed = context.compute()
82 computed = context.compute()
83
83
84 return computed
84 return computed
85
85
86 def _get_landing_commit_or_none(self, db_repo):
86 def _get_landing_commit_or_none(self, db_repo):
87 log.debug("Getting the landing commit.")
87 log.debug("Getting the landing commit.")
88 try:
88 try:
89 commit = db_repo.get_landing_commit()
89 commit = db_repo.get_landing_commit()
90 if not isinstance(commit, EmptyCommit):
90 if not isinstance(commit, EmptyCommit):
91 return commit
91 return commit
92 else:
92 else:
93 log.debug("Repository is empty, no README to render.")
93 log.debug("Repository is empty, no README to render.")
94 except CommitError:
94 except CommitError:
95 log.exception(
95 log.exception(
96 "Problem getting commit when trying to render the README.")
96 "Problem getting commit when trying to render the README.")
97
97
98 def _render_readme_or_none(self, commit, readme_node):
98 def _render_readme_or_none(self, commit, readme_node):
99 log.debug(
99 log.debug(
100 'Found README file `%s` rendering...', readme_node.path)
100 'Found README file `%s` rendering...', readme_node.path)
101 renderer = MarkupRenderer()
101 renderer = MarkupRenderer()
102 try:
102 try:
103 return renderer.render(
103 return renderer.render(
104 readme_node.content, filename=readme_node.path)
104 readme_node.content, filename=readme_node.path)
105 except Exception:
105 except Exception:
106 log.exception(
106 log.exception(
107 "Exception while trying to render the README")
107 "Exception while trying to render the README")
108
108
109 @LoginRequired()
109 @LoginRequired()
110 @HasRepoPermissionAnyDecorator(
110 @HasRepoPermissionAnyDecorator(
111 'repository.read', 'repository.write', 'repository.admin')
111 'repository.read', 'repository.write', 'repository.admin')
112 def index(self, repo_name):
112 def index(self, repo_name):
113
113
114 # Prepare the clone URL
114 # Prepare the clone URL
115
115
116 username = ''
116 username = ''
117 if c.rhodecode_user.username != User.DEFAULT_USER:
117 if c.rhodecode_user.username != User.DEFAULT_USER:
118 username = safe_str(c.rhodecode_user.username)
118 username = safe_str(c.rhodecode_user.username)
119
119
120 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
120 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
121 if '{repo}' in _def_clone_uri:
121 if '{repo}' in _def_clone_uri:
122 _def_clone_uri_by_id = _def_clone_uri.replace(
122 _def_clone_uri_by_id = _def_clone_uri.replace(
123 '{repo}', '_{repoid}')
123 '{repo}', '_{repoid}')
124 elif '{repoid}' in _def_clone_uri:
124 elif '{repoid}' in _def_clone_uri:
125 _def_clone_uri_by_id = _def_clone_uri.replace(
125 _def_clone_uri_by_id = _def_clone_uri.replace(
126 '_{repoid}', '{repo}')
126 '_{repoid}', '{repo}')
127
127
128 c.clone_repo_url = c.rhodecode_db_repo.clone_url(
128 c.clone_repo_url = c.rhodecode_db_repo.clone_url(
129 user=username, uri_tmpl=_def_clone_uri)
129 user=username, uri_tmpl=_def_clone_uri)
130 c.clone_repo_url_id = c.rhodecode_db_repo.clone_url(
130 c.clone_repo_url_id = c.rhodecode_db_repo.clone_url(
131 user=username, uri_tmpl=_def_clone_uri_by_id)
131 user=username, uri_tmpl=_def_clone_uri_by_id)
132
132
133 # If enabled, get statistics data
133 # If enabled, get statistics data
134
134
135 c.show_stats = bool(c.rhodecode_db_repo.enable_statistics)
135 c.show_stats = bool(c.rhodecode_db_repo.enable_statistics)
136
136
137 stats = self.sa.query(Statistics)\
137 stats = self.sa.query(Statistics)\
138 .filter(Statistics.repository == c.rhodecode_db_repo)\
138 .filter(Statistics.repository == c.rhodecode_db_repo)\
139 .scalar()
139 .scalar()
140
140
141 c.stats_percentage = 0
141 c.stats_percentage = 0
142
142
143 if stats and stats.languages:
143 if stats and stats.languages:
144 c.no_data = False is c.rhodecode_db_repo.enable_statistics
144 c.no_data = False is c.rhodecode_db_repo.enable_statistics
145 lang_stats_d = json.loads(stats.languages)
145 lang_stats_d = json.loads(stats.languages)
146
146
147 # Sort first by decreasing count and second by the file extension,
147 # Sort first by decreasing count and second by the file extension,
148 # so we have a consistent output.
148 # so we have a consistent output.
149 lang_stats_items = sorted(lang_stats_d.iteritems(),
149 lang_stats_items = sorted(lang_stats_d.iteritems(),
150 key=lambda k: (-k[1], k[0]))[:10]
150 key=lambda k: (-k[1], k[0]))[:10]
151 lang_stats = [(x, {"count": y,
151 lang_stats = [(x, {"count": y,
152 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
152 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
153 for x, y in lang_stats_items]
153 for x, y in lang_stats_items]
154
154
155 c.trending_languages = json.dumps(lang_stats)
155 c.trending_languages = json.dumps(lang_stats)
156 else:
156 else:
157 c.no_data = True
157 c.no_data = True
158 c.trending_languages = json.dumps({})
158 c.trending_languages = json.dumps({})
159
159
160 c.enable_downloads = c.rhodecode_db_repo.enable_downloads
160 c.enable_downloads = c.rhodecode_db_repo.enable_downloads
161 c.repository_followers = self.scm_model.get_followers(
161 c.repository_followers = self.scm_model.get_followers(
162 c.rhodecode_db_repo)
162 c.rhodecode_db_repo)
163 c.repository_forks = self.scm_model.get_forks(c.rhodecode_db_repo)
163 c.repository_forks = self.scm_model.get_forks(c.rhodecode_db_repo)
164 c.repository_is_user_following = self.scm_model.is_following_repo(
164 c.repository_is_user_following = self.scm_model.is_following_repo(
165 c.repo_name, c.rhodecode_user.user_id)
165 c.repo_name, c.rhodecode_user.user_id)
166
166
167 if c.repository_requirements_missing:
167 if c.repository_requirements_missing:
168 return render('summary/missing_requirements.html')
168 return render('summary/missing_requirements.html')
169
169
170 c.readme_data, c.readme_file = \
170 c.readme_data, c.readme_file = \
171 self.__get_readme_data(c.rhodecode_db_repo)
171 self.__get_readme_data(c.rhodecode_db_repo)
172
172
173 _load_changelog_summary()
173 _load_changelog_summary()
174
174
175 if request.is_xhr:
175 if request.is_xhr:
176 return render('changelog/changelog_summary_data.html')
176 return render('changelog/changelog_summary_data.html')
177
177
178 return render('summary/summary.html')
178 return render('summary/summary.html')
179
179
180 @LoginRequired()
180 @LoginRequired()
181 @XHRRequired()
181 @XHRRequired()
182 @HasRepoPermissionAnyDecorator(
182 @HasRepoPermissionAnyDecorator(
183 'repository.read', 'repository.write', 'repository.admin')
183 'repository.read', 'repository.write', 'repository.admin')
184 @jsonify
184 @jsonify
185 def repo_stats(self, repo_name, commit_id):
185 def repo_stats(self, repo_name, commit_id):
186 _namespace = caches.get_repo_namespace_key(
186 _namespace = caches.get_repo_namespace_key(
187 caches.SUMMARY_STATS, repo_name)
187 caches.SUMMARY_STATS, repo_name)
188 show_stats = bool(c.rhodecode_db_repo.enable_statistics)
188 show_stats = bool(c.rhodecode_db_repo.enable_statistics)
189 cache_manager = caches.get_cache_manager('repo_cache_long', _namespace)
189 cache_manager = caches.get_cache_manager('repo_cache_long', _namespace)
190 _cache_key = caches.compute_key_from_params(
190 _cache_key = caches.compute_key_from_params(
191 repo_name, commit_id, show_stats)
191 repo_name, commit_id, show_stats)
192
192
193 def compute_stats():
193 def compute_stats():
194 code_stats = {}
194 code_stats = {}
195 size = 0
195 size = 0
196 try:
196 try:
197 scm_instance = c.rhodecode_db_repo.scm_instance()
197 scm_instance = c.rhodecode_db_repo.scm_instance()
198 commit = scm_instance.get_commit(commit_id)
198 commit = scm_instance.get_commit(commit_id)
199
199
200 for node in commit.get_filenodes_generator():
200 for node in commit.get_filenodes_generator():
201 size += node.size
201 size += node.size
202 if not show_stats:
202 if not show_stats:
203 continue
203 continue
204 ext = lower(node.extension)
204 ext = lower(node.extension)
205 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
205 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
206 if ext_info:
206 if ext_info:
207 if ext in code_stats:
207 if ext in code_stats:
208 code_stats[ext]['count'] += 1
208 code_stats[ext]['count'] += 1
209 else:
209 else:
210 code_stats[ext] = {"count": 1, "desc": ext_info}
210 code_stats[ext] = {"count": 1, "desc": ext_info}
211 except EmptyRepositoryError:
211 except EmptyRepositoryError:
212 pass
212 pass
213 return {'size': h.format_byte_size_binary(size),
213 return {'size': h.format_byte_size_binary(size),
214 'code_stats': code_stats}
214 'code_stats': code_stats}
215
215
216 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
216 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
217 return stats
217 return stats
218
218
219 def _switcher_reference_data(self, repo_name, references, is_svn):
219 def _switcher_reference_data(self, repo_name, references, is_svn):
220 """Prepare reference data for given `references`"""
220 """Prepare reference data for given `references`"""
221 items = []
221 items = []
222 for name, commit_id in references.items():
222 for name, commit_id in references.items():
223 use_commit_id = '/' in name or is_svn
223 use_commit_id = '/' in name or is_svn
224 items.append({
224 items.append({
225 'name': name,
225 'name': name,
226 'commit_id': commit_id,
226 'commit_id': commit_id,
227 'files_url': h.url(
227 'files_url': h.url(
228 'files_home',
228 'files_home',
229 repo_name=repo_name,
229 repo_name=repo_name,
230 f_path=name if is_svn else '',
230 f_path=name if is_svn else '',
231 revision=commit_id if use_commit_id else name,
231 revision=commit_id if use_commit_id else name,
232 at=name)
232 at=name)
233 })
233 })
234 return items
234 return items
235
235
236 @LoginRequired()
236 @LoginRequired()
237 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
237 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
238 'repository.admin')
238 'repository.admin')
239 @jsonify
239 @jsonify
240 def repo_refs_data(self, repo_name):
240 def repo_refs_data(self, repo_name):
241 repo = c.rhodecode_repo
241 repo = c.rhodecode_repo
242 refs_to_create = [
242 refs_to_create = [
243 (_("Branch"), repo.branches, 'branch'),
243 (_("Branch"), repo.branches, 'branch'),
244 (_("Tag"), repo.tags, 'tag'),
244 (_("Tag"), repo.tags, 'tag'),
245 (_("Bookmark"), repo.bookmarks, 'book'),
245 (_("Bookmark"), repo.bookmarks, 'book'),
246 ]
246 ]
247 res = self._create_reference_data(repo, repo_name, refs_to_create)
247 res = self._create_reference_data(repo, repo_name, refs_to_create)
248 data = {
248 data = {
249 'more': False,
249 'more': False,
250 'results': res
250 'results': res
251 }
251 }
252 return data
252 return data
253
253
254 @LoginRequired()
255 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
256 'repository.admin')
257 @jsonify
258 def repo_default_reviewers_data(self, repo_name):
259 return {
260 'reviewers': [utils.reviewer_as_json(
261 user=c.rhodecode_db_repo.user, reasons=None)]
262 }
263
254 @jsonify
264 @jsonify
255 def repo_refs_changelog_data(self, repo_name):
265 def repo_refs_changelog_data(self, repo_name):
256 repo = c.rhodecode_repo
266 repo = c.rhodecode_repo
257
267
258 refs_to_create = [
268 refs_to_create = [
259 (_("Branches"), repo.branches, 'branch'),
269 (_("Branches"), repo.branches, 'branch'),
260 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
270 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
261 # TODO: enable when vcs can handle bookmarks filters
271 # TODO: enable when vcs can handle bookmarks filters
262 # (_("Bookmarks"), repo.bookmarks, "book"),
272 # (_("Bookmarks"), repo.bookmarks, "book"),
263 ]
273 ]
264 res = self._create_reference_data(repo, repo_name, refs_to_create)
274 res = self._create_reference_data(repo, repo_name, refs_to_create)
265 data = {
275 data = {
266 'more': False,
276 'more': False,
267 'results': res
277 'results': res
268 }
278 }
269 return data
279 return data
270
280
271 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
281 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
272 format_ref_id = utils.get_format_ref_id(repo)
282 format_ref_id = utils.get_format_ref_id(repo)
273
283
274 result = []
284 result = []
275 for title, refs, ref_type in refs_to_create:
285 for title, refs, ref_type in refs_to_create:
276 if refs:
286 if refs:
277 result.append({
287 result.append({
278 'text': title,
288 'text': title,
279 'children': self._create_reference_items(
289 'children': self._create_reference_items(
280 repo, full_repo_name, refs, ref_type, format_ref_id),
290 repo, full_repo_name, refs, ref_type, format_ref_id),
281 })
291 })
282 return result
292 return result
283
293
284 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
294 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
285 format_ref_id):
295 format_ref_id):
286 result = []
296 result = []
287 is_svn = h.is_svn(repo)
297 is_svn = h.is_svn(repo)
288 for ref_name, raw_id in refs.iteritems():
298 for ref_name, raw_id in refs.iteritems():
289 files_url = self._create_files_url(
299 files_url = self._create_files_url(
290 repo, full_repo_name, ref_name, raw_id, is_svn)
300 repo, full_repo_name, ref_name, raw_id, is_svn)
291 result.append({
301 result.append({
292 'text': ref_name,
302 'text': ref_name,
293 'id': format_ref_id(ref_name, raw_id),
303 'id': format_ref_id(ref_name, raw_id),
294 'raw_id': raw_id,
304 'raw_id': raw_id,
295 'type': ref_type,
305 'type': ref_type,
296 'files_url': files_url,
306 'files_url': files_url,
297 })
307 })
298 return result
308 return result
299
309
300 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id,
310 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id,
301 is_svn):
311 is_svn):
302 use_commit_id = '/' in ref_name or is_svn
312 use_commit_id = '/' in ref_name or is_svn
303 return h.url(
313 return h.url(
304 'files_home',
314 'files_home',
305 repo_name=full_repo_name,
315 repo_name=full_repo_name,
306 f_path=ref_name if is_svn else '',
316 f_path=ref_name if is_svn else '',
307 revision=raw_id if use_commit_id else ref_name,
317 revision=raw_id if use_commit_id else ref_name,
308 at=ref_name)
318 at=ref_name)
@@ -1,88 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Utilities to be shared by multiple controllers.
22 Utilities to be shared by multiple controllers.
23
23
24 Should only contain utilities to be shared in the controller layer.
24 Should only contain utilities to be shared in the controller layer.
25 """
25 """
26
26
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.vcs.exceptions import RepositoryError
28 from rhodecode.lib.vcs.exceptions import RepositoryError
29
29
30 def parse_path_ref(ref, default_path=None):
30 def parse_path_ref(ref, default_path=None):
31 """
31 """
32 Parse out a path and reference combination and return both parts of it.
32 Parse out a path and reference combination and return both parts of it.
33
33
34 This is used to allow support of path based comparisons for Subversion
34 This is used to allow support of path based comparisons for Subversion
35 as an iterim solution in parameter handling.
35 as an iterim solution in parameter handling.
36 """
36 """
37 if '@' in ref:
37 if '@' in ref:
38 return ref.rsplit('@', 1)
38 return ref.rsplit('@', 1)
39 else:
39 else:
40 return default_path, ref
40 return default_path, ref
41
41
42
42
43 def get_format_ref_id(repo):
43 def get_format_ref_id(repo):
44 """Returns a `repo` specific reference formatter function"""
44 """Returns a `repo` specific reference formatter function"""
45 if h.is_svn(repo):
45 if h.is_svn(repo):
46 return _format_ref_id_svn
46 return _format_ref_id_svn
47 else:
47 else:
48 return _format_ref_id
48 return _format_ref_id
49
49
50
50
51 def _format_ref_id(name, raw_id):
51 def _format_ref_id(name, raw_id):
52 """Default formatting of a given reference `name`"""
52 """Default formatting of a given reference `name`"""
53 return name
53 return name
54
54
55
55
56 def _format_ref_id_svn(name, raw_id):
56 def _format_ref_id_svn(name, raw_id):
57 """Special way of formatting a reference for Subversion including path"""
57 """Special way of formatting a reference for Subversion including path"""
58 return '%s@%s' % (name, raw_id)
58 return '%s@%s' % (name, raw_id)
59
59
60
60
61 def get_commit_from_ref_name(repo, ref_name, ref_type=None):
61 def get_commit_from_ref_name(repo, ref_name, ref_type=None):
62 """
62 """
63 Gets the commit for a `ref_name` taking into account `ref_type`.
63 Gets the commit for a `ref_name` taking into account `ref_type`.
64 Needed in case a bookmark / tag share the same name.
64 Needed in case a bookmark / tag share the same name.
65
65
66 :param repo: the repo instance
66 :param repo: the repo instance
67 :param ref_name: the name of the ref to get
67 :param ref_name: the name of the ref to get
68 :param ref_type: optional, used to disambiguate colliding refs
68 :param ref_type: optional, used to disambiguate colliding refs
69 """
69 """
70 repo_scm = repo.scm_instance()
70 repo_scm = repo.scm_instance()
71 ref_type_mapping = {
71 ref_type_mapping = {
72 'book': repo_scm.bookmarks,
72 'book': repo_scm.bookmarks,
73 'bookmark': repo_scm.bookmarks,
73 'bookmark': repo_scm.bookmarks,
74 'tag': repo_scm.tags,
74 'tag': repo_scm.tags,
75 'branch': repo_scm.branches,
75 'branch': repo_scm.branches,
76 }
76 }
77
77
78 commit_id = ref_name
78 commit_id = ref_name
79 if repo_scm.alias != 'svn': # pass svn refs straight to backend until
79 if repo_scm.alias != 'svn': # pass svn refs straight to backend until
80 # the branch issue with svn is fixed
80 # the branch issue with svn is fixed
81 if ref_type and ref_type in ref_type_mapping:
81 if ref_type and ref_type in ref_type_mapping:
82 try:
82 try:
83 commit_id = ref_type_mapping[ref_type][ref_name]
83 commit_id = ref_type_mapping[ref_type][ref_name]
84 except KeyError:
84 except KeyError:
85 raise RepositoryError(
85 raise RepositoryError(
86 '%s "%s" does not exist' % (ref_type, ref_name))
86 '%s "%s" does not exist' % (ref_type, ref_name))
87
87
88 return repo_scm.get_commit(commit_id)
88 return repo_scm.get_commit(commit_id)
89
90
91 def reviewer_as_json(user, reasons):
92 """
93 Returns json struct of a reviewer for frontend
94
95 :param user: the reviewer
96 :param reasons: list of strings of why they are reviewers
97 """
98
99 return {
100 'user_id': user.user_id,
101 'reasons': reasons,
102 'username': user.username,
103 'firstname': user.firstname,
104 'lastname': user.lastname,
105 'gravatar_link': h.gravatar_url(user.email, 14),
106 }
@@ -1,895 +1,936 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Some simple helper functions
23 Some simple helper functions
24 """
24 """
25
25
26
26
27 import collections
27 import collections
28 import datetime
28 import datetime
29 import dateutil.relativedelta
29 import dateutil.relativedelta
30 import hashlib
30 import hashlib
31 import logging
31 import logging
32 import re
32 import re
33 import sys
33 import sys
34 import time
34 import time
35 import threading
35 import threading
36 import urllib
36 import urllib
37 import urlobject
37 import urlobject
38 import uuid
38 import uuid
39
39
40 import pygments.lexers
40 import pygments.lexers
41 import sqlalchemy
41 import sqlalchemy
42 import sqlalchemy.engine.url
42 import sqlalchemy.engine.url
43 import webob
43 import webob
44 import routes.util
44 import routes.util
45
45
46 import rhodecode
46 import rhodecode
47
47
48
48
49 def md5(s):
49 def md5(s):
50 return hashlib.md5(s).hexdigest()
50 return hashlib.md5(s).hexdigest()
51
51
52
52
53 def md5_safe(s):
53 def md5_safe(s):
54 return md5(safe_str(s))
54 return md5(safe_str(s))
55
55
56
56
57 def __get_lem(extra_mapping=None):
57 def __get_lem(extra_mapping=None):
58 """
58 """
59 Get language extension map based on what's inside pygments lexers
59 Get language extension map based on what's inside pygments lexers
60 """
60 """
61 d = collections.defaultdict(lambda: [])
61 d = collections.defaultdict(lambda: [])
62
62
63 def __clean(s):
63 def __clean(s):
64 s = s.lstrip('*')
64 s = s.lstrip('*')
65 s = s.lstrip('.')
65 s = s.lstrip('.')
66
66
67 if s.find('[') != -1:
67 if s.find('[') != -1:
68 exts = []
68 exts = []
69 start, stop = s.find('['), s.find(']')
69 start, stop = s.find('['), s.find(']')
70
70
71 for suffix in s[start + 1:stop]:
71 for suffix in s[start + 1:stop]:
72 exts.append(s[:s.find('[')] + suffix)
72 exts.append(s[:s.find('[')] + suffix)
73 return [e.lower() for e in exts]
73 return [e.lower() for e in exts]
74 else:
74 else:
75 return [s.lower()]
75 return [s.lower()]
76
76
77 for lx, t in sorted(pygments.lexers.LEXERS.items()):
77 for lx, t in sorted(pygments.lexers.LEXERS.items()):
78 m = map(__clean, t[-2])
78 m = map(__clean, t[-2])
79 if m:
79 if m:
80 m = reduce(lambda x, y: x + y, m)
80 m = reduce(lambda x, y: x + y, m)
81 for ext in m:
81 for ext in m:
82 desc = lx.replace('Lexer', '')
82 desc = lx.replace('Lexer', '')
83 d[ext].append(desc)
83 d[ext].append(desc)
84
84
85 data = dict(d)
85 data = dict(d)
86
86
87 extra_mapping = extra_mapping or {}
87 extra_mapping = extra_mapping or {}
88 if extra_mapping:
88 if extra_mapping:
89 for k, v in extra_mapping.items():
89 for k, v in extra_mapping.items():
90 if k not in data:
90 if k not in data:
91 # register new mapping2lexer
91 # register new mapping2lexer
92 data[k] = [v]
92 data[k] = [v]
93
93
94 return data
94 return data
95
95
96
96
97 def str2bool(_str):
97 def str2bool(_str):
98 """
98 """
99 returs True/False value from given string, it tries to translate the
99 returs True/False value from given string, it tries to translate the
100 string into boolean
100 string into boolean
101
101
102 :param _str: string value to translate into boolean
102 :param _str: string value to translate into boolean
103 :rtype: boolean
103 :rtype: boolean
104 :returns: boolean from given string
104 :returns: boolean from given string
105 """
105 """
106 if _str is None:
106 if _str is None:
107 return False
107 return False
108 if _str in (True, False):
108 if _str in (True, False):
109 return _str
109 return _str
110 _str = str(_str).strip().lower()
110 _str = str(_str).strip().lower()
111 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
111 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
112
112
113
113
114 def aslist(obj, sep=None, strip=True):
114 def aslist(obj, sep=None, strip=True):
115 """
115 """
116 Returns given string separated by sep as list
116 Returns given string separated by sep as list
117
117
118 :param obj:
118 :param obj:
119 :param sep:
119 :param sep:
120 :param strip:
120 :param strip:
121 """
121 """
122 if isinstance(obj, (basestring,)):
122 if isinstance(obj, (basestring,)):
123 lst = obj.split(sep)
123 lst = obj.split(sep)
124 if strip:
124 if strip:
125 lst = [v.strip() for v in lst]
125 lst = [v.strip() for v in lst]
126 return lst
126 return lst
127 elif isinstance(obj, (list, tuple)):
127 elif isinstance(obj, (list, tuple)):
128 return obj
128 return obj
129 elif obj is None:
129 elif obj is None:
130 return []
130 return []
131 else:
131 else:
132 return [obj]
132 return [obj]
133
133
134
134
135 def convert_line_endings(line, mode):
135 def convert_line_endings(line, mode):
136 """
136 """
137 Converts a given line "line end" accordingly to given mode
137 Converts a given line "line end" accordingly to given mode
138
138
139 Available modes are::
139 Available modes are::
140 0 - Unix
140 0 - Unix
141 1 - Mac
141 1 - Mac
142 2 - DOS
142 2 - DOS
143
143
144 :param line: given line to convert
144 :param line: given line to convert
145 :param mode: mode to convert to
145 :param mode: mode to convert to
146 :rtype: str
146 :rtype: str
147 :return: converted line according to mode
147 :return: converted line according to mode
148 """
148 """
149 if mode == 0:
149 if mode == 0:
150 line = line.replace('\r\n', '\n')
150 line = line.replace('\r\n', '\n')
151 line = line.replace('\r', '\n')
151 line = line.replace('\r', '\n')
152 elif mode == 1:
152 elif mode == 1:
153 line = line.replace('\r\n', '\r')
153 line = line.replace('\r\n', '\r')
154 line = line.replace('\n', '\r')
154 line = line.replace('\n', '\r')
155 elif mode == 2:
155 elif mode == 2:
156 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
156 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
157 return line
157 return line
158
158
159
159
160 def detect_mode(line, default):
160 def detect_mode(line, default):
161 """
161 """
162 Detects line break for given line, if line break couldn't be found
162 Detects line break for given line, if line break couldn't be found
163 given default value is returned
163 given default value is returned
164
164
165 :param line: str line
165 :param line: str line
166 :param default: default
166 :param default: default
167 :rtype: int
167 :rtype: int
168 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
168 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
169 """
169 """
170 if line.endswith('\r\n'):
170 if line.endswith('\r\n'):
171 return 2
171 return 2
172 elif line.endswith('\n'):
172 elif line.endswith('\n'):
173 return 0
173 return 0
174 elif line.endswith('\r'):
174 elif line.endswith('\r'):
175 return 1
175 return 1
176 else:
176 else:
177 return default
177 return default
178
178
179
179
180 def safe_int(val, default=None):
180 def safe_int(val, default=None):
181 """
181 """
182 Returns int() of val if val is not convertable to int use default
182 Returns int() of val if val is not convertable to int use default
183 instead
183 instead
184
184
185 :param val:
185 :param val:
186 :param default:
186 :param default:
187 """
187 """
188
188
189 try:
189 try:
190 val = int(val)
190 val = int(val)
191 except (ValueError, TypeError):
191 except (ValueError, TypeError):
192 val = default
192 val = default
193
193
194 return val
194 return val
195
195
196
196
197 def safe_unicode(str_, from_encoding=None):
197 def safe_unicode(str_, from_encoding=None):
198 """
198 """
199 safe unicode function. Does few trick to turn str_ into unicode
199 safe unicode function. Does few trick to turn str_ into unicode
200
200
201 In case of UnicodeDecode error, we try to return it with encoding detected
201 In case of UnicodeDecode error, we try to return it with encoding detected
202 by chardet library if it fails fallback to unicode with errors replaced
202 by chardet library if it fails fallback to unicode with errors replaced
203
203
204 :param str_: string to decode
204 :param str_: string to decode
205 :rtype: unicode
205 :rtype: unicode
206 :returns: unicode object
206 :returns: unicode object
207 """
207 """
208 if isinstance(str_, unicode):
208 if isinstance(str_, unicode):
209 return str_
209 return str_
210
210
211 if not from_encoding:
211 if not from_encoding:
212 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
212 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
213 'utf8'), sep=',')
213 'utf8'), sep=',')
214 from_encoding = DEFAULT_ENCODINGS
214 from_encoding = DEFAULT_ENCODINGS
215
215
216 if not isinstance(from_encoding, (list, tuple)):
216 if not isinstance(from_encoding, (list, tuple)):
217 from_encoding = [from_encoding]
217 from_encoding = [from_encoding]
218
218
219 try:
219 try:
220 return unicode(str_)
220 return unicode(str_)
221 except UnicodeDecodeError:
221 except UnicodeDecodeError:
222 pass
222 pass
223
223
224 for enc in from_encoding:
224 for enc in from_encoding:
225 try:
225 try:
226 return unicode(str_, enc)
226 return unicode(str_, enc)
227 except UnicodeDecodeError:
227 except UnicodeDecodeError:
228 pass
228 pass
229
229
230 try:
230 try:
231 import chardet
231 import chardet
232 encoding = chardet.detect(str_)['encoding']
232 encoding = chardet.detect(str_)['encoding']
233 if encoding is None:
233 if encoding is None:
234 raise Exception()
234 raise Exception()
235 return str_.decode(encoding)
235 return str_.decode(encoding)
236 except (ImportError, UnicodeDecodeError, Exception):
236 except (ImportError, UnicodeDecodeError, Exception):
237 return unicode(str_, from_encoding[0], 'replace')
237 return unicode(str_, from_encoding[0], 'replace')
238
238
239
239
240 def safe_str(unicode_, to_encoding=None):
240 def safe_str(unicode_, to_encoding=None):
241 """
241 """
242 safe str function. Does few trick to turn unicode_ into string
242 safe str function. Does few trick to turn unicode_ into string
243
243
244 In case of UnicodeEncodeError, we try to return it with encoding detected
244 In case of UnicodeEncodeError, we try to return it with encoding detected
245 by chardet library if it fails fallback to string with errors replaced
245 by chardet library if it fails fallback to string with errors replaced
246
246
247 :param unicode_: unicode to encode
247 :param unicode_: unicode to encode
248 :rtype: str
248 :rtype: str
249 :returns: str object
249 :returns: str object
250 """
250 """
251
251
252 # if it's not basestr cast to str
252 # if it's not basestr cast to str
253 if not isinstance(unicode_, basestring):
253 if not isinstance(unicode_, basestring):
254 return str(unicode_)
254 return str(unicode_)
255
255
256 if isinstance(unicode_, str):
256 if isinstance(unicode_, str):
257 return unicode_
257 return unicode_
258
258
259 if not to_encoding:
259 if not to_encoding:
260 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
260 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
261 'utf8'), sep=',')
261 'utf8'), sep=',')
262 to_encoding = DEFAULT_ENCODINGS
262 to_encoding = DEFAULT_ENCODINGS
263
263
264 if not isinstance(to_encoding, (list, tuple)):
264 if not isinstance(to_encoding, (list, tuple)):
265 to_encoding = [to_encoding]
265 to_encoding = [to_encoding]
266
266
267 for enc in to_encoding:
267 for enc in to_encoding:
268 try:
268 try:
269 return unicode_.encode(enc)
269 return unicode_.encode(enc)
270 except UnicodeEncodeError:
270 except UnicodeEncodeError:
271 pass
271 pass
272
272
273 try:
273 try:
274 import chardet
274 import chardet
275 encoding = chardet.detect(unicode_)['encoding']
275 encoding = chardet.detect(unicode_)['encoding']
276 if encoding is None:
276 if encoding is None:
277 raise UnicodeEncodeError()
277 raise UnicodeEncodeError()
278
278
279 return unicode_.encode(encoding)
279 return unicode_.encode(encoding)
280 except (ImportError, UnicodeEncodeError):
280 except (ImportError, UnicodeEncodeError):
281 return unicode_.encode(to_encoding[0], 'replace')
281 return unicode_.encode(to_encoding[0], 'replace')
282
282
283
283
284 def remove_suffix(s, suffix):
284 def remove_suffix(s, suffix):
285 if s.endswith(suffix):
285 if s.endswith(suffix):
286 s = s[:-1 * len(suffix)]
286 s = s[:-1 * len(suffix)]
287 return s
287 return s
288
288
289
289
290 def remove_prefix(s, prefix):
290 def remove_prefix(s, prefix):
291 if s.startswith(prefix):
291 if s.startswith(prefix):
292 s = s[len(prefix):]
292 s = s[len(prefix):]
293 return s
293 return s
294
294
295
295
296 def find_calling_context(ignore_modules=None):
296 def find_calling_context(ignore_modules=None):
297 """
297 """
298 Look through the calling stack and return the frame which called
298 Look through the calling stack and return the frame which called
299 this function and is part of core module ( ie. rhodecode.* )
299 this function and is part of core module ( ie. rhodecode.* )
300
300
301 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
301 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
302 """
302 """
303
303
304 ignore_modules = ignore_modules or []
304 ignore_modules = ignore_modules or []
305
305
306 f = sys._getframe(2)
306 f = sys._getframe(2)
307 while f.f_back is not None:
307 while f.f_back is not None:
308 name = f.f_globals.get('__name__')
308 name = f.f_globals.get('__name__')
309 if name and name.startswith(__name__.split('.')[0]):
309 if name and name.startswith(__name__.split('.')[0]):
310 if name not in ignore_modules:
310 if name not in ignore_modules:
311 return f
311 return f
312 f = f.f_back
312 f = f.f_back
313 return None
313 return None
314
314
315
315
316 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
316 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
317 """Custom engine_from_config functions."""
317 """Custom engine_from_config functions."""
318 log = logging.getLogger('sqlalchemy.engine')
318 log = logging.getLogger('sqlalchemy.engine')
319 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
319 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
320
320
321 def color_sql(sql):
321 def color_sql(sql):
322 color_seq = '\033[1;33m' # This is yellow: code 33
322 color_seq = '\033[1;33m' # This is yellow: code 33
323 normal = '\x1b[0m'
323 normal = '\x1b[0m'
324 return ''.join([color_seq, sql, normal])
324 return ''.join([color_seq, sql, normal])
325
325
326 if configuration['debug']:
326 if configuration['debug']:
327 # attach events only for debug configuration
327 # attach events only for debug configuration
328
328
329 def before_cursor_execute(conn, cursor, statement,
329 def before_cursor_execute(conn, cursor, statement,
330 parameters, context, executemany):
330 parameters, context, executemany):
331 setattr(conn, 'query_start_time', time.time())
331 setattr(conn, 'query_start_time', time.time())
332 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
332 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
333 calling_context = find_calling_context(ignore_modules=[
333 calling_context = find_calling_context(ignore_modules=[
334 'rhodecode.lib.caching_query',
334 'rhodecode.lib.caching_query',
335 'rhodecode.model.settings',
335 'rhodecode.model.settings',
336 ])
336 ])
337 if calling_context:
337 if calling_context:
338 log.info(color_sql('call context %s:%s' % (
338 log.info(color_sql('call context %s:%s' % (
339 calling_context.f_code.co_filename,
339 calling_context.f_code.co_filename,
340 calling_context.f_lineno,
340 calling_context.f_lineno,
341 )))
341 )))
342
342
343 def after_cursor_execute(conn, cursor, statement,
343 def after_cursor_execute(conn, cursor, statement,
344 parameters, context, executemany):
344 parameters, context, executemany):
345 delattr(conn, 'query_start_time')
345 delattr(conn, 'query_start_time')
346
346
347 sqlalchemy.event.listen(engine, "before_cursor_execute",
347 sqlalchemy.event.listen(engine, "before_cursor_execute",
348 before_cursor_execute)
348 before_cursor_execute)
349 sqlalchemy.event.listen(engine, "after_cursor_execute",
349 sqlalchemy.event.listen(engine, "after_cursor_execute",
350 after_cursor_execute)
350 after_cursor_execute)
351
351
352 return engine
352 return engine
353
353
354
354
355 def get_encryption_key(config):
355 def get_encryption_key(config):
356 secret = config.get('rhodecode.encrypted_values.secret')
356 secret = config.get('rhodecode.encrypted_values.secret')
357 default = config['beaker.session.secret']
357 default = config['beaker.session.secret']
358 return secret or default
358 return secret or default
359
359
360
360
361 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
361 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
362 short_format=False):
362 short_format=False):
363 """
363 """
364 Turns a datetime into an age string.
364 Turns a datetime into an age string.
365 If show_short_version is True, this generates a shorter string with
365 If show_short_version is True, this generates a shorter string with
366 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
366 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
367
367
368 * IMPORTANT*
368 * IMPORTANT*
369 Code of this function is written in special way so it's easier to
369 Code of this function is written in special way so it's easier to
370 backport it to javascript. If you mean to update it, please also update
370 backport it to javascript. If you mean to update it, please also update
371 `jquery.timeago-extension.js` file
371 `jquery.timeago-extension.js` file
372
372
373 :param prevdate: datetime object
373 :param prevdate: datetime object
374 :param now: get current time, if not define we use
374 :param now: get current time, if not define we use
375 `datetime.datetime.now()`
375 `datetime.datetime.now()`
376 :param show_short_version: if it should approximate the date and
376 :param show_short_version: if it should approximate the date and
377 return a shorter string
377 return a shorter string
378 :param show_suffix:
378 :param show_suffix:
379 :param short_format: show short format, eg 2D instead of 2 days
379 :param short_format: show short format, eg 2D instead of 2 days
380 :rtype: unicode
380 :rtype: unicode
381 :returns: unicode words describing age
381 :returns: unicode words describing age
382 """
382 """
383 from pylons.i18n.translation import _, ungettext
383 from pylons.i18n.translation import _, ungettext
384
384
385 def _get_relative_delta(now, prevdate):
385 def _get_relative_delta(now, prevdate):
386 base = dateutil.relativedelta.relativedelta(now, prevdate)
386 base = dateutil.relativedelta.relativedelta(now, prevdate)
387 return {
387 return {
388 'year': base.years,
388 'year': base.years,
389 'month': base.months,
389 'month': base.months,
390 'day': base.days,
390 'day': base.days,
391 'hour': base.hours,
391 'hour': base.hours,
392 'minute': base.minutes,
392 'minute': base.minutes,
393 'second': base.seconds,
393 'second': base.seconds,
394 }
394 }
395
395
396 def _is_leap_year(year):
396 def _is_leap_year(year):
397 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
397 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
398
398
399 def get_month(prevdate):
399 def get_month(prevdate):
400 return prevdate.month
400 return prevdate.month
401
401
402 def get_year(prevdate):
402 def get_year(prevdate):
403 return prevdate.year
403 return prevdate.year
404
404
405 now = now or datetime.datetime.now()
405 now = now or datetime.datetime.now()
406 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
406 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
407 deltas = {}
407 deltas = {}
408 future = False
408 future = False
409
409
410 if prevdate > now:
410 if prevdate > now:
411 now_old = now
411 now_old = now
412 now = prevdate
412 now = prevdate
413 prevdate = now_old
413 prevdate = now_old
414 future = True
414 future = True
415 if future:
415 if future:
416 prevdate = prevdate.replace(microsecond=0)
416 prevdate = prevdate.replace(microsecond=0)
417 # Get date parts deltas
417 # Get date parts deltas
418 for part in order:
418 for part in order:
419 rel_delta = _get_relative_delta(now, prevdate)
419 rel_delta = _get_relative_delta(now, prevdate)
420 deltas[part] = rel_delta[part]
420 deltas[part] = rel_delta[part]
421
421
422 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
422 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
423 # not 1 hour, -59 minutes and -59 seconds)
423 # not 1 hour, -59 minutes and -59 seconds)
424 offsets = [[5, 60], [4, 60], [3, 24]]
424 offsets = [[5, 60], [4, 60], [3, 24]]
425 for element in offsets: # seconds, minutes, hours
425 for element in offsets: # seconds, minutes, hours
426 num = element[0]
426 num = element[0]
427 length = element[1]
427 length = element[1]
428
428
429 part = order[num]
429 part = order[num]
430 carry_part = order[num - 1]
430 carry_part = order[num - 1]
431
431
432 if deltas[part] < 0:
432 if deltas[part] < 0:
433 deltas[part] += length
433 deltas[part] += length
434 deltas[carry_part] -= 1
434 deltas[carry_part] -= 1
435
435
436 # Same thing for days except that the increment depends on the (variable)
436 # Same thing for days except that the increment depends on the (variable)
437 # number of days in the month
437 # number of days in the month
438 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
438 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
439 if deltas['day'] < 0:
439 if deltas['day'] < 0:
440 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
440 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
441 deltas['day'] += 29
441 deltas['day'] += 29
442 else:
442 else:
443 deltas['day'] += month_lengths[get_month(prevdate) - 1]
443 deltas['day'] += month_lengths[get_month(prevdate) - 1]
444
444
445 deltas['month'] -= 1
445 deltas['month'] -= 1
446
446
447 if deltas['month'] < 0:
447 if deltas['month'] < 0:
448 deltas['month'] += 12
448 deltas['month'] += 12
449 deltas['year'] -= 1
449 deltas['year'] -= 1
450
450
451 # Format the result
451 # Format the result
452 if short_format:
452 if short_format:
453 fmt_funcs = {
453 fmt_funcs = {
454 'year': lambda d: u'%dy' % d,
454 'year': lambda d: u'%dy' % d,
455 'month': lambda d: u'%dm' % d,
455 'month': lambda d: u'%dm' % d,
456 'day': lambda d: u'%dd' % d,
456 'day': lambda d: u'%dd' % d,
457 'hour': lambda d: u'%dh' % d,
457 'hour': lambda d: u'%dh' % d,
458 'minute': lambda d: u'%dmin' % d,
458 'minute': lambda d: u'%dmin' % d,
459 'second': lambda d: u'%dsec' % d,
459 'second': lambda d: u'%dsec' % d,
460 }
460 }
461 else:
461 else:
462 fmt_funcs = {
462 fmt_funcs = {
463 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
463 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
464 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
464 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
465 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
465 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
466 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
466 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
467 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
467 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
468 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
468 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
469 }
469 }
470
470
471 i = 0
471 i = 0
472 for part in order:
472 for part in order:
473 value = deltas[part]
473 value = deltas[part]
474 if value != 0:
474 if value != 0:
475
475
476 if i < 5:
476 if i < 5:
477 sub_part = order[i + 1]
477 sub_part = order[i + 1]
478 sub_value = deltas[sub_part]
478 sub_value = deltas[sub_part]
479 else:
479 else:
480 sub_value = 0
480 sub_value = 0
481
481
482 if sub_value == 0 or show_short_version:
482 if sub_value == 0 or show_short_version:
483 _val = fmt_funcs[part](value)
483 _val = fmt_funcs[part](value)
484 if future:
484 if future:
485 if show_suffix:
485 if show_suffix:
486 return _(u'in %s') % _val
486 return _(u'in %s') % _val
487 else:
487 else:
488 return _val
488 return _val
489
489
490 else:
490 else:
491 if show_suffix:
491 if show_suffix:
492 return _(u'%s ago') % _val
492 return _(u'%s ago') % _val
493 else:
493 else:
494 return _val
494 return _val
495
495
496 val = fmt_funcs[part](value)
496 val = fmt_funcs[part](value)
497 val_detail = fmt_funcs[sub_part](sub_value)
497 val_detail = fmt_funcs[sub_part](sub_value)
498
498
499 if short_format:
499 if short_format:
500 datetime_tmpl = u'%s, %s'
500 datetime_tmpl = u'%s, %s'
501 if show_suffix:
501 if show_suffix:
502 datetime_tmpl = _(u'%s, %s ago')
502 datetime_tmpl = _(u'%s, %s ago')
503 if future:
503 if future:
504 datetime_tmpl = _(u'in %s, %s')
504 datetime_tmpl = _(u'in %s, %s')
505 else:
505 else:
506 datetime_tmpl = _(u'%s and %s')
506 datetime_tmpl = _(u'%s and %s')
507 if show_suffix:
507 if show_suffix:
508 datetime_tmpl = _(u'%s and %s ago')
508 datetime_tmpl = _(u'%s and %s ago')
509 if future:
509 if future:
510 datetime_tmpl = _(u'in %s and %s')
510 datetime_tmpl = _(u'in %s and %s')
511
511
512 return datetime_tmpl % (val, val_detail)
512 return datetime_tmpl % (val, val_detail)
513 i += 1
513 i += 1
514 return _(u'just now')
514 return _(u'just now')
515
515
516
516
517 def uri_filter(uri):
517 def uri_filter(uri):
518 """
518 """
519 Removes user:password from given url string
519 Removes user:password from given url string
520
520
521 :param uri:
521 :param uri:
522 :rtype: unicode
522 :rtype: unicode
523 :returns: filtered list of strings
523 :returns: filtered list of strings
524 """
524 """
525 if not uri:
525 if not uri:
526 return ''
526 return ''
527
527
528 proto = ''
528 proto = ''
529
529
530 for pat in ('https://', 'http://'):
530 for pat in ('https://', 'http://'):
531 if uri.startswith(pat):
531 if uri.startswith(pat):
532 uri = uri[len(pat):]
532 uri = uri[len(pat):]
533 proto = pat
533 proto = pat
534 break
534 break
535
535
536 # remove passwords and username
536 # remove passwords and username
537 uri = uri[uri.find('@') + 1:]
537 uri = uri[uri.find('@') + 1:]
538
538
539 # get the port
539 # get the port
540 cred_pos = uri.find(':')
540 cred_pos = uri.find(':')
541 if cred_pos == -1:
541 if cred_pos == -1:
542 host, port = uri, None
542 host, port = uri, None
543 else:
543 else:
544 host, port = uri[:cred_pos], uri[cred_pos + 1:]
544 host, port = uri[:cred_pos], uri[cred_pos + 1:]
545
545
546 return filter(None, [proto, host, port])
546 return filter(None, [proto, host, port])
547
547
548
548
549 def credentials_filter(uri):
549 def credentials_filter(uri):
550 """
550 """
551 Returns a url with removed credentials
551 Returns a url with removed credentials
552
552
553 :param uri:
553 :param uri:
554 """
554 """
555
555
556 uri = uri_filter(uri)
556 uri = uri_filter(uri)
557 # check if we have port
557 # check if we have port
558 if len(uri) > 2 and uri[2]:
558 if len(uri) > 2 and uri[2]:
559 uri[2] = ':' + uri[2]
559 uri[2] = ':' + uri[2]
560
560
561 return ''.join(uri)
561 return ''.join(uri)
562
562
563
563
564 def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
564 def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
565 parsed_url = urlobject.URLObject(qualifed_home_url)
565 parsed_url = urlobject.URLObject(qualifed_home_url)
566 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
566 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
567 args = {
567 args = {
568 'scheme': parsed_url.scheme,
568 'scheme': parsed_url.scheme,
569 'user': '',
569 'user': '',
570 # path if we use proxy-prefix
570 # path if we use proxy-prefix
571 'netloc': parsed_url.netloc+decoded_path,
571 'netloc': parsed_url.netloc+decoded_path,
572 'prefix': decoded_path,
572 'prefix': decoded_path,
573 'repo': repo_name,
573 'repo': repo_name,
574 'repoid': str(repo_id)
574 'repoid': str(repo_id)
575 }
575 }
576 args.update(override)
576 args.update(override)
577 args['user'] = urllib.quote(safe_str(args['user']))
577 args['user'] = urllib.quote(safe_str(args['user']))
578
578
579 for k, v in args.items():
579 for k, v in args.items():
580 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
580 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
581
581
582 # remove leading @ sign if it's present. Case of empty user
582 # remove leading @ sign if it's present. Case of empty user
583 url_obj = urlobject.URLObject(uri_tmpl)
583 url_obj = urlobject.URLObject(uri_tmpl)
584 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
584 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
585
585
586 return safe_unicode(url)
586 return safe_unicode(url)
587
587
588
588
589 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
589 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
590 """
590 """
591 Safe version of get_commit if this commit doesn't exists for a
591 Safe version of get_commit if this commit doesn't exists for a
592 repository it returns a Dummy one instead
592 repository it returns a Dummy one instead
593
593
594 :param repo: repository instance
594 :param repo: repository instance
595 :param commit_id: commit id as str
595 :param commit_id: commit id as str
596 :param pre_load: optional list of commit attributes to load
596 :param pre_load: optional list of commit attributes to load
597 """
597 """
598 # TODO(skreft): remove these circular imports
598 # TODO(skreft): remove these circular imports
599 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
599 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
600 from rhodecode.lib.vcs.exceptions import RepositoryError
600 from rhodecode.lib.vcs.exceptions import RepositoryError
601 if not isinstance(repo, BaseRepository):
601 if not isinstance(repo, BaseRepository):
602 raise Exception('You must pass an Repository '
602 raise Exception('You must pass an Repository '
603 'object as first argument got %s', type(repo))
603 'object as first argument got %s', type(repo))
604
604
605 try:
605 try:
606 commit = repo.get_commit(
606 commit = repo.get_commit(
607 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
607 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
608 except (RepositoryError, LookupError):
608 except (RepositoryError, LookupError):
609 commit = EmptyCommit()
609 commit = EmptyCommit()
610 return commit
610 return commit
611
611
612
612
613 def datetime_to_time(dt):
613 def datetime_to_time(dt):
614 if dt:
614 if dt:
615 return time.mktime(dt.timetuple())
615 return time.mktime(dt.timetuple())
616
616
617
617
618 def time_to_datetime(tm):
618 def time_to_datetime(tm):
619 if tm:
619 if tm:
620 if isinstance(tm, basestring):
620 if isinstance(tm, basestring):
621 try:
621 try:
622 tm = float(tm)
622 tm = float(tm)
623 except ValueError:
623 except ValueError:
624 return
624 return
625 return datetime.datetime.fromtimestamp(tm)
625 return datetime.datetime.fromtimestamp(tm)
626
626
627
627
628 def time_to_utcdatetime(tm):
628 def time_to_utcdatetime(tm):
629 if tm:
629 if tm:
630 if isinstance(tm, basestring):
630 if isinstance(tm, basestring):
631 try:
631 try:
632 tm = float(tm)
632 tm = float(tm)
633 except ValueError:
633 except ValueError:
634 return
634 return
635 return datetime.datetime.utcfromtimestamp(tm)
635 return datetime.datetime.utcfromtimestamp(tm)
636
636
637
637
638 MENTIONS_REGEX = re.compile(
638 MENTIONS_REGEX = re.compile(
639 # ^@ or @ without any special chars in front
639 # ^@ or @ without any special chars in front
640 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
640 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
641 # main body starts with letter, then can be . - _
641 # main body starts with letter, then can be . - _
642 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
642 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
643 re.VERBOSE | re.MULTILINE)
643 re.VERBOSE | re.MULTILINE)
644
644
645
645
646 def extract_mentioned_users(s):
646 def extract_mentioned_users(s):
647 """
647 """
648 Returns unique usernames from given string s that have @mention
648 Returns unique usernames from given string s that have @mention
649
649
650 :param s: string to get mentions
650 :param s: string to get mentions
651 """
651 """
652 usrs = set()
652 usrs = set()
653 for username in MENTIONS_REGEX.findall(s):
653 for username in MENTIONS_REGEX.findall(s):
654 usrs.add(username)
654 usrs.add(username)
655
655
656 return sorted(list(usrs), key=lambda k: k.lower())
656 return sorted(list(usrs), key=lambda k: k.lower())
657
657
658
658
659 class AttributeDict(dict):
659 class AttributeDict(dict):
660 def __getattr__(self, attr):
660 def __getattr__(self, attr):
661 return self.get(attr, None)
661 return self.get(attr, None)
662 __setattr__ = dict.__setitem__
662 __setattr__ = dict.__setitem__
663 __delattr__ = dict.__delitem__
663 __delattr__ = dict.__delitem__
664
664
665
665
666 def fix_PATH(os_=None):
666 def fix_PATH(os_=None):
667 """
667 """
668 Get current active python path, and append it to PATH variable to fix
668 Get current active python path, and append it to PATH variable to fix
669 issues of subprocess calls and different python versions
669 issues of subprocess calls and different python versions
670 """
670 """
671 if os_ is None:
671 if os_ is None:
672 import os
672 import os
673 else:
673 else:
674 os = os_
674 os = os_
675
675
676 cur_path = os.path.split(sys.executable)[0]
676 cur_path = os.path.split(sys.executable)[0]
677 if not os.environ['PATH'].startswith(cur_path):
677 if not os.environ['PATH'].startswith(cur_path):
678 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
678 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
679
679
680
680
681 def obfuscate_url_pw(engine):
681 def obfuscate_url_pw(engine):
682 _url = engine or ''
682 _url = engine or ''
683 try:
683 try:
684 _url = sqlalchemy.engine.url.make_url(engine)
684 _url = sqlalchemy.engine.url.make_url(engine)
685 if _url.password:
685 if _url.password:
686 _url.password = 'XXXXX'
686 _url.password = 'XXXXX'
687 except Exception:
687 except Exception:
688 pass
688 pass
689 return unicode(_url)
689 return unicode(_url)
690
690
691
691
692 def get_server_url(environ):
692 def get_server_url(environ):
693 req = webob.Request(environ)
693 req = webob.Request(environ)
694 return req.host_url + req.script_name
694 return req.host_url + req.script_name
695
695
696
696
697 def unique_id(hexlen=32):
697 def unique_id(hexlen=32):
698 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
698 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
699 return suuid(truncate_to=hexlen, alphabet=alphabet)
699 return suuid(truncate_to=hexlen, alphabet=alphabet)
700
700
701
701
702 def suuid(url=None, truncate_to=22, alphabet=None):
702 def suuid(url=None, truncate_to=22, alphabet=None):
703 """
703 """
704 Generate and return a short URL safe UUID.
704 Generate and return a short URL safe UUID.
705
705
706 If the url parameter is provided, set the namespace to the provided
706 If the url parameter is provided, set the namespace to the provided
707 URL and generate a UUID.
707 URL and generate a UUID.
708
708
709 :param url to get the uuid for
709 :param url to get the uuid for
710 :truncate_to: truncate the basic 22 UUID to shorter version
710 :truncate_to: truncate the basic 22 UUID to shorter version
711
711
712 The IDs won't be universally unique any longer, but the probability of
712 The IDs won't be universally unique any longer, but the probability of
713 a collision will still be very low.
713 a collision will still be very low.
714 """
714 """
715 # Define our alphabet.
715 # Define our alphabet.
716 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
716 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
717
717
718 # If no URL is given, generate a random UUID.
718 # If no URL is given, generate a random UUID.
719 if url is None:
719 if url is None:
720 unique_id = uuid.uuid4().int
720 unique_id = uuid.uuid4().int
721 else:
721 else:
722 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
722 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
723
723
724 alphabet_length = len(_ALPHABET)
724 alphabet_length = len(_ALPHABET)
725 output = []
725 output = []
726 while unique_id > 0:
726 while unique_id > 0:
727 digit = unique_id % alphabet_length
727 digit = unique_id % alphabet_length
728 output.append(_ALPHABET[digit])
728 output.append(_ALPHABET[digit])
729 unique_id = int(unique_id / alphabet_length)
729 unique_id = int(unique_id / alphabet_length)
730 return "".join(output)[:truncate_to]
730 return "".join(output)[:truncate_to]
731
731
732
732
733 def get_current_rhodecode_user():
733 def get_current_rhodecode_user():
734 """
734 """
735 Gets rhodecode user from threadlocal tmpl_context variable if it's
735 Gets rhodecode user from threadlocal tmpl_context variable if it's
736 defined, else returns None.
736 defined, else returns None.
737 """
737 """
738 from pylons import tmpl_context as c
738 from pylons import tmpl_context as c
739 if hasattr(c, 'rhodecode_user'):
739 if hasattr(c, 'rhodecode_user'):
740 return c.rhodecode_user
740 return c.rhodecode_user
741
741
742 return None
742 return None
743
743
744
744
745 def action_logger_generic(action, namespace=''):
745 def action_logger_generic(action, namespace=''):
746 """
746 """
747 A generic logger for actions useful to the system overview, tries to find
747 A generic logger for actions useful to the system overview, tries to find
748 an acting user for the context of the call otherwise reports unknown user
748 an acting user for the context of the call otherwise reports unknown user
749
749
750 :param action: logging message eg 'comment 5 deleted'
750 :param action: logging message eg 'comment 5 deleted'
751 :param type: string
751 :param type: string
752
752
753 :param namespace: namespace of the logging message eg. 'repo.comments'
753 :param namespace: namespace of the logging message eg. 'repo.comments'
754 :param type: string
754 :param type: string
755
755
756 """
756 """
757
757
758 logger_name = 'rhodecode.actions'
758 logger_name = 'rhodecode.actions'
759
759
760 if namespace:
760 if namespace:
761 logger_name += '.' + namespace
761 logger_name += '.' + namespace
762
762
763 log = logging.getLogger(logger_name)
763 log = logging.getLogger(logger_name)
764
764
765 # get a user if we can
765 # get a user if we can
766 user = get_current_rhodecode_user()
766 user = get_current_rhodecode_user()
767
767
768 logfunc = log.info
768 logfunc = log.info
769
769
770 if not user:
770 if not user:
771 user = '<unknown user>'
771 user = '<unknown user>'
772 logfunc = log.warning
772 logfunc = log.warning
773
773
774 logfunc('Logging action by {}: {}'.format(user, action))
774 logfunc('Logging action by {}: {}'.format(user, action))
775
775
776
776
777 def escape_split(text, sep=',', maxsplit=-1):
777 def escape_split(text, sep=',', maxsplit=-1):
778 r"""
778 r"""
779 Allows for escaping of the separator: e.g. arg='foo\, bar'
779 Allows for escaping of the separator: e.g. arg='foo\, bar'
780
780
781 It should be noted that the way bash et. al. do command line parsing, those
781 It should be noted that the way bash et. al. do command line parsing, those
782 single quotes are required.
782 single quotes are required.
783 """
783 """
784 escaped_sep = r'\%s' % sep
784 escaped_sep = r'\%s' % sep
785
785
786 if escaped_sep not in text:
786 if escaped_sep not in text:
787 return text.split(sep, maxsplit)
787 return text.split(sep, maxsplit)
788
788
789 before, _mid, after = text.partition(escaped_sep)
789 before, _mid, after = text.partition(escaped_sep)
790 startlist = before.split(sep, maxsplit) # a regular split is fine here
790 startlist = before.split(sep, maxsplit) # a regular split is fine here
791 unfinished = startlist[-1]
791 unfinished = startlist[-1]
792 startlist = startlist[:-1]
792 startlist = startlist[:-1]
793
793
794 # recurse because there may be more escaped separators
794 # recurse because there may be more escaped separators
795 endlist = escape_split(after, sep, maxsplit)
795 endlist = escape_split(after, sep, maxsplit)
796
796
797 # finish building the escaped value. we use endlist[0] becaue the first
797 # finish building the escaped value. we use endlist[0] becaue the first
798 # part of the string sent in recursion is the rest of the escaped value.
798 # part of the string sent in recursion is the rest of the escaped value.
799 unfinished += sep + endlist[0]
799 unfinished += sep + endlist[0]
800
800
801 return startlist + [unfinished] + endlist[1:] # put together all the parts
801 return startlist + [unfinished] + endlist[1:] # put together all the parts
802
802
803
803
804 class OptionalAttr(object):
804 class OptionalAttr(object):
805 """
805 """
806 Special Optional Option that defines other attribute. Example::
806 Special Optional Option that defines other attribute. Example::
807
807
808 def test(apiuser, userid=Optional(OAttr('apiuser')):
808 def test(apiuser, userid=Optional(OAttr('apiuser')):
809 user = Optional.extract(userid)
809 user = Optional.extract(userid)
810 # calls
810 # calls
811
811
812 """
812 """
813
813
814 def __init__(self, attr_name):
814 def __init__(self, attr_name):
815 self.attr_name = attr_name
815 self.attr_name = attr_name
816
816
817 def __repr__(self):
817 def __repr__(self):
818 return '<OptionalAttr:%s>' % self.attr_name
818 return '<OptionalAttr:%s>' % self.attr_name
819
819
820 def __call__(self):
820 def __call__(self):
821 return self
821 return self
822
822
823
823
824 # alias
824 # alias
825 OAttr = OptionalAttr
825 OAttr = OptionalAttr
826
826
827
827
828 class Optional(object):
828 class Optional(object):
829 """
829 """
830 Defines an optional parameter::
830 Defines an optional parameter::
831
831
832 param = param.getval() if isinstance(param, Optional) else param
832 param = param.getval() if isinstance(param, Optional) else param
833 param = param() if isinstance(param, Optional) else param
833 param = param() if isinstance(param, Optional) else param
834
834
835 is equivalent of::
835 is equivalent of::
836
836
837 param = Optional.extract(param)
837 param = Optional.extract(param)
838
838
839 """
839 """
840
840
841 def __init__(self, type_):
841 def __init__(self, type_):
842 self.type_ = type_
842 self.type_ = type_
843
843
844 def __repr__(self):
844 def __repr__(self):
845 return '<Optional:%s>' % self.type_.__repr__()
845 return '<Optional:%s>' % self.type_.__repr__()
846
846
847 def __call__(self):
847 def __call__(self):
848 return self.getval()
848 return self.getval()
849
849
850 def getval(self):
850 def getval(self):
851 """
851 """
852 returns value from this Optional instance
852 returns value from this Optional instance
853 """
853 """
854 if isinstance(self.type_, OAttr):
854 if isinstance(self.type_, OAttr):
855 # use params name
855 # use params name
856 return self.type_.attr_name
856 return self.type_.attr_name
857 return self.type_
857 return self.type_
858
858
859 @classmethod
859 @classmethod
860 def extract(cls, val):
860 def extract(cls, val):
861 """
861 """
862 Extracts value from Optional() instance
862 Extracts value from Optional() instance
863
863
864 :param val:
864 :param val:
865 :return: original value if it's not Optional instance else
865 :return: original value if it's not Optional instance else
866 value of instance
866 value of instance
867 """
867 """
868 if isinstance(val, cls):
868 if isinstance(val, cls):
869 return val.getval()
869 return val.getval()
870 return val
870 return val
871
871
872
872
873 def get_routes_generator_for_server_url(server_url):
873 def get_routes_generator_for_server_url(server_url):
874 parsed_url = urlobject.URLObject(server_url)
874 parsed_url = urlobject.URLObject(server_url)
875 netloc = safe_str(parsed_url.netloc)
875 netloc = safe_str(parsed_url.netloc)
876 script_name = safe_str(parsed_url.path)
876 script_name = safe_str(parsed_url.path)
877
877
878 if ':' in netloc:
878 if ':' in netloc:
879 server_name, server_port = netloc.split(':')
879 server_name, server_port = netloc.split(':')
880 else:
880 else:
881 server_name = netloc
881 server_name = netloc
882 server_port = (parsed_url.scheme == 'https' and '443' or '80')
882 server_port = (parsed_url.scheme == 'https' and '443' or '80')
883
883
884 environ = {
884 environ = {
885 'REQUEST_METHOD': 'GET',
885 'REQUEST_METHOD': 'GET',
886 'PATH_INFO': '/',
886 'PATH_INFO': '/',
887 'SERVER_NAME': server_name,
887 'SERVER_NAME': server_name,
888 'SERVER_PORT': server_port,
888 'SERVER_PORT': server_port,
889 'SCRIPT_NAME': script_name,
889 'SCRIPT_NAME': script_name,
890 }
890 }
891 if parsed_url.scheme == 'https':
891 if parsed_url.scheme == 'https':
892 environ['HTTPS'] = 'on'
892 environ['HTTPS'] = 'on'
893 environ['wsgi.url_scheme'] = 'https'
893 environ['wsgi.url_scheme'] = 'https'
894
894
895 return routes.util.URLGenerator(rhodecode.CONFIG['routes.map'], environ)
895 return routes.util.URLGenerator(rhodecode.CONFIG['routes.map'], environ)
896
897
898 def glob2re(pat):
899 """
900 Translate a shell PATTERN to a regular expression.
901
902 There is no way to quote meta-characters.
903 """
904
905 i, n = 0, len(pat)
906 res = ''
907 while i < n:
908 c = pat[i]
909 i = i+1
910 if c == '*':
911 #res = res + '.*'
912 res = res + '[^/]*'
913 elif c == '?':
914 #res = res + '.'
915 res = res + '[^/]'
916 elif c == '[':
917 j = i
918 if j < n and pat[j] == '!':
919 j = j+1
920 if j < n and pat[j] == ']':
921 j = j+1
922 while j < n and pat[j] != ']':
923 j = j+1
924 if j >= n:
925 res = res + '\\['
926 else:
927 stuff = pat[i:j].replace('\\','\\\\')
928 i = j+1
929 if stuff[0] == '!':
930 stuff = '^' + stuff[1:]
931 elif stuff[0] == '^':
932 stuff = '\\' + stuff
933 res = '%s[%s]' % (res, stuff)
934 else:
935 res = res + re.escape(c)
936 return res + '\Z(?ms)'
@@ -1,3516 +1,3640 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import os
26 import os
26 import sys
27 import sys
27 import time
28 import time
28 import hashlib
29 import hashlib
29 import logging
30 import logging
30 import datetime
31 import datetime
31 import warnings
32 import warnings
32 import ipaddress
33 import ipaddress
33 import functools
34 import functools
34 import traceback
35 import traceback
35 import collections
36 import collections
36
37
37
38
38 from sqlalchemy import *
39 from sqlalchemy import *
39 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.orm import (
43 from sqlalchemy.orm import (
43 relationship, joinedload, class_mapper, validates, aliased)
44 relationship, joinedload, class_mapper, validates, aliased)
44 from sqlalchemy.sql.expression import true
45 from sqlalchemy.sql.expression import true
45 from beaker.cache import cache_region, region_invalidate
46 from beaker.cache import cache_region, region_invalidate
46 from webob.exc import HTTPNotFound
47 from webob.exc import HTTPNotFound
47 from zope.cachedescriptors.property import Lazy as LazyProperty
48 from zope.cachedescriptors.property import Lazy as LazyProperty
48
49
49 from pylons import url
50 from pylons import url
50 from pylons.i18n.translation import lazy_ugettext as _
51 from pylons.i18n.translation import lazy_ugettext as _
51
52
52 from rhodecode.lib.vcs import get_backend, get_vcs_instance
53 from rhodecode.lib.vcs import get_backend, get_vcs_instance
53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.backends.base import (
56 from rhodecode.lib.vcs.backends.base import (
56 EmptyCommit, Reference, MergeFailureReason)
57 EmptyCommit, Reference, MergeFailureReason)
57 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re)
60 from rhodecode.lib.jsonalchemy import MutationObj, JsonType, JSONDict
62 from rhodecode.lib.jsonalchemy import MutationObj, JsonType, JSONDict
61 from rhodecode.lib.ext_json import json
63 from rhodecode.lib.ext_json import json
62 from rhodecode.lib.caching_query import FromCache
64 from rhodecode.lib.caching_query import FromCache
63 from rhodecode.lib.encrypt import AESCipher
65 from rhodecode.lib.encrypt import AESCipher
64
66
65 from rhodecode.model.meta import Base, Session
67 from rhodecode.model.meta import Base, Session
66
68
67 URL_SEP = '/'
69 URL_SEP = '/'
68 log = logging.getLogger(__name__)
70 log = logging.getLogger(__name__)
69
71
70 # =============================================================================
72 # =============================================================================
71 # BASE CLASSES
73 # BASE CLASSES
72 # =============================================================================
74 # =============================================================================
73
75
74 # this is propagated from .ini file rhodecode.encrypted_values.secret or
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
75 # beaker.session.secret if first is not set.
77 # beaker.session.secret if first is not set.
76 # and initialized at environment.py
78 # and initialized at environment.py
77 ENCRYPTION_KEY = None
79 ENCRYPTION_KEY = None
78
80
79 # used to sort permissions by types, '#' used here is not allowed to be in
81 # used to sort permissions by types, '#' used here is not allowed to be in
80 # usernames, and it's very early in sorted string.printable table.
82 # usernames, and it's very early in sorted string.printable table.
81 PERMISSION_TYPE_SORT = {
83 PERMISSION_TYPE_SORT = {
82 'admin': '####',
84 'admin': '####',
83 'write': '###',
85 'write': '###',
84 'read': '##',
86 'read': '##',
85 'none': '#',
87 'none': '#',
86 }
88 }
87
89
88
90
89 def display_sort(obj):
91 def display_sort(obj):
90 """
92 """
91 Sort function used to sort permissions in .permissions() function of
93 Sort function used to sort permissions in .permissions() function of
92 Repository, RepoGroup, UserGroup. Also it put the default user in front
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
93 of all other resources
95 of all other resources
94 """
96 """
95
97
96 if obj.username == User.DEFAULT_USER:
98 if obj.username == User.DEFAULT_USER:
97 return '#####'
99 return '#####'
98 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
99 return prefix + obj.username
101 return prefix + obj.username
100
102
101
103
102 def _hash_key(k):
104 def _hash_key(k):
103 return md5_safe(k)
105 return md5_safe(k)
104
106
105
107
106 class EncryptedTextValue(TypeDecorator):
108 class EncryptedTextValue(TypeDecorator):
107 """
109 """
108 Special column for encrypted long text data, use like::
110 Special column for encrypted long text data, use like::
109
111
110 value = Column("encrypted_value", EncryptedValue(), nullable=False)
112 value = Column("encrypted_value", EncryptedValue(), nullable=False)
111
113
112 This column is intelligent so if value is in unencrypted form it return
114 This column is intelligent so if value is in unencrypted form it return
113 unencrypted form, but on save it always encrypts
115 unencrypted form, but on save it always encrypts
114 """
116 """
115 impl = Text
117 impl = Text
116
118
117 def process_bind_param(self, value, dialect):
119 def process_bind_param(self, value, dialect):
118 if not value:
120 if not value:
119 return value
121 return value
120 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
122 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
121 # protect against double encrypting if someone manually starts
123 # protect against double encrypting if someone manually starts
122 # doing
124 # doing
123 raise ValueError('value needs to be in unencrypted format, ie. '
125 raise ValueError('value needs to be in unencrypted format, ie. '
124 'not starting with enc$aes')
126 'not starting with enc$aes')
125 return 'enc$aes_hmac$%s' % AESCipher(
127 return 'enc$aes_hmac$%s' % AESCipher(
126 ENCRYPTION_KEY, hmac=True).encrypt(value)
128 ENCRYPTION_KEY, hmac=True).encrypt(value)
127
129
128 def process_result_value(self, value, dialect):
130 def process_result_value(self, value, dialect):
129 import rhodecode
131 import rhodecode
130
132
131 if not value:
133 if not value:
132 return value
134 return value
133
135
134 parts = value.split('$', 3)
136 parts = value.split('$', 3)
135 if not len(parts) == 3:
137 if not len(parts) == 3:
136 # probably not encrypted values
138 # probably not encrypted values
137 return value
139 return value
138 else:
140 else:
139 if parts[0] != 'enc':
141 if parts[0] != 'enc':
140 # parts ok but without our header ?
142 # parts ok but without our header ?
141 return value
143 return value
142 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
144 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
143 'rhodecode.encrypted_values.strict') or True)
145 'rhodecode.encrypted_values.strict') or True)
144 # at that stage we know it's our encryption
146 # at that stage we know it's our encryption
145 if parts[1] == 'aes':
147 if parts[1] == 'aes':
146 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
148 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
147 elif parts[1] == 'aes_hmac':
149 elif parts[1] == 'aes_hmac':
148 decrypted_data = AESCipher(
150 decrypted_data = AESCipher(
149 ENCRYPTION_KEY, hmac=True,
151 ENCRYPTION_KEY, hmac=True,
150 strict_verification=enc_strict_mode).decrypt(parts[2])
152 strict_verification=enc_strict_mode).decrypt(parts[2])
151 else:
153 else:
152 raise ValueError(
154 raise ValueError(
153 'Encryption type part is wrong, must be `aes` '
155 'Encryption type part is wrong, must be `aes` '
154 'or `aes_hmac`, got `%s` instead' % (parts[1]))
156 'or `aes_hmac`, got `%s` instead' % (parts[1]))
155 return decrypted_data
157 return decrypted_data
156
158
157
159
158 class BaseModel(object):
160 class BaseModel(object):
159 """
161 """
160 Base Model for all classes
162 Base Model for all classes
161 """
163 """
162
164
163 @classmethod
165 @classmethod
164 def _get_keys(cls):
166 def _get_keys(cls):
165 """return column names for this model """
167 """return column names for this model """
166 return class_mapper(cls).c.keys()
168 return class_mapper(cls).c.keys()
167
169
168 def get_dict(self):
170 def get_dict(self):
169 """
171 """
170 return dict with keys and values corresponding
172 return dict with keys and values corresponding
171 to this model data """
173 to this model data """
172
174
173 d = {}
175 d = {}
174 for k in self._get_keys():
176 for k in self._get_keys():
175 d[k] = getattr(self, k)
177 d[k] = getattr(self, k)
176
178
177 # also use __json__() if present to get additional fields
179 # also use __json__() if present to get additional fields
178 _json_attr = getattr(self, '__json__', None)
180 _json_attr = getattr(self, '__json__', None)
179 if _json_attr:
181 if _json_attr:
180 # update with attributes from __json__
182 # update with attributes from __json__
181 if callable(_json_attr):
183 if callable(_json_attr):
182 _json_attr = _json_attr()
184 _json_attr = _json_attr()
183 for k, val in _json_attr.iteritems():
185 for k, val in _json_attr.iteritems():
184 d[k] = val
186 d[k] = val
185 return d
187 return d
186
188
187 def get_appstruct(self):
189 def get_appstruct(self):
188 """return list with keys and values tuples corresponding
190 """return list with keys and values tuples corresponding
189 to this model data """
191 to this model data """
190
192
191 l = []
193 l = []
192 for k in self._get_keys():
194 for k in self._get_keys():
193 l.append((k, getattr(self, k),))
195 l.append((k, getattr(self, k),))
194 return l
196 return l
195
197
196 def populate_obj(self, populate_dict):
198 def populate_obj(self, populate_dict):
197 """populate model with data from given populate_dict"""
199 """populate model with data from given populate_dict"""
198
200
199 for k in self._get_keys():
201 for k in self._get_keys():
200 if k in populate_dict:
202 if k in populate_dict:
201 setattr(self, k, populate_dict[k])
203 setattr(self, k, populate_dict[k])
202
204
203 @classmethod
205 @classmethod
204 def query(cls):
206 def query(cls):
205 return Session().query(cls)
207 return Session().query(cls)
206
208
207 @classmethod
209 @classmethod
208 def get(cls, id_):
210 def get(cls, id_):
209 if id_:
211 if id_:
210 return cls.query().get(id_)
212 return cls.query().get(id_)
211
213
212 @classmethod
214 @classmethod
213 def get_or_404(cls, id_):
215 def get_or_404(cls, id_):
214 try:
216 try:
215 id_ = int(id_)
217 id_ = int(id_)
216 except (TypeError, ValueError):
218 except (TypeError, ValueError):
217 raise HTTPNotFound
219 raise HTTPNotFound
218
220
219 res = cls.query().get(id_)
221 res = cls.query().get(id_)
220 if not res:
222 if not res:
221 raise HTTPNotFound
223 raise HTTPNotFound
222 return res
224 return res
223
225
224 @classmethod
226 @classmethod
225 def getAll(cls):
227 def getAll(cls):
226 # deprecated and left for backward compatibility
228 # deprecated and left for backward compatibility
227 return cls.get_all()
229 return cls.get_all()
228
230
229 @classmethod
231 @classmethod
230 def get_all(cls):
232 def get_all(cls):
231 return cls.query().all()
233 return cls.query().all()
232
234
233 @classmethod
235 @classmethod
234 def delete(cls, id_):
236 def delete(cls, id_):
235 obj = cls.query().get(id_)
237 obj = cls.query().get(id_)
236 Session().delete(obj)
238 Session().delete(obj)
237
239
238 @classmethod
240 @classmethod
239 def identity_cache(cls, session, attr_name, value):
241 def identity_cache(cls, session, attr_name, value):
240 exist_in_session = []
242 exist_in_session = []
241 for (item_cls, pkey), instance in session.identity_map.items():
243 for (item_cls, pkey), instance in session.identity_map.items():
242 if cls == item_cls and getattr(instance, attr_name) == value:
244 if cls == item_cls and getattr(instance, attr_name) == value:
243 exist_in_session.append(instance)
245 exist_in_session.append(instance)
244 if exist_in_session:
246 if exist_in_session:
245 if len(exist_in_session) == 1:
247 if len(exist_in_session) == 1:
246 return exist_in_session[0]
248 return exist_in_session[0]
247 log.exception(
249 log.exception(
248 'multiple objects with attr %s and '
250 'multiple objects with attr %s and '
249 'value %s found with same name: %r',
251 'value %s found with same name: %r',
250 attr_name, value, exist_in_session)
252 attr_name, value, exist_in_session)
251
253
252 def __repr__(self):
254 def __repr__(self):
253 if hasattr(self, '__unicode__'):
255 if hasattr(self, '__unicode__'):
254 # python repr needs to return str
256 # python repr needs to return str
255 try:
257 try:
256 return safe_str(self.__unicode__())
258 return safe_str(self.__unicode__())
257 except UnicodeDecodeError:
259 except UnicodeDecodeError:
258 pass
260 pass
259 return '<DB:%s>' % (self.__class__.__name__)
261 return '<DB:%s>' % (self.__class__.__name__)
260
262
261
263
262 class RhodeCodeSetting(Base, BaseModel):
264 class RhodeCodeSetting(Base, BaseModel):
263 __tablename__ = 'rhodecode_settings'
265 __tablename__ = 'rhodecode_settings'
264 __table_args__ = (
266 __table_args__ = (
265 UniqueConstraint('app_settings_name'),
267 UniqueConstraint('app_settings_name'),
266 {'extend_existing': True, 'mysql_engine': 'InnoDB',
268 {'extend_existing': True, 'mysql_engine': 'InnoDB',
267 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
269 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
268 )
270 )
269
271
270 SETTINGS_TYPES = {
272 SETTINGS_TYPES = {
271 'str': safe_str,
273 'str': safe_str,
272 'int': safe_int,
274 'int': safe_int,
273 'unicode': safe_unicode,
275 'unicode': safe_unicode,
274 'bool': str2bool,
276 'bool': str2bool,
275 'list': functools.partial(aslist, sep=',')
277 'list': functools.partial(aslist, sep=',')
276 }
278 }
277 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
279 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
278 GLOBAL_CONF_KEY = 'app_settings'
280 GLOBAL_CONF_KEY = 'app_settings'
279
281
280 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
282 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
281 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
283 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
282 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
284 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
283 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
285 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
284
286
285 def __init__(self, key='', val='', type='unicode'):
287 def __init__(self, key='', val='', type='unicode'):
286 self.app_settings_name = key
288 self.app_settings_name = key
287 self.app_settings_type = type
289 self.app_settings_type = type
288 self.app_settings_value = val
290 self.app_settings_value = val
289
291
290 @validates('_app_settings_value')
292 @validates('_app_settings_value')
291 def validate_settings_value(self, key, val):
293 def validate_settings_value(self, key, val):
292 assert type(val) == unicode
294 assert type(val) == unicode
293 return val
295 return val
294
296
295 @hybrid_property
297 @hybrid_property
296 def app_settings_value(self):
298 def app_settings_value(self):
297 v = self._app_settings_value
299 v = self._app_settings_value
298 _type = self.app_settings_type
300 _type = self.app_settings_type
299 if _type:
301 if _type:
300 _type = self.app_settings_type.split('.')[0]
302 _type = self.app_settings_type.split('.')[0]
301 # decode the encrypted value
303 # decode the encrypted value
302 if 'encrypted' in self.app_settings_type:
304 if 'encrypted' in self.app_settings_type:
303 cipher = EncryptedTextValue()
305 cipher = EncryptedTextValue()
304 v = safe_unicode(cipher.process_result_value(v, None))
306 v = safe_unicode(cipher.process_result_value(v, None))
305
307
306 converter = self.SETTINGS_TYPES.get(_type) or \
308 converter = self.SETTINGS_TYPES.get(_type) or \
307 self.SETTINGS_TYPES['unicode']
309 self.SETTINGS_TYPES['unicode']
308 return converter(v)
310 return converter(v)
309
311
310 @app_settings_value.setter
312 @app_settings_value.setter
311 def app_settings_value(self, val):
313 def app_settings_value(self, val):
312 """
314 """
313 Setter that will always make sure we use unicode in app_settings_value
315 Setter that will always make sure we use unicode in app_settings_value
314
316
315 :param val:
317 :param val:
316 """
318 """
317 val = safe_unicode(val)
319 val = safe_unicode(val)
318 # encode the encrypted value
320 # encode the encrypted value
319 if 'encrypted' in self.app_settings_type:
321 if 'encrypted' in self.app_settings_type:
320 cipher = EncryptedTextValue()
322 cipher = EncryptedTextValue()
321 val = safe_unicode(cipher.process_bind_param(val, None))
323 val = safe_unicode(cipher.process_bind_param(val, None))
322 self._app_settings_value = val
324 self._app_settings_value = val
323
325
324 @hybrid_property
326 @hybrid_property
325 def app_settings_type(self):
327 def app_settings_type(self):
326 return self._app_settings_type
328 return self._app_settings_type
327
329
328 @app_settings_type.setter
330 @app_settings_type.setter
329 def app_settings_type(self, val):
331 def app_settings_type(self, val):
330 if val.split('.')[0] not in self.SETTINGS_TYPES:
332 if val.split('.')[0] not in self.SETTINGS_TYPES:
331 raise Exception('type must be one of %s got %s'
333 raise Exception('type must be one of %s got %s'
332 % (self.SETTINGS_TYPES.keys(), val))
334 % (self.SETTINGS_TYPES.keys(), val))
333 self._app_settings_type = val
335 self._app_settings_type = val
334
336
335 def __unicode__(self):
337 def __unicode__(self):
336 return u"<%s('%s:%s[%s]')>" % (
338 return u"<%s('%s:%s[%s]')>" % (
337 self.__class__.__name__,
339 self.__class__.__name__,
338 self.app_settings_name, self.app_settings_value,
340 self.app_settings_name, self.app_settings_value,
339 self.app_settings_type
341 self.app_settings_type
340 )
342 )
341
343
342
344
343 class RhodeCodeUi(Base, BaseModel):
345 class RhodeCodeUi(Base, BaseModel):
344 __tablename__ = 'rhodecode_ui'
346 __tablename__ = 'rhodecode_ui'
345 __table_args__ = (
347 __table_args__ = (
346 UniqueConstraint('ui_key'),
348 UniqueConstraint('ui_key'),
347 {'extend_existing': True, 'mysql_engine': 'InnoDB',
349 {'extend_existing': True, 'mysql_engine': 'InnoDB',
348 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
350 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
349 )
351 )
350
352
351 HOOK_REPO_SIZE = 'changegroup.repo_size'
353 HOOK_REPO_SIZE = 'changegroup.repo_size'
352 # HG
354 # HG
353 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
355 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
354 HOOK_PULL = 'outgoing.pull_logger'
356 HOOK_PULL = 'outgoing.pull_logger'
355 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
357 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
356 HOOK_PUSH = 'changegroup.push_logger'
358 HOOK_PUSH = 'changegroup.push_logger'
357
359
358 # TODO: johbo: Unify way how hooks are configured for git and hg,
360 # TODO: johbo: Unify way how hooks are configured for git and hg,
359 # git part is currently hardcoded.
361 # git part is currently hardcoded.
360
362
361 # SVN PATTERNS
363 # SVN PATTERNS
362 SVN_BRANCH_ID = 'vcs_svn_branch'
364 SVN_BRANCH_ID = 'vcs_svn_branch'
363 SVN_TAG_ID = 'vcs_svn_tag'
365 SVN_TAG_ID = 'vcs_svn_tag'
364
366
365 ui_id = Column(
367 ui_id = Column(
366 "ui_id", Integer(), nullable=False, unique=True, default=None,
368 "ui_id", Integer(), nullable=False, unique=True, default=None,
367 primary_key=True)
369 primary_key=True)
368 ui_section = Column(
370 ui_section = Column(
369 "ui_section", String(255), nullable=True, unique=None, default=None)
371 "ui_section", String(255), nullable=True, unique=None, default=None)
370 ui_key = Column(
372 ui_key = Column(
371 "ui_key", String(255), nullable=True, unique=None, default=None)
373 "ui_key", String(255), nullable=True, unique=None, default=None)
372 ui_value = Column(
374 ui_value = Column(
373 "ui_value", String(255), nullable=True, unique=None, default=None)
375 "ui_value", String(255), nullable=True, unique=None, default=None)
374 ui_active = Column(
376 ui_active = Column(
375 "ui_active", Boolean(), nullable=True, unique=None, default=True)
377 "ui_active", Boolean(), nullable=True, unique=None, default=True)
376
378
377 def __repr__(self):
379 def __repr__(self):
378 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
380 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
379 self.ui_key, self.ui_value)
381 self.ui_key, self.ui_value)
380
382
381
383
382 class RepoRhodeCodeSetting(Base, BaseModel):
384 class RepoRhodeCodeSetting(Base, BaseModel):
383 __tablename__ = 'repo_rhodecode_settings'
385 __tablename__ = 'repo_rhodecode_settings'
384 __table_args__ = (
386 __table_args__ = (
385 UniqueConstraint(
387 UniqueConstraint(
386 'app_settings_name', 'repository_id',
388 'app_settings_name', 'repository_id',
387 name='uq_repo_rhodecode_setting_name_repo_id'),
389 name='uq_repo_rhodecode_setting_name_repo_id'),
388 {'extend_existing': True, 'mysql_engine': 'InnoDB',
390 {'extend_existing': True, 'mysql_engine': 'InnoDB',
389 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
391 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
390 )
392 )
391
393
392 repository_id = Column(
394 repository_id = Column(
393 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
395 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
394 nullable=False)
396 nullable=False)
395 app_settings_id = Column(
397 app_settings_id = Column(
396 "app_settings_id", Integer(), nullable=False, unique=True,
398 "app_settings_id", Integer(), nullable=False, unique=True,
397 default=None, primary_key=True)
399 default=None, primary_key=True)
398 app_settings_name = Column(
400 app_settings_name = Column(
399 "app_settings_name", String(255), nullable=True, unique=None,
401 "app_settings_name", String(255), nullable=True, unique=None,
400 default=None)
402 default=None)
401 _app_settings_value = Column(
403 _app_settings_value = Column(
402 "app_settings_value", String(4096), nullable=True, unique=None,
404 "app_settings_value", String(4096), nullable=True, unique=None,
403 default=None)
405 default=None)
404 _app_settings_type = Column(
406 _app_settings_type = Column(
405 "app_settings_type", String(255), nullable=True, unique=None,
407 "app_settings_type", String(255), nullable=True, unique=None,
406 default=None)
408 default=None)
407
409
408 repository = relationship('Repository')
410 repository = relationship('Repository')
409
411
410 def __init__(self, repository_id, key='', val='', type='unicode'):
412 def __init__(self, repository_id, key='', val='', type='unicode'):
411 self.repository_id = repository_id
413 self.repository_id = repository_id
412 self.app_settings_name = key
414 self.app_settings_name = key
413 self.app_settings_type = type
415 self.app_settings_type = type
414 self.app_settings_value = val
416 self.app_settings_value = val
415
417
416 @validates('_app_settings_value')
418 @validates('_app_settings_value')
417 def validate_settings_value(self, key, val):
419 def validate_settings_value(self, key, val):
418 assert type(val) == unicode
420 assert type(val) == unicode
419 return val
421 return val
420
422
421 @hybrid_property
423 @hybrid_property
422 def app_settings_value(self):
424 def app_settings_value(self):
423 v = self._app_settings_value
425 v = self._app_settings_value
424 type_ = self.app_settings_type
426 type_ = self.app_settings_type
425 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
427 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
426 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
428 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
427 return converter(v)
429 return converter(v)
428
430
429 @app_settings_value.setter
431 @app_settings_value.setter
430 def app_settings_value(self, val):
432 def app_settings_value(self, val):
431 """
433 """
432 Setter that will always make sure we use unicode in app_settings_value
434 Setter that will always make sure we use unicode in app_settings_value
433
435
434 :param val:
436 :param val:
435 """
437 """
436 self._app_settings_value = safe_unicode(val)
438 self._app_settings_value = safe_unicode(val)
437
439
438 @hybrid_property
440 @hybrid_property
439 def app_settings_type(self):
441 def app_settings_type(self):
440 return self._app_settings_type
442 return self._app_settings_type
441
443
442 @app_settings_type.setter
444 @app_settings_type.setter
443 def app_settings_type(self, val):
445 def app_settings_type(self, val):
444 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
446 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
445 if val not in SETTINGS_TYPES:
447 if val not in SETTINGS_TYPES:
446 raise Exception('type must be one of %s got %s'
448 raise Exception('type must be one of %s got %s'
447 % (SETTINGS_TYPES.keys(), val))
449 % (SETTINGS_TYPES.keys(), val))
448 self._app_settings_type = val
450 self._app_settings_type = val
449
451
450 def __unicode__(self):
452 def __unicode__(self):
451 return u"<%s('%s:%s:%s[%s]')>" % (
453 return u"<%s('%s:%s:%s[%s]')>" % (
452 self.__class__.__name__, self.repository.repo_name,
454 self.__class__.__name__, self.repository.repo_name,
453 self.app_settings_name, self.app_settings_value,
455 self.app_settings_name, self.app_settings_value,
454 self.app_settings_type
456 self.app_settings_type
455 )
457 )
456
458
457
459
458 class RepoRhodeCodeUi(Base, BaseModel):
460 class RepoRhodeCodeUi(Base, BaseModel):
459 __tablename__ = 'repo_rhodecode_ui'
461 __tablename__ = 'repo_rhodecode_ui'
460 __table_args__ = (
462 __table_args__ = (
461 UniqueConstraint(
463 UniqueConstraint(
462 'repository_id', 'ui_section', 'ui_key',
464 'repository_id', 'ui_section', 'ui_key',
463 name='uq_repo_rhodecode_ui_repository_id_section_key'),
465 name='uq_repo_rhodecode_ui_repository_id_section_key'),
464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
466 {'extend_existing': True, 'mysql_engine': 'InnoDB',
465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
467 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
466 )
468 )
467
469
468 repository_id = Column(
470 repository_id = Column(
469 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
471 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
470 nullable=False)
472 nullable=False)
471 ui_id = Column(
473 ui_id = Column(
472 "ui_id", Integer(), nullable=False, unique=True, default=None,
474 "ui_id", Integer(), nullable=False, unique=True, default=None,
473 primary_key=True)
475 primary_key=True)
474 ui_section = Column(
476 ui_section = Column(
475 "ui_section", String(255), nullable=True, unique=None, default=None)
477 "ui_section", String(255), nullable=True, unique=None, default=None)
476 ui_key = Column(
478 ui_key = Column(
477 "ui_key", String(255), nullable=True, unique=None, default=None)
479 "ui_key", String(255), nullable=True, unique=None, default=None)
478 ui_value = Column(
480 ui_value = Column(
479 "ui_value", String(255), nullable=True, unique=None, default=None)
481 "ui_value", String(255), nullable=True, unique=None, default=None)
480 ui_active = Column(
482 ui_active = Column(
481 "ui_active", Boolean(), nullable=True, unique=None, default=True)
483 "ui_active", Boolean(), nullable=True, unique=None, default=True)
482
484
483 repository = relationship('Repository')
485 repository = relationship('Repository')
484
486
485 def __repr__(self):
487 def __repr__(self):
486 return '<%s[%s:%s]%s=>%s]>' % (
488 return '<%s[%s:%s]%s=>%s]>' % (
487 self.__class__.__name__, self.repository.repo_name,
489 self.__class__.__name__, self.repository.repo_name,
488 self.ui_section, self.ui_key, self.ui_value)
490 self.ui_section, self.ui_key, self.ui_value)
489
491
490
492
491 class User(Base, BaseModel):
493 class User(Base, BaseModel):
492 __tablename__ = 'users'
494 __tablename__ = 'users'
493 __table_args__ = (
495 __table_args__ = (
494 UniqueConstraint('username'), UniqueConstraint('email'),
496 UniqueConstraint('username'), UniqueConstraint('email'),
495 Index('u_username_idx', 'username'),
497 Index('u_username_idx', 'username'),
496 Index('u_email_idx', 'email'),
498 Index('u_email_idx', 'email'),
497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
499 {'extend_existing': True, 'mysql_engine': 'InnoDB',
498 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
500 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
499 )
501 )
500 DEFAULT_USER = 'default'
502 DEFAULT_USER = 'default'
501 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
503 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
502 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
504 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
503
505
504 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
506 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
505 username = Column("username", String(255), nullable=True, unique=None, default=None)
507 username = Column("username", String(255), nullable=True, unique=None, default=None)
506 password = Column("password", String(255), nullable=True, unique=None, default=None)
508 password = Column("password", String(255), nullable=True, unique=None, default=None)
507 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
509 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
508 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
510 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
509 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
511 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
510 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
512 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
511 _email = Column("email", String(255), nullable=True, unique=None, default=None)
513 _email = Column("email", String(255), nullable=True, unique=None, default=None)
512 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
514 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
513 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
515 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
514 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
516 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
515 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
517 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
516 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
518 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
517 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
518 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
520 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
519
521
520 user_log = relationship('UserLog')
522 user_log = relationship('UserLog')
521 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
523 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
522
524
523 repositories = relationship('Repository')
525 repositories = relationship('Repository')
524 repository_groups = relationship('RepoGroup')
526 repository_groups = relationship('RepoGroup')
525 user_groups = relationship('UserGroup')
527 user_groups = relationship('UserGroup')
526
528
527 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
529 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
528 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
530 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
529
531
530 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
532 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
531 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
533 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
532 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
534 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
533
535
534 group_member = relationship('UserGroupMember', cascade='all')
536 group_member = relationship('UserGroupMember', cascade='all')
535
537
536 notifications = relationship('UserNotification', cascade='all')
538 notifications = relationship('UserNotification', cascade='all')
537 # notifications assigned to this user
539 # notifications assigned to this user
538 user_created_notifications = relationship('Notification', cascade='all')
540 user_created_notifications = relationship('Notification', cascade='all')
539 # comments created by this user
541 # comments created by this user
540 user_comments = relationship('ChangesetComment', cascade='all')
542 user_comments = relationship('ChangesetComment', cascade='all')
541 # user profile extra info
543 # user profile extra info
542 user_emails = relationship('UserEmailMap', cascade='all')
544 user_emails = relationship('UserEmailMap', cascade='all')
543 user_ip_map = relationship('UserIpMap', cascade='all')
545 user_ip_map = relationship('UserIpMap', cascade='all')
544 user_auth_tokens = relationship('UserApiKeys', cascade='all')
546 user_auth_tokens = relationship('UserApiKeys', cascade='all')
545 # gists
547 # gists
546 user_gists = relationship('Gist', cascade='all')
548 user_gists = relationship('Gist', cascade='all')
547 # user pull requests
549 # user pull requests
548 user_pull_requests = relationship('PullRequest', cascade='all')
550 user_pull_requests = relationship('PullRequest', cascade='all')
549 # external identities
551 # external identities
550 extenal_identities = relationship(
552 extenal_identities = relationship(
551 'ExternalIdentity',
553 'ExternalIdentity',
552 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
554 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
553 cascade='all')
555 cascade='all')
554
556
555 def __unicode__(self):
557 def __unicode__(self):
556 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
558 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
557 self.user_id, self.username)
559 self.user_id, self.username)
558
560
559 @hybrid_property
561 @hybrid_property
560 def email(self):
562 def email(self):
561 return self._email
563 return self._email
562
564
563 @email.setter
565 @email.setter
564 def email(self, val):
566 def email(self, val):
565 self._email = val.lower() if val else None
567 self._email = val.lower() if val else None
566
568
567 @property
569 @property
568 def firstname(self):
570 def firstname(self):
569 # alias for future
571 # alias for future
570 return self.name
572 return self.name
571
573
572 @property
574 @property
573 def emails(self):
575 def emails(self):
574 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
576 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
575 return [self.email] + [x.email for x in other]
577 return [self.email] + [x.email for x in other]
576
578
577 @property
579 @property
578 def auth_tokens(self):
580 def auth_tokens(self):
579 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
581 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
580
582
581 @property
583 @property
582 def extra_auth_tokens(self):
584 def extra_auth_tokens(self):
583 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
585 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
584
586
585 @property
587 @property
586 def feed_token(self):
588 def feed_token(self):
587 feed_tokens = UserApiKeys.query()\
589 feed_tokens = UserApiKeys.query()\
588 .filter(UserApiKeys.user == self)\
590 .filter(UserApiKeys.user == self)\
589 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
591 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
590 .all()
592 .all()
591 if feed_tokens:
593 if feed_tokens:
592 return feed_tokens[0].api_key
594 return feed_tokens[0].api_key
593 else:
595 else:
594 # use the main token so we don't end up with nothing...
596 # use the main token so we don't end up with nothing...
595 return self.api_key
597 return self.api_key
596
598
597 @classmethod
599 @classmethod
598 def extra_valid_auth_tokens(cls, user, role=None):
600 def extra_valid_auth_tokens(cls, user, role=None):
599 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
601 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
600 .filter(or_(UserApiKeys.expires == -1,
602 .filter(or_(UserApiKeys.expires == -1,
601 UserApiKeys.expires >= time.time()))
603 UserApiKeys.expires >= time.time()))
602 if role:
604 if role:
603 tokens = tokens.filter(or_(UserApiKeys.role == role,
605 tokens = tokens.filter(or_(UserApiKeys.role == role,
604 UserApiKeys.role == UserApiKeys.ROLE_ALL))
606 UserApiKeys.role == UserApiKeys.ROLE_ALL))
605 return tokens.all()
607 return tokens.all()
606
608
607 @property
609 @property
608 def ip_addresses(self):
610 def ip_addresses(self):
609 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
611 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
610 return [x.ip_addr for x in ret]
612 return [x.ip_addr for x in ret]
611
613
612 @property
614 @property
613 def username_and_name(self):
615 def username_and_name(self):
614 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
616 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
615
617
616 @property
618 @property
617 def username_or_name_or_email(self):
619 def username_or_name_or_email(self):
618 full_name = self.full_name if self.full_name is not ' ' else None
620 full_name = self.full_name if self.full_name is not ' ' else None
619 return self.username or full_name or self.email
621 return self.username or full_name or self.email
620
622
621 @property
623 @property
622 def full_name(self):
624 def full_name(self):
623 return '%s %s' % (self.firstname, self.lastname)
625 return '%s %s' % (self.firstname, self.lastname)
624
626
625 @property
627 @property
626 def full_name_or_username(self):
628 def full_name_or_username(self):
627 return ('%s %s' % (self.firstname, self.lastname)
629 return ('%s %s' % (self.firstname, self.lastname)
628 if (self.firstname and self.lastname) else self.username)
630 if (self.firstname and self.lastname) else self.username)
629
631
630 @property
632 @property
631 def full_contact(self):
633 def full_contact(self):
632 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
634 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
633
635
634 @property
636 @property
635 def short_contact(self):
637 def short_contact(self):
636 return '%s %s' % (self.firstname, self.lastname)
638 return '%s %s' % (self.firstname, self.lastname)
637
639
638 @property
640 @property
639 def is_admin(self):
641 def is_admin(self):
640 return self.admin
642 return self.admin
641
643
642 @property
644 @property
643 def AuthUser(self):
645 def AuthUser(self):
644 """
646 """
645 Returns instance of AuthUser for this user
647 Returns instance of AuthUser for this user
646 """
648 """
647 from rhodecode.lib.auth import AuthUser
649 from rhodecode.lib.auth import AuthUser
648 return AuthUser(user_id=self.user_id, api_key=self.api_key,
650 return AuthUser(user_id=self.user_id, api_key=self.api_key,
649 username=self.username)
651 username=self.username)
650
652
651 @hybrid_property
653 @hybrid_property
652 def user_data(self):
654 def user_data(self):
653 if not self._user_data:
655 if not self._user_data:
654 return {}
656 return {}
655
657
656 try:
658 try:
657 return json.loads(self._user_data)
659 return json.loads(self._user_data)
658 except TypeError:
660 except TypeError:
659 return {}
661 return {}
660
662
661 @user_data.setter
663 @user_data.setter
662 def user_data(self, val):
664 def user_data(self, val):
663 if not isinstance(val, dict):
665 if not isinstance(val, dict):
664 raise Exception('user_data must be dict, got %s' % type(val))
666 raise Exception('user_data must be dict, got %s' % type(val))
665 try:
667 try:
666 self._user_data = json.dumps(val)
668 self._user_data = json.dumps(val)
667 except Exception:
669 except Exception:
668 log.error(traceback.format_exc())
670 log.error(traceback.format_exc())
669
671
670 @classmethod
672 @classmethod
671 def get_by_username(cls, username, case_insensitive=False,
673 def get_by_username(cls, username, case_insensitive=False,
672 cache=False, identity_cache=False):
674 cache=False, identity_cache=False):
673 session = Session()
675 session = Session()
674
676
675 if case_insensitive:
677 if case_insensitive:
676 q = cls.query().filter(
678 q = cls.query().filter(
677 func.lower(cls.username) == func.lower(username))
679 func.lower(cls.username) == func.lower(username))
678 else:
680 else:
679 q = cls.query().filter(cls.username == username)
681 q = cls.query().filter(cls.username == username)
680
682
681 if cache:
683 if cache:
682 if identity_cache:
684 if identity_cache:
683 val = cls.identity_cache(session, 'username', username)
685 val = cls.identity_cache(session, 'username', username)
684 if val:
686 if val:
685 return val
687 return val
686 else:
688 else:
687 q = q.options(
689 q = q.options(
688 FromCache("sql_cache_short",
690 FromCache("sql_cache_short",
689 "get_user_by_name_%s" % _hash_key(username)))
691 "get_user_by_name_%s" % _hash_key(username)))
690
692
691 return q.scalar()
693 return q.scalar()
692
694
693 @classmethod
695 @classmethod
694 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
696 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
695 q = cls.query().filter(cls.api_key == auth_token)
697 q = cls.query().filter(cls.api_key == auth_token)
696
698
697 if cache:
699 if cache:
698 q = q.options(FromCache("sql_cache_short",
700 q = q.options(FromCache("sql_cache_short",
699 "get_auth_token_%s" % auth_token))
701 "get_auth_token_%s" % auth_token))
700 res = q.scalar()
702 res = q.scalar()
701
703
702 if fallback and not res:
704 if fallback and not res:
703 #fallback to additional keys
705 #fallback to additional keys
704 _res = UserApiKeys.query()\
706 _res = UserApiKeys.query()\
705 .filter(UserApiKeys.api_key == auth_token)\
707 .filter(UserApiKeys.api_key == auth_token)\
706 .filter(or_(UserApiKeys.expires == -1,
708 .filter(or_(UserApiKeys.expires == -1,
707 UserApiKeys.expires >= time.time()))\
709 UserApiKeys.expires >= time.time()))\
708 .first()
710 .first()
709 if _res:
711 if _res:
710 res = _res.user
712 res = _res.user
711 return res
713 return res
712
714
713 @classmethod
715 @classmethod
714 def get_by_email(cls, email, case_insensitive=False, cache=False):
716 def get_by_email(cls, email, case_insensitive=False, cache=False):
715
717
716 if case_insensitive:
718 if case_insensitive:
717 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
719 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
718
720
719 else:
721 else:
720 q = cls.query().filter(cls.email == email)
722 q = cls.query().filter(cls.email == email)
721
723
722 if cache:
724 if cache:
723 q = q.options(FromCache("sql_cache_short",
725 q = q.options(FromCache("sql_cache_short",
724 "get_email_key_%s" % _hash_key(email)))
726 "get_email_key_%s" % _hash_key(email)))
725
727
726 ret = q.scalar()
728 ret = q.scalar()
727 if ret is None:
729 if ret is None:
728 q = UserEmailMap.query()
730 q = UserEmailMap.query()
729 # try fetching in alternate email map
731 # try fetching in alternate email map
730 if case_insensitive:
732 if case_insensitive:
731 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
733 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
732 else:
734 else:
733 q = q.filter(UserEmailMap.email == email)
735 q = q.filter(UserEmailMap.email == email)
734 q = q.options(joinedload(UserEmailMap.user))
736 q = q.options(joinedload(UserEmailMap.user))
735 if cache:
737 if cache:
736 q = q.options(FromCache("sql_cache_short",
738 q = q.options(FromCache("sql_cache_short",
737 "get_email_map_key_%s" % email))
739 "get_email_map_key_%s" % email))
738 ret = getattr(q.scalar(), 'user', None)
740 ret = getattr(q.scalar(), 'user', None)
739
741
740 return ret
742 return ret
741
743
742 @classmethod
744 @classmethod
743 def get_from_cs_author(cls, author):
745 def get_from_cs_author(cls, author):
744 """
746 """
745 Tries to get User objects out of commit author string
747 Tries to get User objects out of commit author string
746
748
747 :param author:
749 :param author:
748 """
750 """
749 from rhodecode.lib.helpers import email, author_name
751 from rhodecode.lib.helpers import email, author_name
750 # Valid email in the attribute passed, see if they're in the system
752 # Valid email in the attribute passed, see if they're in the system
751 _email = email(author)
753 _email = email(author)
752 if _email:
754 if _email:
753 user = cls.get_by_email(_email, case_insensitive=True)
755 user = cls.get_by_email(_email, case_insensitive=True)
754 if user:
756 if user:
755 return user
757 return user
756 # Maybe we can match by username?
758 # Maybe we can match by username?
757 _author = author_name(author)
759 _author = author_name(author)
758 user = cls.get_by_username(_author, case_insensitive=True)
760 user = cls.get_by_username(_author, case_insensitive=True)
759 if user:
761 if user:
760 return user
762 return user
761
763
762 def update_userdata(self, **kwargs):
764 def update_userdata(self, **kwargs):
763 usr = self
765 usr = self
764 old = usr.user_data
766 old = usr.user_data
765 old.update(**kwargs)
767 old.update(**kwargs)
766 usr.user_data = old
768 usr.user_data = old
767 Session().add(usr)
769 Session().add(usr)
768 log.debug('updated userdata with ', kwargs)
770 log.debug('updated userdata with ', kwargs)
769
771
770 def update_lastlogin(self):
772 def update_lastlogin(self):
771 """Update user lastlogin"""
773 """Update user lastlogin"""
772 self.last_login = datetime.datetime.now()
774 self.last_login = datetime.datetime.now()
773 Session().add(self)
775 Session().add(self)
774 log.debug('updated user %s lastlogin', self.username)
776 log.debug('updated user %s lastlogin', self.username)
775
777
776 def update_lastactivity(self):
778 def update_lastactivity(self):
777 """Update user lastactivity"""
779 """Update user lastactivity"""
778 usr = self
780 usr = self
779 old = usr.user_data
781 old = usr.user_data
780 old.update({'last_activity': time.time()})
782 old.update({'last_activity': time.time()})
781 usr.user_data = old
783 usr.user_data = old
782 Session().add(usr)
784 Session().add(usr)
783 log.debug('updated user %s lastactivity', usr.username)
785 log.debug('updated user %s lastactivity', usr.username)
784
786
785 def update_password(self, new_password, change_api_key=False):
787 def update_password(self, new_password, change_api_key=False):
786 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
788 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
787
789
788 self.password = get_crypt_password(new_password)
790 self.password = get_crypt_password(new_password)
789 if change_api_key:
791 if change_api_key:
790 self.api_key = generate_auth_token(self.username)
792 self.api_key = generate_auth_token(self.username)
791 Session().add(self)
793 Session().add(self)
792
794
793 @classmethod
795 @classmethod
794 def get_first_super_admin(cls):
796 def get_first_super_admin(cls):
795 user = User.query().filter(User.admin == true()).first()
797 user = User.query().filter(User.admin == true()).first()
796 if user is None:
798 if user is None:
797 raise Exception('FATAL: Missing administrative account!')
799 raise Exception('FATAL: Missing administrative account!')
798 return user
800 return user
799
801
800 @classmethod
802 @classmethod
801 def get_all_super_admins(cls):
803 def get_all_super_admins(cls):
802 """
804 """
803 Returns all admin accounts sorted by username
805 Returns all admin accounts sorted by username
804 """
806 """
805 return User.query().filter(User.admin == true())\
807 return User.query().filter(User.admin == true())\
806 .order_by(User.username.asc()).all()
808 .order_by(User.username.asc()).all()
807
809
808 @classmethod
810 @classmethod
809 def get_default_user(cls, cache=False):
811 def get_default_user(cls, cache=False):
810 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
812 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
811 if user is None:
813 if user is None:
812 raise Exception('FATAL: Missing default account!')
814 raise Exception('FATAL: Missing default account!')
813 return user
815 return user
814
816
815 def _get_default_perms(self, user, suffix=''):
817 def _get_default_perms(self, user, suffix=''):
816 from rhodecode.model.permission import PermissionModel
818 from rhodecode.model.permission import PermissionModel
817 return PermissionModel().get_default_perms(user.user_perms, suffix)
819 return PermissionModel().get_default_perms(user.user_perms, suffix)
818
820
819 def get_default_perms(self, suffix=''):
821 def get_default_perms(self, suffix=''):
820 return self._get_default_perms(self, suffix)
822 return self._get_default_perms(self, suffix)
821
823
822 def get_api_data(self, include_secrets=False, details='full'):
824 def get_api_data(self, include_secrets=False, details='full'):
823 """
825 """
824 Common function for generating user related data for API
826 Common function for generating user related data for API
825
827
826 :param include_secrets: By default secrets in the API data will be replaced
828 :param include_secrets: By default secrets in the API data will be replaced
827 by a placeholder value to prevent exposing this data by accident. In case
829 by a placeholder value to prevent exposing this data by accident. In case
828 this data shall be exposed, set this flag to ``True``.
830 this data shall be exposed, set this flag to ``True``.
829
831
830 :param details: details can be 'basic|full' basic gives only a subset of
832 :param details: details can be 'basic|full' basic gives only a subset of
831 the available user information that includes user_id, name and emails.
833 the available user information that includes user_id, name and emails.
832 """
834 """
833 user = self
835 user = self
834 user_data = self.user_data
836 user_data = self.user_data
835 data = {
837 data = {
836 'user_id': user.user_id,
838 'user_id': user.user_id,
837 'username': user.username,
839 'username': user.username,
838 'firstname': user.name,
840 'firstname': user.name,
839 'lastname': user.lastname,
841 'lastname': user.lastname,
840 'email': user.email,
842 'email': user.email,
841 'emails': user.emails,
843 'emails': user.emails,
842 }
844 }
843 if details == 'basic':
845 if details == 'basic':
844 return data
846 return data
845
847
846 api_key_length = 40
848 api_key_length = 40
847 api_key_replacement = '*' * api_key_length
849 api_key_replacement = '*' * api_key_length
848
850
849 extras = {
851 extras = {
850 'api_key': api_key_replacement,
852 'api_key': api_key_replacement,
851 'api_keys': [api_key_replacement],
853 'api_keys': [api_key_replacement],
852 'active': user.active,
854 'active': user.active,
853 'admin': user.admin,
855 'admin': user.admin,
854 'extern_type': user.extern_type,
856 'extern_type': user.extern_type,
855 'extern_name': user.extern_name,
857 'extern_name': user.extern_name,
856 'last_login': user.last_login,
858 'last_login': user.last_login,
857 'ip_addresses': user.ip_addresses,
859 'ip_addresses': user.ip_addresses,
858 'language': user_data.get('language')
860 'language': user_data.get('language')
859 }
861 }
860 data.update(extras)
862 data.update(extras)
861
863
862 if include_secrets:
864 if include_secrets:
863 data['api_key'] = user.api_key
865 data['api_key'] = user.api_key
864 data['api_keys'] = user.auth_tokens
866 data['api_keys'] = user.auth_tokens
865 return data
867 return data
866
868
867 def __json__(self):
869 def __json__(self):
868 data = {
870 data = {
869 'full_name': self.full_name,
871 'full_name': self.full_name,
870 'full_name_or_username': self.full_name_or_username,
872 'full_name_or_username': self.full_name_or_username,
871 'short_contact': self.short_contact,
873 'short_contact': self.short_contact,
872 'full_contact': self.full_contact,
874 'full_contact': self.full_contact,
873 }
875 }
874 data.update(self.get_api_data())
876 data.update(self.get_api_data())
875 return data
877 return data
876
878
877
879
878 class UserApiKeys(Base, BaseModel):
880 class UserApiKeys(Base, BaseModel):
879 __tablename__ = 'user_api_keys'
881 __tablename__ = 'user_api_keys'
880 __table_args__ = (
882 __table_args__ = (
881 Index('uak_api_key_idx', 'api_key'),
883 Index('uak_api_key_idx', 'api_key'),
882 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
884 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
883 UniqueConstraint('api_key'),
885 UniqueConstraint('api_key'),
884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
885 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
886 )
888 )
887 __mapper_args__ = {}
889 __mapper_args__ = {}
888
890
889 # ApiKey role
891 # ApiKey role
890 ROLE_ALL = 'token_role_all'
892 ROLE_ALL = 'token_role_all'
891 ROLE_HTTP = 'token_role_http'
893 ROLE_HTTP = 'token_role_http'
892 ROLE_VCS = 'token_role_vcs'
894 ROLE_VCS = 'token_role_vcs'
893 ROLE_API = 'token_role_api'
895 ROLE_API = 'token_role_api'
894 ROLE_FEED = 'token_role_feed'
896 ROLE_FEED = 'token_role_feed'
895 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
897 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
896
898
897 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
899 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
898 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
900 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
899 api_key = Column("api_key", String(255), nullable=False, unique=True)
901 api_key = Column("api_key", String(255), nullable=False, unique=True)
900 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
902 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
901 expires = Column('expires', Float(53), nullable=False)
903 expires = Column('expires', Float(53), nullable=False)
902 role = Column('role', String(255), nullable=True)
904 role = Column('role', String(255), nullable=True)
903 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
905 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
904
906
905 user = relationship('User', lazy='joined')
907 user = relationship('User', lazy='joined')
906
908
907 @classmethod
909 @classmethod
908 def _get_role_name(cls, role):
910 def _get_role_name(cls, role):
909 return {
911 return {
910 cls.ROLE_ALL: _('all'),
912 cls.ROLE_ALL: _('all'),
911 cls.ROLE_HTTP: _('http/web interface'),
913 cls.ROLE_HTTP: _('http/web interface'),
912 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
914 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
913 cls.ROLE_API: _('api calls'),
915 cls.ROLE_API: _('api calls'),
914 cls.ROLE_FEED: _('feed access'),
916 cls.ROLE_FEED: _('feed access'),
915 }.get(role, role)
917 }.get(role, role)
916
918
917 @property
919 @property
918 def expired(self):
920 def expired(self):
919 if self.expires == -1:
921 if self.expires == -1:
920 return False
922 return False
921 return time.time() > self.expires
923 return time.time() > self.expires
922
924
923 @property
925 @property
924 def role_humanized(self):
926 def role_humanized(self):
925 return self._get_role_name(self.role)
927 return self._get_role_name(self.role)
926
928
927
929
928 class UserEmailMap(Base, BaseModel):
930 class UserEmailMap(Base, BaseModel):
929 __tablename__ = 'user_email_map'
931 __tablename__ = 'user_email_map'
930 __table_args__ = (
932 __table_args__ = (
931 Index('uem_email_idx', 'email'),
933 Index('uem_email_idx', 'email'),
932 UniqueConstraint('email'),
934 UniqueConstraint('email'),
933 {'extend_existing': True, 'mysql_engine': 'InnoDB',
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
934 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
935 )
937 )
936 __mapper_args__ = {}
938 __mapper_args__ = {}
937
939
938 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
940 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
939 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
940 _email = Column("email", String(255), nullable=True, unique=False, default=None)
942 _email = Column("email", String(255), nullable=True, unique=False, default=None)
941 user = relationship('User', lazy='joined')
943 user = relationship('User', lazy='joined')
942
944
943 @validates('_email')
945 @validates('_email')
944 def validate_email(self, key, email):
946 def validate_email(self, key, email):
945 # check if this email is not main one
947 # check if this email is not main one
946 main_email = Session().query(User).filter(User.email == email).scalar()
948 main_email = Session().query(User).filter(User.email == email).scalar()
947 if main_email is not None:
949 if main_email is not None:
948 raise AttributeError('email %s is present is user table' % email)
950 raise AttributeError('email %s is present is user table' % email)
949 return email
951 return email
950
952
951 @hybrid_property
953 @hybrid_property
952 def email(self):
954 def email(self):
953 return self._email
955 return self._email
954
956
955 @email.setter
957 @email.setter
956 def email(self, val):
958 def email(self, val):
957 self._email = val.lower() if val else None
959 self._email = val.lower() if val else None
958
960
959
961
960 class UserIpMap(Base, BaseModel):
962 class UserIpMap(Base, BaseModel):
961 __tablename__ = 'user_ip_map'
963 __tablename__ = 'user_ip_map'
962 __table_args__ = (
964 __table_args__ = (
963 UniqueConstraint('user_id', 'ip_addr'),
965 UniqueConstraint('user_id', 'ip_addr'),
964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
965 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
967 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
966 )
968 )
967 __mapper_args__ = {}
969 __mapper_args__ = {}
968
970
969 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
971 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
970 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
972 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
971 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
973 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
972 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
974 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
973 description = Column("description", String(10000), nullable=True, unique=None, default=None)
975 description = Column("description", String(10000), nullable=True, unique=None, default=None)
974 user = relationship('User', lazy='joined')
976 user = relationship('User', lazy='joined')
975
977
976 @classmethod
978 @classmethod
977 def _get_ip_range(cls, ip_addr):
979 def _get_ip_range(cls, ip_addr):
978 net = ipaddress.ip_network(ip_addr, strict=False)
980 net = ipaddress.ip_network(ip_addr, strict=False)
979 return [str(net.network_address), str(net.broadcast_address)]
981 return [str(net.network_address), str(net.broadcast_address)]
980
982
981 def __json__(self):
983 def __json__(self):
982 return {
984 return {
983 'ip_addr': self.ip_addr,
985 'ip_addr': self.ip_addr,
984 'ip_range': self._get_ip_range(self.ip_addr),
986 'ip_range': self._get_ip_range(self.ip_addr),
985 }
987 }
986
988
987 def __unicode__(self):
989 def __unicode__(self):
988 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
990 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
989 self.user_id, self.ip_addr)
991 self.user_id, self.ip_addr)
990
992
991 class UserLog(Base, BaseModel):
993 class UserLog(Base, BaseModel):
992 __tablename__ = 'user_logs'
994 __tablename__ = 'user_logs'
993 __table_args__ = (
995 __table_args__ = (
994 {'extend_existing': True, 'mysql_engine': 'InnoDB',
996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
995 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
996 )
998 )
997 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
999 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
998 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1000 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
999 username = Column("username", String(255), nullable=True, unique=None, default=None)
1001 username = Column("username", String(255), nullable=True, unique=None, default=None)
1000 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1002 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1001 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1003 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1002 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1004 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1003 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1005 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1004 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1006 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1005
1007
1006 def __unicode__(self):
1008 def __unicode__(self):
1007 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1009 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1008 self.repository_name,
1010 self.repository_name,
1009 self.action)
1011 self.action)
1010
1012
1011 @property
1013 @property
1012 def action_as_day(self):
1014 def action_as_day(self):
1013 return datetime.date(*self.action_date.timetuple()[:3])
1015 return datetime.date(*self.action_date.timetuple()[:3])
1014
1016
1015 user = relationship('User')
1017 user = relationship('User')
1016 repository = relationship('Repository', cascade='')
1018 repository = relationship('Repository', cascade='')
1017
1019
1018
1020
1019 class UserGroup(Base, BaseModel):
1021 class UserGroup(Base, BaseModel):
1020 __tablename__ = 'users_groups'
1022 __tablename__ = 'users_groups'
1021 __table_args__ = (
1023 __table_args__ = (
1022 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1023 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1024 )
1026 )
1025
1027
1026 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1028 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1027 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1029 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1028 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1030 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1029 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1031 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1030 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1032 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1031 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1033 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1032 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1034 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1033 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1035 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1034
1036
1035 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1037 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1036 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1038 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1037 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1039 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1038 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1040 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1039 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1041 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1040 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1042 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1041
1043
1042 user = relationship('User')
1044 user = relationship('User')
1043
1045
1044 @hybrid_property
1046 @hybrid_property
1045 def group_data(self):
1047 def group_data(self):
1046 if not self._group_data:
1048 if not self._group_data:
1047 return {}
1049 return {}
1048
1050
1049 try:
1051 try:
1050 return json.loads(self._group_data)
1052 return json.loads(self._group_data)
1051 except TypeError:
1053 except TypeError:
1052 return {}
1054 return {}
1053
1055
1054 @group_data.setter
1056 @group_data.setter
1055 def group_data(self, val):
1057 def group_data(self, val):
1056 try:
1058 try:
1057 self._group_data = json.dumps(val)
1059 self._group_data = json.dumps(val)
1058 except Exception:
1060 except Exception:
1059 log.error(traceback.format_exc())
1061 log.error(traceback.format_exc())
1060
1062
1061 def __unicode__(self):
1063 def __unicode__(self):
1062 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1064 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1063 self.users_group_id,
1065 self.users_group_id,
1064 self.users_group_name)
1066 self.users_group_name)
1065
1067
1066 @classmethod
1068 @classmethod
1067 def get_by_group_name(cls, group_name, cache=False,
1069 def get_by_group_name(cls, group_name, cache=False,
1068 case_insensitive=False):
1070 case_insensitive=False):
1069 if case_insensitive:
1071 if case_insensitive:
1070 q = cls.query().filter(func.lower(cls.users_group_name) ==
1072 q = cls.query().filter(func.lower(cls.users_group_name) ==
1071 func.lower(group_name))
1073 func.lower(group_name))
1072
1074
1073 else:
1075 else:
1074 q = cls.query().filter(cls.users_group_name == group_name)
1076 q = cls.query().filter(cls.users_group_name == group_name)
1075 if cache:
1077 if cache:
1076 q = q.options(FromCache(
1078 q = q.options(FromCache(
1077 "sql_cache_short",
1079 "sql_cache_short",
1078 "get_group_%s" % _hash_key(group_name)))
1080 "get_group_%s" % _hash_key(group_name)))
1079 return q.scalar()
1081 return q.scalar()
1080
1082
1081 @classmethod
1083 @classmethod
1082 def get(cls, user_group_id, cache=False):
1084 def get(cls, user_group_id, cache=False):
1083 user_group = cls.query()
1085 user_group = cls.query()
1084 if cache:
1086 if cache:
1085 user_group = user_group.options(FromCache("sql_cache_short",
1087 user_group = user_group.options(FromCache("sql_cache_short",
1086 "get_users_group_%s" % user_group_id))
1088 "get_users_group_%s" % user_group_id))
1087 return user_group.get(user_group_id)
1089 return user_group.get(user_group_id)
1088
1090
1089 def permissions(self, with_admins=True, with_owner=True):
1091 def permissions(self, with_admins=True, with_owner=True):
1090 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1092 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1091 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1093 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1092 joinedload(UserUserGroupToPerm.user),
1094 joinedload(UserUserGroupToPerm.user),
1093 joinedload(UserUserGroupToPerm.permission),)
1095 joinedload(UserUserGroupToPerm.permission),)
1094
1096
1095 # get owners and admins and permissions. We do a trick of re-writing
1097 # get owners and admins and permissions. We do a trick of re-writing
1096 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1098 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1097 # has a global reference and changing one object propagates to all
1099 # has a global reference and changing one object propagates to all
1098 # others. This means if admin is also an owner admin_row that change
1100 # others. This means if admin is also an owner admin_row that change
1099 # would propagate to both objects
1101 # would propagate to both objects
1100 perm_rows = []
1102 perm_rows = []
1101 for _usr in q.all():
1103 for _usr in q.all():
1102 usr = AttributeDict(_usr.user.get_dict())
1104 usr = AttributeDict(_usr.user.get_dict())
1103 usr.permission = _usr.permission.permission_name
1105 usr.permission = _usr.permission.permission_name
1104 perm_rows.append(usr)
1106 perm_rows.append(usr)
1105
1107
1106 # filter the perm rows by 'default' first and then sort them by
1108 # filter the perm rows by 'default' first and then sort them by
1107 # admin,write,read,none permissions sorted again alphabetically in
1109 # admin,write,read,none permissions sorted again alphabetically in
1108 # each group
1110 # each group
1109 perm_rows = sorted(perm_rows, key=display_sort)
1111 perm_rows = sorted(perm_rows, key=display_sort)
1110
1112
1111 _admin_perm = 'usergroup.admin'
1113 _admin_perm = 'usergroup.admin'
1112 owner_row = []
1114 owner_row = []
1113 if with_owner:
1115 if with_owner:
1114 usr = AttributeDict(self.user.get_dict())
1116 usr = AttributeDict(self.user.get_dict())
1115 usr.owner_row = True
1117 usr.owner_row = True
1116 usr.permission = _admin_perm
1118 usr.permission = _admin_perm
1117 owner_row.append(usr)
1119 owner_row.append(usr)
1118
1120
1119 super_admin_rows = []
1121 super_admin_rows = []
1120 if with_admins:
1122 if with_admins:
1121 for usr in User.get_all_super_admins():
1123 for usr in User.get_all_super_admins():
1122 # if this admin is also owner, don't double the record
1124 # if this admin is also owner, don't double the record
1123 if usr.user_id == owner_row[0].user_id:
1125 if usr.user_id == owner_row[0].user_id:
1124 owner_row[0].admin_row = True
1126 owner_row[0].admin_row = True
1125 else:
1127 else:
1126 usr = AttributeDict(usr.get_dict())
1128 usr = AttributeDict(usr.get_dict())
1127 usr.admin_row = True
1129 usr.admin_row = True
1128 usr.permission = _admin_perm
1130 usr.permission = _admin_perm
1129 super_admin_rows.append(usr)
1131 super_admin_rows.append(usr)
1130
1132
1131 return super_admin_rows + owner_row + perm_rows
1133 return super_admin_rows + owner_row + perm_rows
1132
1134
1133 def permission_user_groups(self):
1135 def permission_user_groups(self):
1134 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1136 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1135 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1137 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1136 joinedload(UserGroupUserGroupToPerm.target_user_group),
1138 joinedload(UserGroupUserGroupToPerm.target_user_group),
1137 joinedload(UserGroupUserGroupToPerm.permission),)
1139 joinedload(UserGroupUserGroupToPerm.permission),)
1138
1140
1139 perm_rows = []
1141 perm_rows = []
1140 for _user_group in q.all():
1142 for _user_group in q.all():
1141 usr = AttributeDict(_user_group.user_group.get_dict())
1143 usr = AttributeDict(_user_group.user_group.get_dict())
1142 usr.permission = _user_group.permission.permission_name
1144 usr.permission = _user_group.permission.permission_name
1143 perm_rows.append(usr)
1145 perm_rows.append(usr)
1144
1146
1145 return perm_rows
1147 return perm_rows
1146
1148
1147 def _get_default_perms(self, user_group, suffix=''):
1149 def _get_default_perms(self, user_group, suffix=''):
1148 from rhodecode.model.permission import PermissionModel
1150 from rhodecode.model.permission import PermissionModel
1149 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1151 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1150
1152
1151 def get_default_perms(self, suffix=''):
1153 def get_default_perms(self, suffix=''):
1152 return self._get_default_perms(self, suffix)
1154 return self._get_default_perms(self, suffix)
1153
1155
1154 def get_api_data(self, with_group_members=True, include_secrets=False):
1156 def get_api_data(self, with_group_members=True, include_secrets=False):
1155 """
1157 """
1156 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1158 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1157 basically forwarded.
1159 basically forwarded.
1158
1160
1159 """
1161 """
1160 user_group = self
1162 user_group = self
1161
1163
1162 data = {
1164 data = {
1163 'users_group_id': user_group.users_group_id,
1165 'users_group_id': user_group.users_group_id,
1164 'group_name': user_group.users_group_name,
1166 'group_name': user_group.users_group_name,
1165 'group_description': user_group.user_group_description,
1167 'group_description': user_group.user_group_description,
1166 'active': user_group.users_group_active,
1168 'active': user_group.users_group_active,
1167 'owner': user_group.user.username,
1169 'owner': user_group.user.username,
1168 }
1170 }
1169 if with_group_members:
1171 if with_group_members:
1170 users = []
1172 users = []
1171 for user in user_group.members:
1173 for user in user_group.members:
1172 user = user.user
1174 user = user.user
1173 users.append(user.get_api_data(include_secrets=include_secrets))
1175 users.append(user.get_api_data(include_secrets=include_secrets))
1174 data['users'] = users
1176 data['users'] = users
1175
1177
1176 return data
1178 return data
1177
1179
1178
1180
1179 class UserGroupMember(Base, BaseModel):
1181 class UserGroupMember(Base, BaseModel):
1180 __tablename__ = 'users_groups_members'
1182 __tablename__ = 'users_groups_members'
1181 __table_args__ = (
1183 __table_args__ = (
1182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1184 )
1186 )
1185
1187
1186 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1188 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1187 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1189 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1188 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1190 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1189
1191
1190 user = relationship('User', lazy='joined')
1192 user = relationship('User', lazy='joined')
1191 users_group = relationship('UserGroup')
1193 users_group = relationship('UserGroup')
1192
1194
1193 def __init__(self, gr_id='', u_id=''):
1195 def __init__(self, gr_id='', u_id=''):
1194 self.users_group_id = gr_id
1196 self.users_group_id = gr_id
1195 self.user_id = u_id
1197 self.user_id = u_id
1196
1198
1197
1199
1198 class RepositoryField(Base, BaseModel):
1200 class RepositoryField(Base, BaseModel):
1199 __tablename__ = 'repositories_fields'
1201 __tablename__ = 'repositories_fields'
1200 __table_args__ = (
1202 __table_args__ = (
1201 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1203 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1202 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1203 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1205 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1204 )
1206 )
1205 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1207 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1206
1208
1207 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1209 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1208 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1210 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1209 field_key = Column("field_key", String(250))
1211 field_key = Column("field_key", String(250))
1210 field_label = Column("field_label", String(1024), nullable=False)
1212 field_label = Column("field_label", String(1024), nullable=False)
1211 field_value = Column("field_value", String(10000), nullable=False)
1213 field_value = Column("field_value", String(10000), nullable=False)
1212 field_desc = Column("field_desc", String(1024), nullable=False)
1214 field_desc = Column("field_desc", String(1024), nullable=False)
1213 field_type = Column("field_type", String(255), nullable=False, unique=None)
1215 field_type = Column("field_type", String(255), nullable=False, unique=None)
1214 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1216 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1215
1217
1216 repository = relationship('Repository')
1218 repository = relationship('Repository')
1217
1219
1218 @property
1220 @property
1219 def field_key_prefixed(self):
1221 def field_key_prefixed(self):
1220 return 'ex_%s' % self.field_key
1222 return 'ex_%s' % self.field_key
1221
1223
1222 @classmethod
1224 @classmethod
1223 def un_prefix_key(cls, key):
1225 def un_prefix_key(cls, key):
1224 if key.startswith(cls.PREFIX):
1226 if key.startswith(cls.PREFIX):
1225 return key[len(cls.PREFIX):]
1227 return key[len(cls.PREFIX):]
1226 return key
1228 return key
1227
1229
1228 @classmethod
1230 @classmethod
1229 def get_by_key_name(cls, key, repo):
1231 def get_by_key_name(cls, key, repo):
1230 row = cls.query()\
1232 row = cls.query()\
1231 .filter(cls.repository == repo)\
1233 .filter(cls.repository == repo)\
1232 .filter(cls.field_key == key).scalar()
1234 .filter(cls.field_key == key).scalar()
1233 return row
1235 return row
1234
1236
1235
1237
1236 class Repository(Base, BaseModel):
1238 class Repository(Base, BaseModel):
1237 __tablename__ = 'repositories'
1239 __tablename__ = 'repositories'
1238 __table_args__ = (
1240 __table_args__ = (
1239 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1241 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1241 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1243 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1242 )
1244 )
1243 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1245 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1244 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1246 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1245
1247
1246 STATE_CREATED = 'repo_state_created'
1248 STATE_CREATED = 'repo_state_created'
1247 STATE_PENDING = 'repo_state_pending'
1249 STATE_PENDING = 'repo_state_pending'
1248 STATE_ERROR = 'repo_state_error'
1250 STATE_ERROR = 'repo_state_error'
1249
1251
1250 LOCK_AUTOMATIC = 'lock_auto'
1252 LOCK_AUTOMATIC = 'lock_auto'
1251 LOCK_API = 'lock_api'
1253 LOCK_API = 'lock_api'
1252 LOCK_WEB = 'lock_web'
1254 LOCK_WEB = 'lock_web'
1253 LOCK_PULL = 'lock_pull'
1255 LOCK_PULL = 'lock_pull'
1254
1256
1255 NAME_SEP = URL_SEP
1257 NAME_SEP = URL_SEP
1256
1258
1257 repo_id = Column(
1259 repo_id = Column(
1258 "repo_id", Integer(), nullable=False, unique=True, default=None,
1260 "repo_id", Integer(), nullable=False, unique=True, default=None,
1259 primary_key=True)
1261 primary_key=True)
1260 _repo_name = Column(
1262 _repo_name = Column(
1261 "repo_name", Text(), nullable=False, default=None)
1263 "repo_name", Text(), nullable=False, default=None)
1262 _repo_name_hash = Column(
1264 _repo_name_hash = Column(
1263 "repo_name_hash", String(255), nullable=False, unique=True)
1265 "repo_name_hash", String(255), nullable=False, unique=True)
1264 repo_state = Column("repo_state", String(255), nullable=True)
1266 repo_state = Column("repo_state", String(255), nullable=True)
1265
1267
1266 clone_uri = Column(
1268 clone_uri = Column(
1267 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1269 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1268 default=None)
1270 default=None)
1269 repo_type = Column(
1271 repo_type = Column(
1270 "repo_type", String(255), nullable=False, unique=False, default=None)
1272 "repo_type", String(255), nullable=False, unique=False, default=None)
1271 user_id = Column(
1273 user_id = Column(
1272 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1274 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1273 unique=False, default=None)
1275 unique=False, default=None)
1274 private = Column(
1276 private = Column(
1275 "private", Boolean(), nullable=True, unique=None, default=None)
1277 "private", Boolean(), nullable=True, unique=None, default=None)
1276 enable_statistics = Column(
1278 enable_statistics = Column(
1277 "statistics", Boolean(), nullable=True, unique=None, default=True)
1279 "statistics", Boolean(), nullable=True, unique=None, default=True)
1278 enable_downloads = Column(
1280 enable_downloads = Column(
1279 "downloads", Boolean(), nullable=True, unique=None, default=True)
1281 "downloads", Boolean(), nullable=True, unique=None, default=True)
1280 description = Column(
1282 description = Column(
1281 "description", String(10000), nullable=True, unique=None, default=None)
1283 "description", String(10000), nullable=True, unique=None, default=None)
1282 created_on = Column(
1284 created_on = Column(
1283 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1285 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1284 default=datetime.datetime.now)
1286 default=datetime.datetime.now)
1285 updated_on = Column(
1287 updated_on = Column(
1286 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1288 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1287 default=datetime.datetime.now)
1289 default=datetime.datetime.now)
1288 _landing_revision = Column(
1290 _landing_revision = Column(
1289 "landing_revision", String(255), nullable=False, unique=False,
1291 "landing_revision", String(255), nullable=False, unique=False,
1290 default=None)
1292 default=None)
1291 enable_locking = Column(
1293 enable_locking = Column(
1292 "enable_locking", Boolean(), nullable=False, unique=None,
1294 "enable_locking", Boolean(), nullable=False, unique=None,
1293 default=False)
1295 default=False)
1294 _locked = Column(
1296 _locked = Column(
1295 "locked", String(255), nullable=True, unique=False, default=None)
1297 "locked", String(255), nullable=True, unique=False, default=None)
1296 _changeset_cache = Column(
1298 _changeset_cache = Column(
1297 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1299 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1298
1300
1299 fork_id = Column(
1301 fork_id = Column(
1300 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1302 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1301 nullable=True, unique=False, default=None)
1303 nullable=True, unique=False, default=None)
1302 group_id = Column(
1304 group_id = Column(
1303 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1305 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1304 unique=False, default=None)
1306 unique=False, default=None)
1305
1307
1306 user = relationship('User', lazy='joined')
1308 user = relationship('User', lazy='joined')
1307 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1309 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1308 group = relationship('RepoGroup', lazy='joined')
1310 group = relationship('RepoGroup', lazy='joined')
1309 repo_to_perm = relationship(
1311 repo_to_perm = relationship(
1310 'UserRepoToPerm', cascade='all',
1312 'UserRepoToPerm', cascade='all',
1311 order_by='UserRepoToPerm.repo_to_perm_id')
1313 order_by='UserRepoToPerm.repo_to_perm_id')
1312 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1314 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1313 stats = relationship('Statistics', cascade='all', uselist=False)
1315 stats = relationship('Statistics', cascade='all', uselist=False)
1314
1316
1315 followers = relationship(
1317 followers = relationship(
1316 'UserFollowing',
1318 'UserFollowing',
1317 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1319 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1318 cascade='all')
1320 cascade='all')
1319 extra_fields = relationship(
1321 extra_fields = relationship(
1320 'RepositoryField', cascade="all, delete, delete-orphan")
1322 'RepositoryField', cascade="all, delete, delete-orphan")
1321 logs = relationship('UserLog')
1323 logs = relationship('UserLog')
1322 comments = relationship(
1324 comments = relationship(
1323 'ChangesetComment', cascade="all, delete, delete-orphan")
1325 'ChangesetComment', cascade="all, delete, delete-orphan")
1324 pull_requests_source = relationship(
1326 pull_requests_source = relationship(
1325 'PullRequest',
1327 'PullRequest',
1326 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1328 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1327 cascade="all, delete, delete-orphan")
1329 cascade="all, delete, delete-orphan")
1328 pull_requests_target = relationship(
1330 pull_requests_target = relationship(
1329 'PullRequest',
1331 'PullRequest',
1330 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1332 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1331 cascade="all, delete, delete-orphan")
1333 cascade="all, delete, delete-orphan")
1332 ui = relationship('RepoRhodeCodeUi', cascade="all")
1334 ui = relationship('RepoRhodeCodeUi', cascade="all")
1333 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1335 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1334 integrations = relationship('Integration',
1336 integrations = relationship('Integration',
1335 cascade="all, delete, delete-orphan")
1337 cascade="all, delete, delete-orphan")
1336
1338
1337 def __unicode__(self):
1339 def __unicode__(self):
1338 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1340 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1339 safe_unicode(self.repo_name))
1341 safe_unicode(self.repo_name))
1340
1342
1341 @hybrid_property
1343 @hybrid_property
1342 def landing_rev(self):
1344 def landing_rev(self):
1343 # always should return [rev_type, rev]
1345 # always should return [rev_type, rev]
1344 if self._landing_revision:
1346 if self._landing_revision:
1345 _rev_info = self._landing_revision.split(':')
1347 _rev_info = self._landing_revision.split(':')
1346 if len(_rev_info) < 2:
1348 if len(_rev_info) < 2:
1347 _rev_info.insert(0, 'rev')
1349 _rev_info.insert(0, 'rev')
1348 return [_rev_info[0], _rev_info[1]]
1350 return [_rev_info[0], _rev_info[1]]
1349 return [None, None]
1351 return [None, None]
1350
1352
1351 @landing_rev.setter
1353 @landing_rev.setter
1352 def landing_rev(self, val):
1354 def landing_rev(self, val):
1353 if ':' not in val:
1355 if ':' not in val:
1354 raise ValueError('value must be delimited with `:` and consist '
1356 raise ValueError('value must be delimited with `:` and consist '
1355 'of <rev_type>:<rev>, got %s instead' % val)
1357 'of <rev_type>:<rev>, got %s instead' % val)
1356 self._landing_revision = val
1358 self._landing_revision = val
1357
1359
1358 @hybrid_property
1360 @hybrid_property
1359 def locked(self):
1361 def locked(self):
1360 if self._locked:
1362 if self._locked:
1361 user_id, timelocked, reason = self._locked.split(':')
1363 user_id, timelocked, reason = self._locked.split(':')
1362 lock_values = int(user_id), timelocked, reason
1364 lock_values = int(user_id), timelocked, reason
1363 else:
1365 else:
1364 lock_values = [None, None, None]
1366 lock_values = [None, None, None]
1365 return lock_values
1367 return lock_values
1366
1368
1367 @locked.setter
1369 @locked.setter
1368 def locked(self, val):
1370 def locked(self, val):
1369 if val and isinstance(val, (list, tuple)):
1371 if val and isinstance(val, (list, tuple)):
1370 self._locked = ':'.join(map(str, val))
1372 self._locked = ':'.join(map(str, val))
1371 else:
1373 else:
1372 self._locked = None
1374 self._locked = None
1373
1375
1374 @hybrid_property
1376 @hybrid_property
1375 def changeset_cache(self):
1377 def changeset_cache(self):
1376 from rhodecode.lib.vcs.backends.base import EmptyCommit
1378 from rhodecode.lib.vcs.backends.base import EmptyCommit
1377 dummy = EmptyCommit().__json__()
1379 dummy = EmptyCommit().__json__()
1378 if not self._changeset_cache:
1380 if not self._changeset_cache:
1379 return dummy
1381 return dummy
1380 try:
1382 try:
1381 return json.loads(self._changeset_cache)
1383 return json.loads(self._changeset_cache)
1382 except TypeError:
1384 except TypeError:
1383 return dummy
1385 return dummy
1384 except Exception:
1386 except Exception:
1385 log.error(traceback.format_exc())
1387 log.error(traceback.format_exc())
1386 return dummy
1388 return dummy
1387
1389
1388 @changeset_cache.setter
1390 @changeset_cache.setter
1389 def changeset_cache(self, val):
1391 def changeset_cache(self, val):
1390 try:
1392 try:
1391 self._changeset_cache = json.dumps(val)
1393 self._changeset_cache = json.dumps(val)
1392 except Exception:
1394 except Exception:
1393 log.error(traceback.format_exc())
1395 log.error(traceback.format_exc())
1394
1396
1395 @hybrid_property
1397 @hybrid_property
1396 def repo_name(self):
1398 def repo_name(self):
1397 return self._repo_name
1399 return self._repo_name
1398
1400
1399 @repo_name.setter
1401 @repo_name.setter
1400 def repo_name(self, value):
1402 def repo_name(self, value):
1401 self._repo_name = value
1403 self._repo_name = value
1402 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1404 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1403
1405
1404 @classmethod
1406 @classmethod
1405 def normalize_repo_name(cls, repo_name):
1407 def normalize_repo_name(cls, repo_name):
1406 """
1408 """
1407 Normalizes os specific repo_name to the format internally stored inside
1409 Normalizes os specific repo_name to the format internally stored inside
1408 database using URL_SEP
1410 database using URL_SEP
1409
1411
1410 :param cls:
1412 :param cls:
1411 :param repo_name:
1413 :param repo_name:
1412 """
1414 """
1413 return cls.NAME_SEP.join(repo_name.split(os.sep))
1415 return cls.NAME_SEP.join(repo_name.split(os.sep))
1414
1416
1415 @classmethod
1417 @classmethod
1416 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1418 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1417 session = Session()
1419 session = Session()
1418 q = session.query(cls).filter(cls.repo_name == repo_name)
1420 q = session.query(cls).filter(cls.repo_name == repo_name)
1419
1421
1420 if cache:
1422 if cache:
1421 if identity_cache:
1423 if identity_cache:
1422 val = cls.identity_cache(session, 'repo_name', repo_name)
1424 val = cls.identity_cache(session, 'repo_name', repo_name)
1423 if val:
1425 if val:
1424 return val
1426 return val
1425 else:
1427 else:
1426 q = q.options(
1428 q = q.options(
1427 FromCache("sql_cache_short",
1429 FromCache("sql_cache_short",
1428 "get_repo_by_name_%s" % _hash_key(repo_name)))
1430 "get_repo_by_name_%s" % _hash_key(repo_name)))
1429
1431
1430 return q.scalar()
1432 return q.scalar()
1431
1433
1432 @classmethod
1434 @classmethod
1433 def get_by_full_path(cls, repo_full_path):
1435 def get_by_full_path(cls, repo_full_path):
1434 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1436 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1435 repo_name = cls.normalize_repo_name(repo_name)
1437 repo_name = cls.normalize_repo_name(repo_name)
1436 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1438 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1437
1439
1438 @classmethod
1440 @classmethod
1439 def get_repo_forks(cls, repo_id):
1441 def get_repo_forks(cls, repo_id):
1440 return cls.query().filter(Repository.fork_id == repo_id)
1442 return cls.query().filter(Repository.fork_id == repo_id)
1441
1443
1442 @classmethod
1444 @classmethod
1443 def base_path(cls):
1445 def base_path(cls):
1444 """
1446 """
1445 Returns base path when all repos are stored
1447 Returns base path when all repos are stored
1446
1448
1447 :param cls:
1449 :param cls:
1448 """
1450 """
1449 q = Session().query(RhodeCodeUi)\
1451 q = Session().query(RhodeCodeUi)\
1450 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1452 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1451 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1453 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1452 return q.one().ui_value
1454 return q.one().ui_value
1453
1455
1454 @classmethod
1456 @classmethod
1455 def is_valid(cls, repo_name):
1457 def is_valid(cls, repo_name):
1456 """
1458 """
1457 returns True if given repo name is a valid filesystem repository
1459 returns True if given repo name is a valid filesystem repository
1458
1460
1459 :param cls:
1461 :param cls:
1460 :param repo_name:
1462 :param repo_name:
1461 """
1463 """
1462 from rhodecode.lib.utils import is_valid_repo
1464 from rhodecode.lib.utils import is_valid_repo
1463
1465
1464 return is_valid_repo(repo_name, cls.base_path())
1466 return is_valid_repo(repo_name, cls.base_path())
1465
1467
1466 @classmethod
1468 @classmethod
1467 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1469 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1468 case_insensitive=True):
1470 case_insensitive=True):
1469 q = Repository.query()
1471 q = Repository.query()
1470
1472
1471 if not isinstance(user_id, Optional):
1473 if not isinstance(user_id, Optional):
1472 q = q.filter(Repository.user_id == user_id)
1474 q = q.filter(Repository.user_id == user_id)
1473
1475
1474 if not isinstance(group_id, Optional):
1476 if not isinstance(group_id, Optional):
1475 q = q.filter(Repository.group_id == group_id)
1477 q = q.filter(Repository.group_id == group_id)
1476
1478
1477 if case_insensitive:
1479 if case_insensitive:
1478 q = q.order_by(func.lower(Repository.repo_name))
1480 q = q.order_by(func.lower(Repository.repo_name))
1479 else:
1481 else:
1480 q = q.order_by(Repository.repo_name)
1482 q = q.order_by(Repository.repo_name)
1481 return q.all()
1483 return q.all()
1482
1484
1483 @property
1485 @property
1484 def forks(self):
1486 def forks(self):
1485 """
1487 """
1486 Return forks of this repo
1488 Return forks of this repo
1487 """
1489 """
1488 return Repository.get_repo_forks(self.repo_id)
1490 return Repository.get_repo_forks(self.repo_id)
1489
1491
1490 @property
1492 @property
1491 def parent(self):
1493 def parent(self):
1492 """
1494 """
1493 Returns fork parent
1495 Returns fork parent
1494 """
1496 """
1495 return self.fork
1497 return self.fork
1496
1498
1497 @property
1499 @property
1498 def just_name(self):
1500 def just_name(self):
1499 return self.repo_name.split(self.NAME_SEP)[-1]
1501 return self.repo_name.split(self.NAME_SEP)[-1]
1500
1502
1501 @property
1503 @property
1502 def groups_with_parents(self):
1504 def groups_with_parents(self):
1503 groups = []
1505 groups = []
1504 if self.group is None:
1506 if self.group is None:
1505 return groups
1507 return groups
1506
1508
1507 cur_gr = self.group
1509 cur_gr = self.group
1508 groups.insert(0, cur_gr)
1510 groups.insert(0, cur_gr)
1509 while 1:
1511 while 1:
1510 gr = getattr(cur_gr, 'parent_group', None)
1512 gr = getattr(cur_gr, 'parent_group', None)
1511 cur_gr = cur_gr.parent_group
1513 cur_gr = cur_gr.parent_group
1512 if gr is None:
1514 if gr is None:
1513 break
1515 break
1514 groups.insert(0, gr)
1516 groups.insert(0, gr)
1515
1517
1516 return groups
1518 return groups
1517
1519
1518 @property
1520 @property
1519 def groups_and_repo(self):
1521 def groups_and_repo(self):
1520 return self.groups_with_parents, self
1522 return self.groups_with_parents, self
1521
1523
1522 @LazyProperty
1524 @LazyProperty
1523 def repo_path(self):
1525 def repo_path(self):
1524 """
1526 """
1525 Returns base full path for that repository means where it actually
1527 Returns base full path for that repository means where it actually
1526 exists on a filesystem
1528 exists on a filesystem
1527 """
1529 """
1528 q = Session().query(RhodeCodeUi).filter(
1530 q = Session().query(RhodeCodeUi).filter(
1529 RhodeCodeUi.ui_key == self.NAME_SEP)
1531 RhodeCodeUi.ui_key == self.NAME_SEP)
1530 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1532 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1531 return q.one().ui_value
1533 return q.one().ui_value
1532
1534
1533 @property
1535 @property
1534 def repo_full_path(self):
1536 def repo_full_path(self):
1535 p = [self.repo_path]
1537 p = [self.repo_path]
1536 # we need to split the name by / since this is how we store the
1538 # we need to split the name by / since this is how we store the
1537 # names in the database, but that eventually needs to be converted
1539 # names in the database, but that eventually needs to be converted
1538 # into a valid system path
1540 # into a valid system path
1539 p += self.repo_name.split(self.NAME_SEP)
1541 p += self.repo_name.split(self.NAME_SEP)
1540 return os.path.join(*map(safe_unicode, p))
1542 return os.path.join(*map(safe_unicode, p))
1541
1543
1542 @property
1544 @property
1543 def cache_keys(self):
1545 def cache_keys(self):
1544 """
1546 """
1545 Returns associated cache keys for that repo
1547 Returns associated cache keys for that repo
1546 """
1548 """
1547 return CacheKey.query()\
1549 return CacheKey.query()\
1548 .filter(CacheKey.cache_args == self.repo_name)\
1550 .filter(CacheKey.cache_args == self.repo_name)\
1549 .order_by(CacheKey.cache_key)\
1551 .order_by(CacheKey.cache_key)\
1550 .all()
1552 .all()
1551
1553
1552 def get_new_name(self, repo_name):
1554 def get_new_name(self, repo_name):
1553 """
1555 """
1554 returns new full repository name based on assigned group and new new
1556 returns new full repository name based on assigned group and new new
1555
1557
1556 :param group_name:
1558 :param group_name:
1557 """
1559 """
1558 path_prefix = self.group.full_path_splitted if self.group else []
1560 path_prefix = self.group.full_path_splitted if self.group else []
1559 return self.NAME_SEP.join(path_prefix + [repo_name])
1561 return self.NAME_SEP.join(path_prefix + [repo_name])
1560
1562
1561 @property
1563 @property
1562 def _config(self):
1564 def _config(self):
1563 """
1565 """
1564 Returns db based config object.
1566 Returns db based config object.
1565 """
1567 """
1566 from rhodecode.lib.utils import make_db_config
1568 from rhodecode.lib.utils import make_db_config
1567 return make_db_config(clear_session=False, repo=self)
1569 return make_db_config(clear_session=False, repo=self)
1568
1570
1569 def permissions(self, with_admins=True, with_owner=True):
1571 def permissions(self, with_admins=True, with_owner=True):
1570 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1572 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1571 q = q.options(joinedload(UserRepoToPerm.repository),
1573 q = q.options(joinedload(UserRepoToPerm.repository),
1572 joinedload(UserRepoToPerm.user),
1574 joinedload(UserRepoToPerm.user),
1573 joinedload(UserRepoToPerm.permission),)
1575 joinedload(UserRepoToPerm.permission),)
1574
1576
1575 # get owners and admins and permissions. We do a trick of re-writing
1577 # get owners and admins and permissions. We do a trick of re-writing
1576 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1578 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1577 # has a global reference and changing one object propagates to all
1579 # has a global reference and changing one object propagates to all
1578 # others. This means if admin is also an owner admin_row that change
1580 # others. This means if admin is also an owner admin_row that change
1579 # would propagate to both objects
1581 # would propagate to both objects
1580 perm_rows = []
1582 perm_rows = []
1581 for _usr in q.all():
1583 for _usr in q.all():
1582 usr = AttributeDict(_usr.user.get_dict())
1584 usr = AttributeDict(_usr.user.get_dict())
1583 usr.permission = _usr.permission.permission_name
1585 usr.permission = _usr.permission.permission_name
1584 perm_rows.append(usr)
1586 perm_rows.append(usr)
1585
1587
1586 # filter the perm rows by 'default' first and then sort them by
1588 # filter the perm rows by 'default' first and then sort them by
1587 # admin,write,read,none permissions sorted again alphabetically in
1589 # admin,write,read,none permissions sorted again alphabetically in
1588 # each group
1590 # each group
1589 perm_rows = sorted(perm_rows, key=display_sort)
1591 perm_rows = sorted(perm_rows, key=display_sort)
1590
1592
1591 _admin_perm = 'repository.admin'
1593 _admin_perm = 'repository.admin'
1592 owner_row = []
1594 owner_row = []
1593 if with_owner:
1595 if with_owner:
1594 usr = AttributeDict(self.user.get_dict())
1596 usr = AttributeDict(self.user.get_dict())
1595 usr.owner_row = True
1597 usr.owner_row = True
1596 usr.permission = _admin_perm
1598 usr.permission = _admin_perm
1597 owner_row.append(usr)
1599 owner_row.append(usr)
1598
1600
1599 super_admin_rows = []
1601 super_admin_rows = []
1600 if with_admins:
1602 if with_admins:
1601 for usr in User.get_all_super_admins():
1603 for usr in User.get_all_super_admins():
1602 # if this admin is also owner, don't double the record
1604 # if this admin is also owner, don't double the record
1603 if usr.user_id == owner_row[0].user_id:
1605 if usr.user_id == owner_row[0].user_id:
1604 owner_row[0].admin_row = True
1606 owner_row[0].admin_row = True
1605 else:
1607 else:
1606 usr = AttributeDict(usr.get_dict())
1608 usr = AttributeDict(usr.get_dict())
1607 usr.admin_row = True
1609 usr.admin_row = True
1608 usr.permission = _admin_perm
1610 usr.permission = _admin_perm
1609 super_admin_rows.append(usr)
1611 super_admin_rows.append(usr)
1610
1612
1611 return super_admin_rows + owner_row + perm_rows
1613 return super_admin_rows + owner_row + perm_rows
1612
1614
1613 def permission_user_groups(self):
1615 def permission_user_groups(self):
1614 q = UserGroupRepoToPerm.query().filter(
1616 q = UserGroupRepoToPerm.query().filter(
1615 UserGroupRepoToPerm.repository == self)
1617 UserGroupRepoToPerm.repository == self)
1616 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1618 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1617 joinedload(UserGroupRepoToPerm.users_group),
1619 joinedload(UserGroupRepoToPerm.users_group),
1618 joinedload(UserGroupRepoToPerm.permission),)
1620 joinedload(UserGroupRepoToPerm.permission),)
1619
1621
1620 perm_rows = []
1622 perm_rows = []
1621 for _user_group in q.all():
1623 for _user_group in q.all():
1622 usr = AttributeDict(_user_group.users_group.get_dict())
1624 usr = AttributeDict(_user_group.users_group.get_dict())
1623 usr.permission = _user_group.permission.permission_name
1625 usr.permission = _user_group.permission.permission_name
1624 perm_rows.append(usr)
1626 perm_rows.append(usr)
1625
1627
1626 return perm_rows
1628 return perm_rows
1627
1629
1628 def get_api_data(self, include_secrets=False):
1630 def get_api_data(self, include_secrets=False):
1629 """
1631 """
1630 Common function for generating repo api data
1632 Common function for generating repo api data
1631
1633
1632 :param include_secrets: See :meth:`User.get_api_data`.
1634 :param include_secrets: See :meth:`User.get_api_data`.
1633
1635
1634 """
1636 """
1635 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1637 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1636 # move this methods on models level.
1638 # move this methods on models level.
1637 from rhodecode.model.settings import SettingsModel
1639 from rhodecode.model.settings import SettingsModel
1638
1640
1639 repo = self
1641 repo = self
1640 _user_id, _time, _reason = self.locked
1642 _user_id, _time, _reason = self.locked
1641
1643
1642 data = {
1644 data = {
1643 'repo_id': repo.repo_id,
1645 'repo_id': repo.repo_id,
1644 'repo_name': repo.repo_name,
1646 'repo_name': repo.repo_name,
1645 'repo_type': repo.repo_type,
1647 'repo_type': repo.repo_type,
1646 'clone_uri': repo.clone_uri or '',
1648 'clone_uri': repo.clone_uri or '',
1647 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1649 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1648 'private': repo.private,
1650 'private': repo.private,
1649 'created_on': repo.created_on,
1651 'created_on': repo.created_on,
1650 'description': repo.description,
1652 'description': repo.description,
1651 'landing_rev': repo.landing_rev,
1653 'landing_rev': repo.landing_rev,
1652 'owner': repo.user.username,
1654 'owner': repo.user.username,
1653 'fork_of': repo.fork.repo_name if repo.fork else None,
1655 'fork_of': repo.fork.repo_name if repo.fork else None,
1654 'enable_statistics': repo.enable_statistics,
1656 'enable_statistics': repo.enable_statistics,
1655 'enable_locking': repo.enable_locking,
1657 'enable_locking': repo.enable_locking,
1656 'enable_downloads': repo.enable_downloads,
1658 'enable_downloads': repo.enable_downloads,
1657 'last_changeset': repo.changeset_cache,
1659 'last_changeset': repo.changeset_cache,
1658 'locked_by': User.get(_user_id).get_api_data(
1660 'locked_by': User.get(_user_id).get_api_data(
1659 include_secrets=include_secrets) if _user_id else None,
1661 include_secrets=include_secrets) if _user_id else None,
1660 'locked_date': time_to_datetime(_time) if _time else None,
1662 'locked_date': time_to_datetime(_time) if _time else None,
1661 'lock_reason': _reason if _reason else None,
1663 'lock_reason': _reason if _reason else None,
1662 }
1664 }
1663
1665
1664 # TODO: mikhail: should be per-repo settings here
1666 # TODO: mikhail: should be per-repo settings here
1665 rc_config = SettingsModel().get_all_settings()
1667 rc_config = SettingsModel().get_all_settings()
1666 repository_fields = str2bool(
1668 repository_fields = str2bool(
1667 rc_config.get('rhodecode_repository_fields'))
1669 rc_config.get('rhodecode_repository_fields'))
1668 if repository_fields:
1670 if repository_fields:
1669 for f in self.extra_fields:
1671 for f in self.extra_fields:
1670 data[f.field_key_prefixed] = f.field_value
1672 data[f.field_key_prefixed] = f.field_value
1671
1673
1672 return data
1674 return data
1673
1675
1674 @classmethod
1676 @classmethod
1675 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1677 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1676 if not lock_time:
1678 if not lock_time:
1677 lock_time = time.time()
1679 lock_time = time.time()
1678 if not lock_reason:
1680 if not lock_reason:
1679 lock_reason = cls.LOCK_AUTOMATIC
1681 lock_reason = cls.LOCK_AUTOMATIC
1680 repo.locked = [user_id, lock_time, lock_reason]
1682 repo.locked = [user_id, lock_time, lock_reason]
1681 Session().add(repo)
1683 Session().add(repo)
1682 Session().commit()
1684 Session().commit()
1683
1685
1684 @classmethod
1686 @classmethod
1685 def unlock(cls, repo):
1687 def unlock(cls, repo):
1686 repo.locked = None
1688 repo.locked = None
1687 Session().add(repo)
1689 Session().add(repo)
1688 Session().commit()
1690 Session().commit()
1689
1691
1690 @classmethod
1692 @classmethod
1691 def getlock(cls, repo):
1693 def getlock(cls, repo):
1692 return repo.locked
1694 return repo.locked
1693
1695
1694 def is_user_lock(self, user_id):
1696 def is_user_lock(self, user_id):
1695 if self.lock[0]:
1697 if self.lock[0]:
1696 lock_user_id = safe_int(self.lock[0])
1698 lock_user_id = safe_int(self.lock[0])
1697 user_id = safe_int(user_id)
1699 user_id = safe_int(user_id)
1698 # both are ints, and they are equal
1700 # both are ints, and they are equal
1699 return all([lock_user_id, user_id]) and lock_user_id == user_id
1701 return all([lock_user_id, user_id]) and lock_user_id == user_id
1700
1702
1701 return False
1703 return False
1702
1704
1703 def get_locking_state(self, action, user_id, only_when_enabled=True):
1705 def get_locking_state(self, action, user_id, only_when_enabled=True):
1704 """
1706 """
1705 Checks locking on this repository, if locking is enabled and lock is
1707 Checks locking on this repository, if locking is enabled and lock is
1706 present returns a tuple of make_lock, locked, locked_by.
1708 present returns a tuple of make_lock, locked, locked_by.
1707 make_lock can have 3 states None (do nothing) True, make lock
1709 make_lock can have 3 states None (do nothing) True, make lock
1708 False release lock, This value is later propagated to hooks, which
1710 False release lock, This value is later propagated to hooks, which
1709 do the locking. Think about this as signals passed to hooks what to do.
1711 do the locking. Think about this as signals passed to hooks what to do.
1710
1712
1711 """
1713 """
1712 # TODO: johbo: This is part of the business logic and should be moved
1714 # TODO: johbo: This is part of the business logic and should be moved
1713 # into the RepositoryModel.
1715 # into the RepositoryModel.
1714
1716
1715 if action not in ('push', 'pull'):
1717 if action not in ('push', 'pull'):
1716 raise ValueError("Invalid action value: %s" % repr(action))
1718 raise ValueError("Invalid action value: %s" % repr(action))
1717
1719
1718 # defines if locked error should be thrown to user
1720 # defines if locked error should be thrown to user
1719 currently_locked = False
1721 currently_locked = False
1720 # defines if new lock should be made, tri-state
1722 # defines if new lock should be made, tri-state
1721 make_lock = None
1723 make_lock = None
1722 repo = self
1724 repo = self
1723 user = User.get(user_id)
1725 user = User.get(user_id)
1724
1726
1725 lock_info = repo.locked
1727 lock_info = repo.locked
1726
1728
1727 if repo and (repo.enable_locking or not only_when_enabled):
1729 if repo and (repo.enable_locking or not only_when_enabled):
1728 if action == 'push':
1730 if action == 'push':
1729 # check if it's already locked !, if it is compare users
1731 # check if it's already locked !, if it is compare users
1730 locked_by_user_id = lock_info[0]
1732 locked_by_user_id = lock_info[0]
1731 if user.user_id == locked_by_user_id:
1733 if user.user_id == locked_by_user_id:
1732 log.debug(
1734 log.debug(
1733 'Got `push` action from user %s, now unlocking', user)
1735 'Got `push` action from user %s, now unlocking', user)
1734 # unlock if we have push from user who locked
1736 # unlock if we have push from user who locked
1735 make_lock = False
1737 make_lock = False
1736 else:
1738 else:
1737 # we're not the same user who locked, ban with
1739 # we're not the same user who locked, ban with
1738 # code defined in settings (default is 423 HTTP Locked) !
1740 # code defined in settings (default is 423 HTTP Locked) !
1739 log.debug('Repo %s is currently locked by %s', repo, user)
1741 log.debug('Repo %s is currently locked by %s', repo, user)
1740 currently_locked = True
1742 currently_locked = True
1741 elif action == 'pull':
1743 elif action == 'pull':
1742 # [0] user [1] date
1744 # [0] user [1] date
1743 if lock_info[0] and lock_info[1]:
1745 if lock_info[0] and lock_info[1]:
1744 log.debug('Repo %s is currently locked by %s', repo, user)
1746 log.debug('Repo %s is currently locked by %s', repo, user)
1745 currently_locked = True
1747 currently_locked = True
1746 else:
1748 else:
1747 log.debug('Setting lock on repo %s by %s', repo, user)
1749 log.debug('Setting lock on repo %s by %s', repo, user)
1748 make_lock = True
1750 make_lock = True
1749
1751
1750 else:
1752 else:
1751 log.debug('Repository %s do not have locking enabled', repo)
1753 log.debug('Repository %s do not have locking enabled', repo)
1752
1754
1753 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1755 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1754 make_lock, currently_locked, lock_info)
1756 make_lock, currently_locked, lock_info)
1755
1757
1756 from rhodecode.lib.auth import HasRepoPermissionAny
1758 from rhodecode.lib.auth import HasRepoPermissionAny
1757 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1759 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1758 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1760 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1759 # if we don't have at least write permission we cannot make a lock
1761 # if we don't have at least write permission we cannot make a lock
1760 log.debug('lock state reset back to FALSE due to lack '
1762 log.debug('lock state reset back to FALSE due to lack '
1761 'of at least read permission')
1763 'of at least read permission')
1762 make_lock = False
1764 make_lock = False
1763
1765
1764 return make_lock, currently_locked, lock_info
1766 return make_lock, currently_locked, lock_info
1765
1767
1766 @property
1768 @property
1767 def last_db_change(self):
1769 def last_db_change(self):
1768 return self.updated_on
1770 return self.updated_on
1769
1771
1770 @property
1772 @property
1771 def clone_uri_hidden(self):
1773 def clone_uri_hidden(self):
1772 clone_uri = self.clone_uri
1774 clone_uri = self.clone_uri
1773 if clone_uri:
1775 if clone_uri:
1774 import urlobject
1776 import urlobject
1775 url_obj = urlobject.URLObject(clone_uri)
1777 url_obj = urlobject.URLObject(clone_uri)
1776 if url_obj.password:
1778 if url_obj.password:
1777 clone_uri = url_obj.with_password('*****')
1779 clone_uri = url_obj.with_password('*****')
1778 return clone_uri
1780 return clone_uri
1779
1781
1780 def clone_url(self, **override):
1782 def clone_url(self, **override):
1781 qualified_home_url = url('home', qualified=True)
1783 qualified_home_url = url('home', qualified=True)
1782
1784
1783 uri_tmpl = None
1785 uri_tmpl = None
1784 if 'with_id' in override:
1786 if 'with_id' in override:
1785 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1787 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1786 del override['with_id']
1788 del override['with_id']
1787
1789
1788 if 'uri_tmpl' in override:
1790 if 'uri_tmpl' in override:
1789 uri_tmpl = override['uri_tmpl']
1791 uri_tmpl = override['uri_tmpl']
1790 del override['uri_tmpl']
1792 del override['uri_tmpl']
1791
1793
1792 # we didn't override our tmpl from **overrides
1794 # we didn't override our tmpl from **overrides
1793 if not uri_tmpl:
1795 if not uri_tmpl:
1794 uri_tmpl = self.DEFAULT_CLONE_URI
1796 uri_tmpl = self.DEFAULT_CLONE_URI
1795 try:
1797 try:
1796 from pylons import tmpl_context as c
1798 from pylons import tmpl_context as c
1797 uri_tmpl = c.clone_uri_tmpl
1799 uri_tmpl = c.clone_uri_tmpl
1798 except Exception:
1800 except Exception:
1799 # in any case if we call this outside of request context,
1801 # in any case if we call this outside of request context,
1800 # ie, not having tmpl_context set up
1802 # ie, not having tmpl_context set up
1801 pass
1803 pass
1802
1804
1803 return get_clone_url(uri_tmpl=uri_tmpl,
1805 return get_clone_url(uri_tmpl=uri_tmpl,
1804 qualifed_home_url=qualified_home_url,
1806 qualifed_home_url=qualified_home_url,
1805 repo_name=self.repo_name,
1807 repo_name=self.repo_name,
1806 repo_id=self.repo_id, **override)
1808 repo_id=self.repo_id, **override)
1807
1809
1808 def set_state(self, state):
1810 def set_state(self, state):
1809 self.repo_state = state
1811 self.repo_state = state
1810 Session().add(self)
1812 Session().add(self)
1811 #==========================================================================
1813 #==========================================================================
1812 # SCM PROPERTIES
1814 # SCM PROPERTIES
1813 #==========================================================================
1815 #==========================================================================
1814
1816
1815 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1817 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1816 return get_commit_safe(
1818 return get_commit_safe(
1817 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1819 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1818
1820
1819 def get_changeset(self, rev=None, pre_load=None):
1821 def get_changeset(self, rev=None, pre_load=None):
1820 warnings.warn("Use get_commit", DeprecationWarning)
1822 warnings.warn("Use get_commit", DeprecationWarning)
1821 commit_id = None
1823 commit_id = None
1822 commit_idx = None
1824 commit_idx = None
1823 if isinstance(rev, basestring):
1825 if isinstance(rev, basestring):
1824 commit_id = rev
1826 commit_id = rev
1825 else:
1827 else:
1826 commit_idx = rev
1828 commit_idx = rev
1827 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1829 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1828 pre_load=pre_load)
1830 pre_load=pre_load)
1829
1831
1830 def get_landing_commit(self):
1832 def get_landing_commit(self):
1831 """
1833 """
1832 Returns landing commit, or if that doesn't exist returns the tip
1834 Returns landing commit, or if that doesn't exist returns the tip
1833 """
1835 """
1834 _rev_type, _rev = self.landing_rev
1836 _rev_type, _rev = self.landing_rev
1835 commit = self.get_commit(_rev)
1837 commit = self.get_commit(_rev)
1836 if isinstance(commit, EmptyCommit):
1838 if isinstance(commit, EmptyCommit):
1837 return self.get_commit()
1839 return self.get_commit()
1838 return commit
1840 return commit
1839
1841
1840 def update_commit_cache(self, cs_cache=None, config=None):
1842 def update_commit_cache(self, cs_cache=None, config=None):
1841 """
1843 """
1842 Update cache of last changeset for repository, keys should be::
1844 Update cache of last changeset for repository, keys should be::
1843
1845
1844 short_id
1846 short_id
1845 raw_id
1847 raw_id
1846 revision
1848 revision
1847 parents
1849 parents
1848 message
1850 message
1849 date
1851 date
1850 author
1852 author
1851
1853
1852 :param cs_cache:
1854 :param cs_cache:
1853 """
1855 """
1854 from rhodecode.lib.vcs.backends.base import BaseChangeset
1856 from rhodecode.lib.vcs.backends.base import BaseChangeset
1855 if cs_cache is None:
1857 if cs_cache is None:
1856 # use no-cache version here
1858 # use no-cache version here
1857 scm_repo = self.scm_instance(cache=False, config=config)
1859 scm_repo = self.scm_instance(cache=False, config=config)
1858 if scm_repo:
1860 if scm_repo:
1859 cs_cache = scm_repo.get_commit(
1861 cs_cache = scm_repo.get_commit(
1860 pre_load=["author", "date", "message", "parents"])
1862 pre_load=["author", "date", "message", "parents"])
1861 else:
1863 else:
1862 cs_cache = EmptyCommit()
1864 cs_cache = EmptyCommit()
1863
1865
1864 if isinstance(cs_cache, BaseChangeset):
1866 if isinstance(cs_cache, BaseChangeset):
1865 cs_cache = cs_cache.__json__()
1867 cs_cache = cs_cache.__json__()
1866
1868
1867 def is_outdated(new_cs_cache):
1869 def is_outdated(new_cs_cache):
1868 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1870 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1869 new_cs_cache['revision'] != self.changeset_cache['revision']):
1871 new_cs_cache['revision'] != self.changeset_cache['revision']):
1870 return True
1872 return True
1871 return False
1873 return False
1872
1874
1873 # check if we have maybe already latest cached revision
1875 # check if we have maybe already latest cached revision
1874 if is_outdated(cs_cache) or not self.changeset_cache:
1876 if is_outdated(cs_cache) or not self.changeset_cache:
1875 _default = datetime.datetime.fromtimestamp(0)
1877 _default = datetime.datetime.fromtimestamp(0)
1876 last_change = cs_cache.get('date') or _default
1878 last_change = cs_cache.get('date') or _default
1877 log.debug('updated repo %s with new cs cache %s',
1879 log.debug('updated repo %s with new cs cache %s',
1878 self.repo_name, cs_cache)
1880 self.repo_name, cs_cache)
1879 self.updated_on = last_change
1881 self.updated_on = last_change
1880 self.changeset_cache = cs_cache
1882 self.changeset_cache = cs_cache
1881 Session().add(self)
1883 Session().add(self)
1882 Session().commit()
1884 Session().commit()
1883 else:
1885 else:
1884 log.debug('Skipping update_commit_cache for repo:`%s` '
1886 log.debug('Skipping update_commit_cache for repo:`%s` '
1885 'commit already with latest changes', self.repo_name)
1887 'commit already with latest changes', self.repo_name)
1886
1888
1887 @property
1889 @property
1888 def tip(self):
1890 def tip(self):
1889 return self.get_commit('tip')
1891 return self.get_commit('tip')
1890
1892
1891 @property
1893 @property
1892 def author(self):
1894 def author(self):
1893 return self.tip.author
1895 return self.tip.author
1894
1896
1895 @property
1897 @property
1896 def last_change(self):
1898 def last_change(self):
1897 return self.scm_instance().last_change
1899 return self.scm_instance().last_change
1898
1900
1899 def get_comments(self, revisions=None):
1901 def get_comments(self, revisions=None):
1900 """
1902 """
1901 Returns comments for this repository grouped by revisions
1903 Returns comments for this repository grouped by revisions
1902
1904
1903 :param revisions: filter query by revisions only
1905 :param revisions: filter query by revisions only
1904 """
1906 """
1905 cmts = ChangesetComment.query()\
1907 cmts = ChangesetComment.query()\
1906 .filter(ChangesetComment.repo == self)
1908 .filter(ChangesetComment.repo == self)
1907 if revisions:
1909 if revisions:
1908 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1910 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1909 grouped = collections.defaultdict(list)
1911 grouped = collections.defaultdict(list)
1910 for cmt in cmts.all():
1912 for cmt in cmts.all():
1911 grouped[cmt.revision].append(cmt)
1913 grouped[cmt.revision].append(cmt)
1912 return grouped
1914 return grouped
1913
1915
1914 def statuses(self, revisions=None):
1916 def statuses(self, revisions=None):
1915 """
1917 """
1916 Returns statuses for this repository
1918 Returns statuses for this repository
1917
1919
1918 :param revisions: list of revisions to get statuses for
1920 :param revisions: list of revisions to get statuses for
1919 """
1921 """
1920 statuses = ChangesetStatus.query()\
1922 statuses = ChangesetStatus.query()\
1921 .filter(ChangesetStatus.repo == self)\
1923 .filter(ChangesetStatus.repo == self)\
1922 .filter(ChangesetStatus.version == 0)
1924 .filter(ChangesetStatus.version == 0)
1923
1925
1924 if revisions:
1926 if revisions:
1925 # Try doing the filtering in chunks to avoid hitting limits
1927 # Try doing the filtering in chunks to avoid hitting limits
1926 size = 500
1928 size = 500
1927 status_results = []
1929 status_results = []
1928 for chunk in xrange(0, len(revisions), size):
1930 for chunk in xrange(0, len(revisions), size):
1929 status_results += statuses.filter(
1931 status_results += statuses.filter(
1930 ChangesetStatus.revision.in_(
1932 ChangesetStatus.revision.in_(
1931 revisions[chunk: chunk+size])
1933 revisions[chunk: chunk+size])
1932 ).all()
1934 ).all()
1933 else:
1935 else:
1934 status_results = statuses.all()
1936 status_results = statuses.all()
1935
1937
1936 grouped = {}
1938 grouped = {}
1937
1939
1938 # maybe we have open new pullrequest without a status?
1940 # maybe we have open new pullrequest without a status?
1939 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1941 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1940 status_lbl = ChangesetStatus.get_status_lbl(stat)
1942 status_lbl = ChangesetStatus.get_status_lbl(stat)
1941 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1943 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1942 for rev in pr.revisions:
1944 for rev in pr.revisions:
1943 pr_id = pr.pull_request_id
1945 pr_id = pr.pull_request_id
1944 pr_repo = pr.target_repo.repo_name
1946 pr_repo = pr.target_repo.repo_name
1945 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1947 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1946
1948
1947 for stat in status_results:
1949 for stat in status_results:
1948 pr_id = pr_repo = None
1950 pr_id = pr_repo = None
1949 if stat.pull_request:
1951 if stat.pull_request:
1950 pr_id = stat.pull_request.pull_request_id
1952 pr_id = stat.pull_request.pull_request_id
1951 pr_repo = stat.pull_request.target_repo.repo_name
1953 pr_repo = stat.pull_request.target_repo.repo_name
1952 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1954 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1953 pr_id, pr_repo]
1955 pr_id, pr_repo]
1954 return grouped
1956 return grouped
1955
1957
1956 # ==========================================================================
1958 # ==========================================================================
1957 # SCM CACHE INSTANCE
1959 # SCM CACHE INSTANCE
1958 # ==========================================================================
1960 # ==========================================================================
1959
1961
1960 def scm_instance(self, **kwargs):
1962 def scm_instance(self, **kwargs):
1961 import rhodecode
1963 import rhodecode
1962
1964
1963 # Passing a config will not hit the cache currently only used
1965 # Passing a config will not hit the cache currently only used
1964 # for repo2dbmapper
1966 # for repo2dbmapper
1965 config = kwargs.pop('config', None)
1967 config = kwargs.pop('config', None)
1966 cache = kwargs.pop('cache', None)
1968 cache = kwargs.pop('cache', None)
1967 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1969 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1968 # if cache is NOT defined use default global, else we have a full
1970 # if cache is NOT defined use default global, else we have a full
1969 # control over cache behaviour
1971 # control over cache behaviour
1970 if cache is None and full_cache and not config:
1972 if cache is None and full_cache and not config:
1971 return self._get_instance_cached()
1973 return self._get_instance_cached()
1972 return self._get_instance(cache=bool(cache), config=config)
1974 return self._get_instance(cache=bool(cache), config=config)
1973
1975
1974 def _get_instance_cached(self):
1976 def _get_instance_cached(self):
1975 @cache_region('long_term')
1977 @cache_region('long_term')
1976 def _get_repo(cache_key):
1978 def _get_repo(cache_key):
1977 return self._get_instance()
1979 return self._get_instance()
1978
1980
1979 invalidator_context = CacheKey.repo_context_cache(
1981 invalidator_context = CacheKey.repo_context_cache(
1980 _get_repo, self.repo_name, None, thread_scoped=True)
1982 _get_repo, self.repo_name, None, thread_scoped=True)
1981
1983
1982 with invalidator_context as context:
1984 with invalidator_context as context:
1983 context.invalidate()
1985 context.invalidate()
1984 repo = context.compute()
1986 repo = context.compute()
1985
1987
1986 return repo
1988 return repo
1987
1989
1988 def _get_instance(self, cache=True, config=None):
1990 def _get_instance(self, cache=True, config=None):
1989 config = config or self._config
1991 config = config or self._config
1990 custom_wire = {
1992 custom_wire = {
1991 'cache': cache # controls the vcs.remote cache
1993 'cache': cache # controls the vcs.remote cache
1992 }
1994 }
1993
1995
1994 repo = get_vcs_instance(
1996 repo = get_vcs_instance(
1995 repo_path=safe_str(self.repo_full_path),
1997 repo_path=safe_str(self.repo_full_path),
1996 config=config,
1998 config=config,
1997 with_wire=custom_wire,
1999 with_wire=custom_wire,
1998 create=False)
2000 create=False)
1999
2001
2000 return repo
2002 return repo
2001
2003
2002 def __json__(self):
2004 def __json__(self):
2003 return {'landing_rev': self.landing_rev}
2005 return {'landing_rev': self.landing_rev}
2004
2006
2005 def get_dict(self):
2007 def get_dict(self):
2006
2008
2007 # Since we transformed `repo_name` to a hybrid property, we need to
2009 # Since we transformed `repo_name` to a hybrid property, we need to
2008 # keep compatibility with the code which uses `repo_name` field.
2010 # keep compatibility with the code which uses `repo_name` field.
2009
2011
2010 result = super(Repository, self).get_dict()
2012 result = super(Repository, self).get_dict()
2011 result['repo_name'] = result.pop('_repo_name', None)
2013 result['repo_name'] = result.pop('_repo_name', None)
2012 return result
2014 return result
2013
2015
2014
2016
2015 class RepoGroup(Base, BaseModel):
2017 class RepoGroup(Base, BaseModel):
2016 __tablename__ = 'groups'
2018 __tablename__ = 'groups'
2017 __table_args__ = (
2019 __table_args__ = (
2018 UniqueConstraint('group_name', 'group_parent_id'),
2020 UniqueConstraint('group_name', 'group_parent_id'),
2019 CheckConstraint('group_id != group_parent_id'),
2021 CheckConstraint('group_id != group_parent_id'),
2020 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2022 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2021 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2023 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2022 )
2024 )
2023 __mapper_args__ = {'order_by': 'group_name'}
2025 __mapper_args__ = {'order_by': 'group_name'}
2024
2026
2025 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2027 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2026
2028
2027 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2029 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2028 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2030 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2029 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2031 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2030 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2032 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2031 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2033 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2032 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2033 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2035 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2034
2036
2035 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2037 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2036 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2038 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2037 parent_group = relationship('RepoGroup', remote_side=group_id)
2039 parent_group = relationship('RepoGroup', remote_side=group_id)
2038 user = relationship('User')
2040 user = relationship('User')
2039 integrations = relationship('Integration',
2041 integrations = relationship('Integration',
2040 cascade="all, delete, delete-orphan")
2042 cascade="all, delete, delete-orphan")
2041
2043
2042 def __init__(self, group_name='', parent_group=None):
2044 def __init__(self, group_name='', parent_group=None):
2043 self.group_name = group_name
2045 self.group_name = group_name
2044 self.parent_group = parent_group
2046 self.parent_group = parent_group
2045
2047
2046 def __unicode__(self):
2048 def __unicode__(self):
2047 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2049 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2048 self.group_name)
2050 self.group_name)
2049
2051
2050 @classmethod
2052 @classmethod
2051 def _generate_choice(cls, repo_group):
2053 def _generate_choice(cls, repo_group):
2052 from webhelpers.html import literal as _literal
2054 from webhelpers.html import literal as _literal
2053 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2055 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2054 return repo_group.group_id, _name(repo_group.full_path_splitted)
2056 return repo_group.group_id, _name(repo_group.full_path_splitted)
2055
2057
2056 @classmethod
2058 @classmethod
2057 def groups_choices(cls, groups=None, show_empty_group=True):
2059 def groups_choices(cls, groups=None, show_empty_group=True):
2058 if not groups:
2060 if not groups:
2059 groups = cls.query().all()
2061 groups = cls.query().all()
2060
2062
2061 repo_groups = []
2063 repo_groups = []
2062 if show_empty_group:
2064 if show_empty_group:
2063 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2065 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2064
2066
2065 repo_groups.extend([cls._generate_choice(x) for x in groups])
2067 repo_groups.extend([cls._generate_choice(x) for x in groups])
2066
2068
2067 repo_groups = sorted(
2069 repo_groups = sorted(
2068 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2070 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2069 return repo_groups
2071 return repo_groups
2070
2072
2071 @classmethod
2073 @classmethod
2072 def url_sep(cls):
2074 def url_sep(cls):
2073 return URL_SEP
2075 return URL_SEP
2074
2076
2075 @classmethod
2077 @classmethod
2076 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2078 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2077 if case_insensitive:
2079 if case_insensitive:
2078 gr = cls.query().filter(func.lower(cls.group_name)
2080 gr = cls.query().filter(func.lower(cls.group_name)
2079 == func.lower(group_name))
2081 == func.lower(group_name))
2080 else:
2082 else:
2081 gr = cls.query().filter(cls.group_name == group_name)
2083 gr = cls.query().filter(cls.group_name == group_name)
2082 if cache:
2084 if cache:
2083 gr = gr.options(FromCache(
2085 gr = gr.options(FromCache(
2084 "sql_cache_short",
2086 "sql_cache_short",
2085 "get_group_%s" % _hash_key(group_name)))
2087 "get_group_%s" % _hash_key(group_name)))
2086 return gr.scalar()
2088 return gr.scalar()
2087
2089
2088 @classmethod
2090 @classmethod
2089 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2091 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2090 case_insensitive=True):
2092 case_insensitive=True):
2091 q = RepoGroup.query()
2093 q = RepoGroup.query()
2092
2094
2093 if not isinstance(user_id, Optional):
2095 if not isinstance(user_id, Optional):
2094 q = q.filter(RepoGroup.user_id == user_id)
2096 q = q.filter(RepoGroup.user_id == user_id)
2095
2097
2096 if not isinstance(group_id, Optional):
2098 if not isinstance(group_id, Optional):
2097 q = q.filter(RepoGroup.group_parent_id == group_id)
2099 q = q.filter(RepoGroup.group_parent_id == group_id)
2098
2100
2099 if case_insensitive:
2101 if case_insensitive:
2100 q = q.order_by(func.lower(RepoGroup.group_name))
2102 q = q.order_by(func.lower(RepoGroup.group_name))
2101 else:
2103 else:
2102 q = q.order_by(RepoGroup.group_name)
2104 q = q.order_by(RepoGroup.group_name)
2103 return q.all()
2105 return q.all()
2104
2106
2105 @property
2107 @property
2106 def parents(self):
2108 def parents(self):
2107 parents_recursion_limit = 10
2109 parents_recursion_limit = 10
2108 groups = []
2110 groups = []
2109 if self.parent_group is None:
2111 if self.parent_group is None:
2110 return groups
2112 return groups
2111 cur_gr = self.parent_group
2113 cur_gr = self.parent_group
2112 groups.insert(0, cur_gr)
2114 groups.insert(0, cur_gr)
2113 cnt = 0
2115 cnt = 0
2114 while 1:
2116 while 1:
2115 cnt += 1
2117 cnt += 1
2116 gr = getattr(cur_gr, 'parent_group', None)
2118 gr = getattr(cur_gr, 'parent_group', None)
2117 cur_gr = cur_gr.parent_group
2119 cur_gr = cur_gr.parent_group
2118 if gr is None:
2120 if gr is None:
2119 break
2121 break
2120 if cnt == parents_recursion_limit:
2122 if cnt == parents_recursion_limit:
2121 # this will prevent accidental infinit loops
2123 # this will prevent accidental infinit loops
2122 log.error(('more than %s parents found for group %s, stopping '
2124 log.error(('more than %s parents found for group %s, stopping '
2123 'recursive parent fetching' % (parents_recursion_limit, self)))
2125 'recursive parent fetching' % (parents_recursion_limit, self)))
2124 break
2126 break
2125
2127
2126 groups.insert(0, gr)
2128 groups.insert(0, gr)
2127 return groups
2129 return groups
2128
2130
2129 @property
2131 @property
2130 def children(self):
2132 def children(self):
2131 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2133 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2132
2134
2133 @property
2135 @property
2134 def name(self):
2136 def name(self):
2135 return self.group_name.split(RepoGroup.url_sep())[-1]
2137 return self.group_name.split(RepoGroup.url_sep())[-1]
2136
2138
2137 @property
2139 @property
2138 def full_path(self):
2140 def full_path(self):
2139 return self.group_name
2141 return self.group_name
2140
2142
2141 @property
2143 @property
2142 def full_path_splitted(self):
2144 def full_path_splitted(self):
2143 return self.group_name.split(RepoGroup.url_sep())
2145 return self.group_name.split(RepoGroup.url_sep())
2144
2146
2145 @property
2147 @property
2146 def repositories(self):
2148 def repositories(self):
2147 return Repository.query()\
2149 return Repository.query()\
2148 .filter(Repository.group == self)\
2150 .filter(Repository.group == self)\
2149 .order_by(Repository.repo_name)
2151 .order_by(Repository.repo_name)
2150
2152
2151 @property
2153 @property
2152 def repositories_recursive_count(self):
2154 def repositories_recursive_count(self):
2153 cnt = self.repositories.count()
2155 cnt = self.repositories.count()
2154
2156
2155 def children_count(group):
2157 def children_count(group):
2156 cnt = 0
2158 cnt = 0
2157 for child in group.children:
2159 for child in group.children:
2158 cnt += child.repositories.count()
2160 cnt += child.repositories.count()
2159 cnt += children_count(child)
2161 cnt += children_count(child)
2160 return cnt
2162 return cnt
2161
2163
2162 return cnt + children_count(self)
2164 return cnt + children_count(self)
2163
2165
2164 def _recursive_objects(self, include_repos=True):
2166 def _recursive_objects(self, include_repos=True):
2165 all_ = []
2167 all_ = []
2166
2168
2167 def _get_members(root_gr):
2169 def _get_members(root_gr):
2168 if include_repos:
2170 if include_repos:
2169 for r in root_gr.repositories:
2171 for r in root_gr.repositories:
2170 all_.append(r)
2172 all_.append(r)
2171 childs = root_gr.children.all()
2173 childs = root_gr.children.all()
2172 if childs:
2174 if childs:
2173 for gr in childs:
2175 for gr in childs:
2174 all_.append(gr)
2176 all_.append(gr)
2175 _get_members(gr)
2177 _get_members(gr)
2176
2178
2177 _get_members(self)
2179 _get_members(self)
2178 return [self] + all_
2180 return [self] + all_
2179
2181
2180 def recursive_groups_and_repos(self):
2182 def recursive_groups_and_repos(self):
2181 """
2183 """
2182 Recursive return all groups, with repositories in those groups
2184 Recursive return all groups, with repositories in those groups
2183 """
2185 """
2184 return self._recursive_objects()
2186 return self._recursive_objects()
2185
2187
2186 def recursive_groups(self):
2188 def recursive_groups(self):
2187 """
2189 """
2188 Returns all children groups for this group including children of children
2190 Returns all children groups for this group including children of children
2189 """
2191 """
2190 return self._recursive_objects(include_repos=False)
2192 return self._recursive_objects(include_repos=False)
2191
2193
2192 def get_new_name(self, group_name):
2194 def get_new_name(self, group_name):
2193 """
2195 """
2194 returns new full group name based on parent and new name
2196 returns new full group name based on parent and new name
2195
2197
2196 :param group_name:
2198 :param group_name:
2197 """
2199 """
2198 path_prefix = (self.parent_group.full_path_splitted if
2200 path_prefix = (self.parent_group.full_path_splitted if
2199 self.parent_group else [])
2201 self.parent_group else [])
2200 return RepoGroup.url_sep().join(path_prefix + [group_name])
2202 return RepoGroup.url_sep().join(path_prefix + [group_name])
2201
2203
2202 def permissions(self, with_admins=True, with_owner=True):
2204 def permissions(self, with_admins=True, with_owner=True):
2203 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2205 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2204 q = q.options(joinedload(UserRepoGroupToPerm.group),
2206 q = q.options(joinedload(UserRepoGroupToPerm.group),
2205 joinedload(UserRepoGroupToPerm.user),
2207 joinedload(UserRepoGroupToPerm.user),
2206 joinedload(UserRepoGroupToPerm.permission),)
2208 joinedload(UserRepoGroupToPerm.permission),)
2207
2209
2208 # get owners and admins and permissions. We do a trick of re-writing
2210 # get owners and admins and permissions. We do a trick of re-writing
2209 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2211 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2210 # has a global reference and changing one object propagates to all
2212 # has a global reference and changing one object propagates to all
2211 # others. This means if admin is also an owner admin_row that change
2213 # others. This means if admin is also an owner admin_row that change
2212 # would propagate to both objects
2214 # would propagate to both objects
2213 perm_rows = []
2215 perm_rows = []
2214 for _usr in q.all():
2216 for _usr in q.all():
2215 usr = AttributeDict(_usr.user.get_dict())
2217 usr = AttributeDict(_usr.user.get_dict())
2216 usr.permission = _usr.permission.permission_name
2218 usr.permission = _usr.permission.permission_name
2217 perm_rows.append(usr)
2219 perm_rows.append(usr)
2218
2220
2219 # filter the perm rows by 'default' first and then sort them by
2221 # filter the perm rows by 'default' first and then sort them by
2220 # admin,write,read,none permissions sorted again alphabetically in
2222 # admin,write,read,none permissions sorted again alphabetically in
2221 # each group
2223 # each group
2222 perm_rows = sorted(perm_rows, key=display_sort)
2224 perm_rows = sorted(perm_rows, key=display_sort)
2223
2225
2224 _admin_perm = 'group.admin'
2226 _admin_perm = 'group.admin'
2225 owner_row = []
2227 owner_row = []
2226 if with_owner:
2228 if with_owner:
2227 usr = AttributeDict(self.user.get_dict())
2229 usr = AttributeDict(self.user.get_dict())
2228 usr.owner_row = True
2230 usr.owner_row = True
2229 usr.permission = _admin_perm
2231 usr.permission = _admin_perm
2230 owner_row.append(usr)
2232 owner_row.append(usr)
2231
2233
2232 super_admin_rows = []
2234 super_admin_rows = []
2233 if with_admins:
2235 if with_admins:
2234 for usr in User.get_all_super_admins():
2236 for usr in User.get_all_super_admins():
2235 # if this admin is also owner, don't double the record
2237 # if this admin is also owner, don't double the record
2236 if usr.user_id == owner_row[0].user_id:
2238 if usr.user_id == owner_row[0].user_id:
2237 owner_row[0].admin_row = True
2239 owner_row[0].admin_row = True
2238 else:
2240 else:
2239 usr = AttributeDict(usr.get_dict())
2241 usr = AttributeDict(usr.get_dict())
2240 usr.admin_row = True
2242 usr.admin_row = True
2241 usr.permission = _admin_perm
2243 usr.permission = _admin_perm
2242 super_admin_rows.append(usr)
2244 super_admin_rows.append(usr)
2243
2245
2244 return super_admin_rows + owner_row + perm_rows
2246 return super_admin_rows + owner_row + perm_rows
2245
2247
2246 def permission_user_groups(self):
2248 def permission_user_groups(self):
2247 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2249 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2248 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2250 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2249 joinedload(UserGroupRepoGroupToPerm.users_group),
2251 joinedload(UserGroupRepoGroupToPerm.users_group),
2250 joinedload(UserGroupRepoGroupToPerm.permission),)
2252 joinedload(UserGroupRepoGroupToPerm.permission),)
2251
2253
2252 perm_rows = []
2254 perm_rows = []
2253 for _user_group in q.all():
2255 for _user_group in q.all():
2254 usr = AttributeDict(_user_group.users_group.get_dict())
2256 usr = AttributeDict(_user_group.users_group.get_dict())
2255 usr.permission = _user_group.permission.permission_name
2257 usr.permission = _user_group.permission.permission_name
2256 perm_rows.append(usr)
2258 perm_rows.append(usr)
2257
2259
2258 return perm_rows
2260 return perm_rows
2259
2261
2260 def get_api_data(self):
2262 def get_api_data(self):
2261 """
2263 """
2262 Common function for generating api data
2264 Common function for generating api data
2263
2265
2264 """
2266 """
2265 group = self
2267 group = self
2266 data = {
2268 data = {
2267 'group_id': group.group_id,
2269 'group_id': group.group_id,
2268 'group_name': group.group_name,
2270 'group_name': group.group_name,
2269 'group_description': group.group_description,
2271 'group_description': group.group_description,
2270 'parent_group': group.parent_group.group_name if group.parent_group else None,
2272 'parent_group': group.parent_group.group_name if group.parent_group else None,
2271 'repositories': [x.repo_name for x in group.repositories],
2273 'repositories': [x.repo_name for x in group.repositories],
2272 'owner': group.user.username,
2274 'owner': group.user.username,
2273 }
2275 }
2274 return data
2276 return data
2275
2277
2276
2278
2277 class Permission(Base, BaseModel):
2279 class Permission(Base, BaseModel):
2278 __tablename__ = 'permissions'
2280 __tablename__ = 'permissions'
2279 __table_args__ = (
2281 __table_args__ = (
2280 Index('p_perm_name_idx', 'permission_name'),
2282 Index('p_perm_name_idx', 'permission_name'),
2281 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2282 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2284 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2283 )
2285 )
2284 PERMS = [
2286 PERMS = [
2285 ('hg.admin', _('RhodeCode Super Administrator')),
2287 ('hg.admin', _('RhodeCode Super Administrator')),
2286
2288
2287 ('repository.none', _('Repository no access')),
2289 ('repository.none', _('Repository no access')),
2288 ('repository.read', _('Repository read access')),
2290 ('repository.read', _('Repository read access')),
2289 ('repository.write', _('Repository write access')),
2291 ('repository.write', _('Repository write access')),
2290 ('repository.admin', _('Repository admin access')),
2292 ('repository.admin', _('Repository admin access')),
2291
2293
2292 ('group.none', _('Repository group no access')),
2294 ('group.none', _('Repository group no access')),
2293 ('group.read', _('Repository group read access')),
2295 ('group.read', _('Repository group read access')),
2294 ('group.write', _('Repository group write access')),
2296 ('group.write', _('Repository group write access')),
2295 ('group.admin', _('Repository group admin access')),
2297 ('group.admin', _('Repository group admin access')),
2296
2298
2297 ('usergroup.none', _('User group no access')),
2299 ('usergroup.none', _('User group no access')),
2298 ('usergroup.read', _('User group read access')),
2300 ('usergroup.read', _('User group read access')),
2299 ('usergroup.write', _('User group write access')),
2301 ('usergroup.write', _('User group write access')),
2300 ('usergroup.admin', _('User group admin access')),
2302 ('usergroup.admin', _('User group admin access')),
2301
2303
2302 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2304 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2303 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2305 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2304
2306
2305 ('hg.usergroup.create.false', _('User Group creation disabled')),
2307 ('hg.usergroup.create.false', _('User Group creation disabled')),
2306 ('hg.usergroup.create.true', _('User Group creation enabled')),
2308 ('hg.usergroup.create.true', _('User Group creation enabled')),
2307
2309
2308 ('hg.create.none', _('Repository creation disabled')),
2310 ('hg.create.none', _('Repository creation disabled')),
2309 ('hg.create.repository', _('Repository creation enabled')),
2311 ('hg.create.repository', _('Repository creation enabled')),
2310 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2312 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2311 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2313 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2312
2314
2313 ('hg.fork.none', _('Repository forking disabled')),
2315 ('hg.fork.none', _('Repository forking disabled')),
2314 ('hg.fork.repository', _('Repository forking enabled')),
2316 ('hg.fork.repository', _('Repository forking enabled')),
2315
2317
2316 ('hg.register.none', _('Registration disabled')),
2318 ('hg.register.none', _('Registration disabled')),
2317 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2319 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2318 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2320 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2319
2321
2320 ('hg.extern_activate.manual', _('Manual activation of external account')),
2322 ('hg.extern_activate.manual', _('Manual activation of external account')),
2321 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2323 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2322
2324
2323 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2325 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2324 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2326 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2325 ]
2327 ]
2326
2328
2327 # definition of system default permissions for DEFAULT user
2329 # definition of system default permissions for DEFAULT user
2328 DEFAULT_USER_PERMISSIONS = [
2330 DEFAULT_USER_PERMISSIONS = [
2329 'repository.read',
2331 'repository.read',
2330 'group.read',
2332 'group.read',
2331 'usergroup.read',
2333 'usergroup.read',
2332 'hg.create.repository',
2334 'hg.create.repository',
2333 'hg.repogroup.create.false',
2335 'hg.repogroup.create.false',
2334 'hg.usergroup.create.false',
2336 'hg.usergroup.create.false',
2335 'hg.create.write_on_repogroup.true',
2337 'hg.create.write_on_repogroup.true',
2336 'hg.fork.repository',
2338 'hg.fork.repository',
2337 'hg.register.manual_activate',
2339 'hg.register.manual_activate',
2338 'hg.extern_activate.auto',
2340 'hg.extern_activate.auto',
2339 'hg.inherit_default_perms.true',
2341 'hg.inherit_default_perms.true',
2340 ]
2342 ]
2341
2343
2342 # defines which permissions are more important higher the more important
2344 # defines which permissions are more important higher the more important
2343 # Weight defines which permissions are more important.
2345 # Weight defines which permissions are more important.
2344 # The higher number the more important.
2346 # The higher number the more important.
2345 PERM_WEIGHTS = {
2347 PERM_WEIGHTS = {
2346 'repository.none': 0,
2348 'repository.none': 0,
2347 'repository.read': 1,
2349 'repository.read': 1,
2348 'repository.write': 3,
2350 'repository.write': 3,
2349 'repository.admin': 4,
2351 'repository.admin': 4,
2350
2352
2351 'group.none': 0,
2353 'group.none': 0,
2352 'group.read': 1,
2354 'group.read': 1,
2353 'group.write': 3,
2355 'group.write': 3,
2354 'group.admin': 4,
2356 'group.admin': 4,
2355
2357
2356 'usergroup.none': 0,
2358 'usergroup.none': 0,
2357 'usergroup.read': 1,
2359 'usergroup.read': 1,
2358 'usergroup.write': 3,
2360 'usergroup.write': 3,
2359 'usergroup.admin': 4,
2361 'usergroup.admin': 4,
2360
2362
2361 'hg.repogroup.create.false': 0,
2363 'hg.repogroup.create.false': 0,
2362 'hg.repogroup.create.true': 1,
2364 'hg.repogroup.create.true': 1,
2363
2365
2364 'hg.usergroup.create.false': 0,
2366 'hg.usergroup.create.false': 0,
2365 'hg.usergroup.create.true': 1,
2367 'hg.usergroup.create.true': 1,
2366
2368
2367 'hg.fork.none': 0,
2369 'hg.fork.none': 0,
2368 'hg.fork.repository': 1,
2370 'hg.fork.repository': 1,
2369 'hg.create.none': 0,
2371 'hg.create.none': 0,
2370 'hg.create.repository': 1
2372 'hg.create.repository': 1
2371 }
2373 }
2372
2374
2373 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2375 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2374 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2376 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2375 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2377 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2376
2378
2377 def __unicode__(self):
2379 def __unicode__(self):
2378 return u"<%s('%s:%s')>" % (
2380 return u"<%s('%s:%s')>" % (
2379 self.__class__.__name__, self.permission_id, self.permission_name
2381 self.__class__.__name__, self.permission_id, self.permission_name
2380 )
2382 )
2381
2383
2382 @classmethod
2384 @classmethod
2383 def get_by_key(cls, key):
2385 def get_by_key(cls, key):
2384 return cls.query().filter(cls.permission_name == key).scalar()
2386 return cls.query().filter(cls.permission_name == key).scalar()
2385
2387
2386 @classmethod
2388 @classmethod
2387 def get_default_repo_perms(cls, user_id, repo_id=None):
2389 def get_default_repo_perms(cls, user_id, repo_id=None):
2388 q = Session().query(UserRepoToPerm, Repository, Permission)\
2390 q = Session().query(UserRepoToPerm, Repository, Permission)\
2389 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2391 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2390 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2392 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2391 .filter(UserRepoToPerm.user_id == user_id)
2393 .filter(UserRepoToPerm.user_id == user_id)
2392 if repo_id:
2394 if repo_id:
2393 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2395 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2394 return q.all()
2396 return q.all()
2395
2397
2396 @classmethod
2398 @classmethod
2397 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2399 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2398 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2400 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2399 .join(
2401 .join(
2400 Permission,
2402 Permission,
2401 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2403 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2402 .join(
2404 .join(
2403 Repository,
2405 Repository,
2404 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2406 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2405 .join(
2407 .join(
2406 UserGroup,
2408 UserGroup,
2407 UserGroupRepoToPerm.users_group_id ==
2409 UserGroupRepoToPerm.users_group_id ==
2408 UserGroup.users_group_id)\
2410 UserGroup.users_group_id)\
2409 .join(
2411 .join(
2410 UserGroupMember,
2412 UserGroupMember,
2411 UserGroupRepoToPerm.users_group_id ==
2413 UserGroupRepoToPerm.users_group_id ==
2412 UserGroupMember.users_group_id)\
2414 UserGroupMember.users_group_id)\
2413 .filter(
2415 .filter(
2414 UserGroupMember.user_id == user_id,
2416 UserGroupMember.user_id == user_id,
2415 UserGroup.users_group_active == true())
2417 UserGroup.users_group_active == true())
2416 if repo_id:
2418 if repo_id:
2417 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2419 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2418 return q.all()
2420 return q.all()
2419
2421
2420 @classmethod
2422 @classmethod
2421 def get_default_group_perms(cls, user_id, repo_group_id=None):
2423 def get_default_group_perms(cls, user_id, repo_group_id=None):
2422 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2424 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2423 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2425 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2424 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2426 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2425 .filter(UserRepoGroupToPerm.user_id == user_id)
2427 .filter(UserRepoGroupToPerm.user_id == user_id)
2426 if repo_group_id:
2428 if repo_group_id:
2427 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2429 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2428 return q.all()
2430 return q.all()
2429
2431
2430 @classmethod
2432 @classmethod
2431 def get_default_group_perms_from_user_group(
2433 def get_default_group_perms_from_user_group(
2432 cls, user_id, repo_group_id=None):
2434 cls, user_id, repo_group_id=None):
2433 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2435 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2434 .join(
2436 .join(
2435 Permission,
2437 Permission,
2436 UserGroupRepoGroupToPerm.permission_id ==
2438 UserGroupRepoGroupToPerm.permission_id ==
2437 Permission.permission_id)\
2439 Permission.permission_id)\
2438 .join(
2440 .join(
2439 RepoGroup,
2441 RepoGroup,
2440 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2442 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2441 .join(
2443 .join(
2442 UserGroup,
2444 UserGroup,
2443 UserGroupRepoGroupToPerm.users_group_id ==
2445 UserGroupRepoGroupToPerm.users_group_id ==
2444 UserGroup.users_group_id)\
2446 UserGroup.users_group_id)\
2445 .join(
2447 .join(
2446 UserGroupMember,
2448 UserGroupMember,
2447 UserGroupRepoGroupToPerm.users_group_id ==
2449 UserGroupRepoGroupToPerm.users_group_id ==
2448 UserGroupMember.users_group_id)\
2450 UserGroupMember.users_group_id)\
2449 .filter(
2451 .filter(
2450 UserGroupMember.user_id == user_id,
2452 UserGroupMember.user_id == user_id,
2451 UserGroup.users_group_active == true())
2453 UserGroup.users_group_active == true())
2452 if repo_group_id:
2454 if repo_group_id:
2453 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2455 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2454 return q.all()
2456 return q.all()
2455
2457
2456 @classmethod
2458 @classmethod
2457 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2459 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2458 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2460 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2459 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2461 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2460 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2462 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2461 .filter(UserUserGroupToPerm.user_id == user_id)
2463 .filter(UserUserGroupToPerm.user_id == user_id)
2462 if user_group_id:
2464 if user_group_id:
2463 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2465 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2464 return q.all()
2466 return q.all()
2465
2467
2466 @classmethod
2468 @classmethod
2467 def get_default_user_group_perms_from_user_group(
2469 def get_default_user_group_perms_from_user_group(
2468 cls, user_id, user_group_id=None):
2470 cls, user_id, user_group_id=None):
2469 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2471 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2470 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2472 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2471 .join(
2473 .join(
2472 Permission,
2474 Permission,
2473 UserGroupUserGroupToPerm.permission_id ==
2475 UserGroupUserGroupToPerm.permission_id ==
2474 Permission.permission_id)\
2476 Permission.permission_id)\
2475 .join(
2477 .join(
2476 TargetUserGroup,
2478 TargetUserGroup,
2477 UserGroupUserGroupToPerm.target_user_group_id ==
2479 UserGroupUserGroupToPerm.target_user_group_id ==
2478 TargetUserGroup.users_group_id)\
2480 TargetUserGroup.users_group_id)\
2479 .join(
2481 .join(
2480 UserGroup,
2482 UserGroup,
2481 UserGroupUserGroupToPerm.user_group_id ==
2483 UserGroupUserGroupToPerm.user_group_id ==
2482 UserGroup.users_group_id)\
2484 UserGroup.users_group_id)\
2483 .join(
2485 .join(
2484 UserGroupMember,
2486 UserGroupMember,
2485 UserGroupUserGroupToPerm.user_group_id ==
2487 UserGroupUserGroupToPerm.user_group_id ==
2486 UserGroupMember.users_group_id)\
2488 UserGroupMember.users_group_id)\
2487 .filter(
2489 .filter(
2488 UserGroupMember.user_id == user_id,
2490 UserGroupMember.user_id == user_id,
2489 UserGroup.users_group_active == true())
2491 UserGroup.users_group_active == true())
2490 if user_group_id:
2492 if user_group_id:
2491 q = q.filter(
2493 q = q.filter(
2492 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2494 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2493
2495
2494 return q.all()
2496 return q.all()
2495
2497
2496
2498
2497 class UserRepoToPerm(Base, BaseModel):
2499 class UserRepoToPerm(Base, BaseModel):
2498 __tablename__ = 'repo_to_perm'
2500 __tablename__ = 'repo_to_perm'
2499 __table_args__ = (
2501 __table_args__ = (
2500 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2502 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2503 )
2505 )
2504 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2506 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2505 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2507 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2507 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2509 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2508
2510
2509 user = relationship('User')
2511 user = relationship('User')
2510 repository = relationship('Repository')
2512 repository = relationship('Repository')
2511 permission = relationship('Permission')
2513 permission = relationship('Permission')
2512
2514
2513 @classmethod
2515 @classmethod
2514 def create(cls, user, repository, permission):
2516 def create(cls, user, repository, permission):
2515 n = cls()
2517 n = cls()
2516 n.user = user
2518 n.user = user
2517 n.repository = repository
2519 n.repository = repository
2518 n.permission = permission
2520 n.permission = permission
2519 Session().add(n)
2521 Session().add(n)
2520 return n
2522 return n
2521
2523
2522 def __unicode__(self):
2524 def __unicode__(self):
2523 return u'<%s => %s >' % (self.user, self.repository)
2525 return u'<%s => %s >' % (self.user, self.repository)
2524
2526
2525
2527
2526 class UserUserGroupToPerm(Base, BaseModel):
2528 class UserUserGroupToPerm(Base, BaseModel):
2527 __tablename__ = 'user_user_group_to_perm'
2529 __tablename__ = 'user_user_group_to_perm'
2528 __table_args__ = (
2530 __table_args__ = (
2529 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2531 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2531 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2533 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2532 )
2534 )
2533 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2535 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2534 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2535 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2537 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2536 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2538 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2537
2539
2538 user = relationship('User')
2540 user = relationship('User')
2539 user_group = relationship('UserGroup')
2541 user_group = relationship('UserGroup')
2540 permission = relationship('Permission')
2542 permission = relationship('Permission')
2541
2543
2542 @classmethod
2544 @classmethod
2543 def create(cls, user, user_group, permission):
2545 def create(cls, user, user_group, permission):
2544 n = cls()
2546 n = cls()
2545 n.user = user
2547 n.user = user
2546 n.user_group = user_group
2548 n.user_group = user_group
2547 n.permission = permission
2549 n.permission = permission
2548 Session().add(n)
2550 Session().add(n)
2549 return n
2551 return n
2550
2552
2551 def __unicode__(self):
2553 def __unicode__(self):
2552 return u'<%s => %s >' % (self.user, self.user_group)
2554 return u'<%s => %s >' % (self.user, self.user_group)
2553
2555
2554
2556
2555 class UserToPerm(Base, BaseModel):
2557 class UserToPerm(Base, BaseModel):
2556 __tablename__ = 'user_to_perm'
2558 __tablename__ = 'user_to_perm'
2557 __table_args__ = (
2559 __table_args__ = (
2558 UniqueConstraint('user_id', 'permission_id'),
2560 UniqueConstraint('user_id', 'permission_id'),
2559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2562 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2561 )
2563 )
2562 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2564 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2563 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2564 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2565
2567
2566 user = relationship('User')
2568 user = relationship('User')
2567 permission = relationship('Permission', lazy='joined')
2569 permission = relationship('Permission', lazy='joined')
2568
2570
2569 def __unicode__(self):
2571 def __unicode__(self):
2570 return u'<%s => %s >' % (self.user, self.permission)
2572 return u'<%s => %s >' % (self.user, self.permission)
2571
2573
2572
2574
2573 class UserGroupRepoToPerm(Base, BaseModel):
2575 class UserGroupRepoToPerm(Base, BaseModel):
2574 __tablename__ = 'users_group_repo_to_perm'
2576 __tablename__ = 'users_group_repo_to_perm'
2575 __table_args__ = (
2577 __table_args__ = (
2576 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2578 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2579 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2578 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2580 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2579 )
2581 )
2580 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2582 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2581 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2583 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2582 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2584 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2583 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2585 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2584
2586
2585 users_group = relationship('UserGroup')
2587 users_group = relationship('UserGroup')
2586 permission = relationship('Permission')
2588 permission = relationship('Permission')
2587 repository = relationship('Repository')
2589 repository = relationship('Repository')
2588
2590
2589 @classmethod
2591 @classmethod
2590 def create(cls, users_group, repository, permission):
2592 def create(cls, users_group, repository, permission):
2591 n = cls()
2593 n = cls()
2592 n.users_group = users_group
2594 n.users_group = users_group
2593 n.repository = repository
2595 n.repository = repository
2594 n.permission = permission
2596 n.permission = permission
2595 Session().add(n)
2597 Session().add(n)
2596 return n
2598 return n
2597
2599
2598 def __unicode__(self):
2600 def __unicode__(self):
2599 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2601 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2600
2602
2601
2603
2602 class UserGroupUserGroupToPerm(Base, BaseModel):
2604 class UserGroupUserGroupToPerm(Base, BaseModel):
2603 __tablename__ = 'user_group_user_group_to_perm'
2605 __tablename__ = 'user_group_user_group_to_perm'
2604 __table_args__ = (
2606 __table_args__ = (
2605 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2607 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2606 CheckConstraint('target_user_group_id != user_group_id'),
2608 CheckConstraint('target_user_group_id != user_group_id'),
2607 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2609 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2608 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2610 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2609 )
2611 )
2610 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)
2612 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)
2611 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2613 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2612 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2614 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2613 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2615 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2614
2616
2615 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2617 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2616 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2618 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2617 permission = relationship('Permission')
2619 permission = relationship('Permission')
2618
2620
2619 @classmethod
2621 @classmethod
2620 def create(cls, target_user_group, user_group, permission):
2622 def create(cls, target_user_group, user_group, permission):
2621 n = cls()
2623 n = cls()
2622 n.target_user_group = target_user_group
2624 n.target_user_group = target_user_group
2623 n.user_group = user_group
2625 n.user_group = user_group
2624 n.permission = permission
2626 n.permission = permission
2625 Session().add(n)
2627 Session().add(n)
2626 return n
2628 return n
2627
2629
2628 def __unicode__(self):
2630 def __unicode__(self):
2629 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2631 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2630
2632
2631
2633
2632 class UserGroupToPerm(Base, BaseModel):
2634 class UserGroupToPerm(Base, BaseModel):
2633 __tablename__ = 'users_group_to_perm'
2635 __tablename__ = 'users_group_to_perm'
2634 __table_args__ = (
2636 __table_args__ = (
2635 UniqueConstraint('users_group_id', 'permission_id',),
2637 UniqueConstraint('users_group_id', 'permission_id',),
2636 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2638 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2637 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2639 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2638 )
2640 )
2639 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2641 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2642 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2641 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2643 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2642
2644
2643 users_group = relationship('UserGroup')
2645 users_group = relationship('UserGroup')
2644 permission = relationship('Permission')
2646 permission = relationship('Permission')
2645
2647
2646
2648
2647 class UserRepoGroupToPerm(Base, BaseModel):
2649 class UserRepoGroupToPerm(Base, BaseModel):
2648 __tablename__ = 'user_repo_group_to_perm'
2650 __tablename__ = 'user_repo_group_to_perm'
2649 __table_args__ = (
2651 __table_args__ = (
2650 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2652 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2651 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2653 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2652 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2654 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2653 )
2655 )
2654
2656
2655 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2657 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2656 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2658 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2657 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2659 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2658 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2660 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2659
2661
2660 user = relationship('User')
2662 user = relationship('User')
2661 group = relationship('RepoGroup')
2663 group = relationship('RepoGroup')
2662 permission = relationship('Permission')
2664 permission = relationship('Permission')
2663
2665
2664 @classmethod
2666 @classmethod
2665 def create(cls, user, repository_group, permission):
2667 def create(cls, user, repository_group, permission):
2666 n = cls()
2668 n = cls()
2667 n.user = user
2669 n.user = user
2668 n.group = repository_group
2670 n.group = repository_group
2669 n.permission = permission
2671 n.permission = permission
2670 Session().add(n)
2672 Session().add(n)
2671 return n
2673 return n
2672
2674
2673
2675
2674 class UserGroupRepoGroupToPerm(Base, BaseModel):
2676 class UserGroupRepoGroupToPerm(Base, BaseModel):
2675 __tablename__ = 'users_group_repo_group_to_perm'
2677 __tablename__ = 'users_group_repo_group_to_perm'
2676 __table_args__ = (
2678 __table_args__ = (
2677 UniqueConstraint('users_group_id', 'group_id'),
2679 UniqueConstraint('users_group_id', 'group_id'),
2678 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2679 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2681 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2680 )
2682 )
2681
2683
2682 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)
2684 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)
2683 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2684 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2686 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2685 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2687 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2686
2688
2687 users_group = relationship('UserGroup')
2689 users_group = relationship('UserGroup')
2688 permission = relationship('Permission')
2690 permission = relationship('Permission')
2689 group = relationship('RepoGroup')
2691 group = relationship('RepoGroup')
2690
2692
2691 @classmethod
2693 @classmethod
2692 def create(cls, user_group, repository_group, permission):
2694 def create(cls, user_group, repository_group, permission):
2693 n = cls()
2695 n = cls()
2694 n.users_group = user_group
2696 n.users_group = user_group
2695 n.group = repository_group
2697 n.group = repository_group
2696 n.permission = permission
2698 n.permission = permission
2697 Session().add(n)
2699 Session().add(n)
2698 return n
2700 return n
2699
2701
2700 def __unicode__(self):
2702 def __unicode__(self):
2701 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2703 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2702
2704
2703
2705
2704 class Statistics(Base, BaseModel):
2706 class Statistics(Base, BaseModel):
2705 __tablename__ = 'statistics'
2707 __tablename__ = 'statistics'
2706 __table_args__ = (
2708 __table_args__ = (
2707 UniqueConstraint('repository_id'),
2709 UniqueConstraint('repository_id'),
2708 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2709 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2711 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2710 )
2712 )
2711 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2713 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2712 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2714 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2713 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2715 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2714 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2716 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2715 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2717 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2716 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2718 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2717
2719
2718 repository = relationship('Repository', single_parent=True)
2720 repository = relationship('Repository', single_parent=True)
2719
2721
2720
2722
2721 class UserFollowing(Base, BaseModel):
2723 class UserFollowing(Base, BaseModel):
2722 __tablename__ = 'user_followings'
2724 __tablename__ = 'user_followings'
2723 __table_args__ = (
2725 __table_args__ = (
2724 UniqueConstraint('user_id', 'follows_repository_id'),
2726 UniqueConstraint('user_id', 'follows_repository_id'),
2725 UniqueConstraint('user_id', 'follows_user_id'),
2727 UniqueConstraint('user_id', 'follows_user_id'),
2726 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2727 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2729 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2728 )
2730 )
2729
2731
2730 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2732 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2731 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2733 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2732 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2734 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2733 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2735 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2734 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2736 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2735
2737
2736 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2738 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2737
2739
2738 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2740 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2739 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2741 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2740
2742
2741 @classmethod
2743 @classmethod
2742 def get_repo_followers(cls, repo_id):
2744 def get_repo_followers(cls, repo_id):
2743 return cls.query().filter(cls.follows_repo_id == repo_id)
2745 return cls.query().filter(cls.follows_repo_id == repo_id)
2744
2746
2745
2747
2746 class CacheKey(Base, BaseModel):
2748 class CacheKey(Base, BaseModel):
2747 __tablename__ = 'cache_invalidation'
2749 __tablename__ = 'cache_invalidation'
2748 __table_args__ = (
2750 __table_args__ = (
2749 UniqueConstraint('cache_key'),
2751 UniqueConstraint('cache_key'),
2750 Index('key_idx', 'cache_key'),
2752 Index('key_idx', 'cache_key'),
2751 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2752 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2753 )
2755 )
2754 CACHE_TYPE_ATOM = 'ATOM'
2756 CACHE_TYPE_ATOM = 'ATOM'
2755 CACHE_TYPE_RSS = 'RSS'
2757 CACHE_TYPE_RSS = 'RSS'
2756 CACHE_TYPE_README = 'README'
2758 CACHE_TYPE_README = 'README'
2757
2759
2758 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2760 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2759 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2761 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2760 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2762 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2761 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2763 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2762
2764
2763 def __init__(self, cache_key, cache_args=''):
2765 def __init__(self, cache_key, cache_args=''):
2764 self.cache_key = cache_key
2766 self.cache_key = cache_key
2765 self.cache_args = cache_args
2767 self.cache_args = cache_args
2766 self.cache_active = False
2768 self.cache_active = False
2767
2769
2768 def __unicode__(self):
2770 def __unicode__(self):
2769 return u"<%s('%s:%s[%s]')>" % (
2771 return u"<%s('%s:%s[%s]')>" % (
2770 self.__class__.__name__,
2772 self.__class__.__name__,
2771 self.cache_id, self.cache_key, self.cache_active)
2773 self.cache_id, self.cache_key, self.cache_active)
2772
2774
2773 def _cache_key_partition(self):
2775 def _cache_key_partition(self):
2774 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2776 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2775 return prefix, repo_name, suffix
2777 return prefix, repo_name, suffix
2776
2778
2777 def get_prefix(self):
2779 def get_prefix(self):
2778 """
2780 """
2779 Try to extract prefix from existing cache key. The key could consist
2781 Try to extract prefix from existing cache key. The key could consist
2780 of prefix, repo_name, suffix
2782 of prefix, repo_name, suffix
2781 """
2783 """
2782 # this returns prefix, repo_name, suffix
2784 # this returns prefix, repo_name, suffix
2783 return self._cache_key_partition()[0]
2785 return self._cache_key_partition()[0]
2784
2786
2785 def get_suffix(self):
2787 def get_suffix(self):
2786 """
2788 """
2787 get suffix that might have been used in _get_cache_key to
2789 get suffix that might have been used in _get_cache_key to
2788 generate self.cache_key. Only used for informational purposes
2790 generate self.cache_key. Only used for informational purposes
2789 in repo_edit.html.
2791 in repo_edit.html.
2790 """
2792 """
2791 # prefix, repo_name, suffix
2793 # prefix, repo_name, suffix
2792 return self._cache_key_partition()[2]
2794 return self._cache_key_partition()[2]
2793
2795
2794 @classmethod
2796 @classmethod
2795 def delete_all_cache(cls):
2797 def delete_all_cache(cls):
2796 """
2798 """
2797 Delete all cache keys from database.
2799 Delete all cache keys from database.
2798 Should only be run when all instances are down and all entries
2800 Should only be run when all instances are down and all entries
2799 thus stale.
2801 thus stale.
2800 """
2802 """
2801 cls.query().delete()
2803 cls.query().delete()
2802 Session().commit()
2804 Session().commit()
2803
2805
2804 @classmethod
2806 @classmethod
2805 def get_cache_key(cls, repo_name, cache_type):
2807 def get_cache_key(cls, repo_name, cache_type):
2806 """
2808 """
2807
2809
2808 Generate a cache key for this process of RhodeCode instance.
2810 Generate a cache key for this process of RhodeCode instance.
2809 Prefix most likely will be process id or maybe explicitly set
2811 Prefix most likely will be process id or maybe explicitly set
2810 instance_id from .ini file.
2812 instance_id from .ini file.
2811 """
2813 """
2812 import rhodecode
2814 import rhodecode
2813 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2815 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2814
2816
2815 repo_as_unicode = safe_unicode(repo_name)
2817 repo_as_unicode = safe_unicode(repo_name)
2816 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2818 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2817 if cache_type else repo_as_unicode
2819 if cache_type else repo_as_unicode
2818
2820
2819 return u'{}{}'.format(prefix, key)
2821 return u'{}{}'.format(prefix, key)
2820
2822
2821 @classmethod
2823 @classmethod
2822 def set_invalidate(cls, repo_name, delete=False):
2824 def set_invalidate(cls, repo_name, delete=False):
2823 """
2825 """
2824 Mark all caches of a repo as invalid in the database.
2826 Mark all caches of a repo as invalid in the database.
2825 """
2827 """
2826
2828
2827 try:
2829 try:
2828 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2830 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2829 if delete:
2831 if delete:
2830 log.debug('cache objects deleted for repo %s',
2832 log.debug('cache objects deleted for repo %s',
2831 safe_str(repo_name))
2833 safe_str(repo_name))
2832 qry.delete()
2834 qry.delete()
2833 else:
2835 else:
2834 log.debug('cache objects marked as invalid for repo %s',
2836 log.debug('cache objects marked as invalid for repo %s',
2835 safe_str(repo_name))
2837 safe_str(repo_name))
2836 qry.update({"cache_active": False})
2838 qry.update({"cache_active": False})
2837
2839
2838 Session().commit()
2840 Session().commit()
2839 except Exception:
2841 except Exception:
2840 log.exception(
2842 log.exception(
2841 'Cache key invalidation failed for repository %s',
2843 'Cache key invalidation failed for repository %s',
2842 safe_str(repo_name))
2844 safe_str(repo_name))
2843 Session().rollback()
2845 Session().rollback()
2844
2846
2845 @classmethod
2847 @classmethod
2846 def get_active_cache(cls, cache_key):
2848 def get_active_cache(cls, cache_key):
2847 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2849 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2848 if inv_obj:
2850 if inv_obj:
2849 return inv_obj
2851 return inv_obj
2850 return None
2852 return None
2851
2853
2852 @classmethod
2854 @classmethod
2853 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2855 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2854 thread_scoped=False):
2856 thread_scoped=False):
2855 """
2857 """
2856 @cache_region('long_term')
2858 @cache_region('long_term')
2857 def _heavy_calculation(cache_key):
2859 def _heavy_calculation(cache_key):
2858 return 'result'
2860 return 'result'
2859
2861
2860 cache_context = CacheKey.repo_context_cache(
2862 cache_context = CacheKey.repo_context_cache(
2861 _heavy_calculation, repo_name, cache_type)
2863 _heavy_calculation, repo_name, cache_type)
2862
2864
2863 with cache_context as context:
2865 with cache_context as context:
2864 context.invalidate()
2866 context.invalidate()
2865 computed = context.compute()
2867 computed = context.compute()
2866
2868
2867 assert computed == 'result'
2869 assert computed == 'result'
2868 """
2870 """
2869 from rhodecode.lib import caches
2871 from rhodecode.lib import caches
2870 return caches.InvalidationContext(
2872 return caches.InvalidationContext(
2871 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2873 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2872
2874
2873
2875
2874 class ChangesetComment(Base, BaseModel):
2876 class ChangesetComment(Base, BaseModel):
2875 __tablename__ = 'changeset_comments'
2877 __tablename__ = 'changeset_comments'
2876 __table_args__ = (
2878 __table_args__ = (
2877 Index('cc_revision_idx', 'revision'),
2879 Index('cc_revision_idx', 'revision'),
2878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2879 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2880 )
2882 )
2881
2883
2882 COMMENT_OUTDATED = u'comment_outdated'
2884 COMMENT_OUTDATED = u'comment_outdated'
2883
2885
2884 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2885 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2886 revision = Column('revision', String(40), nullable=True)
2888 revision = Column('revision', String(40), nullable=True)
2887 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2888 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2889 line_no = Column('line_no', Unicode(10), nullable=True)
2891 line_no = Column('line_no', Unicode(10), nullable=True)
2890 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2891 f_path = Column('f_path', Unicode(1000), nullable=True)
2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2892 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2893 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2894 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2895 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2896 renderer = Column('renderer', Unicode(64), nullable=True)
2898 renderer = Column('renderer', Unicode(64), nullable=True)
2897 display_state = Column('display_state', Unicode(128), nullable=True)
2899 display_state = Column('display_state', Unicode(128), nullable=True)
2898
2900
2899 author = relationship('User', lazy='joined')
2901 author = relationship('User', lazy='joined')
2900 repo = relationship('Repository')
2902 repo = relationship('Repository')
2901 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2902 pull_request = relationship('PullRequest', lazy='joined')
2904 pull_request = relationship('PullRequest', lazy='joined')
2903 pull_request_version = relationship('PullRequestVersion')
2905 pull_request_version = relationship('PullRequestVersion')
2904
2906
2905 @classmethod
2907 @classmethod
2906 def get_users(cls, revision=None, pull_request_id=None):
2908 def get_users(cls, revision=None, pull_request_id=None):
2907 """
2909 """
2908 Returns user associated with this ChangesetComment. ie those
2910 Returns user associated with this ChangesetComment. ie those
2909 who actually commented
2911 who actually commented
2910
2912
2911 :param cls:
2913 :param cls:
2912 :param revision:
2914 :param revision:
2913 """
2915 """
2914 q = Session().query(User)\
2916 q = Session().query(User)\
2915 .join(ChangesetComment.author)
2917 .join(ChangesetComment.author)
2916 if revision:
2918 if revision:
2917 q = q.filter(cls.revision == revision)
2919 q = q.filter(cls.revision == revision)
2918 elif pull_request_id:
2920 elif pull_request_id:
2919 q = q.filter(cls.pull_request_id == pull_request_id)
2921 q = q.filter(cls.pull_request_id == pull_request_id)
2920 return q.all()
2922 return q.all()
2921
2923
2922 def render(self, mentions=False):
2924 def render(self, mentions=False):
2923 from rhodecode.lib import helpers as h
2925 from rhodecode.lib import helpers as h
2924 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2925
2927
2926 def __repr__(self):
2928 def __repr__(self):
2927 if self.comment_id:
2929 if self.comment_id:
2928 return '<DB:ChangesetComment #%s>' % self.comment_id
2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2929 else:
2931 else:
2930 return '<DB:ChangesetComment at %#x>' % id(self)
2932 return '<DB:ChangesetComment at %#x>' % id(self)
2931
2933
2932
2934
2933 class ChangesetStatus(Base, BaseModel):
2935 class ChangesetStatus(Base, BaseModel):
2934 __tablename__ = 'changeset_statuses'
2936 __tablename__ = 'changeset_statuses'
2935 __table_args__ = (
2937 __table_args__ = (
2936 Index('cs_revision_idx', 'revision'),
2938 Index('cs_revision_idx', 'revision'),
2937 Index('cs_version_idx', 'version'),
2939 Index('cs_version_idx', 'version'),
2938 UniqueConstraint('repo_id', 'revision', 'version'),
2940 UniqueConstraint('repo_id', 'revision', 'version'),
2939 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2940 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2941 )
2943 )
2942 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2943 STATUS_APPROVED = 'approved'
2945 STATUS_APPROVED = 'approved'
2944 STATUS_REJECTED = 'rejected'
2946 STATUS_REJECTED = 'rejected'
2945 STATUS_UNDER_REVIEW = 'under_review'
2947 STATUS_UNDER_REVIEW = 'under_review'
2946
2948
2947 STATUSES = [
2949 STATUSES = [
2948 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2949 (STATUS_APPROVED, _("Approved")),
2951 (STATUS_APPROVED, _("Approved")),
2950 (STATUS_REJECTED, _("Rejected")),
2952 (STATUS_REJECTED, _("Rejected")),
2951 (STATUS_UNDER_REVIEW, _("Under Review")),
2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2952 ]
2954 ]
2953
2955
2954 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2955 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2956 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2957 revision = Column('revision', String(40), nullable=False)
2959 revision = Column('revision', String(40), nullable=False)
2958 status = Column('status', String(128), nullable=False, default=DEFAULT)
2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2959 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2960 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2961 version = Column('version', Integer(), nullable=False, default=0)
2963 version = Column('version', Integer(), nullable=False, default=0)
2962 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2963
2965
2964 author = relationship('User', lazy='joined')
2966 author = relationship('User', lazy='joined')
2965 repo = relationship('Repository')
2967 repo = relationship('Repository')
2966 comment = relationship('ChangesetComment', lazy='joined')
2968 comment = relationship('ChangesetComment', lazy='joined')
2967 pull_request = relationship('PullRequest', lazy='joined')
2969 pull_request = relationship('PullRequest', lazy='joined')
2968
2970
2969 def __unicode__(self):
2971 def __unicode__(self):
2970 return u"<%s('%s[%s]:%s')>" % (
2972 return u"<%s('%s[%s]:%s')>" % (
2971 self.__class__.__name__,
2973 self.__class__.__name__,
2972 self.status, self.version, self.author
2974 self.status, self.version, self.author
2973 )
2975 )
2974
2976
2975 @classmethod
2977 @classmethod
2976 def get_status_lbl(cls, value):
2978 def get_status_lbl(cls, value):
2977 return dict(cls.STATUSES).get(value)
2979 return dict(cls.STATUSES).get(value)
2978
2980
2979 @property
2981 @property
2980 def status_lbl(self):
2982 def status_lbl(self):
2981 return ChangesetStatus.get_status_lbl(self.status)
2983 return ChangesetStatus.get_status_lbl(self.status)
2982
2984
2983
2985
2984 class _PullRequestBase(BaseModel):
2986 class _PullRequestBase(BaseModel):
2985 """
2987 """
2986 Common attributes of pull request and version entries.
2988 Common attributes of pull request and version entries.
2987 """
2989 """
2988
2990
2989 # .status values
2991 # .status values
2990 STATUS_NEW = u'new'
2992 STATUS_NEW = u'new'
2991 STATUS_OPEN = u'open'
2993 STATUS_OPEN = u'open'
2992 STATUS_CLOSED = u'closed'
2994 STATUS_CLOSED = u'closed'
2993
2995
2994 title = Column('title', Unicode(255), nullable=True)
2996 title = Column('title', Unicode(255), nullable=True)
2995 description = Column(
2997 description = Column(
2996 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2997 nullable=True)
2999 nullable=True)
2998 # new/open/closed status of pull request (not approve/reject/etc)
3000 # new/open/closed status of pull request (not approve/reject/etc)
2999 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3000 created_on = Column(
3002 created_on = Column(
3001 'created_on', DateTime(timezone=False), nullable=False,
3003 'created_on', DateTime(timezone=False), nullable=False,
3002 default=datetime.datetime.now)
3004 default=datetime.datetime.now)
3003 updated_on = Column(
3005 updated_on = Column(
3004 'updated_on', DateTime(timezone=False), nullable=False,
3006 'updated_on', DateTime(timezone=False), nullable=False,
3005 default=datetime.datetime.now)
3007 default=datetime.datetime.now)
3006
3008
3007 @declared_attr
3009 @declared_attr
3008 def user_id(cls):
3010 def user_id(cls):
3009 return Column(
3011 return Column(
3010 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3011 unique=None)
3013 unique=None)
3012
3014
3013 # 500 revisions max
3015 # 500 revisions max
3014 _revisions = Column(
3016 _revisions = Column(
3015 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3016
3018
3017 @declared_attr
3019 @declared_attr
3018 def source_repo_id(cls):
3020 def source_repo_id(cls):
3019 # TODO: dan: rename column to source_repo_id
3021 # TODO: dan: rename column to source_repo_id
3020 return Column(
3022 return Column(
3021 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3022 nullable=False)
3024 nullable=False)
3023
3025
3024 source_ref = Column('org_ref', Unicode(255), nullable=False)
3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3025
3027
3026 @declared_attr
3028 @declared_attr
3027 def target_repo_id(cls):
3029 def target_repo_id(cls):
3028 # TODO: dan: rename column to target_repo_id
3030 # TODO: dan: rename column to target_repo_id
3029 return Column(
3031 return Column(
3030 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3031 nullable=False)
3033 nullable=False)
3032
3034
3033 target_ref = Column('other_ref', Unicode(255), nullable=False)
3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3034
3036
3035 # TODO: dan: rename column to last_merge_source_rev
3037 # TODO: dan: rename column to last_merge_source_rev
3036 _last_merge_source_rev = Column(
3038 _last_merge_source_rev = Column(
3037 'last_merge_org_rev', String(40), nullable=True)
3039 'last_merge_org_rev', String(40), nullable=True)
3038 # TODO: dan: rename column to last_merge_target_rev
3040 # TODO: dan: rename column to last_merge_target_rev
3039 _last_merge_target_rev = Column(
3041 _last_merge_target_rev = Column(
3040 'last_merge_other_rev', String(40), nullable=True)
3042 'last_merge_other_rev', String(40), nullable=True)
3041 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3042 merge_rev = Column('merge_rev', String(40), nullable=True)
3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3043
3045
3044 @hybrid_property
3046 @hybrid_property
3045 def revisions(self):
3047 def revisions(self):
3046 return self._revisions.split(':') if self._revisions else []
3048 return self._revisions.split(':') if self._revisions else []
3047
3049
3048 @revisions.setter
3050 @revisions.setter
3049 def revisions(self, val):
3051 def revisions(self, val):
3050 self._revisions = ':'.join(val)
3052 self._revisions = ':'.join(val)
3051
3053
3052 @declared_attr
3054 @declared_attr
3053 def author(cls):
3055 def author(cls):
3054 return relationship('User', lazy='joined')
3056 return relationship('User', lazy='joined')
3055
3057
3056 @declared_attr
3058 @declared_attr
3057 def source_repo(cls):
3059 def source_repo(cls):
3058 return relationship(
3060 return relationship(
3059 'Repository',
3061 'Repository',
3060 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3061
3063
3062 @property
3064 @property
3063 def source_ref_parts(self):
3065 def source_ref_parts(self):
3064 refs = self.source_ref.split(':')
3066 refs = self.source_ref.split(':')
3065 return Reference(refs[0], refs[1], refs[2])
3067 return Reference(refs[0], refs[1], refs[2])
3066
3068
3067 @declared_attr
3069 @declared_attr
3068 def target_repo(cls):
3070 def target_repo(cls):
3069 return relationship(
3071 return relationship(
3070 'Repository',
3072 'Repository',
3071 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3072
3074
3073 @property
3075 @property
3074 def target_ref_parts(self):
3076 def target_ref_parts(self):
3075 refs = self.target_ref.split(':')
3077 refs = self.target_ref.split(':')
3076 return Reference(refs[0], refs[1], refs[2])
3078 return Reference(refs[0], refs[1], refs[2])
3077
3079
3078
3080
3079 class PullRequest(Base, _PullRequestBase):
3081 class PullRequest(Base, _PullRequestBase):
3080 __tablename__ = 'pull_requests'
3082 __tablename__ = 'pull_requests'
3081 __table_args__ = (
3083 __table_args__ = (
3082 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3083 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3084 )
3086 )
3085
3087
3086 pull_request_id = Column(
3088 pull_request_id = Column(
3087 'pull_request_id', Integer(), nullable=False, primary_key=True)
3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3088
3090
3089 def __repr__(self):
3091 def __repr__(self):
3090 if self.pull_request_id:
3092 if self.pull_request_id:
3091 return '<DB:PullRequest #%s>' % self.pull_request_id
3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3092 else:
3094 else:
3093 return '<DB:PullRequest at %#x>' % id(self)
3095 return '<DB:PullRequest at %#x>' % id(self)
3094
3096
3095 reviewers = relationship('PullRequestReviewers',
3097 reviewers = relationship('PullRequestReviewers',
3096 cascade="all, delete, delete-orphan")
3098 cascade="all, delete, delete-orphan")
3097 statuses = relationship('ChangesetStatus')
3099 statuses = relationship('ChangesetStatus')
3098 comments = relationship('ChangesetComment',
3100 comments = relationship('ChangesetComment',
3099 cascade="all, delete, delete-orphan")
3101 cascade="all, delete, delete-orphan")
3100 versions = relationship('PullRequestVersion',
3102 versions = relationship('PullRequestVersion',
3101 cascade="all, delete, delete-orphan")
3103 cascade="all, delete, delete-orphan")
3102
3104
3103 def is_closed(self):
3105 def is_closed(self):
3104 return self.status == self.STATUS_CLOSED
3106 return self.status == self.STATUS_CLOSED
3105
3107
3106 def get_api_data(self):
3108 def get_api_data(self):
3107 from rhodecode.model.pull_request import PullRequestModel
3109 from rhodecode.model.pull_request import PullRequestModel
3108 pull_request = self
3110 pull_request = self
3109 merge_status = PullRequestModel().merge_status(pull_request)
3111 merge_status = PullRequestModel().merge_status(pull_request)
3110 data = {
3112 data = {
3111 'pull_request_id': pull_request.pull_request_id,
3113 'pull_request_id': pull_request.pull_request_id,
3112 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3114 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3113 pull_request_id=self.pull_request_id,
3115 pull_request_id=self.pull_request_id,
3114 qualified=True),
3116 qualified=True),
3115 'title': pull_request.title,
3117 'title': pull_request.title,
3116 'description': pull_request.description,
3118 'description': pull_request.description,
3117 'status': pull_request.status,
3119 'status': pull_request.status,
3118 'created_on': pull_request.created_on,
3120 'created_on': pull_request.created_on,
3119 'updated_on': pull_request.updated_on,
3121 'updated_on': pull_request.updated_on,
3120 'commit_ids': pull_request.revisions,
3122 'commit_ids': pull_request.revisions,
3121 'review_status': pull_request.calculated_review_status(),
3123 'review_status': pull_request.calculated_review_status(),
3122 'mergeable': {
3124 'mergeable': {
3123 'status': merge_status[0],
3125 'status': merge_status[0],
3124 'message': unicode(merge_status[1]),
3126 'message': unicode(merge_status[1]),
3125 },
3127 },
3126 'source': {
3128 'source': {
3127 'clone_url': pull_request.source_repo.clone_url(),
3129 'clone_url': pull_request.source_repo.clone_url(),
3128 'repository': pull_request.source_repo.repo_name,
3130 'repository': pull_request.source_repo.repo_name,
3129 'reference': {
3131 'reference': {
3130 'name': pull_request.source_ref_parts.name,
3132 'name': pull_request.source_ref_parts.name,
3131 'type': pull_request.source_ref_parts.type,
3133 'type': pull_request.source_ref_parts.type,
3132 'commit_id': pull_request.source_ref_parts.commit_id,
3134 'commit_id': pull_request.source_ref_parts.commit_id,
3133 },
3135 },
3134 },
3136 },
3135 'target': {
3137 'target': {
3136 'clone_url': pull_request.target_repo.clone_url(),
3138 'clone_url': pull_request.target_repo.clone_url(),
3137 'repository': pull_request.target_repo.repo_name,
3139 'repository': pull_request.target_repo.repo_name,
3138 'reference': {
3140 'reference': {
3139 'name': pull_request.target_ref_parts.name,
3141 'name': pull_request.target_ref_parts.name,
3140 'type': pull_request.target_ref_parts.type,
3142 'type': pull_request.target_ref_parts.type,
3141 'commit_id': pull_request.target_ref_parts.commit_id,
3143 'commit_id': pull_request.target_ref_parts.commit_id,
3142 },
3144 },
3143 },
3145 },
3144 'author': pull_request.author.get_api_data(include_secrets=False,
3146 'author': pull_request.author.get_api_data(include_secrets=False,
3145 details='basic'),
3147 details='basic'),
3146 'reviewers': [
3148 'reviewers': [
3147 {
3149 {
3148 'user': reviewer.get_api_data(include_secrets=False,
3150 'user': reviewer.get_api_data(include_secrets=False,
3149 details='basic'),
3151 details='basic'),
3150 'review_status': st[0][1].status if st else 'not_reviewed',
3152 'review_status': st[0][1].status if st else 'not_reviewed',
3151 }
3153 }
3152 for reviewer, st in pull_request.reviewers_statuses()
3154 for reviewer, st in pull_request.reviewers_statuses()
3153 ]
3155 ]
3154 }
3156 }
3155
3157
3156 return data
3158 return data
3157
3159
3158 def __json__(self):
3160 def __json__(self):
3159 return {
3161 return {
3160 'revisions': self.revisions,
3162 'revisions': self.revisions,
3161 }
3163 }
3162
3164
3163 def calculated_review_status(self):
3165 def calculated_review_status(self):
3164 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3166 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3165 # because it's tricky on how to use ChangesetStatusModel from there
3167 # because it's tricky on how to use ChangesetStatusModel from there
3166 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3168 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3167 from rhodecode.model.changeset_status import ChangesetStatusModel
3169 from rhodecode.model.changeset_status import ChangesetStatusModel
3168 return ChangesetStatusModel().calculated_review_status(self)
3170 return ChangesetStatusModel().calculated_review_status(self)
3169
3171
3170 def reviewers_statuses(self):
3172 def reviewers_statuses(self):
3171 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3173 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3172 from rhodecode.model.changeset_status import ChangesetStatusModel
3174 from rhodecode.model.changeset_status import ChangesetStatusModel
3173 return ChangesetStatusModel().reviewers_statuses(self)
3175 return ChangesetStatusModel().reviewers_statuses(self)
3174
3176
3175
3177
3176 class PullRequestVersion(Base, _PullRequestBase):
3178 class PullRequestVersion(Base, _PullRequestBase):
3177 __tablename__ = 'pull_request_versions'
3179 __tablename__ = 'pull_request_versions'
3178 __table_args__ = (
3180 __table_args__ = (
3179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3180 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3181 )
3183 )
3182
3184
3183 pull_request_version_id = Column(
3185 pull_request_version_id = Column(
3184 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3186 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3185 pull_request_id = Column(
3187 pull_request_id = Column(
3186 'pull_request_id', Integer(),
3188 'pull_request_id', Integer(),
3187 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3189 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3188 pull_request = relationship('PullRequest')
3190 pull_request = relationship('PullRequest')
3189
3191
3190 def __repr__(self):
3192 def __repr__(self):
3191 if self.pull_request_version_id:
3193 if self.pull_request_version_id:
3192 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3194 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3193 else:
3195 else:
3194 return '<DB:PullRequestVersion at %#x>' % id(self)
3196 return '<DB:PullRequestVersion at %#x>' % id(self)
3195
3197
3196
3198
3197 class PullRequestReviewers(Base, BaseModel):
3199 class PullRequestReviewers(Base, BaseModel):
3198 __tablename__ = 'pull_request_reviewers'
3200 __tablename__ = 'pull_request_reviewers'
3199 __table_args__ = (
3201 __table_args__ = (
3200 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3202 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3201 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3203 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3202 )
3204 )
3203
3205
3204 def __init__(self, user=None, pull_request=None):
3206 def __init__(self, user=None, pull_request=None):
3205 self.user = user
3207 self.user = user
3206 self.pull_request = pull_request
3208 self.pull_request = pull_request
3207
3209
3208 pull_requests_reviewers_id = Column(
3210 pull_requests_reviewers_id = Column(
3209 'pull_requests_reviewers_id', Integer(), nullable=False,
3211 'pull_requests_reviewers_id', Integer(), nullable=False,
3210 primary_key=True)
3212 primary_key=True)
3211 pull_request_id = Column(
3213 pull_request_id = Column(
3212 "pull_request_id", Integer(),
3214 "pull_request_id", Integer(),
3213 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3215 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3214 user_id = Column(
3216 user_id = Column(
3215 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3217 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3216
3218
3217 user = relationship('User')
3219 user = relationship('User')
3218 pull_request = relationship('PullRequest')
3220 pull_request = relationship('PullRequest')
3219
3221
3220
3222
3221 class Notification(Base, BaseModel):
3223 class Notification(Base, BaseModel):
3222 __tablename__ = 'notifications'
3224 __tablename__ = 'notifications'
3223 __table_args__ = (
3225 __table_args__ = (
3224 Index('notification_type_idx', 'type'),
3226 Index('notification_type_idx', 'type'),
3225 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3227 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3226 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3228 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3227 )
3229 )
3228
3230
3229 TYPE_CHANGESET_COMMENT = u'cs_comment'
3231 TYPE_CHANGESET_COMMENT = u'cs_comment'
3230 TYPE_MESSAGE = u'message'
3232 TYPE_MESSAGE = u'message'
3231 TYPE_MENTION = u'mention'
3233 TYPE_MENTION = u'mention'
3232 TYPE_REGISTRATION = u'registration'
3234 TYPE_REGISTRATION = u'registration'
3233 TYPE_PULL_REQUEST = u'pull_request'
3235 TYPE_PULL_REQUEST = u'pull_request'
3234 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3236 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3235
3237
3236 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3238 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3237 subject = Column('subject', Unicode(512), nullable=True)
3239 subject = Column('subject', Unicode(512), nullable=True)
3238 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3240 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3239 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3241 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3240 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3242 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3241 type_ = Column('type', Unicode(255))
3243 type_ = Column('type', Unicode(255))
3242
3244
3243 created_by_user = relationship('User')
3245 created_by_user = relationship('User')
3244 notifications_to_users = relationship('UserNotification', lazy='joined',
3246 notifications_to_users = relationship('UserNotification', lazy='joined',
3245 cascade="all, delete, delete-orphan")
3247 cascade="all, delete, delete-orphan")
3246
3248
3247 @property
3249 @property
3248 def recipients(self):
3250 def recipients(self):
3249 return [x.user for x in UserNotification.query()\
3251 return [x.user for x in UserNotification.query()\
3250 .filter(UserNotification.notification == self)\
3252 .filter(UserNotification.notification == self)\
3251 .order_by(UserNotification.user_id.asc()).all()]
3253 .order_by(UserNotification.user_id.asc()).all()]
3252
3254
3253 @classmethod
3255 @classmethod
3254 def create(cls, created_by, subject, body, recipients, type_=None):
3256 def create(cls, created_by, subject, body, recipients, type_=None):
3255 if type_ is None:
3257 if type_ is None:
3256 type_ = Notification.TYPE_MESSAGE
3258 type_ = Notification.TYPE_MESSAGE
3257
3259
3258 notification = cls()
3260 notification = cls()
3259 notification.created_by_user = created_by
3261 notification.created_by_user = created_by
3260 notification.subject = subject
3262 notification.subject = subject
3261 notification.body = body
3263 notification.body = body
3262 notification.type_ = type_
3264 notification.type_ = type_
3263 notification.created_on = datetime.datetime.now()
3265 notification.created_on = datetime.datetime.now()
3264
3266
3265 for u in recipients:
3267 for u in recipients:
3266 assoc = UserNotification()
3268 assoc = UserNotification()
3267 assoc.notification = notification
3269 assoc.notification = notification
3268
3270
3269 # if created_by is inside recipients mark his notification
3271 # if created_by is inside recipients mark his notification
3270 # as read
3272 # as read
3271 if u.user_id == created_by.user_id:
3273 if u.user_id == created_by.user_id:
3272 assoc.read = True
3274 assoc.read = True
3273
3275
3274 u.notifications.append(assoc)
3276 u.notifications.append(assoc)
3275 Session().add(notification)
3277 Session().add(notification)
3276
3278
3277 return notification
3279 return notification
3278
3280
3279 @property
3281 @property
3280 def description(self):
3282 def description(self):
3281 from rhodecode.model.notification import NotificationModel
3283 from rhodecode.model.notification import NotificationModel
3282 return NotificationModel().make_description(self)
3284 return NotificationModel().make_description(self)
3283
3285
3284
3286
3285 class UserNotification(Base, BaseModel):
3287 class UserNotification(Base, BaseModel):
3286 __tablename__ = 'user_to_notification'
3288 __tablename__ = 'user_to_notification'
3287 __table_args__ = (
3289 __table_args__ = (
3288 UniqueConstraint('user_id', 'notification_id'),
3290 UniqueConstraint('user_id', 'notification_id'),
3289 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3291 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3290 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3292 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3291 )
3293 )
3292 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3294 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3293 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3295 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3294 read = Column('read', Boolean, default=False)
3296 read = Column('read', Boolean, default=False)
3295 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3297 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3296
3298
3297 user = relationship('User', lazy="joined")
3299 user = relationship('User', lazy="joined")
3298 notification = relationship('Notification', lazy="joined",
3300 notification = relationship('Notification', lazy="joined",
3299 order_by=lambda: Notification.created_on.desc(),)
3301 order_by=lambda: Notification.created_on.desc(),)
3300
3302
3301 def mark_as_read(self):
3303 def mark_as_read(self):
3302 self.read = True
3304 self.read = True
3303 Session().add(self)
3305 Session().add(self)
3304
3306
3305
3307
3306 class Gist(Base, BaseModel):
3308 class Gist(Base, BaseModel):
3307 __tablename__ = 'gists'
3309 __tablename__ = 'gists'
3308 __table_args__ = (
3310 __table_args__ = (
3309 Index('g_gist_access_id_idx', 'gist_access_id'),
3311 Index('g_gist_access_id_idx', 'gist_access_id'),
3310 Index('g_created_on_idx', 'created_on'),
3312 Index('g_created_on_idx', 'created_on'),
3311 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3312 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3314 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3313 )
3315 )
3314 GIST_PUBLIC = u'public'
3316 GIST_PUBLIC = u'public'
3315 GIST_PRIVATE = u'private'
3317 GIST_PRIVATE = u'private'
3316 DEFAULT_FILENAME = u'gistfile1.txt'
3318 DEFAULT_FILENAME = u'gistfile1.txt'
3317
3319
3318 ACL_LEVEL_PUBLIC = u'acl_public'
3320 ACL_LEVEL_PUBLIC = u'acl_public'
3319 ACL_LEVEL_PRIVATE = u'acl_private'
3321 ACL_LEVEL_PRIVATE = u'acl_private'
3320
3322
3321 gist_id = Column('gist_id', Integer(), primary_key=True)
3323 gist_id = Column('gist_id', Integer(), primary_key=True)
3322 gist_access_id = Column('gist_access_id', Unicode(250))
3324 gist_access_id = Column('gist_access_id', Unicode(250))
3323 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3325 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3324 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3326 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3325 gist_expires = Column('gist_expires', Float(53), nullable=False)
3327 gist_expires = Column('gist_expires', Float(53), nullable=False)
3326 gist_type = Column('gist_type', Unicode(128), nullable=False)
3328 gist_type = Column('gist_type', Unicode(128), nullable=False)
3327 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3329 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3328 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3330 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3329 acl_level = Column('acl_level', Unicode(128), nullable=True)
3331 acl_level = Column('acl_level', Unicode(128), nullable=True)
3330
3332
3331 owner = relationship('User')
3333 owner = relationship('User')
3332
3334
3333 def __repr__(self):
3335 def __repr__(self):
3334 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3336 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3335
3337
3336 @classmethod
3338 @classmethod
3337 def get_or_404(cls, id_):
3339 def get_or_404(cls, id_):
3338 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3340 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3339 if not res:
3341 if not res:
3340 raise HTTPNotFound
3342 raise HTTPNotFound
3341 return res
3343 return res
3342
3344
3343 @classmethod
3345 @classmethod
3344 def get_by_access_id(cls, gist_access_id):
3346 def get_by_access_id(cls, gist_access_id):
3345 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3347 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3346
3348
3347 def gist_url(self):
3349 def gist_url(self):
3348 import rhodecode
3350 import rhodecode
3349 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3351 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3350 if alias_url:
3352 if alias_url:
3351 return alias_url.replace('{gistid}', self.gist_access_id)
3353 return alias_url.replace('{gistid}', self.gist_access_id)
3352
3354
3353 return url('gist', gist_id=self.gist_access_id, qualified=True)
3355 return url('gist', gist_id=self.gist_access_id, qualified=True)
3354
3356
3355 @classmethod
3357 @classmethod
3356 def base_path(cls):
3358 def base_path(cls):
3357 """
3359 """
3358 Returns base path when all gists are stored
3360 Returns base path when all gists are stored
3359
3361
3360 :param cls:
3362 :param cls:
3361 """
3363 """
3362 from rhodecode.model.gist import GIST_STORE_LOC
3364 from rhodecode.model.gist import GIST_STORE_LOC
3363 q = Session().query(RhodeCodeUi)\
3365 q = Session().query(RhodeCodeUi)\
3364 .filter(RhodeCodeUi.ui_key == URL_SEP)
3366 .filter(RhodeCodeUi.ui_key == URL_SEP)
3365 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3367 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3366 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3368 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3367
3369
3368 def get_api_data(self):
3370 def get_api_data(self):
3369 """
3371 """
3370 Common function for generating gist related data for API
3372 Common function for generating gist related data for API
3371 """
3373 """
3372 gist = self
3374 gist = self
3373 data = {
3375 data = {
3374 'gist_id': gist.gist_id,
3376 'gist_id': gist.gist_id,
3375 'type': gist.gist_type,
3377 'type': gist.gist_type,
3376 'access_id': gist.gist_access_id,
3378 'access_id': gist.gist_access_id,
3377 'description': gist.gist_description,
3379 'description': gist.gist_description,
3378 'url': gist.gist_url(),
3380 'url': gist.gist_url(),
3379 'expires': gist.gist_expires,
3381 'expires': gist.gist_expires,
3380 'created_on': gist.created_on,
3382 'created_on': gist.created_on,
3381 'modified_at': gist.modified_at,
3383 'modified_at': gist.modified_at,
3382 'content': None,
3384 'content': None,
3383 'acl_level': gist.acl_level,
3385 'acl_level': gist.acl_level,
3384 }
3386 }
3385 return data
3387 return data
3386
3388
3387 def __json__(self):
3389 def __json__(self):
3388 data = dict(
3390 data = dict(
3389 )
3391 )
3390 data.update(self.get_api_data())
3392 data.update(self.get_api_data())
3391 return data
3393 return data
3392 # SCM functions
3394 # SCM functions
3393
3395
3394 def scm_instance(self, **kwargs):
3396 def scm_instance(self, **kwargs):
3395 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3397 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3396 return get_vcs_instance(
3398 return get_vcs_instance(
3397 repo_path=safe_str(full_repo_path), create=False)
3399 repo_path=safe_str(full_repo_path), create=False)
3398
3400
3399
3401
3400 class DbMigrateVersion(Base, BaseModel):
3402 class DbMigrateVersion(Base, BaseModel):
3401 __tablename__ = 'db_migrate_version'
3403 __tablename__ = 'db_migrate_version'
3402 __table_args__ = (
3404 __table_args__ = (
3403 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3405 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3404 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3406 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3405 )
3407 )
3406 repository_id = Column('repository_id', String(250), primary_key=True)
3408 repository_id = Column('repository_id', String(250), primary_key=True)
3407 repository_path = Column('repository_path', Text)
3409 repository_path = Column('repository_path', Text)
3408 version = Column('version', Integer)
3410 version = Column('version', Integer)
3409
3411
3410
3412
3411 class ExternalIdentity(Base, BaseModel):
3413 class ExternalIdentity(Base, BaseModel):
3412 __tablename__ = 'external_identities'
3414 __tablename__ = 'external_identities'
3413 __table_args__ = (
3415 __table_args__ = (
3414 Index('local_user_id_idx', 'local_user_id'),
3416 Index('local_user_id_idx', 'local_user_id'),
3415 Index('external_id_idx', 'external_id'),
3417 Index('external_id_idx', 'external_id'),
3416 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3417 'mysql_charset': 'utf8'})
3419 'mysql_charset': 'utf8'})
3418
3420
3419 external_id = Column('external_id', Unicode(255), default=u'',
3421 external_id = Column('external_id', Unicode(255), default=u'',
3420 primary_key=True)
3422 primary_key=True)
3421 external_username = Column('external_username', Unicode(1024), default=u'')
3423 external_username = Column('external_username', Unicode(1024), default=u'')
3422 local_user_id = Column('local_user_id', Integer(),
3424 local_user_id = Column('local_user_id', Integer(),
3423 ForeignKey('users.user_id'), primary_key=True)
3425 ForeignKey('users.user_id'), primary_key=True)
3424 provider_name = Column('provider_name', Unicode(255), default=u'',
3426 provider_name = Column('provider_name', Unicode(255), default=u'',
3425 primary_key=True)
3427 primary_key=True)
3426 access_token = Column('access_token', String(1024), default=u'')
3428 access_token = Column('access_token', String(1024), default=u'')
3427 alt_token = Column('alt_token', String(1024), default=u'')
3429 alt_token = Column('alt_token', String(1024), default=u'')
3428 token_secret = Column('token_secret', String(1024), default=u'')
3430 token_secret = Column('token_secret', String(1024), default=u'')
3429
3431
3430 @classmethod
3432 @classmethod
3431 def by_external_id_and_provider(cls, external_id, provider_name,
3433 def by_external_id_and_provider(cls, external_id, provider_name,
3432 local_user_id=None):
3434 local_user_id=None):
3433 """
3435 """
3434 Returns ExternalIdentity instance based on search params
3436 Returns ExternalIdentity instance based on search params
3435
3437
3436 :param external_id:
3438 :param external_id:
3437 :param provider_name:
3439 :param provider_name:
3438 :return: ExternalIdentity
3440 :return: ExternalIdentity
3439 """
3441 """
3440 query = cls.query()
3442 query = cls.query()
3441 query = query.filter(cls.external_id == external_id)
3443 query = query.filter(cls.external_id == external_id)
3442 query = query.filter(cls.provider_name == provider_name)
3444 query = query.filter(cls.provider_name == provider_name)
3443 if local_user_id:
3445 if local_user_id:
3444 query = query.filter(cls.local_user_id == local_user_id)
3446 query = query.filter(cls.local_user_id == local_user_id)
3445 return query.first()
3447 return query.first()
3446
3448
3447 @classmethod
3449 @classmethod
3448 def user_by_external_id_and_provider(cls, external_id, provider_name):
3450 def user_by_external_id_and_provider(cls, external_id, provider_name):
3449 """
3451 """
3450 Returns User instance based on search params
3452 Returns User instance based on search params
3451
3453
3452 :param external_id:
3454 :param external_id:
3453 :param provider_name:
3455 :param provider_name:
3454 :return: User
3456 :return: User
3455 """
3457 """
3456 query = User.query()
3458 query = User.query()
3457 query = query.filter(cls.external_id == external_id)
3459 query = query.filter(cls.external_id == external_id)
3458 query = query.filter(cls.provider_name == provider_name)
3460 query = query.filter(cls.provider_name == provider_name)
3459 query = query.filter(User.user_id == cls.local_user_id)
3461 query = query.filter(User.user_id == cls.local_user_id)
3460 return query.first()
3462 return query.first()
3461
3463
3462 @classmethod
3464 @classmethod
3463 def by_local_user_id(cls, local_user_id):
3465 def by_local_user_id(cls, local_user_id):
3464 """
3466 """
3465 Returns all tokens for user
3467 Returns all tokens for user
3466
3468
3467 :param local_user_id:
3469 :param local_user_id:
3468 :return: ExternalIdentity
3470 :return: ExternalIdentity
3469 """
3471 """
3470 query = cls.query()
3472 query = cls.query()
3471 query = query.filter(cls.local_user_id == local_user_id)
3473 query = query.filter(cls.local_user_id == local_user_id)
3472 return query
3474 return query
3473
3475
3474
3476
3475 class Integration(Base, BaseModel):
3477 class Integration(Base, BaseModel):
3476 __tablename__ = 'integrations'
3478 __tablename__ = 'integrations'
3477 __table_args__ = (
3479 __table_args__ = (
3478 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3480 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3479 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3481 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3480 )
3482 )
3481
3483
3482 integration_id = Column('integration_id', Integer(), primary_key=True)
3484 integration_id = Column('integration_id', Integer(), primary_key=True)
3483 integration_type = Column('integration_type', String(255))
3485 integration_type = Column('integration_type', String(255))
3484 enabled = Column('enabled', Boolean(), nullable=False)
3486 enabled = Column('enabled', Boolean(), nullable=False)
3485 name = Column('name', String(255), nullable=False)
3487 name = Column('name', String(255), nullable=False)
3486 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3488 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3487 default=False)
3489 default=False)
3488
3490
3489 settings = Column(
3491 settings = Column(
3490 'settings_json', MutationObj.as_mutable(
3492 'settings_json', MutationObj.as_mutable(
3491 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3493 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3492 repo_id = Column(
3494 repo_id = Column(
3493 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3495 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3494 nullable=True, unique=None, default=None)
3496 nullable=True, unique=None, default=None)
3495 repo = relationship('Repository', lazy='joined')
3497 repo = relationship('Repository', lazy='joined')
3496
3498
3497 repo_group_id = Column(
3499 repo_group_id = Column(
3498 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3500 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3499 nullable=True, unique=None, default=None)
3501 nullable=True, unique=None, default=None)
3500 repo_group = relationship('RepoGroup', lazy='joined')
3502 repo_group = relationship('RepoGroup', lazy='joined')
3501
3503
3502 @property
3504 @property
3503 def scope(self):
3505 def scope(self):
3504 if self.repo:
3506 if self.repo:
3505 return repr(self.repo)
3507 return repr(self.repo)
3506 if self.repo_group:
3508 if self.repo_group:
3507 if self.child_repos_only:
3509 if self.child_repos_only:
3508 return repr(self.repo_group) + ' (child repos only)'
3510 return repr(self.repo_group) + ' (child repos only)'
3509 else:
3511 else:
3510 return repr(self.repo_group) + ' (recursive)'
3512 return repr(self.repo_group) + ' (recursive)'
3511 if self.child_repos_only:
3513 if self.child_repos_only:
3512 return 'root_repos'
3514 return 'root_repos'
3513 return 'global'
3515 return 'global'
3514
3516
3515 def __repr__(self):
3517 def __repr__(self):
3516 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3518 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3519
3520
3521 class RepoReviewRuleUser(Base, BaseModel):
3522 __tablename__ = 'repo_review_rules_users'
3523 __table_args__ = (
3524 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3525 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3526 )
3527 repo_review_rule_user_id = Column(
3528 'repo_review_rule_user_id', Integer(), primary_key=True)
3529 repo_review_rule_id = Column("repo_review_rule_id",
3530 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3532 nullable=False)
3533 user = relationship('User')
3534
3535
3536 class RepoReviewRuleUserGroup(Base, BaseModel):
3537 __tablename__ = 'repo_review_rules_users_groups'
3538 __table_args__ = (
3539 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3540 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3541 )
3542 repo_review_rule_users_group_id = Column(
3543 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3544 repo_review_rule_id = Column("repo_review_rule_id",
3545 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3546 users_group_id = Column("users_group_id", Integer(),
3547 ForeignKey('users_groups.users_group_id'), nullable=False)
3548 users_group = relationship('UserGroup')
3549
3550
3551 class RepoReviewRule(Base, BaseModel):
3552 __tablename__ = 'repo_review_rules'
3553 __table_args__ = (
3554 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3555 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3556 )
3557
3558 repo_review_rule_id = Column(
3559 'repo_review_rule_id', Integer(), primary_key=True)
3560 repo_id = Column(
3561 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3562 repo = relationship('Repository', backref='review_rules')
3563
3564 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3565 default=u'*') # glob
3566 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3567 default=u'*') # glob
3568
3569 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3570 nullable=False, default=False)
3571 rule_users = relationship('RepoReviewRuleUser')
3572 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3573
3574 @hybrid_property
3575 def branch_pattern(self):
3576 return self._branch_pattern or '*'
3577
3578 def _validate_glob(self, value):
3579 re.compile('^' + glob2re(value) + '$')
3580
3581 @branch_pattern.setter
3582 def branch_pattern(self, value):
3583 self._validate_glob(value)
3584 self._branch_pattern = value or '*'
3585
3586 @hybrid_property
3587 def file_pattern(self):
3588 return self._file_pattern or '*'
3589
3590 @file_pattern.setter
3591 def file_pattern(self, value):
3592 self._validate_glob(value)
3593 self._file_pattern = value or '*'
3594
3595 def matches(self, branch, files_changed):
3596 """
3597 Check if this review rule matches a branch/files in a pull request
3598
3599 :param branch: branch name for the commit
3600 :param files_changed: list of file paths changed in the pull request
3601 """
3602
3603 branch = branch or ''
3604 files_changed = files_changed or []
3605
3606 branch_matches = True
3607 if branch:
3608 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3609 branch_matches = bool(branch_regex.search(branch))
3610
3611 files_matches = True
3612 if self.file_pattern != '*':
3613 files_matches = False
3614 file_regex = re.compile(glob2re(self.file_pattern))
3615 for filename in files_changed:
3616 if file_regex.search(filename):
3617 files_matches = True
3618 break
3619
3620 return branch_matches and files_matches
3621
3622 @property
3623 def review_users(self):
3624 """ Returns the users which this rule applies to """
3625
3626 users = set()
3627 users |= set([
3628 rule_user.user for rule_user in self.rule_users
3629 if rule_user.user.active])
3630 users |= set(
3631 member.user
3632 for rule_user_group in self.rule_user_groups
3633 for member in rule_user_group.users_group.members
3634 if member.user.active
3635 )
3636 return users
3637
3638 def __repr__(self):
3639 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3640 self.repo_review_rule_id, self.repo)
@@ -1,15 +1,26 b''
1 import os
1 import os
2 import re
2
3
3 import ipaddress
4 import ipaddress
4 import colander
5 import colander
5
6
6 from rhodecode.translation import _
7 from rhodecode.translation import _
8 from rhodecode.lib.utils2 import glob2re
7
9
8
10
9 def ip_addr_validator(node, value):
11 def ip_addr_validator(node, value):
10 try:
12 try:
11 # this raises an ValueError if address is not IpV4 or IpV6
13 # this raises an ValueError if address is not IpV4 or IpV6
12 ipaddress.ip_network(value, strict=False)
14 ipaddress.ip_network(value, strict=False)
13 except ValueError:
15 except ValueError:
14 msg = _(u'Please enter a valid IPv4 or IpV6 address')
16 msg = _(u'Please enter a valid IPv4 or IpV6 address')
15 raise colander.Invalid(node, msg)
17 raise colander.Invalid(node, msg)
18
19
20 def glob_validator(node, value):
21 try:
22 re.compile('^' + glob2re(value) + '$')
23 except Exception:
24 raise
25 msg = _(u'Invalid glob pattern')
26 raise colander.Invalid(node, msg)
@@ -1,117 +1,139 b''
1 .deform {
1 .deform {
2
2
3 * {
3 * {
4 box-sizing: border-box;
4 box-sizing: border-box;
5 }
5 }
6
6
7 .required:after {
7 .required:after {
8 color: #e32;
8 color: #e32;
9 content: '*';
9 content: '*';
10 display:inline;
10 display:inline;
11 }
11 }
12
12
13 .control-label {
13 .control-label {
14 width: 200px;
14 width: 200px;
15 padding: 10px;
15 padding: 10px;
16 float: left;
16 float: left;
17 }
17 }
18 .control-inputs {
18 .control-inputs {
19 width: 400px;
19 width: 400px;
20 float: left;
20 float: left;
21 }
21 }
22 .form-group .radio, .form-group .checkbox {
22 .form-group .radio, .form-group .checkbox {
23 position: relative;
23 position: relative;
24 display: block;
24 display: block;
25 /* margin-bottom: 10px; */
25 /* margin-bottom: 10px; */
26 }
26 }
27
27
28 .form-group {
28 .form-group {
29 clear: left;
29 clear: left;
30 margin-bottom: 20px;
30 margin-bottom: 20px;
31
31
32 &:after { /* clear fix */
32 &:after { /* clear fix */
33 content: " ";
33 content: " ";
34 display: block;
34 display: block;
35 clear: left;
35 clear: left;
36 }
36 }
37 }
37 }
38
38
39 .form-control {
39 .form-control {
40 width: 100%;
40 width: 100%;
41 padding: 0.9em;
41 padding: 0.9em;
42 border: 1px solid #979797;
42 border: 1px solid #979797;
43 border-radius: 2px;
43 border-radius: 2px;
44 }
44 }
45 .form-control.select2-container {
45 .form-control.select2-container {
46 padding: 0; /* padding already applied in .drop-menu a */
46 padding: 0; /* padding already applied in .drop-menu a */
47 }
47 }
48
48
49 .form-control.readonly {
49 .form-control.readonly {
50 background: #eeeeee;
50 background: #eeeeee;
51 cursor: not-allowed;
51 cursor: not-allowed;
52 }
52 }
53
53
54 .error-block {
54 .error-block {
55 color: red;
55 color: red;
56 margin: 0;
56 margin: 0;
57 }
57 }
58
58
59 .help-block {
59 .help-block {
60 margin: 0;
60 margin: 0;
61 }
61 }
62
62
63 .deform-seq-container .control-inputs {
63 .deform-seq-container .control-inputs {
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .deform-seq-container .deform-seq-item-handle {
67 .deform-seq-container .deform-seq-item-handle {
68 width: 8.3%;
68 width: 8.3%;
69 float: left;
69 float: left;
70 }
70 }
71
71
72 .deform-seq-container .deform-seq-item-group {
72 .deform-seq-container .deform-seq-item-group {
73 width: 91.6%;
73 width: 91.6%;
74 float: left;
74 float: left;
75 }
75 }
76
76
77 .form-control {
77 .form-control {
78 input {
78 input {
79 height: 40px;
79 height: 40px;
80 }
80 }
81 input[type=checkbox], input[type=radio] {
81 input[type=checkbox], input[type=radio] {
82 height: auto;
82 height: auto;
83 }
83 }
84 select {
84 select {
85 height: 40px;
85 height: 40px;
86 }
86 }
87 }
87 }
88
88
89 .form-control.select2-container {
89 .form-control.select2-container {
90 height: 40px;
90 height: 40px;
91 }
91 }
92
92
93 .deform-two-field-sequence .deform-seq-container .deform-seq-item label {
93 .deform-full-field-sequence.control-inputs {
94 width: 100%;
95 }
96
97 .deform-table-sequence {
98 .deform-seq-container {
99 .deform-seq-item {
100 margin: 0;
101 label {
94 display: none;
102 display: none;
95 }
103 }
96 .deform-two-field-sequence .deform-seq-container .deform-seq-item:first-child label {
104 .panel-heading {
97 display: block;
98 }
99 .deform-two-field-sequence .deform-seq-container .deform-seq-item .panel-heading {
100 display: none;
105 display: none;
101 }
106 }
102 .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group {
107 .deform-seq-item-group > .panel {
103 margin: 0;
104 }
105 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group {
106 width: 45%; padding: 0 2px; float: left; clear: none;
107 }
108 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel {
109 padding: 0;
108 padding: 0;
110 margin: 5px 0;
109 margin: 5px 0;
111 border: none;
110 border: none;
112 }
111 &> .panel-body {
113 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel > .panel-body {
114 padding: 0;
112 padding: 0;
115 }
113 }
116
114 }
115 &:first-child label {
116 display: block;
117 }
118 }
119 }
120 }
121 .deform-table-2-sequence {
122 .deform-seq-container {
123 .deform-seq-item {
124 .form-group {
125 width: 45% !important; padding: 0 2px; float: left; clear: none;
117 }
126 }
127 }
128 }
129 }
130 .deform-table-3-sequence {
131 .deform-seq-container {
132 .deform-seq-item {
133 .form-group {
134 width: 30% !important; padding: 0 2px; float: left; clear: none;
135 }
136 }
137 }
138 }
139 }
@@ -1,51 +1,53 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('home', '/', []);
15 pyroutes.register('home', '/', []);
16 pyroutes.register('user_autocomplete_data', '/_users', []);
16 pyroutes.register('user_autocomplete_data', '/_users', []);
17 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
17 pyroutes.register('new_repo', '/_admin/create_repository', []);
18 pyroutes.register('new_repo', '/_admin/create_repository', []);
18 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
19 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
19 pyroutes.register('gists', '/_admin/gists', []);
20 pyroutes.register('gists', '/_admin/gists', []);
20 pyroutes.register('new_gist', '/_admin/gists/new', []);
21 pyroutes.register('new_gist', '/_admin/gists/new', []);
21 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
22 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
22 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
23 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
23 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
24 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
24 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
25 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
26 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
25 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
27 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
26 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
28 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
27 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
29 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
28 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
30 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
29 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
31 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
30 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
32 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
31 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
33 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
32 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
34 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
33 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
35 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
34 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
36 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
35 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
37 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
36 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
38 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
37 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
39 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
38 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
40 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
39 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
41 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
40 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
42 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
41 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
43 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
42 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
44 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
43 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
44 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
48 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
47 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
50 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
49 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
51 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
50 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
52 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
51 }
53 }
@@ -1,205 +1,214 b''
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * Pull request reviewers
20 * Pull request reviewers
21 */
21 */
22 var removeReviewMember = function(reviewer_id, mark_delete){
22 var removeReviewMember = function(reviewer_id, mark_delete){
23 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
23 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
24
24
25 if(typeof(mark_delete) === undefined){
25 if(typeof(mark_delete) === undefined){
26 mark_delete = false;
26 mark_delete = false;
27 }
27 }
28
28
29 if(mark_delete === true){
29 if(mark_delete === true){
30 if (reviewer){
30 if (reviewer){
31 // mark as to-remove
31 // mark as to-remove
32 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
32 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
33 obj.addClass('to-delete');
33 obj.addClass('to-delete');
34 // now delete the input
34 // now delete the input
35 $('#reviewer_{0}_input'.format(reviewer_id)).remove();
35 $('#reviewer_{0}_input'.format(reviewer_id)).remove();
36 }
36 }
37 }
37 }
38 else{
38 else{
39 $('#reviewer_{0}'.format(reviewer_id)).remove();
39 $('#reviewer_{0}'.format(reviewer_id)).remove();
40 }
40 }
41 };
41 };
42
42
43 var addReviewMember = function(id,fname,lname,nname,gravatar_link){
43 var addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons) {
44 var members = $('#review_members').get(0);
44 var members = $('#review_members').get(0);
45 var reasons_html = '';
46 if (reasons) {
47 for (var i = 0; i < reasons.length; i++) {
48 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(
49 reasons[i]
50 );
51 }
52 }
45 var tmpl = '<li id="reviewer_{2}">'+
53 var tmpl = '<li id="reviewer_{2}">'+
46 '<div class="reviewer_status">'+
54 '<div class="reviewer_status">'+
47 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
55 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
48 '</div>'+
56 '</div>'+
49 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
57 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
50 '<span class="reviewer_name user">{1}</span>'+
58 '<span class="reviewer_name user">{1}</span>'+
59 reasons_html +
51 '<input type="hidden" value="{2}" name="review_members" />'+
60 '<input type="hidden" value="{2}" name="review_members" />'+
52 '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' +
61 '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' +
53 '<i class="icon-remove-sign"></i>'+
62 '<i class="icon-remove-sign"></i>'+
54 '</div>'+
63 '</div>'+
55 '</div>'+
64 '</div>'+
56 '</li>' ;
65 '</li>' ;
57 var displayname = "{0} ({1} {2})".format(
66 var displayname = "{0} ({1} {2})".format(
58 nname, escapeHtml(fname), escapeHtml(lname));
67 nname, escapeHtml(fname), escapeHtml(lname));
59 var element = tmpl.format(gravatar_link,displayname,id);
68 var element = tmpl.format(gravatar_link,displayname,id);
60 // check if we don't have this ID already in
69 // check if we don't have this ID already in
61 var ids = [];
70 var ids = [];
62 var _els = $('#review_members li').toArray();
71 var _els = $('#review_members li').toArray();
63 for (el in _els){
72 for (el in _els){
64 ids.push(_els[el].id)
73 ids.push(_els[el].id)
65 }
74 }
66 if(ids.indexOf('reviewer_'+id) == -1){
75 if(ids.indexOf('reviewer_'+id) == -1){
67 // only add if it's not there
76 // only add if it's not there
68 members.innerHTML += element;
77 members.innerHTML += element;
69 }
78 }
70
79
71 };
80 };
72
81
73 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
82 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
74 var url = pyroutes.url(
83 var url = pyroutes.url(
75 'pullrequest_update',
84 'pullrequest_update',
76 {"repo_name": repo_name, "pull_request_id": pull_request_id});
85 {"repo_name": repo_name, "pull_request_id": pull_request_id});
77 postData.csrf_token = CSRF_TOKEN;
86 postData.csrf_token = CSRF_TOKEN;
78 var success = function(o) {
87 var success = function(o) {
79 window.location.reload();
88 window.location.reload();
80 };
89 };
81 ajaxPOST(url, postData, success);
90 ajaxPOST(url, postData, success);
82 };
91 };
83
92
84 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
93 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
85 if (reviewers_ids === undefined){
94 if (reviewers_ids === undefined){
86 var reviewers_ids = [];
95 var reviewers_ids = [];
87 var ids = $('#review_members input').toArray();
96 var ids = $('#review_members input').toArray();
88 for(var i=0; i<ids.length;i++){
97 for(var i=0; i<ids.length;i++){
89 var id = ids[i].value
98 var id = ids[i].value
90 reviewers_ids.push(id);
99 reviewers_ids.push(id);
91 }
100 }
92 }
101 }
93 var postData = {
102 var postData = {
94 '_method':'put',
103 '_method':'put',
95 'reviewers_ids': reviewers_ids};
104 'reviewers_ids': reviewers_ids};
96 _updatePullRequest(repo_name, pull_request_id, postData);
105 _updatePullRequest(repo_name, pull_request_id, postData);
97 };
106 };
98
107
99 /**
108 /**
100 * PULL REQUEST reject & close
109 * PULL REQUEST reject & close
101 */
110 */
102 var closePullRequest = function(repo_name, pull_request_id) {
111 var closePullRequest = function(repo_name, pull_request_id) {
103 var postData = {
112 var postData = {
104 '_method': 'put',
113 '_method': 'put',
105 'close_pull_request': true};
114 'close_pull_request': true};
106 _updatePullRequest(repo_name, pull_request_id, postData);
115 _updatePullRequest(repo_name, pull_request_id, postData);
107 };
116 };
108
117
109 /**
118 /**
110 * PULL REQUEST update commits
119 * PULL REQUEST update commits
111 */
120 */
112 var updateCommits = function(repo_name, pull_request_id) {
121 var updateCommits = function(repo_name, pull_request_id) {
113 var postData = {
122 var postData = {
114 '_method': 'put',
123 '_method': 'put',
115 'update_commits': true};
124 'update_commits': true};
116 _updatePullRequest(repo_name, pull_request_id, postData);
125 _updatePullRequest(repo_name, pull_request_id, postData);
117 };
126 };
118
127
119
128
120 /**
129 /**
121 * PULL REQUEST edit info
130 * PULL REQUEST edit info
122 */
131 */
123 var editPullRequest = function(repo_name, pull_request_id, title, description) {
132 var editPullRequest = function(repo_name, pull_request_id, title, description) {
124 var url = pyroutes.url(
133 var url = pyroutes.url(
125 'pullrequest_update',
134 'pullrequest_update',
126 {"repo_name": repo_name, "pull_request_id": pull_request_id});
135 {"repo_name": repo_name, "pull_request_id": pull_request_id});
127
136
128 var postData = {
137 var postData = {
129 '_method': 'put',
138 '_method': 'put',
130 'title': title,
139 'title': title,
131 'description': description,
140 'description': description,
132 'edit_pull_request': true,
141 'edit_pull_request': true,
133 'csrf_token': CSRF_TOKEN
142 'csrf_token': CSRF_TOKEN
134 };
143 };
135 var success = function(o) {
144 var success = function(o) {
136 window.location.reload();
145 window.location.reload();
137 };
146 };
138 ajaxPOST(url, postData, success);
147 ajaxPOST(url, postData, success);
139 };
148 };
140
149
141 var initPullRequestsCodeMirror = function (textAreaId) {
150 var initPullRequestsCodeMirror = function (textAreaId) {
142 var ta = $(textAreaId).get(0);
151 var ta = $(textAreaId).get(0);
143 var initialHeight = '100px';
152 var initialHeight = '100px';
144
153
145 // default options
154 // default options
146 var codeMirrorOptions = {
155 var codeMirrorOptions = {
147 mode: "text",
156 mode: "text",
148 lineNumbers: false,
157 lineNumbers: false,
149 indentUnit: 4,
158 indentUnit: 4,
150 theme: 'rc-input'
159 theme: 'rc-input'
151 };
160 };
152
161
153 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
162 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
154 // marker for manually set description
163 // marker for manually set description
155 codeMirrorInstance._userDefinedDesc = false;
164 codeMirrorInstance._userDefinedDesc = false;
156 codeMirrorInstance.setSize(null, initialHeight);
165 codeMirrorInstance.setSize(null, initialHeight);
157 codeMirrorInstance.on("change", function(instance, changeObj) {
166 codeMirrorInstance.on("change", function(instance, changeObj) {
158 var height = initialHeight;
167 var height = initialHeight;
159 var lines = instance.lineCount();
168 var lines = instance.lineCount();
160 if (lines > 6 && lines < 20) {
169 if (lines > 6 && lines < 20) {
161 height = "auto"
170 height = "auto"
162 }
171 }
163 else if (lines >= 20) {
172 else if (lines >= 20) {
164 height = 20 * 15;
173 height = 20 * 15;
165 }
174 }
166 instance.setSize(null, height);
175 instance.setSize(null, height);
167
176
168 // detect if the change was trigger by auto desc, or user input
177 // detect if the change was trigger by auto desc, or user input
169 changeOrigin = changeObj.origin;
178 changeOrigin = changeObj.origin;
170
179
171 if (changeOrigin === "setValue") {
180 if (changeOrigin === "setValue") {
172 cmLog.debug('Change triggered by setValue');
181 cmLog.debug('Change triggered by setValue');
173 }
182 }
174 else {
183 else {
175 cmLog.debug('user triggered change !');
184 cmLog.debug('user triggered change !');
176 // set special marker to indicate user has created an input.
185 // set special marker to indicate user has created an input.
177 instance._userDefinedDesc = true;
186 instance._userDefinedDesc = true;
178 }
187 }
179
188
180 });
189 });
181
190
182 return codeMirrorInstance
191 return codeMirrorInstance
183 };
192 };
184
193
185 /**
194 /**
186 * Reviewer autocomplete
195 * Reviewer autocomplete
187 */
196 */
188 var ReviewerAutoComplete = function(input_id) {
197 var ReviewerAutoComplete = function(input_id) {
189 $('#'+input_id).autocomplete({
198 $('#'+input_id).autocomplete({
190 serviceUrl: pyroutes.url('user_autocomplete_data'),
199 serviceUrl: pyroutes.url('user_autocomplete_data'),
191 minChars:2,
200 minChars:2,
192 maxHeight:400,
201 maxHeight:400,
193 deferRequestBy: 300, //miliseconds
202 deferRequestBy: 300, //miliseconds
194 showNoSuggestionNotice: true,
203 showNoSuggestionNotice: true,
195 tabDisabled: true,
204 tabDisabled: true,
196 autoSelectFirst: true,
205 autoSelectFirst: true,
197 formatResult: autocompleteFormatResult,
206 formatResult: autocompleteFormatResult,
198 lookupFilter: autocompleteFilterResult,
207 lookupFilter: autocompleteFilterResult,
199 onSelect: function(suggestion, data){
208 onSelect: function(suggestion, data){
200 addReviewMember(data.id, data.first_name, data.last_name,
209 addReviewMember(data.id, data.first_name, data.last_name,
201 data.username, data.icon_link);
210 data.username, data.icon_link);
202 $('#'+input_id).val('');
211 $('#'+input_id).val('');
203 }
212 }
204 });
213 });
205 };
214 };
@@ -1,84 +1,100 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.html"/>
5 <%inherit file="/base/base.html"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('%s repository settings') % c.repo_info.repo_name}
8 ${_('%s repository settings') % c.repo_info.repo_name}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Settings')}
15 ${_('Settings')}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='options')}
23 ${self.repo_menu(active='options')}
24 </%def>
24 </%def>
25
25
26 <%def name="main_content()">
26 <%def name="main_content()">
27 <%include file="/admin/repos/repo_edit_${c.active}.html"/>
27 <%include file="/admin/repos/repo_edit_${c.active}.html"/>
28 </%def>
28 </%def>
29
29
30
30
31 <%def name="main()">
31 <%def name="main()">
32 <div class="box">
32 <div class="box">
33 <div class="title">
33 <div class="title">
34 ${self.repo_page_title(c.rhodecode_db_repo)}
34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 ${self.breadcrumbs()}
35 ${self.breadcrumbs()}
36 </div>
36 </div>
37
37
38 <div class="sidebar-col-wrapper scw-small">
38 <div class="sidebar-col-wrapper scw-small">
39 ##main
39 ##main
40 <div class="sidebar">
40 <div class="sidebar">
41 <ul class="nav nav-pills nav-stacked">
41 <ul class="nav nav-pills nav-stacked">
42 <li class="${'active' if c.active=='settings' else ''}">
42 <li class="${'active' if c.active=='settings' else ''}">
43 <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
43 <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
44 </li>
44 </li>
45 <li class="${'active' if c.active=='permissions' else ''}">
45 <li class="${'active' if c.active=='permissions' else ''}">
46 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
46 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
47 </li>
47 </li>
48 <li class="${'active' if c.active=='advanced' else ''}">
48 <li class="${'active' if c.active=='advanced' else ''}">
49 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
49 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
50 </li>
50 </li>
51 <li class="${'active' if c.active=='vcs' else ''}">
51 <li class="${'active' if c.active=='vcs' else ''}">
52 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
52 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
53 </li>
53 </li>
54 <li class="${'active' if c.active=='fields' else ''}">
54 <li class="${'active' if c.active=='fields' else ''}">
55 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
55 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
56 </li>
56 </li>
57 <li class="${'active' if c.active=='issuetracker' else ''}">
57 <li class="${'active' if c.active=='issuetracker' else ''}">
58 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
58 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
59 </li>
59 </li>
60 <li class="${'active' if c.active=='caches' else ''}">
60 <li class="${'active' if c.active=='caches' else ''}">
61 <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
61 <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
62 </li>
62 </li>
63 %if c.repo_info.repo_type != 'svn':
63 %if c.repo_info.repo_type != 'svn':
64 <li class="${'active' if c.active=='remote' else ''}">
64 <li class="${'active' if c.active=='remote' else ''}">
65 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
65 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
66 </li>
66 </li>
67 %endif
67 %endif
68 <li class="${'active' if c.active=='statistics' else ''}">
68 <li class="${'active' if c.active=='statistics' else ''}">
69 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
69 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
70 </li>
70 </li>
71 <li class="${'active' if c.active=='integrations' else ''}">
71 <li class="${'active' if c.active=='integrations' else ''}">
72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
73 </li>
73 </li>
74 ## TODO: dan: replace repo navigation with navlist registry like with
75 ## admin menu. First must find way to allow runtime configuration
76 ## it to account for the c.repo_info.repo_type != 'svn' call above
77 <%
78 reviewer_settings = False
79 try:
80 import rc_reviewers
81 reviewer_settings = True
82 except ImportError:
83 pass
84 %>
85 %if reviewer_settings:
86 <li class="${'active' if c.active=='reviewers' else ''}">
87 <a href="${h.route_path('repo_reviewers_home', repo_name=c.repo_name)}">${_('Reviewers')}</a>
88 </li>
89 %endif
74 </ul>
90 </ul>
75 </div>
91 </div>
76
92
77 <div class="main-content-full-width">
93 <div class="main-content-full-width">
78 ${self.main_content()}
94 ${self.main_content()}
79 </div>
95 </div>
80
96
81 </div>
97 </div>
82 </div>
98 </div>
83
99
84 </%def> No newline at end of file
100 </%def>
@@ -1,541 +1,567 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('New pull request')}
8 ${_('New pull request')}
9 </%def>
9 </%def>
10
10
11 <%def name="menu_bar_nav()">
11 <%def name="menu_bar_nav()">
12 ${self.menu_items(active='repositories')}
12 ${self.menu_items(active='repositories')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_subnav()">
15 <%def name="menu_bar_subnav()">
16 ${self.repo_menu(active='showpullrequest')}
16 ${self.repo_menu(active='showpullrequest')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.repo_page_title(c.rhodecode_db_repo)}
22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25
25
26 ${h.secure_form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
26 ${h.secure_form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
27 <div class="box pr-summary">
27 <div class="box pr-summary">
28
28
29 <div class="summary-details block-left">
29 <div class="summary-details block-left">
30
30
31 <div class="form">
31 <div class="form">
32 <!-- fields -->
32 <!-- fields -->
33
33
34 <div class="fields" >
34 <div class="fields" >
35
35
36 <div class="field">
36 <div class="field">
37 <div class="label">
37 <div class="label">
38 <label for="pullrequest_title">${_('Title')}:</label>
38 <label for="pullrequest_title">${_('Title')}:</label>
39 </div>
39 </div>
40 <div class="input">
40 <div class="input">
41 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
41 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
42 </div>
42 </div>
43 </div>
43 </div>
44
44
45 <div class="field">
45 <div class="field">
46 <div class="label label-textarea">
46 <div class="label label-textarea">
47 <label for="pullrequest_desc">${_('Description')}:</label>
47 <label for="pullrequest_desc">${_('Description')}:</label>
48 </div>
48 </div>
49 <div class="textarea text-area editor">
49 <div class="textarea text-area editor">
50 ${h.textarea('pullrequest_desc',size=30, )}
50 ${h.textarea('pullrequest_desc',size=30, )}
51 <span class="help-block">
51 <span class="help-block">
52 ${_('Write a short description on this pull request')}
52 ${_('Write a short description on this pull request')}
53 </span>
53 </span>
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="field">
57 <div class="field">
58 <div class="label label-textarea">
58 <div class="label label-textarea">
59 <label for="pullrequest_desc">${_('Commit flow')}:</label>
59 <label for="pullrequest_desc">${_('Commit flow')}:</label>
60 </div>
60 </div>
61
61
62 ## TODO: johbo: Abusing the "content" class here to get the
62 ## TODO: johbo: Abusing the "content" class here to get the
63 ## desired effect. Should be replaced by a proper solution.
63 ## desired effect. Should be replaced by a proper solution.
64
64
65 ##ORG
65 ##ORG
66 <div class="content">
66 <div class="content">
67 <strong>${_('Origin repository')}:</strong>
67 <strong>${_('Origin repository')}:</strong>
68 ${c.rhodecode_db_repo.description}
68 ${c.rhodecode_db_repo.description}
69 </div>
69 </div>
70 <div class="content">
70 <div class="content">
71 ${h.hidden('source_repo')}
71 ${h.hidden('source_repo')}
72 ${h.hidden('source_ref')}
72 ${h.hidden('source_ref')}
73 </div>
73 </div>
74
74
75 ##OTHER, most Probably the PARENT OF THIS FORK
75 ##OTHER, most Probably the PARENT OF THIS FORK
76 <div class="content">
76 <div class="content">
77 ## filled with JS
77 ## filled with JS
78 <div id="target_repo_desc"></div>
78 <div id="target_repo_desc"></div>
79 </div>
79 </div>
80
80
81 <div class="content">
81 <div class="content">
82 ${h.hidden('target_repo')}
82 ${h.hidden('target_repo')}
83 ${h.hidden('target_ref')}
83 ${h.hidden('target_ref')}
84 <span id="target_ref_loading" style="display: none">
84 <span id="target_ref_loading" style="display: none">
85 ${_('Loading refs...')}
85 ${_('Loading refs...')}
86 </span>
86 </span>
87 </div>
87 </div>
88 </div>
88 </div>
89
89
90 <div class="field">
90 <div class="field">
91 <div class="label label-textarea">
91 <div class="label label-textarea">
92 <label for="pullrequest_submit"></label>
92 <label for="pullrequest_submit"></label>
93 </div>
93 </div>
94 <div class="input">
94 <div class="input">
95 <div class="pr-submit-button">
95 <div class="pr-submit-button">
96 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
96 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
97 </div>
97 </div>
98 <div id="pr_open_message"></div>
98 <div id="pr_open_message"></div>
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <div class="pr-spacing-container"></div>
102 <div class="pr-spacing-container"></div>
103 </div>
103 </div>
104 </div>
104 </div>
105 </div>
105 </div>
106 <div>
106 <div>
107 <div class="reviewers-title block-right">
107 <div class="reviewers-title block-right">
108 <div class="pr-details-title">
108 <div class="pr-details-title">
109 ${_('Pull request reviewers')}
109 ${_('Pull request reviewers')}
110 </div>
110 </div>
111 </div>
111 </div>
112 <div id="reviewers" class="block-right pr-details-content reviewers">
112 <div id="reviewers" class="block-right pr-details-content reviewers">
113 ## members goes here, filled via JS based on initial selection !
113 ## members goes here, filled via JS based on initial selection !
114 <ul id="review_members" class="group_members"></ul>
114 <ul id="review_members" class="group_members"></ul>
115 <div id="add_reviewer_input" class='ac'>
115 <div id="add_reviewer_input" class='ac'>
116 <div class="reviewer_ac">
116 <div class="reviewer_ac">
117 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
117 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
118 <div id="reviewers_container"></div>
118 <div id="reviewers_container"></div>
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122 </div>
122 </div>
123 </div>
123 </div>
124 <div class="box">
124 <div class="box">
125 <div>
125 <div>
126 ## overview pulled by ajax
126 ## overview pulled by ajax
127 <div id="pull_request_overview"></div>
127 <div id="pull_request_overview"></div>
128 </div>
128 </div>
129 </div>
129 </div>
130 ${h.end_form()}
130 ${h.end_form()}
131 </div>
131 </div>
132
132
133 <script type="text/javascript">
133 <script type="text/javascript">
134 $(function(){
134 $(function(){
135 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
135 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
136 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
136 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
137 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
137 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
138 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
138 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
139 var targetRepoName = '${c.repo_name}';
139 var targetRepoName = '${c.repo_name}';
140
140
141 var $pullRequestForm = $('#pull_request_form');
141 var $pullRequestForm = $('#pull_request_form');
142 var $sourceRepo = $('#source_repo', $pullRequestForm);
142 var $sourceRepo = $('#source_repo', $pullRequestForm);
143 var $targetRepo = $('#target_repo', $pullRequestForm);
143 var $targetRepo = $('#target_repo', $pullRequestForm);
144 var $sourceRef = $('#source_ref', $pullRequestForm);
144 var $sourceRef = $('#source_ref', $pullRequestForm);
145 var $targetRef = $('#target_ref', $pullRequestForm);
145 var $targetRef = $('#target_ref', $pullRequestForm);
146
146
147 var calculateContainerWidth = function() {
147 var calculateContainerWidth = function() {
148 var maxWidth = 0;
148 var maxWidth = 0;
149 var repoSelect2Containers = ['#source_repo', '#target_repo'];
149 var repoSelect2Containers = ['#source_repo', '#target_repo'];
150 $.each(repoSelect2Containers, function(idx, value) {
150 $.each(repoSelect2Containers, function(idx, value) {
151 $(value).select2('container').width('auto');
151 $(value).select2('container').width('auto');
152 var curWidth = $(value).select2('container').width();
152 var curWidth = $(value).select2('container').width();
153 if (maxWidth <= curWidth) {
153 if (maxWidth <= curWidth) {
154 maxWidth = curWidth;
154 maxWidth = curWidth;
155 }
155 }
156 $.each(repoSelect2Containers, function(idx, value) {
156 $.each(repoSelect2Containers, function(idx, value) {
157 $(value).select2('container').width(maxWidth + 10);
157 $(value).select2('container').width(maxWidth + 10);
158 });
158 });
159 });
159 });
160 };
160 };
161
161
162 var initRefSelection = function(selectedRef) {
162 var initRefSelection = function(selectedRef) {
163 return function(element, callback) {
163 return function(element, callback) {
164 // translate our select2 id into a text, it's a mapping to show
164 // translate our select2 id into a text, it's a mapping to show
165 // simple label when selecting by internal ID.
165 // simple label when selecting by internal ID.
166 var id, refData;
166 var id, refData;
167 if (selectedRef === undefined) {
167 if (selectedRef === undefined) {
168 id = element.val();
168 id = element.val();
169 refData = element.val().split(':');
169 refData = element.val().split(':');
170 } else {
170 } else {
171 id = selectedRef;
171 id = selectedRef;
172 refData = selectedRef.split(':');
172 refData = selectedRef.split(':');
173 }
173 }
174
174
175 var text = refData[1];
175 var text = refData[1];
176 if (refData[0] === 'rev') {
176 if (refData[0] === 'rev') {
177 text = text.substring(0, 12);
177 text = text.substring(0, 12);
178 }
178 }
179
179
180 var data = {id: id, text: text};
180 var data = {id: id, text: text};
181
181
182 callback(data);
182 callback(data);
183 };
183 };
184 };
184 };
185
185
186 var formatRefSelection = function(item) {
186 var formatRefSelection = function(item) {
187 var prefix = '';
187 var prefix = '';
188 var refData = item.id.split(':');
188 var refData = item.id.split(':');
189 if (refData[0] === 'branch') {
189 if (refData[0] === 'branch') {
190 prefix = '<i class="icon-branch"></i>';
190 prefix = '<i class="icon-branch"></i>';
191 }
191 }
192 else if (refData[0] === 'book') {
192 else if (refData[0] === 'book') {
193 prefix = '<i class="icon-bookmark"></i>';
193 prefix = '<i class="icon-bookmark"></i>';
194 }
194 }
195 else if (refData[0] === 'tag') {
195 else if (refData[0] === 'tag') {
196 prefix = '<i class="icon-tag"></i>';
196 prefix = '<i class="icon-tag"></i>';
197 }
197 }
198
198
199 var originalOption = item.element;
199 var originalOption = item.element;
200 return prefix + item.text;
200 return prefix + item.text;
201 };
201 };
202
202
203 // custom code mirror
203 // custom code mirror
204 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
204 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
205
205
206 var queryTargetRepo = function(self, query) {
206 var queryTargetRepo = function(self, query) {
207 // cache ALL results if query is empty
207 // cache ALL results if query is empty
208 var cacheKey = query.term || '__';
208 var cacheKey = query.term || '__';
209 var cachedData = self.cachedDataSource[cacheKey];
209 var cachedData = self.cachedDataSource[cacheKey];
210
210
211 if (cachedData) {
211 if (cachedData) {
212 query.callback({results: cachedData.results});
212 query.callback({results: cachedData.results});
213 } else {
213 } else {
214 $.ajax({
214 $.ajax({
215 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': targetRepoName}),
215 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': targetRepoName}),
216 data: {query: query.term},
216 data: {query: query.term},
217 dataType: 'json',
217 dataType: 'json',
218 type: 'GET',
218 type: 'GET',
219 success: function(data) {
219 success: function(data) {
220 self.cachedDataSource[cacheKey] = data;
220 self.cachedDataSource[cacheKey] = data;
221 query.callback({results: data.results});
221 query.callback({results: data.results});
222 },
222 },
223 error: function(data, textStatus, errorThrown) {
223 error: function(data, textStatus, errorThrown) {
224 alert(
224 alert(
225 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
225 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
226 }
226 }
227 });
227 });
228 }
228 }
229 };
229 };
230
230
231 var queryTargetRefs = function(initialData, query) {
231 var queryTargetRefs = function(initialData, query) {
232 var data = {results: []};
232 var data = {results: []};
233 // filter initialData
233 // filter initialData
234 $.each(initialData, function() {
234 $.each(initialData, function() {
235 var section = this.text;
235 var section = this.text;
236 var children = [];
236 var children = [];
237 $.each(this.children, function() {
237 $.each(this.children, function() {
238 if (query.term.length === 0 ||
238 if (query.term.length === 0 ||
239 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
239 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
240 children.push({'id': this.id, 'text': this.text})
240 children.push({'id': this.id, 'text': this.text})
241 }
241 }
242 });
242 });
243 data.results.push({'text': section, 'children': children})
243 data.results.push({'text': section, 'children': children})
244 });
244 });
245 query.callback({results: data.results});
245 query.callback({results: data.results});
246 };
246 };
247
247
248 var prButtonLock = function(lockEnabled, msg) {
248 var prButtonLock = function(lockEnabled, msg) {
249 if (lockEnabled) {
249 if (lockEnabled) {
250 $('#save').attr('disabled', 'disabled');
250 $('#save').attr('disabled', 'disabled');
251 }
251 }
252 else {
252 else {
253 $('#save').removeAttr('disabled');
253 $('#save').removeAttr('disabled');
254 }
254 }
255
255
256 $('#pr_open_message').html(msg);
256 $('#pr_open_message').html(msg);
257
257
258 };
258 };
259
259
260 var loadRepoRefDiffPreview = function() {
260 var loadRepoRefDiffPreview = function() {
261 var sourceRepo = $sourceRepo.eq(0).val();
261 var sourceRepo = $sourceRepo.eq(0).val();
262 var sourceRef = $sourceRef.eq(0).val().split(':');
262 var sourceRef = $sourceRef.eq(0).val().split(':');
263
263
264 var targetRepo = $targetRepo.eq(0).val();
264 var targetRepo = $targetRepo.eq(0).val();
265 var targetRef = $targetRef.eq(0).val().split(':');
265 var targetRef = $targetRef.eq(0).val().split(':');
266
266
267 var url_data = {
267 var url_data = {
268 'repo_name': targetRepo,
268 'repo_name': targetRepo,
269 'target_repo': sourceRepo,
269 'target_repo': sourceRepo,
270 'source_ref': targetRef[2],
270 'source_ref': targetRef[2],
271 'source_ref_type': 'rev',
271 'source_ref_type': 'rev',
272 'target_ref': sourceRef[2],
272 'target_ref': sourceRef[2],
273 'target_ref_type': 'rev',
273 'target_ref_type': 'rev',
274 'merge': true,
274 'merge': true,
275 '_': Date.now() // bypass browser caching
275 '_': Date.now() // bypass browser caching
276 }; // gather the source/target ref and repo here
276 }; // gather the source/target ref and repo here
277
277
278 if (sourceRef.length !== 3 || targetRef.length !== 3) {
278 if (sourceRef.length !== 3 || targetRef.length !== 3) {
279 prButtonLock(true, "${_('Please select origin and destination')}");
279 prButtonLock(true, "${_('Please select origin and destination')}");
280 return;
280 return;
281 }
281 }
282 var url = pyroutes.url('compare_url', url_data);
282 var url = pyroutes.url('compare_url', url_data);
283
283
284 // lock PR button, so we cannot send PR before it's calculated
284 // lock PR button, so we cannot send PR before it's calculated
285 prButtonLock(true, "${_('Loading compare ...')}");
285 prButtonLock(true, "${_('Loading compare ...')}");
286
286
287 if (loadRepoRefDiffPreview._currentRequest) {
287 if (loadRepoRefDiffPreview._currentRequest) {
288 loadRepoRefDiffPreview._currentRequest.abort();
288 loadRepoRefDiffPreview._currentRequest.abort();
289 }
289 }
290
290
291 loadRepoRefDiffPreview._currentRequest = $.get(url)
291 loadRepoRefDiffPreview._currentRequest = $.get(url)
292 .error(function(data, textStatus, errorThrown) {
292 .error(function(data, textStatus, errorThrown) {
293 alert(
293 alert(
294 "Error while processing request.\nError code {0} ({1}).".format(
294 "Error while processing request.\nError code {0} ({1}).".format(
295 data.status, data.statusText));
295 data.status, data.statusText));
296 })
296 })
297 .done(function(data) {
297 .done(function(data) {
298 loadRepoRefDiffPreview._currentRequest = null;
298 loadRepoRefDiffPreview._currentRequest = null;
299 $('#pull_request_overview').html(data);
299 $('#pull_request_overview').html(data);
300 var commitElements = $(data).find('tr[commit_id]');
300 var commitElements = $(data).find('tr[commit_id]');
301
301
302 var prTitleAndDesc = getTitleAndDescription(sourceRef[1],
302 var prTitleAndDesc = getTitleAndDescription(sourceRef[1],
303 commitElements, 5);
303 commitElements, 5);
304
304
305 var title = prTitleAndDesc[0];
305 var title = prTitleAndDesc[0];
306 var proposedDescription = prTitleAndDesc[1];
306 var proposedDescription = prTitleAndDesc[1];
307
307
308 var useGeneratedTitle = (
308 var useGeneratedTitle = (
309 $('#pullrequest_title').hasClass('autogenerated-title') ||
309 $('#pullrequest_title').hasClass('autogenerated-title') ||
310 $('#pullrequest_title').val() === "");
310 $('#pullrequest_title').val() === "");
311
311
312 if (title && useGeneratedTitle) {
312 if (title && useGeneratedTitle) {
313 // use generated title if we haven't specified our own
313 // use generated title if we haven't specified our own
314 $('#pullrequest_title').val(title);
314 $('#pullrequest_title').val(title);
315 $('#pullrequest_title').addClass('autogenerated-title');
315 $('#pullrequest_title').addClass('autogenerated-title');
316
316
317 }
317 }
318
318
319 var useGeneratedDescription = (
319 var useGeneratedDescription = (
320 !codeMirrorInstance._userDefinedDesc ||
320 !codeMirrorInstance._userDefinedDesc ||
321 codeMirrorInstance.getValue() === "");
321 codeMirrorInstance.getValue() === "");
322
322
323 if (proposedDescription && useGeneratedDescription) {
323 if (proposedDescription && useGeneratedDescription) {
324 // set proposed content, if we haven't defined our own,
324 // set proposed content, if we haven't defined our own,
325 // or we don't have description written
325 // or we don't have description written
326 codeMirrorInstance._userDefinedDesc = false; // reset state
326 codeMirrorInstance._userDefinedDesc = false; // reset state
327 codeMirrorInstance.setValue(proposedDescription);
327 codeMirrorInstance.setValue(proposedDescription);
328 }
328 }
329
329
330 var msg = '';
330 var msg = '';
331 if (commitElements.length === 1) {
331 if (commitElements.length === 1) {
332 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
332 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
333 } else {
333 } else {
334 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
334 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
335 }
335 }
336
336
337 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
337 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
338
338
339 if (commitElements.length) {
339 if (commitElements.length) {
340 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
340 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
341 prButtonLock(false, msg.replace('__COMMITS__', commitsLink));
341 prButtonLock(false, msg.replace('__COMMITS__', commitsLink));
342 }
342 }
343 else {
343 else {
344 prButtonLock(true, "${_('There are no commits to merge.')}");
344 prButtonLock(true, "${_('There are no commits to merge.')}");
345 }
345 }
346
346
347
347
348 });
348 });
349 };
349 };
350
350
351 /**
351 /**
352 Generate Title and Description for a PullRequest.
352 Generate Title and Description for a PullRequest.
353 In case of 1 commits, the title and description is that one commit
353 In case of 1 commits, the title and description is that one commit
354 in case of multiple commits, we iterate on them with max N number of commits,
354 in case of multiple commits, we iterate on them with max N number of commits,
355 and build description in a form
355 and build description in a form
356 - commitN
356 - commitN
357 - commitN+1
357 - commitN+1
358 ...
358 ...
359
359
360 Title is then constructed from branch names, or other references,
360 Title is then constructed from branch names, or other references,
361 replacing '-' and '_' into spaces
361 replacing '-' and '_' into spaces
362
362
363 * @param sourceRef
363 * @param sourceRef
364 * @param elements
364 * @param elements
365 * @param limit
365 * @param limit
366 * @returns {*[]}
366 * @returns {*[]}
367 */
367 */
368 var getTitleAndDescription = function(sourceRef, elements, limit) {
368 var getTitleAndDescription = function(sourceRef, elements, limit) {
369 var title = '';
369 var title = '';
370 var desc = '';
370 var desc = '';
371
371
372 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
372 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
373 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
373 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
374 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
374 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
375 });
375 });
376 // only 1 commit, use commit message as title
376 // only 1 commit, use commit message as title
377 if (elements.length == 1) {
377 if (elements.length == 1) {
378 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
378 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
379 }
379 }
380 else {
380 else {
381 // use reference name
381 // use reference name
382 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
382 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
383 }
383 }
384
384
385 return [title, desc]
385 return [title, desc]
386 };
386 };
387
387
388 var Select2Box = function(element, overrides) {
388 var Select2Box = function(element, overrides) {
389 var globalDefaults = {
389 var globalDefaults = {
390 dropdownAutoWidth: true,
390 dropdownAutoWidth: true,
391 containerCssClass: "drop-menu",
391 containerCssClass: "drop-menu",
392 dropdownCssClass: "drop-menu-dropdown",
392 dropdownCssClass: "drop-menu-dropdown",
393 };
393 };
394
394
395 var initSelect2 = function(defaultOptions) {
395 var initSelect2 = function(defaultOptions) {
396 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
396 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
397 element.select2(options);
397 element.select2(options);
398 };
398 };
399
399
400 return {
400 return {
401 initRef: function() {
401 initRef: function() {
402 var defaultOptions = {
402 var defaultOptions = {
403 minimumResultsForSearch: 5,
403 minimumResultsForSearch: 5,
404 formatSelection: formatRefSelection
404 formatSelection: formatRefSelection
405 };
405 };
406
406
407 initSelect2(defaultOptions);
407 initSelect2(defaultOptions);
408 },
408 },
409
409
410 initRepo: function(defaultValue, readOnly) {
410 initRepo: function(defaultValue, readOnly) {
411 var defaultOptions = {
411 var defaultOptions = {
412 initSelection : function (element, callback) {
412 initSelection : function (element, callback) {
413 var data = {id: defaultValue, text: defaultValue};
413 var data = {id: defaultValue, text: defaultValue};
414 callback(data);
414 callback(data);
415 }
415 }
416 };
416 };
417
417
418 initSelect2(defaultOptions);
418 initSelect2(defaultOptions);
419
419
420 element.select2('val', defaultSourceRepo);
420 element.select2('val', defaultSourceRepo);
421 if (readOnly === true) {
421 if (readOnly === true) {
422 element.select2('readonly', true);
422 element.select2('readonly', true);
423 };
423 };
424 }
424 }
425 };
425 };
426 };
426 };
427
427
428 var initTargetRefs = function(refsData, selectedRef){
428 var initTargetRefs = function(refsData, selectedRef){
429 Select2Box($targetRef, {
429 Select2Box($targetRef, {
430 query: function(query) {
430 query: function(query) {
431 queryTargetRefs(refsData, query);
431 queryTargetRefs(refsData, query);
432 },
432 },
433 initSelection : initRefSelection(selectedRef)
433 initSelection : initRefSelection(selectedRef)
434 }).initRef();
434 }).initRef();
435
435
436 if (!(selectedRef === undefined)) {
436 if (!(selectedRef === undefined)) {
437 $targetRef.select2('val', selectedRef);
437 $targetRef.select2('val', selectedRef);
438 }
438 }
439 };
439 };
440
440
441 var targetRepoChanged = function(repoData) {
441 var targetRepoChanged = function(repoData) {
442 // reset && add the reviewer based on selected repo
443 $('#review_members').html('');
444 addReviewMember(
445 repoData.user.user_id, repoData.user.firstname,
446 repoData.user.lastname, repoData.user.username,
447 repoData.user.gravatar_link);
448
449 // generate new DESC of target repo displayed next to select
442 // generate new DESC of target repo displayed next to select
450 $('#target_repo_desc').html(
443 $('#target_repo_desc').html(
451 "<strong>${_('Destination repository')}</strong>: {0}".format(repoData['description'])
444 "<strong>${_('Destination repository')}</strong>: {0}".format(repoData['description'])
452 );
445 );
453
446
454 // generate dynamic select2 for refs.
447 // generate dynamic select2 for refs.
455 initTargetRefs(repoData['refs']['select2_refs'],
448 initTargetRefs(repoData['refs']['select2_refs'],
456 repoData['refs']['selected_ref']);
449 repoData['refs']['selected_ref']);
457
450
458 };
451 };
459
452
460 var sourceRefSelect2 = Select2Box(
453 var sourceRefSelect2 = Select2Box(
461 $sourceRef, {
454 $sourceRef, {
462 placeholder: "${_('Select commit reference')}",
455 placeholder: "${_('Select commit reference')}",
463 query: function(query) {
456 query: function(query) {
464 var initialData = defaultSourceRepoData['refs']['select2_refs'];
457 var initialData = defaultSourceRepoData['refs']['select2_refs'];
465 queryTargetRefs(initialData, query)
458 queryTargetRefs(initialData, query)
466 },
459 },
467 initSelection: initRefSelection()
460 initSelection: initRefSelection()
468 }
461 }
469 );
462 );
470
463
471 var sourceRepoSelect2 = Select2Box($sourceRepo, {
464 var sourceRepoSelect2 = Select2Box($sourceRepo, {
472 query: function(query) {}
465 query: function(query) {}
473 });
466 });
474
467
475 var targetRepoSelect2 = Select2Box($targetRepo, {
468 var targetRepoSelect2 = Select2Box($targetRepo, {
476 cachedDataSource: {},
469 cachedDataSource: {},
477 query: $.debounce(250, function(query) {
470 query: $.debounce(250, function(query) {
478 queryTargetRepo(this, query);
471 queryTargetRepo(this, query);
479 }),
472 }),
480 formatResult: formatResult
473 formatResult: formatResult
481 });
474 });
482
475
483 sourceRefSelect2.initRef();
476 sourceRefSelect2.initRef();
484
477
485 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
478 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
486
479
487 targetRepoSelect2.initRepo(defaultTargetRepo, false);
480 targetRepoSelect2.initRepo(defaultTargetRepo, false);
488
481
489 $sourceRef.on('change', function(e){
482 $sourceRef.on('change', function(e){
490 loadRepoRefDiffPreview();
483 loadRepoRefDiffPreview();
484 loadDefaultReviewers();
491 });
485 });
492
486
493 $targetRef.on('change', function(e){
487 $targetRef.on('change', function(e){
494 loadRepoRefDiffPreview();
488 loadRepoRefDiffPreview();
489 loadDefaultReviewers();
495 });
490 });
496
491
497 $targetRepo.on('change', function(e){
492 $targetRepo.on('change', function(e){
498 var repoName = $(this).val();
493 var repoName = $(this).val();
499 calculateContainerWidth();
494 calculateContainerWidth();
500 $targetRef.select2('destroy');
495 $targetRef.select2('destroy');
501 $('#target_ref_loading').show();
496 $('#target_ref_loading').show();
502
497
503 $.ajax({
498 $.ajax({
504 url: pyroutes.url('pullrequest_repo_refs',
499 url: pyroutes.url('pullrequest_repo_refs',
505 {'repo_name': targetRepoName, 'target_repo_name':repoName}),
500 {'repo_name': targetRepoName, 'target_repo_name':repoName}),
506 data: {},
501 data: {},
507 dataType: 'json',
502 dataType: 'json',
508 type: 'GET',
503 type: 'GET',
509 success: function(data) {
504 success: function(data) {
510 $('#target_ref_loading').hide();
505 $('#target_ref_loading').hide();
511 targetRepoChanged(data);
506 targetRepoChanged(data);
512 loadRepoRefDiffPreview();
507 loadRepoRefDiffPreview();
513 },
508 },
514 error: function(data, textStatus, errorThrown) {
509 error: function(data, textStatus, errorThrown) {
515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
510 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
516 }
511 }
517 })
512 })
518
513
519 });
514 });
520
515
516 var loadDefaultReviewers = function() {
517 if (loadDefaultReviewers._currentRequest) {
518 loadDefaultReviewers._currentRequest.abort();
519 }
520 var url = pyroutes.url('repo_default_reviewers_data', {'repo_name': targetRepoName});
521
522 var sourceRepo = $sourceRepo.eq(0).val();
523 var sourceRef = $sourceRef.eq(0).val().split(':');
524 var targetRepo = $targetRepo.eq(0).val();
525 var targetRef = $targetRef.eq(0).val().split(':');
526 url += '?source_repo=' + sourceRepo;
527 url += '&source_ref=' + sourceRef[2];
528 url += '&target_repo=' + targetRepo;
529 url += '&target_ref=' + targetRef[2];
530
531 loadDefaultReviewers._currentRequest = $.get(url)
532 .done(function(data) {
533 loadDefaultReviewers._currentRequest = null;
534
535 // reset && add the reviewer based on selected repo
536 $('#review_members').html('');
537 for (var i = 0; i < data.reviewers.length; i++) {
538 var reviewer = data.reviewers[i];
539 addReviewMember(
540 reviewer.user_id, reviewer.firstname,
541 reviewer.lastname, reviewer.username,
542 reviewer.gravatar_link, reviewer.reasons);
543 }
544 });
545 };
521 prButtonLock(true, "${_('Please select origin and destination')}");
546 prButtonLock(true, "${_('Please select origin and destination')}");
522
547
523 // auto-load on init, the target refs select2
548 // auto-load on init, the target refs select2
524 calculateContainerWidth();
549 calculateContainerWidth();
525 targetRepoChanged(defaultTargetRepoData);
550 targetRepoChanged(defaultTargetRepoData);
526
551
527 $('#pullrequest_title').on('keyup', function(e){
552 $('#pullrequest_title').on('keyup', function(e){
528 $(this).removeClass('autogenerated-title');
553 $(this).removeClass('autogenerated-title');
529 });
554 });
530
555
531 %if c.default_source_ref:
556 %if c.default_source_ref:
532 // in case we have a pre-selected value, use it now
557 // in case we have a pre-selected value, use it now
533 $sourceRef.select2('val', '${c.default_source_ref}');
558 $sourceRef.select2('val', '${c.default_source_ref}');
534 loadRepoRefDiffPreview();
559 loadRepoRefDiffPreview();
560 loadDefaultReviewers();
535 %endif
561 %endif
536
562
537 ReviewerAutoComplete('user');
563 ReviewerAutoComplete('user');
538 });
564 });
539 </script>
565 </script>
540
566
541 </%def>
567 </%def>
General Comments 0
You need to be logged in to leave comments. Login now